@reldens/storage 0.117.0 → 0.119.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 -680
- 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 +317 -108
- 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 +8 -8
- 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 +10 -1
- package/tests/fixtures/expected-entities/entities-translations.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 +30 -10
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +15 -14
- package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
- package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +3 -1
- package/tests/fixtures/product-details-fixtures.js +4 -1
- package/tests/fixtures/reviews-fixtures.js +2 -1
- package/tests/fixtures/sql/test-schema.sql +11 -2
- 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 +135 -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 +111 -65
- package/tests/unit/test-drivers.js +7 -1
- package/tests/unit/test-models-generation.js +28 -18
- package/tests/utils/column-value-assert.js +139 -0
- 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 +143 -13
- package/tests/utils/test-runner.js +26 -5
- package/tests/utils/whole-result-assert.js +243 -0
|
@@ -52,6 +52,16 @@ let dataServer = new PrismaDataServer(serverConfig);
|
|
|
52
52
|
- The `prisma` CLI package is resolved through `prisma/package.json`, its `exports["."]` target `build/types.js` is not shipped in 7.9.1
|
|
53
53
|
- When a package does not resolve locally, `TestHelpers.registerNpmGlobalPaths()` adds the `npm root -g` folder (and its nested `@prisma/client/node_modules`) to `NODE_PATH` and retries, so a global Prisma install is found too
|
|
54
54
|
- Local install for the Prisma driver tests: `npm install --no-save prisma@7.9.1 @prisma/client@7.9.1 @prisma/adapter-mariadb@7.9.1`
|
|
55
|
+
- Only `knex` and `mysql2` ship with the package. The other drivers are opt in, each behind its own flag and install:
|
|
56
|
+
- `RELDENS_TEST_OBJECTION_ENABLED=1` with `npm install --no-save objection@3.1.5`
|
|
57
|
+
- `RELDENS_TEST_MIKRO_ORM_ENABLED=1` with `npm install --no-save @mikro-orm/core@7.2.0 @mikro-orm/mysql@7.2.0`
|
|
58
|
+
- `RELDENS_TEST_KYSELY_ENABLED=1` with `npm install --no-save kysely`
|
|
59
|
+
- `RELDENS_TEST_DRIZZLE_ENABLED=1` with `npm install --no-save drizzle-orm`
|
|
60
|
+
- Global installs (`npm install -g ...`) work for every driver except Prisma, `TestHelpers.registerNpmGlobalPaths()` adds the npm global root and the project `node_modules` to `NODE_PATH` before the loaders resolve the packages, but `prisma generate` only resolves `@prisma/client` from the project
|
|
61
|
+
- Everything in one command: `npm install --no-save objection@3.1.5 @mikro-orm/core@7.2.0 @mikro-orm/mysql@7.2.0 @mikro-orm/mongodb@7.2.0 kysely drizzle-orm prisma@7.10.0 @prisma/client@7.10.0 @prisma/adapter-mariadb@7.10.0`
|
|
62
|
+
- Always use that single command: any later `npm install`, with or without `--no-save`, prunes the `--no-save` packages from a previous run
|
|
63
|
+
- Run every driver: `RELDENS_TEST_OBJECTION_ENABLED=1 RELDENS_TEST_MIKRO_ORM_ENABLED=1 RELDENS_TEST_PRISMA_ENABLED=1 RELDENS_TEST_KYSELY_ENABLED=1 RELDENS_TEST_DRIZZLE_ENABLED=1 npm run test`
|
|
64
|
+
- `TestHelpers.getTestDbConfig()` sets `connectionLimit: 2` and every test data server passes `poolConfig: {min: 0, max: 2}`, so six drivers plus the sample data servers stay under the MySQL connection limit
|
|
55
65
|
- Full setup, enable and run instructions: `.claude/prisma-setup.md`
|
|
56
66
|
|
|
57
67
|
**Key Points:**
|
|
@@ -189,7 +199,7 @@ describe('Driver: '+driverName, () => {
|
|
|
189
199
|
|
|
190
200
|
### Flow: Connection → Tables → Entity Generation → Repositories
|
|
191
201
|
|
|
192
|
-
|
|
202
|
+
Every active driver follows this pattern:
|
|
193
203
|
|
|
194
204
|
```
|
|
195
205
|
1. new DataServer({config, rawEntities})
|
|
@@ -396,7 +406,7 @@ static async generateTestEntities(dataServer, driverName)
|
|
|
396
406
|
await dataServer.connect();
|
|
397
407
|
}
|
|
398
408
|
}
|
|
399
|
-
// Run EntitiesGenerator
|
|
409
|
+
// Run EntitiesGenerator: introspects DB, writes entity + model files to generated-entities/
|
|
400
410
|
await this.runEntitiesGenerator(dataServer, driverName);
|
|
401
411
|
this.fixGeneratedRequirePaths();
|
|
402
412
|
this.compareGeneratedWithExpected(driverName, 'entities');
|
|
@@ -413,7 +423,7 @@ static async generateTestEntities(dataServer, driverName)
|
|
|
413
423
|
|
|
414
424
|
### Entity Generation Flow
|
|
415
425
|
|
|
416
|
-
`runEntitiesGenerator()` creates an `EntitiesGenerator` instance pointed at the connected `dataServer`, calls `generator.generate()`, which internally calls `dataServer.fetchEntitiesFromDatabase()` to read real table metadata from `information_schema`. This is why tables must exist before calling `generateTestEntities()
|
|
426
|
+
`runEntitiesGenerator()` creates an `EntitiesGenerator` instance pointed at the connected `dataServer`, calls `generator.generate()`, which internally calls `dataServer.fetchEntitiesFromDatabase()` to read real table metadata from `information_schema`. This is why tables must exist before calling `generateTestEntities()`: without tables, introspection returns empty and generation fails.
|
|
417
427
|
|
|
418
428
|
After generation, `loadGeneratedEntities()` loads the `generated-entities/models/[driver]/registered-models-[driver].js` file, sets `dataServer.rawEntities`, and calls `dataServer.generateEntities()` to register all driver instances in the entity manager.
|
|
419
429
|
|
|
@@ -623,13 +633,13 @@ static async generatePrismaClient(){
|
|
|
623
633
|
|
|
624
634
|
### Per Driver Test Suite
|
|
625
635
|
|
|
626
|
-
**1. Initialization
|
|
627
|
-
- `setupDriver()
|
|
628
|
-
- `executeRawSQL()
|
|
629
|
-
- `generateTestEntities()
|
|
630
|
-
- `dataServer.getEntity()
|
|
636
|
+
**1. Initialization, runs once per driver (`DriverRegistry.initialize()`):**
|
|
637
|
+
- `setupDriver()`: connect to database
|
|
638
|
+
- `executeRawSQL()`: create tables from SQL schema
|
|
639
|
+
- `generateTestEntities()`: introspect DB, run EntitiesGenerator, load entities
|
|
640
|
+
- `dataServer.getEntity()`: obtain repository references
|
|
631
641
|
|
|
632
|
-
**2. Test execution
|
|
642
|
+
**2. Test execution, per group method in each test class:**
|
|
633
643
|
|
|
634
644
|
Each test class (DriversTest, NestedFiltersTest, RelationsTest, RawQueriesTest) has group methods. Each group method begins with `await TestHelpers.cleanDatabase(this.dataServer)` to DELETE all rows, then runs its tests via `runner.test()`.
|
|
635
645
|
|
|
@@ -642,9 +652,9 @@ async testCreateOperations() {
|
|
|
642
652
|
}
|
|
643
653
|
```
|
|
644
654
|
|
|
645
|
-
**3. Teardown
|
|
646
|
-
- `dropTestTables()
|
|
647
|
-
- `teardownDriver()
|
|
655
|
+
**3. Teardown, runs once per driver (`DriverRegistry.cleanup()`):**
|
|
656
|
+
- `dropTestTables()`: DROP all test tables
|
|
657
|
+
- `teardownDriver()`: disconnect from database
|
|
648
658
|
|
|
649
659
|
### All Drivers Execution
|
|
650
660
|
|
|
@@ -762,9 +772,9 @@ async testCreateOperations() {
|
|
|
762
772
|
|
|
763
773
|
## Summary: The Complete Flow
|
|
764
774
|
|
|
765
|
-
**For each driver (
|
|
775
|
+
**For each active driver (knex always, the others behind their `RELDENS_TEST_*_ENABLED` flag):**
|
|
766
776
|
|
|
767
|
-
**Initialization
|
|
777
|
+
**Initialization, once per driver:**
|
|
768
778
|
|
|
769
779
|
1. Connect to database
|
|
770
780
|
- ObjectionJS: create Knex instance
|
|
@@ -782,7 +792,7 @@ async testCreateOperations() {
|
|
|
782
792
|
- Prisma only: generate schema.prisma (subprocess), generate PrismaClient (subprocess), reconnect
|
|
783
793
|
- Load registered-models file, call dataServer.generateEntities()
|
|
784
794
|
|
|
785
|
-
**Per test group
|
|
795
|
+
**Per test group, cleanDatabase() at start of each group method:**
|
|
786
796
|
- SET FOREIGN_KEY_CHECKS=0
|
|
787
797
|
- DELETE FROM test_reviews
|
|
788
798
|
- DELETE FROM test_products
|
|
@@ -791,7 +801,7 @@ async testCreateOperations() {
|
|
|
791
801
|
|
|
792
802
|
**Per test:** create test data, perform operations, assert results.
|
|
793
803
|
|
|
794
|
-
**Teardown
|
|
804
|
+
**Teardown, once per driver:**
|
|
795
805
|
|
|
796
806
|
1. Drop all tables
|
|
797
807
|
- SET FOREIGN_KEY_CHECKS=0
|
|
@@ -830,7 +840,7 @@ async testCreateOperations() {
|
|
|
830
840
|
- ✅ Added error checking for entity generation
|
|
831
841
|
|
|
832
842
|
**test-raw-queries.js:**
|
|
833
|
-
-
|
|
843
|
+
- Added, covers rawQuery with single and multiple SQL statements
|
|
834
844
|
|
|
835
845
|
### Test Helpers (All Fixed)
|
|
836
846
|
|
package/CLAUDE.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
## Package Overview
|
|
20
20
|
|
|
21
21
|
**@reldens/storage** is the database abstraction layer for Reldens. It provides:
|
|
22
|
-
- Multi-ORM support (objection-js, mikro-orm
|
|
22
|
+
- Multi-ORM support (knex by default, plus kysely, drizzle, objection-js, mikro-orm and prisma as optional drivers)
|
|
23
23
|
- Entity/model generation from database schemas
|
|
24
24
|
- Unified API across different ORM drivers
|
|
25
25
|
- Database connection management
|
|
@@ -166,6 +166,58 @@ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --passwor
|
|
|
166
166
|
- `_runtimeDataModel` in Prisma 7 is pruned: fields only contain `{ name, kind, type, relationName, dbName }` - `isId`, `isRequired`, `hasDefaultValue` are stripped. ID field detection falls back to `field.name === 'id' && field.kind === 'scalar'`
|
|
167
167
|
- `prisma.config.js` at project root is generated automatically by `PrismaSchemaGenerator.generateConfigFile()` and cleaned up after tests
|
|
168
168
|
|
|
169
|
+
**Knex, Kysely and Drizzle Drivers** (`lib/knex/`, `lib/kysely/`, `lib/drizzle/`):
|
|
170
|
+
- All three extend `QueryBuilderDriver` (`lib/query-builder-driver.js`), which implements 26 of the 30
|
|
171
|
+
`BaseDriver` methods once as compositions of five primitives: `insertRow()`, `selectRows()`, `update()`,
|
|
172
|
+
`delete()`, `count()`. Each concrete driver implements only those five plus its own filter translation.
|
|
173
|
+
- `RelationsLoader` (`lib/relations-loader.js`) loads relations with one extra query per relation level
|
|
174
|
+
(`WHERE to_column IN (parent values)`), reading the plain `relationMappings` data
|
|
175
|
+
(`{relation, tableName, from, to}`) emitted into the generated models. `HasManyRelation` yields an array,
|
|
176
|
+
`BelongsToOneRelation` and `HasOneRelation` yield one row or `null`. Nested paths (`a.b`) recurse through the
|
|
177
|
+
related driver's own loader. It also implements `createWithRelations()`, creating BelongsToOne parents before
|
|
178
|
+
the row and HasOne/HasMany children after it.
|
|
179
|
+
- Relation filters become `IN (SELECT to_column FROM related WHERE ...)` sub queries, so `countWithRelations()`
|
|
180
|
+
equals `count()` and joins never inflate the counts.
|
|
181
|
+
- `Mysql2ConnectionConfig` (`lib/mysql2-connection-config.js`) holds the mysql2 option whitelist that used to
|
|
182
|
+
live inside `ObjectionJsDataServer`; the Knex, Kysely and Drizzle data servers all sanitize through it.
|
|
183
|
+
- `ObjectionJsDataServer` now extends `KnexDataServer`; the Knex connection code exists once.
|
|
184
|
+
`connectionErrorMessage()` is overridden so a failed Objection connection still logs
|
|
185
|
+
`'Connection failed, Objection JS error.'`.
|
|
186
|
+
- Knex is the only driver package shipped as a dependency and the default driver. Objection, MikroORM, Kysely,
|
|
187
|
+
Drizzle and Prisma are NOT: the consumer passes `objectionModules` (`{Model}`), `mikroOrmModules`
|
|
188
|
+
(`{MikroORM, EntityCaseNamingStrategy, Collection, MySqlDriver|MongoDriver}`), `kyselyModules`
|
|
189
|
+
(`{Kysely, MysqlDialect, sql, db}`), `drizzleModules` (`{drizzle, orm, db}`) or `prismaModules`, each validated
|
|
190
|
+
on `connect()` by its `*ModulesValidator`. When the object is missing the data server resolves the packages from
|
|
191
|
+
the project `node_modules` through the matching `*ModulesLoader` (all built on `PackageResolver`).
|
|
192
|
+
- Model generation: `QueryBuilderModelsGeneration` (`lib/query-builder-models-generation.js`) serves `knex` and
|
|
193
|
+
`kysely`, `DrizzleModelsGeneration` (`lib/drizzle/drizzle-models-generation.js`) extends it and emits the
|
|
194
|
+
`drizzle-orm/mysql-core` column builders through `TypeMapper.mapDbTypeToDrizzleBuilder()`. Both are registered
|
|
195
|
+
in `ModelsGeneration.driversGeneration`. Templates: `query-builder-model.template`, `drizzle-model.template`.
|
|
196
|
+
- `parseEnumValues()` moved from `EntitiesGeneration` to `BaseGenerator` so the Drizzle `mysqlEnum` builder can
|
|
197
|
+
use it.
|
|
198
|
+
- Tests: the `knex` driver runs on every `npm run test`. Every other driver runs only when its flag is `1` and its
|
|
199
|
+
packages resolve, locally or from the npm global root (`TestHelpers.registerNpmGlobalPaths()` adds
|
|
200
|
+
`npm root -g` and the project `node_modules` to `NODE_PATH`): `RELDENS_TEST_OBJECTION_ENABLED`,
|
|
201
|
+
`RELDENS_TEST_MIKRO_ORM_ENABLED`, `RELDENS_TEST_PRISMA_ENABLED`, `RELDENS_TEST_KYSELY_ENABLED`,
|
|
202
|
+
`RELDENS_TEST_DRIZZLE_ENABLED`. Install every optional driver in one go without touching `package.json`
|
|
203
|
+
(Prisma must be local, `prisma generate` never resolves `@prisma/client` from the global root):
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
npm install --no-save objection@3.1.5 @mikro-orm/core@7.2.0 @mikro-orm/mysql@7.2.0 @mikro-orm/mongodb@7.2.0 kysely drizzle-orm prisma@7.10.0 @prisma/client@7.10.0 @prisma/adapter-mariadb@7.10.0
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Then run all six drivers:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
RELDENS_TEST_OBJECTION_ENABLED=1 RELDENS_TEST_MIKRO_ORM_ENABLED=1 RELDENS_TEST_PRISMA_ENABLED=1 RELDENS_TEST_KYSELY_ENABLED=1 RELDENS_TEST_DRIZZLE_ENABLED=1 npm run test
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Always install them in that single command: every `npm install`, with or without `--no-save`, prunes the
|
|
216
|
+
`--no-save` packages left by a previous run, so installing them in separate commands removes the earlier
|
|
217
|
+
ones. The cross driver equivalence suite runs
|
|
218
|
+
only when two or more drivers are active, and the benchmark table at the end of the run covers the drivers,
|
|
219
|
+
queries and CRUD suites of every active driver.
|
|
220
|
+
|
|
169
221
|
### Generators
|
|
170
222
|
|
|
171
223
|
**EntitiesGeneration** (`lib/generators/entities-generation.js`):
|
|
@@ -454,8 +506,9 @@ The `prepareDataWithRelations()` method (lines 156-199) automatically converts F
|
|
|
454
506
|
- ENUM changes: entity updates with new available values
|
|
455
507
|
|
|
456
508
|
### Driver-Specific Notes
|
|
457
|
-
- **
|
|
458
|
-
- **
|
|
509
|
+
- **Knex**: Default driver, the only one bundled with the package, fastest in the test benchmarks
|
|
510
|
+
- **ObjectionJS**: Optional, no npm release in about two years, kept for existing projects
|
|
511
|
+
- **MikroORM**: Optional, use for MongoDB or NoSQL requirements
|
|
459
512
|
- **Prisma**: Requires schema generation first, then entity generation
|
|
460
513
|
- Cannot mix drivers - regenerate all when switching drivers
|
|
461
514
|
- Each driver has different relation syntax in generated models
|
|
@@ -517,10 +570,11 @@ Using the default connection from schema:
|
|
|
517
570
|
```javascript
|
|
518
571
|
const { PrismaClientLoader } = require('@reldens/storage');
|
|
519
572
|
const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
|
|
573
|
+
const { Logger } = require('@reldens/utils');
|
|
520
574
|
|
|
521
575
|
let prismaModules = PrismaClientLoader.load(process.cwd(), null, null, {PrismaAdapter: PrismaMariaDb});
|
|
522
576
|
if(!prismaModules){
|
|
523
|
-
|
|
577
|
+
Logger.error('Failed to load Prisma client');
|
|
524
578
|
process.exit(1);
|
|
525
579
|
}
|
|
526
580
|
```
|
|
@@ -529,6 +583,7 @@ Using custom connection:
|
|
|
529
583
|
```javascript
|
|
530
584
|
const { PrismaClientLoader } = require('@reldens/storage');
|
|
531
585
|
const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
|
|
586
|
+
const { Logger } = require('@reldens/utils');
|
|
532
587
|
|
|
533
588
|
let prismaModules = PrismaClientLoader.load(
|
|
534
589
|
process.cwd(),
|
|
@@ -545,7 +600,7 @@ let prismaModules = PrismaClientLoader.load(
|
|
|
545
600
|
);
|
|
546
601
|
|
|
547
602
|
if(!prismaModules){
|
|
548
|
-
|
|
603
|
+
Logger.error('Failed to load Prisma client');
|
|
549
604
|
process.exit(1);
|
|
550
605
|
}
|
|
551
606
|
```
|
|
@@ -598,7 +653,7 @@ not mentioned in the test output at all. Install them locally without saving:
|
|
|
598
653
|
|
|
599
654
|
### DataServer Flow
|
|
600
655
|
|
|
601
|
-
|
|
656
|
+
Every driver follows this pattern:
|
|
602
657
|
|
|
603
658
|
```
|
|
604
659
|
1. new DataServer({config, rawEntities})
|
|
@@ -608,10 +663,10 @@ All three drivers follow this pattern:
|
|
|
608
663
|
3. await executeRawSQL(dataServer, schemaSql) ← Tables must exist before next step
|
|
609
664
|
↓
|
|
610
665
|
4. await generateTestEntities(dataServer, driverName)
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
666
|
+
- Introspects database schema
|
|
667
|
+
- Creates models from tables
|
|
668
|
+
- [Prisma only]: Generate schema + client + reconnect
|
|
669
|
+
- Calls dataServer.generateEntities()
|
|
615
670
|
↓
|
|
616
671
|
5. dataServer.getEntity('entityName') ← Returns repository
|
|
617
672
|
```
|
|
@@ -646,7 +701,8 @@ This happens ONCE during the `before()` hook inside `generateTestEntities()`.
|
|
|
646
701
|
### Test Files
|
|
647
702
|
|
|
648
703
|
**Integration tests:**
|
|
649
|
-
- `tests/integration/test-
|
|
704
|
+
- `tests/integration/test-drivers.js`: Full CRUD cycle for every active driver
|
|
705
|
+
- `tests/integration/test-cross-driver-equivalence.js`: Same call on every active driver, fails on any divergence
|
|
650
706
|
- `tests/integration/test-nested-filters.js`: Complex filter syntax (AND/OR/NOT/IN/LIKE)
|
|
651
707
|
- `tests/integration/test-relations.js`: Relation loading and nested relations
|
|
652
708
|
|
package/README.md
CHANGED
|
@@ -9,18 +9,71 @@ It ensures consistent data access methods across different database types and OR
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
### ORM Support
|
|
12
|
-
- **
|
|
12
|
+
- **Knex** - SQL query builder, the default driver and the only one bundled with the package
|
|
13
|
+
- MySQL/MariaDB through mysql2
|
|
14
|
+
- Same filter grammar as the Objection JS driver
|
|
15
|
+
- **Kysely** - Type safe SQL query builder (optional)
|
|
16
|
+
- MySQL/MariaDB through mysql2
|
|
17
|
+
- Injected by the consumer through the `kyselyModules` object
|
|
18
|
+
- **Drizzle** - Modern TypeScript ORM (optional)
|
|
19
|
+
- MySQL/MariaDB through mysql2
|
|
20
|
+
- Injected by the consumer through the `drizzleModules` object
|
|
21
|
+
- **Objection JS** (via Knex) - For SQL databases (optional)
|
|
13
22
|
- MySQL, MariaDB, PostgreSQL support
|
|
14
23
|
- Complex relation mappings
|
|
15
|
-
-
|
|
16
|
-
- **Mikro-ORM** - For MongoDB/NoSQL support
|
|
24
|
+
- Injected by the consumer through the `objectionModules` object
|
|
25
|
+
- **Mikro-ORM** - For MongoDB/NoSQL support (optional)
|
|
17
26
|
- MongoDB native support
|
|
18
27
|
- Entity metadata decorators
|
|
19
|
-
-
|
|
20
|
-
- **Prisma** - Modern database toolkit
|
|
28
|
+
- Injected by the consumer through the `mikroOrmModules` object
|
|
29
|
+
- **Prisma** - Modern database toolkit (optional)
|
|
21
30
|
- Type-safe queries
|
|
22
31
|
- Schema-first approach
|
|
23
|
-
-
|
|
32
|
+
- Injected by the consumer through the `prismaModules` object
|
|
33
|
+
|
|
34
|
+
### Driver packages
|
|
35
|
+
|
|
36
|
+
The package only depends on `@reldens/utils`, `@reldens/server-utils`, `knex` and `mysql2`. Every other driver
|
|
37
|
+
expects its packages installed in the consumer project. Install the one you need:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Kysely
|
|
41
|
+
npm install kysely
|
|
42
|
+
|
|
43
|
+
# Drizzle
|
|
44
|
+
npm install drizzle-orm
|
|
45
|
+
|
|
46
|
+
# Objection JS
|
|
47
|
+
npm install objection@3.1.5
|
|
48
|
+
|
|
49
|
+
# Mikro-ORM for MySQL/MariaDB
|
|
50
|
+
npm install @mikro-orm/core@7.2.0 @mikro-orm/mysql@7.2.0
|
|
51
|
+
|
|
52
|
+
# Mikro-ORM for MongoDB
|
|
53
|
+
npm install @mikro-orm/core@7.2.0 @mikro-orm/mongodb@7.2.0
|
|
54
|
+
|
|
55
|
+
# Prisma
|
|
56
|
+
npm install prisma @prisma/client @prisma/adapter-mariadb
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Each data server accepts the driver classes through its `[driver]Modules` option, validated on `connect()`.
|
|
60
|
+
When the option is missing, the data server resolves the packages from the project `node_modules` through the
|
|
61
|
+
matching `*ModulesLoader` (`KyselyModulesLoader`, `DrizzleModulesLoader`, `ObjectionModulesLoader`,
|
|
62
|
+
`MikroOrmModulesLoader`), all exported by the package.
|
|
63
|
+
|
|
64
|
+
To try every driver at once without touching `package.json`, for example to run the full test suite:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm install --no-save objection@3.1.5 @mikro-orm/core@7.2.0 @mikro-orm/mysql@7.2.0 @mikro-orm/mongodb@7.2.0 kysely drizzle-orm prisma@7.10.0 @prisma/client@7.10.0 @prisma/adapter-mariadb@7.10.0
|
|
68
|
+
RELDENS_TEST_OBJECTION_ENABLED=1 RELDENS_TEST_MIKRO_ORM_ENABLED=1 RELDENS_TEST_PRISMA_ENABLED=1 RELDENS_TEST_KYSELY_ENABLED=1 RELDENS_TEST_DRIZZLE_ENABLED=1 npm run test
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Keep all the packages in that one command: any later `npm install`, with or without `--no-save`, prunes the
|
|
72
|
+
`--no-save` packages from a previous run. Without the flags `npm run test` only exercises the bundled knex driver.
|
|
73
|
+
|
|
74
|
+
The Knex, Kysely and Drizzle drivers share `QueryBuilderDriver` and load relations with one extra query per
|
|
75
|
+
relation level, using the `relationMappings` data emitted into the generated models. Relation filters become
|
|
76
|
+
`IN (SELECT ...)` sub queries, so counts are never inflated by joins.
|
|
24
77
|
|
|
25
78
|
### Entity Management
|
|
26
79
|
- Standardized CRUD operations across all drivers
|
|
@@ -35,14 +88,14 @@ It ensures consistent data access methods across different database types and OR
|
|
|
35
88
|
|
|
36
89
|
**Generate entity files directly from your database structure:**
|
|
37
90
|
```bash
|
|
38
|
-
npx reldens-storage generateEntities --user=[dbuser] --pass=[dbpass] --database=[dbname] --driver=[
|
|
91
|
+
npx reldens-storage generateEntities --user=[dbuser] --pass=[dbpass] --database=[dbname] --driver=[knex]
|
|
39
92
|
```
|
|
40
93
|
|
|
41
94
|
**Entity Generation Options:**
|
|
42
95
|
- `--user=[username]` - Database username (required)
|
|
43
96
|
- `--pass=[password]` - Database password (required)
|
|
44
97
|
- `--database=[name]` - Database name (required)
|
|
45
|
-
- `--driver=[driver]` - ORM driver: objection-js, mikro-orm
|
|
98
|
+
- `--driver=[driver]` - ORM driver: knex, kysely, drizzle, objection-js, mikro-orm or prisma (default: knex). Only knex ships with the package, every other driver expects its packages installed in the project and passed as `[driver]Modules`, or resolved from the project `node_modules`.
|
|
46
99
|
- `--client=[client]` - Database client: mysql, mysql2, or mongodb (default: mysql2)
|
|
47
100
|
- `--host=[host]` - Database host (default: localhost)
|
|
48
101
|
- `--port=[port]` - Database port (default: 3306)
|
|
@@ -103,10 +156,17 @@ RELDENS_DB_PARAMS="authPlugin=mysql_native_password&sslmode=require&sslcert=ca-c
|
|
|
103
156
|
## Usage Examples
|
|
104
157
|
|
|
105
158
|
### SQL with Objection JS
|
|
159
|
+
|
|
160
|
+
Objection is not installed by this package, install it in your project first:
|
|
161
|
+
```bash
|
|
162
|
+
npm install objection@3.1.5
|
|
163
|
+
```
|
|
164
|
+
|
|
106
165
|
```javascript
|
|
107
166
|
const { ObjectionJsDataServer } = require('@reldens/storage');
|
|
167
|
+
const { Model } = require('objection');
|
|
108
168
|
|
|
109
|
-
|
|
169
|
+
let server = new ObjectionJsDataServer({
|
|
110
170
|
client: 'mysql2',
|
|
111
171
|
config: {
|
|
112
172
|
user: 'reldens',
|
|
@@ -114,18 +174,34 @@ const server = new ObjectionJsDataServer({
|
|
|
114
174
|
database: 'reldens',
|
|
115
175
|
host: 'localhost',
|
|
116
176
|
port: 3306
|
|
117
|
-
}
|
|
177
|
+
},
|
|
178
|
+
rawEntities: yourEntities,
|
|
179
|
+
objectionModules: {Model}
|
|
118
180
|
});
|
|
119
181
|
|
|
120
182
|
await server.connect();
|
|
121
|
-
|
|
183
|
+
let entities = server.generateEntities();
|
|
122
184
|
```
|
|
123
185
|
|
|
186
|
+
The `objectionModules` object only needs `Model`, the Objection base model class. The generated Objection models
|
|
187
|
+
read it from the package export `ObjectionJsRawModel`, which resolves `objection` from the project when it is
|
|
188
|
+
installed and is `false` otherwise.
|
|
189
|
+
|
|
124
190
|
### MongoDB with Mikro-ORM
|
|
191
|
+
|
|
192
|
+
Mikro-ORM is not installed by this package, install the core plus the driver for your database:
|
|
193
|
+
```bash
|
|
194
|
+
npm install @mikro-orm/core@7.2.0 @mikro-orm/mongodb@7.2.0
|
|
195
|
+
# or, for MySQL/MariaDB:
|
|
196
|
+
npm install @mikro-orm/core@7.2.0 @mikro-orm/mysql@7.2.0
|
|
197
|
+
```
|
|
198
|
+
|
|
125
199
|
```javascript
|
|
126
200
|
const { MikroOrmDataServer } = require('@reldens/storage');
|
|
201
|
+
const { MikroORM, EntityCaseNamingStrategy, Collection } = require('@mikro-orm/core');
|
|
202
|
+
const { MongoDriver } = require('@mikro-orm/mongodb');
|
|
127
203
|
|
|
128
|
-
|
|
204
|
+
let server = new MikroOrmDataServer({
|
|
129
205
|
client: 'mongodb',
|
|
130
206
|
config: {
|
|
131
207
|
user: 'reldens',
|
|
@@ -135,13 +211,117 @@ const server = new MikroOrmDataServer({
|
|
|
135
211
|
port: 27017
|
|
136
212
|
},
|
|
137
213
|
connectStringOptions: 'authSource=reldens&readPreference=primary&ssl=false',
|
|
214
|
+
rawEntities: yourEntities,
|
|
215
|
+
mikroOrmModules: {MikroORM, EntityCaseNamingStrategy, Collection, MongoDriver}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
await server.connect();
|
|
219
|
+
let entities = server.generateEntities();
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The `mikroOrmModules` object:
|
|
223
|
+
- `MikroORM`, `EntityCaseNamingStrategy`, `Collection`: from `@mikro-orm/core` (required)
|
|
224
|
+
- `MongoDriver`: from `@mikro-orm/mongodb`, required when `client` is `mongodb`
|
|
225
|
+
- `MySqlDriver`: from `@mikro-orm/mysql`, required for any other client
|
|
226
|
+
|
|
227
|
+
The generated Mikro-ORM models read `EntitySchema` from the package export `MikroOrmCore`, which resolves
|
|
228
|
+
`@mikro-orm/core` from the project when it is installed and is `false` otherwise.
|
|
229
|
+
|
|
230
|
+
### SQL with Knex
|
|
231
|
+
|
|
232
|
+
Knex is already a dependency of this package, nothing extra to install:
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
const { KnexDataServer } = require('@reldens/storage');
|
|
236
|
+
|
|
237
|
+
let server = new KnexDataServer({
|
|
238
|
+
client: 'mysql2',
|
|
239
|
+
config: {
|
|
240
|
+
user: 'reldens',
|
|
241
|
+
password: 'reldens',
|
|
242
|
+
database: 'reldens',
|
|
243
|
+
host: 'localhost',
|
|
244
|
+
port: 3306
|
|
245
|
+
},
|
|
138
246
|
rawEntities: yourEntities
|
|
139
247
|
});
|
|
140
248
|
|
|
141
249
|
await server.connect();
|
|
142
|
-
|
|
250
|
+
let entities = server.generateEntities();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Using Kysely
|
|
254
|
+
|
|
255
|
+
Kysely is not installed by this package, install it in your project first:
|
|
256
|
+
```bash
|
|
257
|
+
npm install kysely
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
const { KyselyDataServer } = require('@reldens/storage');
|
|
262
|
+
const { Kysely, MysqlDialect, sql } = require('kysely');
|
|
263
|
+
|
|
264
|
+
let server = new KyselyDataServer({
|
|
265
|
+
config: {
|
|
266
|
+
user: 'reldens',
|
|
267
|
+
password: 'reldens',
|
|
268
|
+
database: 'reldens',
|
|
269
|
+
host: 'localhost',
|
|
270
|
+
port: 3306
|
|
271
|
+
},
|
|
272
|
+
rawEntities: yourEntities,
|
|
273
|
+
kyselyModules: {Kysely, MysqlDialect, sql}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
await server.connect();
|
|
277
|
+
let entities = server.generateEntities();
|
|
143
278
|
```
|
|
144
279
|
|
|
280
|
+
The `kyselyModules` object:
|
|
281
|
+
- `sql`: the Kysely `sql` tag, used for the raw queries (required)
|
|
282
|
+
- `Kysely`: the Kysely class (required unless `db` is passed)
|
|
283
|
+
- `MysqlDialect`: the Kysely MySQL dialect (required unless `db` is passed)
|
|
284
|
+
- `db`: an already instantiated Kysely instance (optional, skips the instance construction)
|
|
285
|
+
|
|
286
|
+
Kysely is an ESM only package, so Node.js 22.12 or later is required to require it from CommonJS.
|
|
287
|
+
|
|
288
|
+
### Using Drizzle
|
|
289
|
+
|
|
290
|
+
Drizzle is not installed by this package, install it in your project first:
|
|
291
|
+
```bash
|
|
292
|
+
npm install drizzle-orm
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
const { DrizzleDataServer } = require('@reldens/storage');
|
|
297
|
+
const { drizzle } = require('drizzle-orm/mysql2');
|
|
298
|
+
const DrizzleOrm = require('drizzle-orm');
|
|
299
|
+
|
|
300
|
+
let server = new DrizzleDataServer({
|
|
301
|
+
config: {
|
|
302
|
+
user: 'reldens',
|
|
303
|
+
password: 'reldens',
|
|
304
|
+
database: 'reldens',
|
|
305
|
+
host: 'localhost',
|
|
306
|
+
port: 3306
|
|
307
|
+
},
|
|
308
|
+
rawEntities: yourEntities,
|
|
309
|
+
drizzleModules: {drizzle, orm: DrizzleOrm}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
await server.connect();
|
|
313
|
+
let entities = server.generateEntities();
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The `drizzleModules` object:
|
|
317
|
+
- `orm`: the `drizzle-orm` namespace, used for the conditions and the `sql` tag (required)
|
|
318
|
+
- `drizzle`: the factory exported by `drizzle-orm/mysql2` (required unless `db` is passed)
|
|
319
|
+
- `db`: an already instantiated Drizzle instance (optional, skips the instance construction)
|
|
320
|
+
|
|
321
|
+
Both objects are validated on `connect()`, the drivers refuse to start when a required class or method is missing.
|
|
322
|
+
The generated Drizzle models require `drizzle-orm/mysql-core` directly, so the package must be installed in the
|
|
323
|
+
project that loads them.
|
|
324
|
+
|
|
145
325
|
### Using Prisma
|
|
146
326
|
|
|
147
327
|
Prisma is not installed by this package. Install it in your project first:
|
|
@@ -175,7 +355,7 @@ const { PrismaDataServer } = require('@reldens/storage');
|
|
|
175
355
|
const { PrismaClient, Prisma } = require('./prisma/client');
|
|
176
356
|
const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
|
|
177
357
|
|
|
178
|
-
|
|
358
|
+
let server = new PrismaDataServer({
|
|
179
359
|
client: 'mysql',
|
|
180
360
|
config: {
|
|
181
361
|
user: 'reldens',
|
|
@@ -189,7 +369,7 @@ const server = new PrismaDataServer({
|
|
|
189
369
|
});
|
|
190
370
|
|
|
191
371
|
await server.connect();
|
|
192
|
-
|
|
372
|
+
let entities = server.generateEntities();
|
|
193
373
|
```
|
|
194
374
|
|
|
195
375
|
The `prismaModules` object:
|
|
@@ -211,10 +391,11 @@ Using the default connection from schema:
|
|
|
211
391
|
```javascript
|
|
212
392
|
const { PrismaClientLoader } = require('@reldens/storage');
|
|
213
393
|
const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
|
|
394
|
+
const { Logger } = require('@reldens/utils');
|
|
214
395
|
|
|
215
396
|
let prismaModules = PrismaClientLoader.load(process.cwd(), null, null, {PrismaAdapter: PrismaMariaDb});
|
|
216
397
|
if(!prismaModules){
|
|
217
|
-
|
|
398
|
+
Logger.error('Failed to load Prisma client');
|
|
218
399
|
process.exit(1);
|
|
219
400
|
}
|
|
220
401
|
```
|
|
@@ -223,6 +404,7 @@ Using custom connection:
|
|
|
223
404
|
```javascript
|
|
224
405
|
const { PrismaClientLoader } = require('@reldens/storage');
|
|
225
406
|
const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
|
|
407
|
+
const { Logger } = require('@reldens/utils');
|
|
226
408
|
|
|
227
409
|
let prismaModules = PrismaClientLoader.load(
|
|
228
410
|
process.cwd(),
|
|
@@ -239,7 +421,7 @@ let prismaModules = PrismaClientLoader.load(
|
|
|
239
421
|
);
|
|
240
422
|
|
|
241
423
|
if(!prismaModules){
|
|
242
|
-
|
|
424
|
+
Logger.error('Failed to load Prisma client');
|
|
243
425
|
process.exit(1);
|
|
244
426
|
}
|
|
245
427
|
```
|
|
@@ -292,8 +474,8 @@ class CustomDriver extends BaseDriver {
|
|
|
292
474
|
const { ServerManager } = require('@reldens/server');
|
|
293
475
|
const CustomDataServer = require('./custom-data-server');
|
|
294
476
|
|
|
295
|
-
|
|
296
|
-
|
|
477
|
+
let customDriver = new CustomDataServer(options);
|
|
478
|
+
let appServer = new ServerManager(serverConfig, eventsManager, customDriver);
|
|
297
479
|
```
|
|
298
480
|
|
|
299
481
|
### Required Methods
|
|
@@ -349,11 +531,11 @@ All relations use the `related_*` prefix:
|
|
|
349
531
|
Example usage:
|
|
350
532
|
```javascript
|
|
351
533
|
// Load user with related player
|
|
352
|
-
|
|
534
|
+
let user = await dataServer.getEntity('users')
|
|
353
535
|
.loadByIdWithRelations(userId, ['related_player']);
|
|
354
536
|
|
|
355
537
|
// Access nested relations
|
|
356
|
-
|
|
538
|
+
let player = await dataServer.getEntity('players')
|
|
357
539
|
.loadByIdWithRelations(playerId, ['related_state', 'related_scenes']);
|
|
358
540
|
```
|
|
359
541
|
|
|
@@ -376,7 +558,7 @@ const player = await dataServer.getEntity('players')
|
|
|
376
558
|
|
|
377
559
|
### Database Support
|
|
378
560
|
|
|
379
|
-
- **MySQL/MariaDB**: Via ObjectionJS or Prisma
|
|
561
|
+
- **MySQL/MariaDB**: Via Knex (default), Kysely, Drizzle, ObjectionJS, MikroORM or Prisma
|
|
380
562
|
- **PostgreSQL**: Via Prisma
|
|
381
563
|
- **MongoDB**: Via MikroORM
|
|
382
564
|
|