@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
@@ -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
- All three drivers follow this pattern:
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 introspects DB, writes entity + model files to generated-entities/
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()` without tables, introspection returns empty and generation fails.
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 runs once per driver (`DriverRegistry.initialize()`):**
627
- - `setupDriver()` connect to database
628
- - `executeRawSQL()` create tables from SQL schema
629
- - `generateTestEntities()` introspect DB, run EntitiesGenerator, load entities
630
- - `dataServer.getEntity()` obtain repository references
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 per group method in each test class:**
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 runs once per driver (`DriverRegistry.cleanup()`):**
646
- - `dropTestTables()` DROP all test tables
647
- - `teardownDriver()` disconnect from database
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 (objection-js, mikro-orm, prisma):**
775
+ **For each active driver (knex always, the others behind their `RELDENS_TEST_*_ENABLED` flag):**
766
776
 
767
- **Initialization once per driver:**
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 cleanDatabase() at start of each group method:**
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 once per driver:**
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
- - Added covers rawQuery with single and multiple SQL statements
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, prisma)
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
- - **ObjectionJS**: Recommended driver, mature and stable
458
- - **MikroORM**: Use for MongoDB or NoSQL requirements
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
- console.error('Failed to load Prisma client');
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
- console.error('Failed to load Prisma client');
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
- All three drivers follow this pattern:
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
- ├─ Introspects database schema
612
- ├─ Creates models from tables
613
- ├─ [Prisma only]: Generate schema + client + reconnect
614
- └─ Calls dataServer.generateEntities()
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-cross-driver-compatibility.js`: Full CRUD cycle for all 3 drivers
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
- - **Objection JS** (via Knex) - For SQL databases (recommended)
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
- - Query builder with filtering and sorting
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
- - Automatic schema synchronization
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
- - Introspection and migration tools
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=[objection-js]
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, or prisma (default: objection-js)
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
- const server = new ObjectionJsDataServer({
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
- const entities = server.generateEntities();
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
- const server = new MikroOrmDataServer({
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
- const entities = server.generateEntities();
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
- const server = new PrismaDataServer({
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
- const entities = server.generateEntities();
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
- console.error('Failed to load Prisma client');
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
- console.error('Failed to load Prisma client');
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
- const customDriver = new CustomDataServer(options);
296
- const appServer = new ServerManager(serverConfig, eventsManager, customDriver);
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
- const user = await dataServer.getEntity('users')
534
+ let user = await dataServer.getEntity('users')
353
535
  .loadByIdWithRelations(userId, ['related_player']);
354
536
 
355
537
  // Access nested relations
356
- const player = await dataServer.getEntity('players')
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