@reldens/storage 0.116.0 → 0.118.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/.claude/test-architecture.md +27 -17
  2. package/CLAUDE.md +67 -11
  3. package/README.md +204 -22
  4. package/bin/reldens-storage.js +43 -10
  5. package/index.js +53 -12
  6. package/lib/drizzle/drizzle-data-server.js +123 -0
  7. package/lib/drizzle/drizzle-driver.js +228 -0
  8. package/lib/drizzle/drizzle-models-generation.js +49 -0
  9. package/lib/drizzle/drizzle-modules-loader.js +32 -0
  10. package/lib/drizzle/drizzle-modules-validator.js +74 -0
  11. package/lib/entities-generator.js +36 -7
  12. package/lib/entity-templates/drizzle-model.template +28 -0
  13. package/lib/entity-templates/query-builder-model.template +17 -0
  14. package/lib/generators/base-generator.js +13 -0
  15. package/lib/generators/entities-generation.js +0 -13
  16. package/lib/generators/models-generation.js +179 -693
  17. package/lib/generators/relations-detection.js +171 -0
  18. package/lib/knex/knex-data-server.js +128 -0
  19. package/lib/knex/knex-driver.js +157 -0
  20. package/lib/knex/knex-modules-loader.js +28 -0
  21. package/lib/knex/knex-modules-validator.js +45 -0
  22. package/lib/kysely/kysely-data-server.js +128 -0
  23. package/lib/kysely/kysely-driver.js +176 -0
  24. package/lib/kysely/kysely-modules-loader.js +28 -0
  25. package/lib/kysely/kysely-modules-validator.js +53 -0
  26. package/lib/mikro-orm/mikro-orm-data-server.js +48 -29
  27. package/lib/mikro-orm/mikro-orm-driver.js +385 -121
  28. package/lib/mikro-orm/mikro-orm-models-generation.js +178 -0
  29. package/lib/mikro-orm/mikro-orm-modules-loader.js +50 -0
  30. package/lib/mikro-orm/mikro-orm-modules-validator.js +46 -0
  31. package/lib/mysql2-connection-config.js +38 -0
  32. package/lib/objection-js/objection-js-data-server.js +71 -125
  33. package/lib/objection-js/objection-js-driver.js +4 -1
  34. package/lib/objection-js/objection-js-models-generation.js +98 -0
  35. package/lib/objection-js/objection-modules-loader.js +28 -0
  36. package/lib/objection-js/objection-modules-validator.js +31 -0
  37. package/lib/package-resolver.js +45 -0
  38. package/lib/prisma/prisma-client-loader.js +11 -1
  39. package/lib/prisma/prisma-driver.js +16 -2
  40. package/lib/prisma/prisma-filter-processor.js +5 -6
  41. package/lib/prisma/prisma-models-generation.js +236 -0
  42. package/lib/prisma/prisma-schema-generator.js +5 -1
  43. package/lib/prisma/prisma-type-caster.js +14 -0
  44. package/lib/query-builder-driver.js +210 -0
  45. package/lib/query-builder-models-generation.js +80 -0
  46. package/lib/relations-loader.js +192 -0
  47. package/lib/type-mapper.js +38 -0
  48. package/package.json +7 -7
  49. package/tests/.env.test.example +6 -0
  50. package/tests/fixtures/categories-fixtures.js +8 -0
  51. package/tests/fixtures/expected-entities/entities/test-product-details-entity.js +72 -0
  52. package/tests/fixtures/expected-entities/entities-config.js +6 -4
  53. package/tests/fixtures/expected-entities/entities-translations.js +25 -14
  54. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +2 -0
  55. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +13 -8
  56. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-product-details-model.js +80 -0
  57. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +20 -14
  58. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
  59. package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +2 -0
  60. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-product-details-model.js +33 -0
  61. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +9 -0
  62. package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +2 -0
  63. package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +43 -0
  64. package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +2 -0
  65. package/tests/fixtures/product-details-fixtures.js +22 -0
  66. package/tests/fixtures/reviews-fixtures.js +2 -1
  67. package/tests/fixtures/sql/test-schema.sql +23 -1
  68. package/tests/fixtures/table-columns-fixtures.js +97 -0
  69. package/tests/integration/test-cross-driver-equivalence.js +286 -0
  70. package/tests/integration/test-nested-filters.js +265 -408
  71. package/tests/integration/test-relations.js +199 -0
  72. package/tests/integration/test-reldens-sample-data.js +255 -0
  73. package/tests/integration/test-reldens-shape-relations.js +292 -0
  74. package/tests/run-tests.js +112 -60
  75. package/tests/unit/test-drivers.js +7 -1
  76. package/tests/unit/test-models-generation.js +174 -0
  77. package/tests/utils/column-value-assert.js +139 -0
  78. package/tests/utils/driver-registry.js +1 -1
  79. package/tests/utils/entity-projection.js +111 -0
  80. package/tests/utils/observed-value.js +41 -0
  81. package/tests/utils/projection-difference.js +151 -0
  82. package/tests/utils/relations-shape-support.js +104 -0
  83. package/tests/utils/reldens-schema-loader.js +274 -0
  84. package/tests/utils/test-helpers.js +152 -19
  85. package/tests/utils/test-runner.js +26 -5
  86. package/tests/utils/whole-result-assert.js +243 -0
@@ -0,0 +1,104 @@
1
+ /**
2
+ *
3
+ * Reldens - Relations Shape Support
4
+ * Reusable fixtures setup and relation/foreign-key assertions for the child -> parent -> grandparent chain
5
+ * (test_reviews -> test_products -> test_categories), both links being NOT NULL m:1 foreign keys.
6
+ * Every assertion reports the observed value so a failure states what the driver actually returned.
7
+ *
8
+ */
9
+
10
+ const { TestHelpers } = require('./test-helpers');
11
+ const { ObservedValue } = require('./observed-value');
12
+ const { assert } = require('./test-runner');
13
+ const { CategoriesFixtures } = require('../fixtures/categories-fixtures');
14
+ const { ProductsFixtures } = require('../fixtures/products-fixtures');
15
+ const { ReviewsFixtures } = require('../fixtures/reviews-fixtures');
16
+
17
+ class RelationsShapeSupport
18
+ {
19
+
20
+ constructor(dataServer, repos)
21
+ {
22
+ this.dataServer = dataServer;
23
+ this.repos = {
24
+ child: repos.testReviews,
25
+ parent: repos.testProducts,
26
+ grandParent: repos.testCategories
27
+ };
28
+ }
29
+
30
+ async insertChainFixtures()
31
+ {
32
+ await TestHelpers.cleanDatabase(this.dataServer);
33
+ let fixturesByTable = {
34
+ test_categories: [CategoriesFixtures.category_relations_1, CategoriesFixtures.category_relations_2],
35
+ test_products: [ProductsFixtures.product_relations_1, ProductsFixtures.product_relations_2],
36
+ test_reviews: [ReviewsFixtures.review_relations_1, ReviewsFixtures.review_relations_2]
37
+ };
38
+ for(let tableName of Object.keys(fixturesByTable)){
39
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, tableName, fixturesByTable[tableName]);
40
+ }
41
+ return true;
42
+ }
43
+
44
+ cleared(repoKey)
45
+ {
46
+ if(this.dataServer.orm && this.dataServer.orm.em){
47
+ this.dataServer.orm.em.clear();
48
+ }
49
+ return this.repos[repoKey];
50
+ }
51
+
52
+ async loadChildWithHint(relationsHint)
53
+ {
54
+ return await this.cleared('child').loadOneByWithRelations(
55
+ 'product_id',
56
+ ProductsFixtures.product_relations_1.id,
57
+ relationsHint
58
+ );
59
+ }
60
+
61
+ assertEntityColumns(entityValue, expectedColumns, label)
62
+ {
63
+ let described = ObservedValue.log(label, entityValue);
64
+ assert.ok(entityValue, label+' is falsy, the relation was not populated, observed: '+described);
65
+ assert.notStrictEqual('number', typeof entityValue, label+' is a RAW NUMBER, observed: '+described);
66
+ assert.strictEqual('object', typeof entityValue, label+' is not an entity, observed: '+described);
67
+ for(let columnName of Object.keys(expectedColumns)){
68
+ assert.strictEqual(
69
+ entityValue[columnName],
70
+ expectedColumns[columnName],
71
+ label+'.'+columnName+' observed: '+ObservedValue.describe(entityValue[columnName])
72
+ +' - expected: '+String(expectedColumns[columnName])
73
+ );
74
+ }
75
+ return true;
76
+ }
77
+
78
+ assertNumericColumn(actualValue, expectedValue, label)
79
+ {
80
+ let described = ObservedValue.log(label, actualValue);
81
+ assert.ok(null !== actualValue, label+' is null, observed: '+described);
82
+ assert.strictEqual(Number(actualValue), expectedValue, label+' observed: '+described);
83
+ return true;
84
+ }
85
+
86
+ async assertStoredColumn(repoKey, id, columnName, expectedValue, label)
87
+ {
88
+ let record = await this.cleared(repoKey).loadOneBy('id', id);
89
+ assert.ok(record, label+' row was not found in the database, id: '+id);
90
+ this.assertNumericColumn(record[columnName], expectedValue, label+'.'+columnName+' re-read from database');
91
+ let filtered = await this.cleared(repoKey).loadBy(columnName, expectedValue);
92
+ let filteredIds = filtered.map((row) => Number(row.id));
93
+ ObservedValue.log(label+' ids where '+columnName+' = '+expectedValue, filteredIds);
94
+ assert.ok(
95
+ filteredIds.includes(Number(id)),
96
+ label+' id '+id+' was not returned when filtering by '+columnName+' = '+expectedValue
97
+ +', so that column value is not stored in the database; observed ids: '+filteredIds.join(',')
98
+ );
99
+ return true;
100
+ }
101
+
102
+ }
103
+
104
+ module.exports.RelationsShapeSupport = RelationsShapeSupport;
@@ -0,0 +1,274 @@
1
+ /**
2
+ *
3
+ * Reldens - ReldensSchemaLoader
4
+ *
5
+ */
6
+
7
+ const { TestHelpers } = require('./test-helpers');
8
+ const { Logger, sc } = require('@reldens/utils');
9
+ const { FileHandler } = require('@reldens/server-utils');
10
+
11
+ class ReldensSchemaLoader
12
+ {
13
+
14
+ static migrationsPath()
15
+ {
16
+ return process.env.RELDENS_TEST_MIGRATIONS_PATH || '';
17
+ }
18
+
19
+ static sqlFileNames()
20
+ {
21
+ return ['reldens-install-v4.0.0.sql', 'reldens-basic-config-v4.0.0.sql', 'reldens-sample-data-v4.0.0.sql'];
22
+ }
23
+
24
+ static isAvailable()
25
+ {
26
+ let migrationsPath = this.migrationsPath();
27
+ if('' === migrationsPath){
28
+ return false;
29
+ }
30
+ for(let fileName of this.sqlFileNames()){
31
+ if(!FileHandler.exists(FileHandler.joinPaths(migrationsPath, fileName))){
32
+ Logger.warning('Reldens schema file not found: '+fileName);
33
+ return false;
34
+ }
35
+ }
36
+ return true;
37
+ }
38
+
39
+ static databaseName()
40
+ {
41
+ return process.env.RELDENS_TEST_RELDENS_DB_NAME || 'reldens_storage_test_full';
42
+ }
43
+
44
+ static generationPath()
45
+ {
46
+ return FileHandler.joinPaths(process.cwd(), 'tests', 'generated-reldens-schema');
47
+ }
48
+
49
+ static async ensureDatabase()
50
+ {
51
+ let BootstrapServerClass = TestHelpers.getDataServerClass('knex');
52
+ if(!BootstrapServerClass){
53
+ Logger.critical('Reldens schema bootstrap data server class not found.');
54
+ return false;
55
+ }
56
+ let bootstrapServer = new BootstrapServerClass({
57
+ client: TestHelpers.driverClient('knex'),
58
+ config: {...TestHelpers.getTestDbConfig(), multipleStatements: true},
59
+ rawEntities: {},
60
+ multipleStatements: true,
61
+ poolConfig: {min: 0, max: 2}
62
+ });
63
+ await bootstrapServer.connect();
64
+ if(!bootstrapServer.initialized){
65
+ Logger.critical('Reldens schema bootstrap connection failed.');
66
+ return false;
67
+ }
68
+ await TestHelpers.executeRawSQL(bootstrapServer, 'DROP DATABASE IF EXISTS '+this.databaseName()+';');
69
+ await TestHelpers.executeRawSQL(bootstrapServer, 'CREATE DATABASE '+this.databaseName()+';');
70
+ await bootstrapServer.disconnect();
71
+ Logger.info('Reldens schema database is ready: '+this.databaseName());
72
+ return true;
73
+ }
74
+
75
+ static async regeneratedPrismaModules(config)
76
+ {
77
+ let prismaConfig = Object.assign({}, config, {client: 'mysql'});
78
+ if(!await TestHelpers.generatePrismaSchema(prismaConfig)){
79
+ Logger.critical('Reldens schema Prisma schema generation failed.');
80
+ return false;
81
+ }
82
+ if(!await TestHelpers.generatePrismaClient()){
83
+ Logger.critical('Reldens schema Prisma client generation failed.');
84
+ return false;
85
+ }
86
+ delete require.cache[require.resolve(FileHandler.joinPaths(process.cwd(), 'prisma', 'client'))];
87
+ return await TestHelpers.loadPrismaModules(process.cwd(), config);
88
+ }
89
+
90
+ static async refreshPrismaClient(dataServer, driverName)
91
+ {
92
+ if('prisma' !== driverName){
93
+ return true;
94
+ }
95
+ let config = TestHelpers.getTestDbConfig();
96
+ config.database = this.databaseName();
97
+ let prismaModules = await this.regeneratedPrismaModules(config);
98
+ if(!prismaModules){
99
+ return false;
100
+ }
101
+ await dataServer.disconnect();
102
+ dataServer.prismaModules = prismaModules;
103
+ dataServer.prisma = prismaModules.client;
104
+ dataServer.initialized = Date.now();
105
+ return true;
106
+ }
107
+
108
+ static async setupDataServer(driverName)
109
+ {
110
+ let DataServerClass = TestHelpers.getDataServerClass(driverName);
111
+ if(!DataServerClass){
112
+ Logger.critical('Reldens schema data server class not found for '+driverName);
113
+ return false;
114
+ }
115
+ let config = TestHelpers.getTestDbConfig();
116
+ config.database = this.databaseName();
117
+ let serverConfig = {
118
+ client: TestHelpers.driverClient(driverName),
119
+ config: {...config, multipleStatements: true},
120
+ rawEntities: {},
121
+ multipleStatements: true,
122
+ poolConfig: {min: 0, max: 2}
123
+ };
124
+ TestHelpers.appendDriverModules(serverConfig, driverName);
125
+ if('prisma' === driverName){
126
+ serverConfig.prismaModules = await TestHelpers.loadPrismaModules(process.cwd(), config);
127
+ if(!serverConfig.prismaModules){
128
+ return false;
129
+ }
130
+ }
131
+ let dataServer = new DataServerClass(serverConfig);
132
+ await dataServer.connect();
133
+ if(!dataServer.initialized){
134
+ Logger.critical('Reldens schema data server failed to initialize for '+driverName);
135
+ return false;
136
+ }
137
+ return dataServer;
138
+ }
139
+
140
+ static async loadSchemaAndData(dataServer)
141
+ {
142
+ for(let fileName of this.sqlFileNames()){
143
+ let sql = FileHandler.readFile(FileHandler.joinPaths(this.migrationsPath(), fileName));
144
+ if(!sql){
145
+ Logger.critical('Could not read reldens schema file: '+fileName);
146
+ return false;
147
+ }
148
+ await TestHelpers.executeRawSQL(dataServer, sql);
149
+ Logger.info('Loaded reldens schema file: '+fileName);
150
+ }
151
+ return true;
152
+ }
153
+
154
+ static async generateEntities(dataServer, driverName)
155
+ {
156
+ let { EntitiesGenerator } = require('../../lib/entities-generator');
157
+ let config = TestHelpers.getTestDbConfig();
158
+ let generator = new EntitiesGenerator({
159
+ connectionData: {
160
+ driver: driverName,
161
+ client: TestHelpers.driverClient(driverName),
162
+ user: config.user,
163
+ password: config.password,
164
+ host: config.host,
165
+ database: this.databaseName(),
166
+ port: config.port
167
+ },
168
+ projectPath: this.generationPath(),
169
+ isOverride: true,
170
+ server: dataServer
171
+ });
172
+ if(!await generator.generate()){
173
+ Logger.critical('Reldens schema entities generation failed for '+driverName);
174
+ return false;
175
+ }
176
+ return this.fixGeneratedRequirePaths(driverName);
177
+ }
178
+
179
+ static modelsFolderDepth = 5;
180
+
181
+ static entitiesFolderDepth = 4;
182
+
183
+ static packageRequirePath(folderDepth)
184
+ {
185
+ return '..'+'/..'.repeat(folderDepth - 1)+'/index';
186
+ }
187
+
188
+ static fixGeneratedRequirePaths(driverName)
189
+ {
190
+ let modelsFolder = FileHandler.joinPaths(this.generationPath(), 'generated-entities', 'models', driverName);
191
+ if(!this.replacePackageRequire(modelsFolder, this.packageRequirePath(this.modelsFolderDepth))){
192
+ return false;
193
+ }
194
+ return this.replacePackageRequire(
195
+ FileHandler.joinPaths(this.generationPath(), 'generated-entities', 'entities'),
196
+ this.packageRequirePath(this.entitiesFolderDepth)
197
+ );
198
+ }
199
+
200
+ static replacePackageRequire(folderPath, packagePath)
201
+ {
202
+ let fileNames = FileHandler.getFilesInFolder(folderPath, ['.js']);
203
+ if(!sc.isArray(fileNames) || 0 === fileNames.length){
204
+ Logger.critical('No reldens schema files were generated at: '+folderPath);
205
+ return false;
206
+ }
207
+ for(let fileName of fileNames){
208
+ let filePath = FileHandler.joinPaths(folderPath, fileName);
209
+ let content = FileHandler.readFile(filePath);
210
+ FileHandler.writeFile(
211
+ filePath,
212
+ content.replace("require('@reldens/storage')", "require('"+packagePath+"')")
213
+ );
214
+ }
215
+ Logger.info('Fixed the require paths on '+fileNames.length+' reldens schema files at: '+folderPath);
216
+ return true;
217
+ }
218
+
219
+ static async loadEntities(dataServer, driverName)
220
+ {
221
+ let modelsFolder = FileHandler.joinPaths(this.generationPath(), 'generated-entities', 'models', driverName);
222
+ let modelsPath = FileHandler.joinPaths(modelsFolder, 'registered-models-'+driverName+'.js');
223
+ if(!FileHandler.exists(modelsPath)){
224
+ Logger.critical('Reldens schema registered models not found: '+modelsPath);
225
+ return false;
226
+ }
227
+ dataServer.rawEntities = require(modelsPath).rawRegisteredEntities;
228
+ if('mikro-orm' === driverName){
229
+ await dataServer.disconnect();
230
+ await dataServer.connect();
231
+ }
232
+ dataServer.generateEntities();
233
+ if(0 === Object.keys(dataServer.entities).length){
234
+ Logger.critical('No reldens schema entities generated for '+driverName);
235
+ return false;
236
+ }
237
+ Logger.info('Loaded '+Object.keys(dataServer.entities).length+' reldens schema entities for '+driverName);
238
+ return true;
239
+ }
240
+
241
+ static async setup(driverName)
242
+ {
243
+ if(!await this.ensureDatabase()){
244
+ return false;
245
+ }
246
+ let dataServer = await this.setupDataServer(driverName);
247
+ if(!dataServer){
248
+ return false;
249
+ }
250
+ if(!await this.loadSchemaAndData(dataServer)){
251
+ return false;
252
+ }
253
+ if(!await this.refreshPrismaClient(dataServer, driverName)){
254
+ return false;
255
+ }
256
+ if(!await this.generateEntities(dataServer, driverName)){
257
+ return false;
258
+ }
259
+ if(!await this.loadEntities(dataServer, driverName)){
260
+ return false;
261
+ }
262
+ return dataServer;
263
+ }
264
+
265
+ static async teardown(dataServer)
266
+ {
267
+ await TestHelpers.teardownDriver(dataServer);
268
+ FileHandler.remove(this.generationPath());
269
+ return true;
270
+ }
271
+
272
+ }
273
+
274
+ module.exports.ReldensSchemaLoader = ReldensSchemaLoader;
@@ -5,6 +5,11 @@
5
5
  */
6
6
 
7
7
  const { FileHandler } = require('@reldens/server-utils');
8
+ const { KyselyModulesLoader } = require('../../lib/kysely/kysely-modules-loader');
9
+ const { DrizzleModulesLoader } = require('../../lib/drizzle/drizzle-modules-loader');
10
+ const { MikroOrmModulesLoader } = require('../../lib/mikro-orm/mikro-orm-modules-loader');
11
+ const { ObjectionModulesLoader } = require('../../lib/objection-js/objection-modules-loader');
12
+ const { KnexModulesLoader } = require('../../lib/knex/knex-modules-loader');
8
13
  const { Logger, sc } = require('@reldens/utils');
9
14
  const { exec, execSync } = require('child_process');
10
15
  const { promisify } = require('util');
@@ -22,7 +27,8 @@ class TestHelpers
22
27
  user: process.env.RELDENS_TEST_DB_USER || 'test_user',
23
28
  password: process.env.RELDENS_TEST_DB_PASSWORD || 'test_password',
24
29
  database: process.env.RELDENS_TEST_DB_NAME || 'reldens_storage_test',
25
- client: process.env.RELDENS_TEST_DB_CLIENT || 'mysql'
30
+ client: process.env.RELDENS_TEST_DB_CLIENT || 'mysql',
31
+ connectionLimit: Number(process.env.RELDENS_TEST_DB_CONNECTION_LIMIT || 2)
26
32
  };
27
33
  }
28
34
 
@@ -41,10 +47,11 @@ class TestHelpers
41
47
  }
42
48
  }
43
49
  let serverConfig = {
44
- client: driverName === 'objection-js' ? 'mysql2' : 'mysql',
50
+ client: this.driverClient(driverName),
45
51
  config: {...config, multipleStatements: true},
46
52
  rawEntities: rawEntities,
47
- multipleStatements: true
53
+ multipleStatements: true,
54
+ poolConfig: {min: 0, max: 2}
48
55
  };
49
56
  if('prisma' === driverName){
50
57
  let prismaModules = await this.loadPrismaModules(process.cwd(), config);
@@ -53,6 +60,7 @@ class TestHelpers
53
60
  }
54
61
  serverConfig.prismaModules = prismaModules;
55
62
  }
63
+ this.appendDriverModules(serverConfig, driverName);
56
64
  let DataServerClass = this.getDataServerClass(driverName);
57
65
  let dataServer = new DataServerClass(serverConfig);
58
66
  let connected = await dataServer.connect();
@@ -81,16 +89,67 @@ class TestHelpers
81
89
  let { PrismaDataServer } = require('../../lib/prisma/prisma-data-server');
82
90
  return PrismaDataServer;
83
91
  }
92
+ if('knex' === driverName){
93
+ let { KnexDataServer } = require('../../lib/knex/knex-data-server');
94
+ return KnexDataServer;
95
+ }
96
+ if('kysely' === driverName){
97
+ let { KyselyDataServer } = require('../../lib/kysely/kysely-data-server');
98
+ return KyselyDataServer;
99
+ }
100
+ if('drizzle' === driverName){
101
+ let { DrizzleDataServer } = require('../../lib/drizzle/drizzle-data-server');
102
+ return DrizzleDataServer;
103
+ }
84
104
  Logger.critical('Unknown driver: '+driverName);
85
105
  return false;
86
106
  }
87
107
 
108
+ static driverClient(driverName)
109
+ {
110
+ if('objection-js' === driverName){
111
+ return 'mysql2';
112
+ }
113
+ if('knex' === driverName){
114
+ return 'mysql2';
115
+ }
116
+ return 'mysql';
117
+ }
118
+
119
+ static appendDriverModules(serverConfig, driverName)
120
+ {
121
+ this.registerNpmGlobalPaths();
122
+ if('kysely' === driverName){
123
+ serverConfig.kyselyModules = KyselyModulesLoader.load(false);
124
+ return serverConfig;
125
+ }
126
+ if('drizzle' === driverName){
127
+ serverConfig.drizzleModules = DrizzleModulesLoader.load(false);
128
+ return serverConfig;
129
+ }
130
+ if('mikro-orm' === driverName){
131
+ serverConfig.mikroOrmModules = MikroOrmModulesLoader.load(false, serverConfig.client);
132
+ return serverConfig;
133
+ }
134
+ if('objection-js' === driverName){
135
+ serverConfig.objectionModules = ObjectionModulesLoader.load(false);
136
+ return serverConfig;
137
+ }
138
+ if('knex' === driverName){
139
+ serverConfig.knexModules = KnexModulesLoader.load(false);
140
+ }
141
+ return serverConfig;
142
+ }
143
+
88
144
  static async teardownDriver(dataServer)
89
145
  {
90
146
  if(!dataServer){
91
147
  return;
92
148
  }
93
149
  try {
150
+ if(dataServer.db){
151
+ await dataServer.disconnect();
152
+ }
94
153
  if(dataServer.knex){
95
154
  await dataServer.knex.destroy();
96
155
  }
@@ -156,6 +215,7 @@ class TestHelpers
156
215
  try {
157
216
  await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
158
217
  await dataServer.rawQuery('DELETE FROM test_reviews;');
218
+ await dataServer.rawQuery('DELETE FROM test_product_details;');
159
219
  await dataServer.rawQuery('DELETE FROM test_products;');
160
220
  await dataServer.rawQuery('DELETE FROM test_categories;');
161
221
  await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
@@ -210,6 +270,7 @@ class TestHelpers
210
270
  try {
211
271
  await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
212
272
  await dataServer.rawQuery('DROP TABLE IF EXISTS test_reviews;');
273
+ await dataServer.rawQuery('DROP TABLE IF EXISTS test_product_details;');
213
274
  await dataServer.rawQuery('DROP TABLE IF EXISTS test_products;');
214
275
  await dataServer.rawQuery('DROP TABLE IF EXISTS test_categories;');
215
276
  await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
@@ -292,14 +353,16 @@ class TestHelpers
292
353
  static verifyAllPackages()
293
354
  {
294
355
  let required = [
295
- {name: '@mikro-orm/core', version: '7.2.0'},
296
- {name: '@mikro-orm/mongodb', version: '7.2.0'},
297
- {name: '@mikro-orm/mysql', version: '7.2.0'},
298
356
  {name: 'knex', version: '3.3.0'},
299
- {name: 'mysql', version: '2.18.1'},
300
- {name: 'mysql2', version: '3.24.3'},
301
- {name: 'objection', version: '3.1.5'}
357
+ {name: 'mysql2', version: '3.24.4'}
302
358
  ];
359
+ if(this.isObjectionFlagEnabled()){
360
+ required.push({name: 'objection', version: '3.1.5', optional: true});
361
+ }
362
+ if(this.isMikroOrmFlagEnabled()){
363
+ required.push({name: '@mikro-orm/core', version: '7.2.0', optional: true});
364
+ required.push({name: '@mikro-orm/mysql', version: '7.2.0', optional: true});
365
+ }
303
366
  if(this.isPrismaFlagEnabled()){
304
367
  required.push({name: 'prisma', version: '7.9.1', optional: true});
305
368
  required.push({name: '@prisma/client', version: '7.9.1', optional: true});
@@ -359,7 +422,11 @@ class TestHelpers
359
422
  Logger.warning('Could not resolve the npm global root: '+error.message);
360
423
  return '';
361
424
  }
362
- let extraPaths = [globalRoot, FileHandler.joinPaths(globalRoot, '@prisma', 'client', 'node_modules')];
425
+ let extraPaths = [
426
+ globalRoot,
427
+ FileHandler.joinPaths(globalRoot, '@prisma', 'client', 'node_modules'),
428
+ FileHandler.joinPaths(process.cwd(), 'node_modules')
429
+ ];
363
430
  let currentPaths = process.env.NODE_PATH ? process.env.NODE_PATH.split(delimiter) : [];
364
431
  process.env.NODE_PATH = [...currentPaths, ...extraPaths].join(delimiter);
365
432
  NodeModule._initPaths();
@@ -381,12 +448,66 @@ class TestHelpers
381
448
  return this.isPrismaAvailable();
382
449
  }
383
450
 
451
+ static isObjectionFlagEnabled()
452
+ {
453
+ return '1' === String(process.env.RELDENS_TEST_OBJECTION_ENABLED);
454
+ }
455
+
456
+ static isObjectionEnabled()
457
+ {
458
+ if(!this.isObjectionFlagEnabled()){
459
+ return false;
460
+ }
461
+ return this.canResolvePackage('objection');
462
+ }
463
+
464
+ static isMikroOrmFlagEnabled()
465
+ {
466
+ return '1' === String(process.env.RELDENS_TEST_MIKRO_ORM_ENABLED);
467
+ }
468
+
469
+ static isMikroOrmEnabled()
470
+ {
471
+ if(!this.isMikroOrmFlagEnabled()){
472
+ return false;
473
+ }
474
+ return this.canResolvePackage('@mikro-orm/core') && this.canResolvePackage('@mikro-orm/mysql');
475
+ }
476
+
477
+ static isKyselyEnabled()
478
+ {
479
+ if('1' !== String(process.env.RELDENS_TEST_KYSELY_ENABLED)){
480
+ return false;
481
+ }
482
+ return this.canResolvePackage('kysely');
483
+ }
484
+
485
+ static isDrizzleEnabled()
486
+ {
487
+ if('1' !== String(process.env.RELDENS_TEST_DRIZZLE_ENABLED)){
488
+ return false;
489
+ }
490
+ return this.canResolvePackage('drizzle-orm');
491
+ }
492
+
384
493
  static activeDriverNames()
385
494
  {
386
- let driverNames = ['objection-js', 'mikro-orm'];
495
+ let driverNames = ['knex'];
496
+ if(this.isObjectionEnabled()){
497
+ driverNames.push('objection-js');
498
+ }
499
+ if(this.isMikroOrmEnabled()){
500
+ driverNames.push('mikro-orm');
501
+ }
387
502
  if(this.isPrismaEnabled()){
388
503
  driverNames.push('prisma');
389
504
  }
505
+ if(this.isKyselyEnabled()){
506
+ driverNames.push('kysely');
507
+ }
508
+ if(this.isDrizzleEnabled()){
509
+ driverNames.push('drizzle');
510
+ }
390
511
  return driverNames;
391
512
  }
392
513
 
@@ -479,7 +600,14 @@ class TestHelpers
479
600
  }
480
601
  let adapterModule = require('@prisma/adapter-mariadb');
481
602
  let adapterConfig = config
482
- ? { host: config.host, port: config.port, user: config.user, password: config.password, database: config.database }
603
+ ? {
604
+ host: config.host,
605
+ port: config.port,
606
+ user: config.user,
607
+ password: config.password,
608
+ database: config.database,
609
+ connectionLimit: sc.get(config, 'connectionLimit', 2)
610
+ }
483
611
  : process.env.RELDENS_DB_URL;
484
612
  let client = new prismaModule.PrismaClient({ adapter: new adapterModule.PrismaMariaDb(adapterConfig) });
485
613
  await client.$connect();
@@ -507,6 +635,10 @@ class TestHelpers
507
635
  Logger.info('Cleaning up Prisma config file: '+prismaConfigPath);
508
636
  FileHandler.remove(prismaConfigPath);
509
637
  }
638
+ if('1' === process.env.RELDENS_TEST_KEEP_GENERATED){
639
+ Logger.info('Keeping generated entities folder, RELDENS_TEST_KEEP_GENERATED is active.');
640
+ return;
641
+ }
510
642
  let entitiesPath = FileHandler.joinPaths(process.cwd(), 'generated-entities');
511
643
  if(FileHandler.exists(entitiesPath)){
512
644
  Logger.info('Cleaning up generated entities folder: '+entitiesPath);
@@ -719,9 +851,7 @@ class TestHelpers
719
851
  for(let filename of expectedFiles){
720
852
  let expectedContent = FileHandler.readFile(FileHandler.joinPaths(expectedPath, filename));
721
853
  let generatedContent = FileHandler.readFile(FileHandler.joinPaths(generatedPath, filename));
722
- let normalizedExpected = expectedContent.replace(/\r\n/g, '\n');
723
- let normalizedGenerated = generatedContent.replace(/\r\n/g, '\n');
724
- if(normalizedExpected !== normalizedGenerated){
854
+ if(this.normalizeGeneratedContent(expectedContent) !== this.normalizeGeneratedContent(generatedContent)){
725
855
  Logger.warning('File content mismatch: '+filename);
726
856
  Logger.warning('Expected: '+FileHandler.joinPaths(expectedPath, filename));
727
857
  Logger.warning('Generated: '+FileHandler.joinPaths(generatedPath, filename));
@@ -735,9 +865,7 @@ class TestHelpers
735
865
  if(!isExpectedDir && !isGeneratedDir){
736
866
  let expectedContent = FileHandler.readFile(expectedPath);
737
867
  let generatedContent = FileHandler.readFile(generatedPath);
738
- let normalizedExpected = expectedContent.replace(/\r\n/g, '\n');
739
- let normalizedGenerated = generatedContent.replace(/\r\n/g, '\n');
740
- if(normalizedExpected !== normalizedGenerated){
868
+ if(this.normalizeGeneratedContent(expectedContent) !== this.normalizeGeneratedContent(generatedContent)){
741
869
  Logger.warning('File content mismatch: '+relativePath);
742
870
  }
743
871
  Logger.info('File '+relativePath+' matches expected output');
@@ -746,6 +874,11 @@ class TestHelpers
746
874
  throw new Error('Path type mismatch for: '+relativePath);
747
875
  }
748
876
 
877
+ static normalizeGeneratedContent(content)
878
+ {
879
+ return content.replace(/\r\n/g, '\n').replace(/[ \t]+\n/g, '\n');
880
+ }
881
+
749
882
  static async loadGeneratedEntities(dataServer, driverName)
750
883
  {
751
884
  let modelsPath = FileHandler.joinPaths(
@@ -795,7 +928,7 @@ class TestHelpers
795
928
  this.fixGeneratedRequirePaths();
796
929
  this.compareGeneratedWithExpected(driverName, 'entities');
797
930
  this.compareGeneratedWithExpected(driverName, 'models/'+driverName);
798
- if('objection-js' === driverName){
931
+ if('knex' === driverName){
799
932
  this.compareGeneratedWithExpected(driverName, 'entities-config.js');
800
933
  this.compareGeneratedWithExpected(driverName, 'entities-translations.js');
801
934
  }