@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.
- package/.claude/test-architecture.md +27 -17
- package/CLAUDE.md +67 -11
- package/README.md +204 -22
- package/bin/reldens-storage.js +43 -10
- package/index.js +53 -12
- package/lib/drizzle/drizzle-data-server.js +123 -0
- package/lib/drizzle/drizzle-driver.js +228 -0
- package/lib/drizzle/drizzle-models-generation.js +49 -0
- package/lib/drizzle/drizzle-modules-loader.js +32 -0
- package/lib/drizzle/drizzle-modules-validator.js +74 -0
- package/lib/entities-generator.js +36 -7
- package/lib/entity-templates/drizzle-model.template +28 -0
- package/lib/entity-templates/query-builder-model.template +17 -0
- package/lib/generators/base-generator.js +13 -0
- package/lib/generators/entities-generation.js +0 -13
- package/lib/generators/models-generation.js +179 -693
- package/lib/generators/relations-detection.js +171 -0
- package/lib/knex/knex-data-server.js +128 -0
- package/lib/knex/knex-driver.js +157 -0
- package/lib/knex/knex-modules-loader.js +28 -0
- package/lib/knex/knex-modules-validator.js +45 -0
- package/lib/kysely/kysely-data-server.js +128 -0
- package/lib/kysely/kysely-driver.js +176 -0
- package/lib/kysely/kysely-modules-loader.js +28 -0
- package/lib/kysely/kysely-modules-validator.js +53 -0
- package/lib/mikro-orm/mikro-orm-data-server.js +48 -29
- package/lib/mikro-orm/mikro-orm-driver.js +385 -121
- package/lib/mikro-orm/mikro-orm-models-generation.js +178 -0
- package/lib/mikro-orm/mikro-orm-modules-loader.js +50 -0
- package/lib/mikro-orm/mikro-orm-modules-validator.js +46 -0
- package/lib/mysql2-connection-config.js +38 -0
- package/lib/objection-js/objection-js-data-server.js +71 -125
- package/lib/objection-js/objection-js-driver.js +4 -1
- package/lib/objection-js/objection-js-models-generation.js +98 -0
- package/lib/objection-js/objection-modules-loader.js +28 -0
- package/lib/objection-js/objection-modules-validator.js +31 -0
- package/lib/package-resolver.js +45 -0
- package/lib/prisma/prisma-client-loader.js +11 -1
- package/lib/prisma/prisma-driver.js +16 -2
- package/lib/prisma/prisma-filter-processor.js +5 -6
- package/lib/prisma/prisma-models-generation.js +236 -0
- package/lib/prisma/prisma-schema-generator.js +5 -1
- package/lib/prisma/prisma-type-caster.js +14 -0
- package/lib/query-builder-driver.js +210 -0
- package/lib/query-builder-models-generation.js +80 -0
- package/lib/relations-loader.js +192 -0
- package/lib/type-mapper.js +38 -0
- package/package.json +7 -7
- package/tests/.env.test.example +6 -0
- package/tests/fixtures/categories-fixtures.js +8 -0
- package/tests/fixtures/expected-entities/entities/test-product-details-entity.js +72 -0
- package/tests/fixtures/expected-entities/entities-config.js +6 -4
- package/tests/fixtures/expected-entities/entities-translations.js +25 -14
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +2 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +13 -8
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-product-details-model.js +80 -0
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +20 -14
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +2 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-product-details-model.js +33 -0
- package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +9 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +2 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +43 -0
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +2 -0
- package/tests/fixtures/product-details-fixtures.js +22 -0
- package/tests/fixtures/reviews-fixtures.js +2 -1
- package/tests/fixtures/sql/test-schema.sql +23 -1
- package/tests/fixtures/table-columns-fixtures.js +97 -0
- package/tests/integration/test-cross-driver-equivalence.js +286 -0
- package/tests/integration/test-nested-filters.js +265 -408
- package/tests/integration/test-relations.js +199 -0
- package/tests/integration/test-reldens-sample-data.js +255 -0
- package/tests/integration/test-reldens-shape-relations.js +292 -0
- package/tests/run-tests.js +112 -60
- package/tests/unit/test-drivers.js +7 -1
- package/tests/unit/test-models-generation.js +174 -0
- package/tests/utils/column-value-assert.js +139 -0
- package/tests/utils/driver-registry.js +1 -1
- package/tests/utils/entity-projection.js +111 -0
- package/tests/utils/observed-value.js +41 -0
- package/tests/utils/projection-difference.js +151 -0
- package/tests/utils/relations-shape-support.js +104 -0
- package/tests/utils/reldens-schema-loader.js +274 -0
- package/tests/utils/test-helpers.js +152 -19
- package/tests/utils/test-runner.js +26 -5
- 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
|
|
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: '
|
|
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 = [
|
|
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 = ['
|
|
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
|
-
? {
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
931
|
+
if('knex' === driverName){
|
|
799
932
|
this.compareGeneratedWithExpected(driverName, 'entities-config.js');
|
|
800
933
|
this.compareGeneratedWithExpected(driverName, 'entities-translations.js');
|
|
801
934
|
}
|