@reldens/storage 0.111.0 → 0.113.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.
@@ -0,0 +1,162 @@
1
+ # Prisma Setup For Development And Tests
2
+
3
+ Prisma is not a dependency of this package. `package.json` does not list `prisma`, `@prisma/client` or
4
+ `@prisma/adapter-mariadb`, and no file under `lib/` requires them. The Prisma driver receives everything it needs
5
+ through the `prismaModules` object, so Prisma only has to exist in the project that uses the driver, or in this
6
+ repo when you want to run the Prisma driver tests.
7
+
8
+ ## Install, Test, Uninstall Workflow
9
+
10
+ The goal is to run the Prisma driver tests and leave no trace in `package.json`, `package-lock.json` or
11
+ `node_modules`. The local `--no-save` sequence does exactly that and is the reliable one (PowerShell):
12
+
13
+ ```powershell
14
+ npm install --no-save prisma@7.9.1 @prisma/client@7.9.1 @prisma/adapter-mariadb@7.9.1
15
+ $env:RELDENS_TEST_PRISMA_ENABLED = "1"
16
+ $env:RELDENS_LOG_LEVEL = "9"
17
+ npm run test
18
+ npm uninstall --no-save prisma @prisma/client @prisma/adapter-mariadb
19
+ ```
20
+
21
+ Same sequence in Bash / Git Bash:
22
+
23
+ ```bash
24
+ npm install --no-save prisma@7.9.1 @prisma/client@7.9.1 @prisma/adapter-mariadb@7.9.1
25
+ RELDENS_TEST_PRISMA_ENABLED=1 RELDENS_LOG_LEVEL=9 npm run test
26
+ npm uninstall --no-save prisma @prisma/client @prisma/adapter-mariadb
27
+ ```
28
+
29
+ The global variant: global packages are never found by `require()`, so when the local resolution fails
30
+ `TestHelpers.registerNpmGlobalPaths()` runs `npm root -g`, appends that folder and its nested
31
+ `@prisma/client/node_modules` to `NODE_PATH`, re-initializes the module paths and stores the root in
32
+ `RELDENS_TEST_NPM_GLOBAL_ROOT` (inherited by the Prisma subprocess worker). No manual `NODE_PATH` is needed, but
33
+ the packages must be under the folder `npm root -g` prints (PowerShell):
34
+
35
+ ```powershell
36
+ npm install -g prisma@7.9.1 @prisma/client@7.9.1 @prisma/adapter-mariadb@7.9.1
37
+ $env:RELDENS_TEST_PRISMA_ENABLED = "1"
38
+ $env:RELDENS_LOG_LEVEL = "9"
39
+ npm run test
40
+ npm uninstall -g prisma @prisma/client @prisma/adapter-mariadb
41
+ ```
42
+
43
+ Only the `--no-save` sequence was executed end to end with the Prisma driver passing. The global lookup was
44
+ executed once against a global root that did not contain the packages, so its resolution path is verified but a
45
+ full Prisma driver run from a global install is not. If the pre-flight still says
46
+ `Prisma package not installed`, the packages are not under the folder `npm root -g` prints.
47
+
48
+ ## Installing Prisma
49
+
50
+ ### Local install without saving (recommended)
51
+
52
+ Run inside this repo, it installs the three packages into `node_modules` without touching `package.json` or
53
+ `package-lock.json`:
54
+
55
+ ```bash
56
+ npm install --no-save prisma@7.9.1 @prisma/client@7.9.1 @prisma/adapter-mariadb@7.9.1
57
+ ```
58
+
59
+ A later plain `npm install` removes them again, because they are not in the lock file. Re-run the command when
60
+ that happens.
61
+
62
+ To remove them by hand:
63
+
64
+ ```bash
65
+ npm uninstall --no-save prisma @prisma/client @prisma/adapter-mariadb
66
+ ```
67
+
68
+ ### Global install
69
+
70
+ A global install alone does not work, because Node `require()` and `require.resolve()` never look into the
71
+ global `node_modules`:
72
+
73
+ - `TestHelpers.isPrismaAvailable()` resolves the three packages with `require.resolve()`.
74
+ - `tests/utils/test-helpers.js` and `tests/utils/prisma-subprocess-worker.js` require `@prisma/adapter-mariadb`.
75
+ - The generated client at `prisma/client` requires `@prisma/client-runtime-utils` at runtime.
76
+
77
+ Only `npx prisma` finds a global CLI. The test helpers compensate: `TestHelpers.registerNpmGlobalPaths()` adds
78
+ the `npm root -g` folder and its nested `@prisma/client/node_modules` to `NODE_PATH` at runtime when the local
79
+ resolution fails, see the workflow section above.
80
+
81
+ ```bash
82
+ npm install -g prisma@7.9.1 @prisma/client@7.9.1 @prisma/adapter-mariadb@7.9.1
83
+ ```
84
+
85
+ To remove the global install:
86
+
87
+ ```bash
88
+ npm uninstall -g prisma @prisma/client @prisma/adapter-mariadb
89
+ ```
90
+
91
+ ## Enabling The Prisma Driver In The Tests
92
+
93
+ Two conditions must be true, checked by `TestHelpers.isPrismaEnabled()`:
94
+
95
+ 1. `RELDENS_TEST_PRISMA_ENABLED` is exactly `1`. Default is disabled: `tests/.env.test.example` ships it as `0`.
96
+ 2. `prisma`, `@prisma/client` and `@prisma/adapter-mariadb` resolve from this repo. The `prisma` CLI package is
97
+ resolved through `prisma/package.json`, because its `exports` entry `"."` points to `build/types.js`, a file that
98
+ is not shipped in 7.9.1, so `require.resolve('prisma')` throws even when the package is installed.
99
+
100
+ When the flag is not `1` the pre-flight package check does not list the Prisma packages and the drivers unit test
101
+ does not include the Prisma driver, so Prisma is not mentioned in the output at all.
102
+
103
+ When either condition fails the Prisma driver is left out of `TestHelpers.activeDriverNames()`, so
104
+ `DriverRegistry` never sets it up and `run-tests.js` never runs it. With the flag set to `1` but the packages
105
+ missing, the pre-flight package check reports the three packages as optional warnings instead of failing.
106
+
107
+ You can enable it in `tests/.env.test`:
108
+
109
+ ```
110
+ RELDENS_TEST_PRISMA_ENABLED=1
111
+ ```
112
+
113
+ `run-tests.js` loads `tests/.env.test` with `process.loadEnvFile()` only when `RELDENS_TEST_DB_HOST` is not
114
+ already set, and `loadEnvFile()` never overrides variables already present in the environment, so a value set in
115
+ the shell always wins over the file.
116
+
117
+ ## Running The Tests
118
+
119
+ Bash / Git Bash:
120
+
121
+ ```bash
122
+ RELDENS_TEST_PRISMA_ENABLED=1 RELDENS_LOG_LEVEL=9 npm run test
123
+ ```
124
+
125
+ That command is correct: the two variables reach `node tests/run-tests.js`, the Prisma driver is enabled and the
126
+ `Logger` prints everything. Without `RELDENS_LOG_LEVEL=9` the run prints only the npm header.
127
+
128
+ PowerShell does not accept the `VAR=value command` prefix, use:
129
+
130
+ ```powershell
131
+ $env:RELDENS_TEST_PRISMA_ENABLED = "1"
132
+ $env:RELDENS_LOG_LEVEL = "9"
133
+ npm run test
134
+ ```
135
+
136
+ Only the Prisma driver (the `test:driver` script ends with an empty `--driver=`, so the argument must be passed
137
+ through `npm run test`):
138
+
139
+ ```bash
140
+ RELDENS_TEST_PRISMA_ENABLED=1 RELDENS_LOG_LEVEL=9 npm run test -- --driver=prisma
141
+ ```
142
+
143
+ Expected pre-flight output with the flag set and Prisma installed: `Package prisma verified: 7.9.1`,
144
+ `Package @prisma/client verified: 7.9.1` and `Package @prisma/adapter-mariadb verified: 7.9.1`. With the flag set
145
+ and Prisma missing: three `Optional package not installed` warnings and the Prisma driver absent from the driver
146
+ registry. With the flag unset: no Prisma line at all. Verified results: 324 tests without the Prisma driver, 406
147
+ with it.
148
+
149
+ ## What The Prisma Driver Test Setup Does
150
+
151
+ `TestHelpers.setupDriver('prisma')`:
152
+
153
+ 1. Forks `tests/utils/prisma-subprocess-worker.js`, which writes `prisma/schema.prisma` and `prisma.config.js`
154
+ in the repo root, runs `npx prisma db pull` and `npx prisma generate`, and disconnects.
155
+ 2. `TestHelpers.loadPrismaModules()` requires the generated client from `prisma/client`, requires
156
+ `@prisma/adapter-mariadb`, instantiates the client with the adapter and returns
157
+ `{PrismaClient, Prisma, PrismaAdapter, client}` (the tests use the MariaDB adapter, the driver accepts any).
158
+ 3. Passes that object as `prismaModules` to `PrismaDataServer`, whose `connect()` validates it with
159
+ `PrismaModulesValidator`.
160
+
161
+ `TestHelpers.cleanupGeneratedFiles()` removes `prisma/`, `prisma.config.js` and `generated-entities/` at the
162
+ start and at the end of a run unless `--skip-cleanup` is passed.
@@ -4,7 +4,7 @@
4
4
 
5
5
  ### Why Dynamic Model Creation Instead of Generated Models?
6
6
 
7
- **Problem:** Generated models in `.test-entities/` use `const { ObjectionJsRawModel } = require('@reldens/storage')` which loads the package index.js, which requires PrismaDataServer, which requires `@prisma/client` at top-level, which doesn't exist yet.
7
+ **Problem:** Generated models in `.test-entities/` use `const { ObjectionJsRawModel } = require('@reldens/storage')` which loads the package index.js. The Prisma client used by the tests does not exist until the tests generate it, so nothing may depend on it at module load time. (The package index no longer requires any `@prisma/*` package, Prisma is injected through the `prismaModules` object.)
8
8
 
9
9
  **Solution:** Create dynamic models in-memory without requiring the package:
10
10
 
@@ -23,32 +23,41 @@ class DynamicModel extends Model {
23
23
 
24
24
  **Problem:** Cannot use `require('@prisma/client')` at top-level because the client doesn't exist until generated by tests.
25
25
 
26
- **Solution:** Generate client first, then load from specific path using PrismaClientLoader:
26
+ **Solution:** Generate client first, then build the `prismaModules` object from the generated path and the dev installed adapter (`TestHelpers.loadPrismaModules()`):
27
27
 
28
28
  ```javascript
29
29
  // 1. Generate Prisma client (subprocess)
30
- await generatePrismaSchema(config);
31
- await generatePrismaClient();
32
-
33
- // 2. Load client from generated path (NOT '@prisma/client')
34
- const { PrismaClientLoader } = require('../../lib/prisma/prisma-client-loader');
35
- let prismaClient = PrismaClientLoader.load(process.cwd(), null, config);
36
-
37
- // 3. Pass loaded client to DataServer
38
- serverConfig.prismaClient = prismaClient;
30
+ await this.runPrismaSubprocess(process.cwd(), config);
31
+
32
+ // 2. Load the generated client module and the adapter (NOT '@prisma/client')
33
+ let prismaModule = require(FileHandler.joinPaths(projectRoot, 'prisma', 'client'));
34
+ let adapterModule = require('@prisma/adapter-mariadb');
35
+ let client = new prismaModule.PrismaClient({adapter: new adapterModule.PrismaMariaDb(adapterConfig)});
36
+
37
+ // 3. Pass the object to DataServer
38
+ serverConfig.prismaModules = {
39
+ PrismaClient: prismaModule.PrismaClient,
40
+ Prisma: prismaModule.Prisma,
41
+ PrismaAdapter: adapterModule.PrismaMariaDb,
42
+ client
43
+ };
39
44
  let dataServer = new PrismaDataServer(serverConfig);
40
45
  ```
41
46
 
42
- **This pattern is used in:**
43
- - `reldens-cms` installer (`bin/reldens-cms.js:87-108`)
44
- - `reldens` main application
45
- - Test suite setup
47
+ **Prisma is optional for the test suite:**
48
+ - `TestHelpers.isPrismaEnabled()` returns true only when `RELDENS_TEST_PRISMA_ENABLED=1` and `prisma`, `@prisma/client` and `@prisma/adapter-mariadb` resolve
49
+ - `TestHelpers.activeDriverNames()` feeds `DriverRegistry` and `run-tests.js`, so the Prisma driver is skipped entirely otherwise
50
+ - `verifyAllPackages()` lists the three Prisma packages only when the flag is `1`, and then as optional (warning, not failure)
51
+ - `tests/unit/test-drivers.js` includes the Prisma driver only when `TestHelpers.isPrismaEnabled()` is true
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
+ - 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
+ - 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
+ - Full setup, enable and run instructions: `.claude/prisma-setup.md`
46
56
 
47
57
  **Key Points:**
48
58
  - Always load Prisma client from explicit path, never default `@prisma/client`
49
- - Generate client via subprocess before requiring PrismaDataServer
50
- - Use `PrismaClientLoader.load()` to handle path resolution and connection config
51
- - Pass client instance to DataServer via `prismaClient` prop
59
+ - Generate client via subprocess before connecting PrismaDataServer
60
+ - Pass the `prismaModules` object to DataServer, `PrismaDataServer.connect()` validates it with `PrismaModulesValidator`
52
61
 
53
62
  ---
54
63
 
package/CLAUDE.md CHANGED
@@ -137,6 +137,16 @@ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --passwor
137
137
  - `prisma-type-caster.js`: Type casting and normalization
138
138
  - `prisma-relation-resolver.js`: Relation mapping and transformations
139
139
  - `prisma-client-loader.js`: Utility for loading Prisma Client instances
140
+ - `prisma-modules-validator.js`: Validates the `prismaModules` object passed by the consumer
141
+ - **Prisma is not a dependency of this package**: no Prisma package is listed in `package.json`, no `lib/` file
142
+ requires `@prisma/*`. Consumers install `prisma`, `@prisma/client` and `@prisma/adapter-mariadb` themselves and
143
+ pass everything through one `prismaModules` object: `{PrismaClient, Prisma, PrismaAdapter, adapter, client}`.
144
+ `PrismaAdapter` is any Prisma driver adapter class (instantiated with the connection string), `adapter` is an
145
+ already instantiated adapter used as is, the package never forces a specific adapter.
146
+ `PrismaDataServer.connect()` validates it with `PrismaModulesValidator` (capability checks on the instance:
147
+ `$connect`, `$disconnect`, `$queryRaw`, `$queryRawUnsafe`, `$executeRawUnsafe`, `$transaction`,
148
+ `_runtimeDataModel`) and builds the client from `PrismaClient` + `PrismaClientLoader.resolveAdapter()` only
149
+ when no `client` was passed. `Prisma.DbNull` is read from `prismaModules.Prisma`.
140
150
  - Features:
141
151
  - Schema-first approach with auto-introspection
142
152
  - Type-safe queries with Prisma Client
@@ -284,6 +294,26 @@ All generated entity relations follow the `related_*` prefix pattern:
284
294
 
285
295
  This pattern is consistent across all ORM drivers and is defined in `entities-config.js`.
286
296
 
297
+ ## Reference Properties Delete Rule
298
+
299
+ Every generated `type: 'reference'` property also carries the foreign key referential action as
300
+ `onDelete: '<rule>'`, so consumers can tell what a parent delete does to the child rows without querying the
301
+ schema themselves.
302
+
303
+ - `MysqlTablesProvider` joins `information_schema.REFERENTIAL_CONSTRAINTS` on the FK constraint name and stores
304
+ the raw `DELETE_RULE` as `column.referencedDeleteRule`.
305
+ - `BaseGenerator.mapDeleteRule()` normalizes it to camel case, matching the Prisma vocabulary: `CASCADE` becomes
306
+ `cascade`, `SET NULL` becomes `setNull`, `NO ACTION` becomes `noAction`, `SET DEFAULT` becomes `setDefault`,
307
+ `RESTRICT` stays `restrict`.
308
+ - `BaseGenerator.addReferenceDeleteRule()` pushes the attribute, called from
309
+ `EntitiesGeneration.addTypeAttribute()` right after the `alias`. Only the entities are affected, the ORM model
310
+ generation does not emit it.
311
+ - The property is omitted when there is no rule to report, so an entity generated before this existed simply has
312
+ no `onDelete` and consumers must treat a missing value as unknown rather than assuming a default.
313
+
314
+ Regenerating entities is required after changing a foreign key referential action: the rule is read live from
315
+ `information_schema`, not from `prisma/schema.prisma`, and `prisma db pull` alone does not update it.
316
+
287
317
  ## Prisma Driver Validation System
288
318
 
289
319
  ### ensureRequiredFields() Method
@@ -450,7 +480,7 @@ The `prepareDataWithRelations()` method (lines 156-199) automatically converts F
450
480
 
451
481
  **Method:**
452
482
  ```javascript
453
- PrismaClientLoader.load(projectPath, customPath, connectionData)
483
+ PrismaClientLoader.load(projectPath, customPath, connectionData, prismaModules)
454
484
  ```
455
485
 
456
486
  **Parameters:**
@@ -463,27 +493,33 @@ PrismaClientLoader.load(projectPath, customPath, connectionData)
463
493
  - `host` (string): Database host
464
494
  - `port` (number): Database port
465
495
  - `database` (string): Database name
496
+ - `prismaModules` (object): Must contain a `PrismaAdapter` class or an `adapter` instance, required from the
497
+ consumer project (any Prisma driver adapter)
466
498
 
467
- **Returns:** PrismaClient instance or null on error
499
+ **Returns:** the completed `prismaModules` object (`PrismaClient`, `Prisma`, the adapter, `client`) or null on error
468
500
 
469
501
  **Behavior:**
470
502
  - If `customPath` is provided, uses that path
471
503
  - Otherwise, uses a default path: `projectPath/prisma/client`
472
504
  - Validates that Prisma Client exists at the path
473
- - Requires `prismaModule.PrismaClient` export
474
- - If `connectionData` is null: Creates adapter using `process.env.DATABASE_URL`
475
- - If `connectionData` is provided: Builds connection string and creates `PrismaMariaDb` adapter
476
- - Returns initialized PrismaClient instance
477
- - **Prisma v7**: Uses `@prisma/adapter-mariadb` - `PrismaClient` constructor receives `{ adapter }` instead of `{ datasources }`
505
+ - Requires `prismaModule.PrismaClient` export, copies `PrismaClient` and `Prisma` from it into `prismaModules`
506
+ - If `connectionData` is null: Creates adapter using `process.env.RELDENS_DB_URL`
507
+ - If `connectionData` is provided: Builds connection string and creates the adapter
508
+ - `createWithAdapter(prismaModules, connectionUrl)` validates the object with `PrismaModulesValidator`, sets
509
+ `prismaModules.client` and returns the object
510
+ - `resolveAdapter(prismaModules, connectionUrl)` returns `prismaModules.adapter` when present, otherwise
511
+ `new prismaModules.PrismaAdapter(connectionUrl)`; also used by `PrismaDataServer.connect()`
512
+ - **Prisma v7**: `PrismaClient` constructor receives `{ adapter }` instead of `{ datasources }`
478
513
 
479
514
  **Usage Examples:**
480
515
 
481
516
  Using the default connection from schema:
482
517
  ```javascript
483
518
  const { PrismaClientLoader } = require('@reldens/storage');
519
+ const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
484
520
 
485
- const prismaClient = PrismaClientLoader.load(process.cwd(), null, null);
486
- if(!prismaClient){
521
+ let prismaModules = PrismaClientLoader.load(process.cwd(), null, null, {PrismaAdapter: PrismaMariaDb});
522
+ if(!prismaModules){
487
523
  console.error('Failed to load Prisma client');
488
524
  process.exit(1);
489
525
  }
@@ -492,8 +528,9 @@ if(!prismaClient){
492
528
  Using custom connection:
493
529
  ```javascript
494
530
  const { PrismaClientLoader } = require('@reldens/storage');
531
+ const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
495
532
 
496
- const prismaClient = PrismaClientLoader.load(
533
+ let prismaModules = PrismaClientLoader.load(
497
534
  process.cwd(),
498
535
  null,
499
536
  {
@@ -503,15 +540,27 @@ const prismaClient = PrismaClientLoader.load(
503
540
  host: 'localhost',
504
541
  port: 3306,
505
542
  database: 'mydb'
506
- }
543
+ },
544
+ {PrismaAdapter: PrismaMariaDb}
507
545
  );
508
546
 
509
- if(!prismaClient){
547
+ if(!prismaModules){
510
548
  console.error('Failed to load Prisma client');
511
549
  process.exit(1);
512
550
  }
513
551
  ```
514
552
 
553
+ **CLI:** `bin/reldens-storage.js` resolves the adapter package from `--prismaAdapter` (default
554
+ `@prisma/adapter-mariadb`, looked up in `[path]/node_modules` or used as an absolute path) and the class from
555
+ `--prismaAdapterClass` (default `PrismaMariaDb`), then passes `{PrismaAdapter}` to the loader and the resulting
556
+ `prismaModules` to `EntitiesGenerator`.
557
+
558
+ **Tests:** the Prisma driver runs only when `RELDENS_TEST_PRISMA_ENABLED=1` is set (in the shell or in
559
+ `tests/.env.test`) and the three Prisma packages resolve (`TestHelpers.isPrismaEnabled()`). Otherwise Prisma is
560
+ not mentioned in the test output at all. Install them locally without saving:
561
+ `npm install --no-save prisma@7.9.1 @prisma/client@7.9.1 @prisma/adapter-mariadb@7.9.1`. See
562
+ `.claude/prisma-setup.md` for the full install, run and uninstall workflow.
563
+
515
564
  **Used By:**
516
565
  - `bin/reldens-storage.js`: CLI entity generator
517
566
  - External packages: `@reldens/cms` CLI tools (update-password, generate-entities, generate-sitemap)
package/README.md CHANGED
@@ -47,6 +47,9 @@ npx reldens-storage generateEntities --user=[dbuser] --pass=[dbpass] --database=
47
47
  - `--host=[host]` - Database host (default: localhost)
48
48
  - `--port=[port]` - Database port (default: 3306)
49
49
  - `--path=[path]` - Project path for output files (default: current directory)
50
+ - `--prismaClientPath=[path]` - Prisma only: path to the generated Prisma client (default: `[path]/prisma/client`)
51
+ - `--prismaAdapter=[package-or-path]` - Prisma only: driver adapter package resolved from `[path]/node_modules`, or an absolute path (default: `@prisma/adapter-mariadb`)
52
+ - `--prismaAdapterClass=[export-name]` - Prisma only: adapter class exported by that package (default: `PrismaMariaDb`)
50
53
  - `--override` - Regenerate all files even if they exist
51
54
 
52
55
  **Smart Generation:**
@@ -141,7 +144,12 @@ const entities = server.generateEntities();
141
144
 
142
145
  ### Using Prisma
143
146
 
144
- First, generate your Prisma schema:
147
+ Prisma is not installed by this package. Install it in your project first:
148
+ ```bash
149
+ npm install prisma @prisma/client @prisma/adapter-mariadb
150
+ ```
151
+
152
+ Then generate your Prisma schema:
145
153
  ```bash
146
154
  npx reldens-generate-prisma-schema --host=localhost --port=3306 --user=dbuser --password=dbpass --database=dbname
147
155
  ```
@@ -160,9 +168,12 @@ Or pass parameters directly:
160
168
  npx reldens-generate-prisma-schema --host=your-rds-host.amazonaws.com --port=3306 --user=dbuser --password=dbpass --database=dbname --dbParams="authPlugin=mysql_native_password&sslmode=require"
161
169
  ```
162
170
 
163
- Then, use the PrismaDataServer in your code:
171
+ Then, pass your Prisma classes to the PrismaDataServer through the `prismaModules` object. Any Prisma driver
172
+ adapter works, `@prisma/adapter-mariadb` is only the example:
164
173
  ```javascript
165
174
  const { PrismaDataServer } = require('@reldens/storage');
175
+ const { PrismaClient, Prisma } = require('./prisma/client');
176
+ const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
166
177
 
167
178
  const server = new PrismaDataServer({
168
179
  client: 'mysql',
@@ -173,13 +184,23 @@ const server = new PrismaDataServer({
173
184
  host: 'localhost',
174
185
  port: 3306
175
186
  },
176
- rawEntities: yourEntities
187
+ rawEntities: yourEntities,
188
+ prismaModules: {PrismaClient, Prisma, PrismaAdapter: PrismaMariaDb}
177
189
  });
178
190
 
179
191
  await server.connect();
180
192
  const entities = server.generateEntities();
181
193
  ```
182
194
 
195
+ The `prismaModules` object:
196
+ - `PrismaClient`: the class exported by your generated client (required unless `client` is passed)
197
+ - `Prisma`: the namespace exported by your generated client, used for `Prisma.DbNull` (required)
198
+ - `PrismaAdapter`: any Prisma driver adapter class, instantiated with the connection string (required unless `adapter` or `client` is passed)
199
+ - `adapter`: an already instantiated Prisma driver adapter, used as is (optional, replaces `PrismaAdapter`)
200
+ - `client`: an already instantiated Prisma client (optional, skips the client construction)
201
+
202
+ The object is validated on `connect()`, the driver refuses to start when a required class or method is missing.
203
+
183
204
  Note: The PrismaDataServer requires the Prisma schema to be generated first. Make sure to run the `reldens-generate-prisma-schema` command before using PrismaDataServer.
184
205
 
185
206
  ### Loading Prisma Client Programmatically
@@ -189,9 +210,10 @@ If you need to load a Prisma Client instance in your CLI tools or applications:
189
210
  Using the default connection from schema:
190
211
  ```javascript
191
212
  const { PrismaClientLoader } = require('@reldens/storage');
213
+ const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
192
214
 
193
- const prismaClient = PrismaClientLoader.load(process.cwd(), null, null);
194
- if(!prismaClient){
215
+ let prismaModules = PrismaClientLoader.load(process.cwd(), null, null, {PrismaAdapter: PrismaMariaDb});
216
+ if(!prismaModules){
195
217
  console.error('Failed to load Prisma client');
196
218
  process.exit(1);
197
219
  }
@@ -200,8 +222,9 @@ if(!prismaClient){
200
222
  Using custom connection:
201
223
  ```javascript
202
224
  const { PrismaClientLoader } = require('@reldens/storage');
225
+ const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
203
226
 
204
- const prismaClient = PrismaClientLoader.load(
227
+ let prismaModules = PrismaClientLoader.load(
205
228
  process.cwd(),
206
229
  null,
207
230
  {
@@ -211,10 +234,11 @@ const prismaClient = PrismaClientLoader.load(
211
234
  host: 'localhost',
212
235
  port: 3306,
213
236
  database: 'mydb'
214
- }
237
+ },
238
+ {PrismaAdapter: PrismaMariaDb}
215
239
  );
216
240
 
217
- if(!prismaClient){
241
+ if(!prismaModules){
218
242
  console.error('Failed to load Prisma client');
219
243
  process.exit(1);
220
244
  }
@@ -224,6 +248,9 @@ Parameters:
224
248
  - `projectPath`: Project root directory
225
249
  - `customPath`: Optional custom path to a Prisma client (null for default)
226
250
  - `connectionData`: Optional database connection configuration object (null to use schema default)
251
+ - `prismaModules`: Object with the `PrismaAdapter` class (or an `adapter` instance)
252
+
253
+ Returns the completed `prismaModules` object (`PrismaClient`, `Prisma`, the adapter and the instantiated `client`), ready to be passed to `PrismaDataServer`, or null on error.
227
254
 
228
255
  ## Custom Drivers
229
256
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  *
@@ -8,6 +8,7 @@
8
8
 
9
9
  const { EntitiesGenerator } = require('../lib/entities-generator');
10
10
  const { PrismaClientLoader } = require('../lib/prisma/prisma-client-loader');
11
+ const { FileHandler } = require('@reldens/server-utils');
11
12
  const { Logger, sc } = require('@reldens/utils');
12
13
 
13
14
  class StorageEntitiesGenerator
@@ -21,6 +22,8 @@ class StorageEntitiesGenerator
21
22
  this.projectPath = process.cwd();
22
23
  this.isOverride = false;
23
24
  this.prismaClientPath = '';
25
+ this.prismaAdapter = '@prisma/adapter-mariadb';
26
+ this.prismaAdapterClass = 'PrismaMariaDb';
24
27
  this.parseArguments();
25
28
  }
26
29
 
@@ -49,6 +52,14 @@ class StorageEntitiesGenerator
49
52
  this.prismaClientPath = value;
50
53
  continue;
51
54
  }
55
+ if('prismaAdapter' === key){
56
+ this.prismaAdapter = value;
57
+ continue;
58
+ }
59
+ if('prismaAdapterClass' === key){
60
+ this.prismaAdapterClass = value;
61
+ continue;
62
+ }
52
63
  if('pass' === key){
53
64
  this.config['password'] = value;
54
65
  continue;
@@ -102,6 +113,8 @@ class StorageEntitiesGenerator
102
113
  +' --driver=[driver-map-key]'
103
114
  +' --client=[db-client]'
104
115
  +' --prismaClientPath=[path-to-prisma-client]'
116
+ +' --prismaAdapter=[prisma-adapter-package-or-path]'
117
+ +' --prismaAdapterClass=[prisma-adapter-export-name]'
105
118
  +' --path=[project-path] --override',
106
119
  'Optional flags:',
107
120
  ' --override Regenerate all files even if they exist'
@@ -111,13 +124,34 @@ class StorageEntitiesGenerator
111
124
  return true;
112
125
  }
113
126
 
114
- loadPrismaClient(connectionData)
127
+ loadPrismaModules(connectionData)
115
128
  {
116
- let loadedClient = PrismaClientLoader.load(this.projectPath, this.prismaClientPath, connectionData);
117
- if(!loadedClient){
129
+ let adapterPath = FileHandler.joinPaths(this.projectPath, 'node_modules', this.prismaAdapter);
130
+ if(!FileHandler.exists(adapterPath)){
131
+ adapterPath = this.prismaAdapter;
132
+ }
133
+ if(!FileHandler.exists(adapterPath)){
134
+ Logger.critical(
135
+ 'Prisma adapter "'+this.prismaAdapter+'" not found in the project.'
136
+ +' Run: npm install prisma @prisma/client '+this.prismaAdapter
137
+ );
138
+ return null;
139
+ }
140
+ let adapterModule = require(adapterPath);
141
+ if(!sc.isFunction(adapterModule[this.prismaAdapterClass])){
142
+ Logger.critical('Prisma adapter class "'+this.prismaAdapterClass+'" not exported by: '+adapterPath);
143
+ return null;
144
+ }
145
+ let loadedModules = PrismaClientLoader.load(
146
+ this.projectPath,
147
+ this.prismaClientPath,
148
+ connectionData,
149
+ {PrismaAdapter: adapterModule[this.prismaAdapterClass]}
150
+ );
151
+ if(!loadedModules){
118
152
  Logger.info('Please run "npx prisma generate" first or provide --prismaClientPath argument.');
119
153
  }
120
- return loadedClient;
154
+ return loadedModules;
121
155
  }
122
156
 
123
157
  async run()
@@ -135,11 +169,11 @@ class StorageEntitiesGenerator
135
169
  isOverride: this.isOverride
136
170
  };
137
171
  if('prisma' === connectionData.driver){
138
- let prismaClient = this.loadPrismaClient(connectionData);
139
- if(!prismaClient){
172
+ let prismaModules = this.loadPrismaModules(connectionData);
173
+ if(!prismaModules){
140
174
  return false;
141
175
  }
142
- generatorProps.prismaClient = prismaClient;
176
+ generatorProps.prismaModules = prismaModules;
143
177
  }
144
178
  let generator = new EntitiesGenerator(generatorProps);
145
179
  let success = await generator.generate();
package/index.js CHANGED
@@ -20,6 +20,7 @@ const { PrismaDriver } = require('./lib/prisma/prisma-driver');
20
20
  const { PrismaDataServer } = require('./lib/prisma/prisma-data-server');
21
21
  const { PrismaSchemaGenerator } = require('./lib/prisma/prisma-schema-generator');
22
22
  const { PrismaClientLoader } = require('./lib/prisma/prisma-client-loader');
23
+ const { PrismaModulesValidator } = require('./lib/prisma/prisma-modules-validator');
23
24
  const { RELATION_PREFIX } = require('./lib/relation-key');
24
25
 
25
26
  module.exports = {
@@ -49,6 +50,7 @@ module.exports = {
49
50
  PrismaDriver,
50
51
  PrismaSchemaGenerator,
51
52
  PrismaClientLoader,
53
+ PrismaModulesValidator,
52
54
  // entities:
53
55
  EntitiesGenerator,
54
56
  EntityProperties,
@@ -49,7 +49,7 @@ class EntitiesGenerator
49
49
  this.server = sc.get(props, 'server', false);
50
50
  this.connectionData = sc.get(props, 'connectionData', false);
51
51
  this.isOverride = sc.get(props, 'isOverride', false);
52
- this.prismaClient = sc.get(props, 'prismaClient', false);
52
+ this.prismaModules = sc.get(props, 'prismaModules', false);
53
53
  this.generatedEntities = {};
54
54
  this.existingEntities = {};
55
55
  this.existingEntityFields = {};
@@ -264,12 +264,12 @@ class EntitiesGenerator
264
264
  (this.server ? sc.get(this.driversClassMap, this.server.constructor.name, false) : false)
265
265
  );
266
266
  if('prisma' === driverKey){
267
- if(!this.prismaClient && this.server.prisma){
268
- this.prismaClient = this.server.prisma;
267
+ if(!this.prismaModules && this.server.prismaModules){
268
+ this.prismaModules = this.server.prismaModules;
269
269
  }
270
270
  //Logger.debug('Extract Prisma relations metadata.');
271
271
  this.prismaRelationsMetadata = new PrismaRelationsMetadataExtractor({
272
- prismaClient: this.prismaClient,
272
+ prismaClient: sc.get(this.prismaModules, 'client', false),
273
273
  projectRoot: this.server ? this.server.projectRoot : false,
274
274
  projectPath: this.projectPath
275
275
  }).extract();
@@ -357,8 +357,8 @@ class EntitiesGenerator
357
357
  },
358
358
  projectRoot: this.projectPath
359
359
  };
360
- if('prisma' === driverKey && this.prismaClient){
361
- serverConfig.prismaClient = this.prismaClient;
360
+ if('prisma' === driverKey && this.prismaModules){
361
+ serverConfig.prismaModules = this.prismaModules;
362
362
  }
363
363
  this.server = new driverClassMapped(serverConfig);
364
364
  return this.server;
@@ -4,14 +4,14 @@
4
4
  *
5
5
  */
6
6
 
7
- const { Logger } = require('@reldens/utils');
7
+ const { PrismaModulesValidator } = require('./prisma-modules-validator');
8
8
  const { FileHandler } = require('@reldens/server-utils');
9
- const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
9
+ const { Logger, sc } = require('@reldens/utils');
10
10
 
11
11
  class PrismaClientLoader
12
12
  {
13
13
 
14
- static load(projectPath, customPath, connectionData)
14
+ static load(projectPath, customPath, connectionData, prismaModules)
15
15
  {
16
16
  let prismaClientPath = customPath;
17
17
  if(!prismaClientPath){
@@ -27,9 +27,13 @@ class PrismaClientLoader
27
27
  Logger.critical('PrismaClient class not found at: '+prismaClientPath);
28
28
  return null;
29
29
  }
30
+ let completedModules = Object.assign({}, prismaModules, {
31
+ PrismaClient: prismaModule.PrismaClient,
32
+ Prisma: prismaModule.Prisma
33
+ });
30
34
  if(!connectionData){
31
35
  Logger.info('Creating PrismaClient with default connection from schema');
32
- return PrismaClientLoader.createWithAdapter(prismaModule.PrismaClient, process.env.RELDENS_DB_URL);
36
+ return PrismaClientLoader.createWithAdapter(completedModules, process.env.RELDENS_DB_URL);
33
37
  }
34
38
  let connectionString = connectionData.client+'://'
35
39
  +connectionData.user
@@ -38,17 +42,37 @@ class PrismaClientLoader
38
42
  +':'+connectionData.port
39
43
  +'/'+connectionData.database;
40
44
  Logger.info('Creating PrismaClient with connection to: '+connectionData.database);
41
- return PrismaClientLoader.createWithAdapter(prismaModule.PrismaClient, connectionString);
45
+ return PrismaClientLoader.createWithAdapter(completedModules, connectionString);
42
46
  }
43
47
 
44
48
  /**
45
- * @param {Function} PrismaClientClass
49
+ * @param {Object} prismaModules
50
+ * @param {string} connectionUrl
51
+ * @returns {Object|null}
52
+ */
53
+ static createWithAdapter(prismaModules, connectionUrl)
54
+ {
55
+ if(!PrismaModulesValidator.validate(prismaModules)){
56
+ return null;
57
+ }
58
+ prismaModules.client = new prismaModules.PrismaClient({
59
+ adapter: PrismaClientLoader.resolveAdapter(prismaModules, connectionUrl),
60
+ log: ['error']
61
+ });
62
+ return prismaModules;
63
+ }
64
+
65
+ /**
66
+ * @param {Object} prismaModules
46
67
  * @param {string} connectionUrl
47
68
  * @returns {Object}
48
69
  */
49
- static createWithAdapter(PrismaClientClass, connectionUrl)
70
+ static resolveAdapter(prismaModules, connectionUrl)
50
71
  {
51
- return new PrismaClientClass({adapter: new PrismaMariaDb(connectionUrl), log: ['error']});
72
+ if(sc.isObject(prismaModules.adapter)){
73
+ return prismaModules.adapter;
74
+ }
75
+ return new prismaModules.PrismaAdapter(connectionUrl);
52
76
  }
53
77
 
54
78
  }
@@ -7,9 +7,8 @@
7
7
  const { BaseDataServer } = require('../base-data-server');
8
8
  const { PrismaDriver } = require('./prisma-driver');
9
9
  const { MySQLTablesProvider } = require('../mysql-tables-provider');
10
- const { PrismaClient, Prisma } = require('@prisma/client');
11
- const { PrismaMariaDb } = require('@prisma/adapter-mariadb');
12
- const { FileHandler } = require('@reldens/server-utils');
10
+ const { PrismaModulesValidator } = require('./prisma-modules-validator');
11
+ const { PrismaClientLoader } = require('./prisma-client-loader');
13
12
  const { Logger, sc } = require('@reldens/utils');
14
13
 
15
14
  class PrismaDataServer extends BaseDataServer
@@ -18,7 +17,8 @@ class PrismaDataServer extends BaseDataServer
18
17
  constructor(props)
19
18
  {
20
19
  super(props);
21
- this.prisma = sc.get(props, 'prismaClient', false);
20
+ this.prismaModules = sc.get(props, 'prismaModules', false);
21
+ this.prisma = sc.get(this.prismaModules, 'client', false);
22
22
  this.projectRoot = sc.get(props, 'projectRoot', false);
23
23
  this.allRelationMappings = {};
24
24
  }
@@ -28,16 +28,16 @@ class PrismaDataServer extends BaseDataServer
28
28
  if(this.initialized){
29
29
  return this.initialized;
30
30
  }
31
+ if(!PrismaModulesValidator.validate(this.prismaModules)){
32
+ return false;
33
+ }
31
34
  try {
32
- if(!this.prisma && this.defaultClientIsGenerated()){
33
- this.prisma = new PrismaClient({
34
- adapter: new PrismaMariaDb(this.connectString),
35
+ if(!this.prisma){
36
+ this.prisma = new this.prismaModules.PrismaClient({
37
+ adapter: PrismaClientLoader.resolveAdapter(this.prismaModules, this.connectString),
35
38
  log: this.debug ? ['query', 'info', 'warn', 'error'] : ['error']
36
39
  });
37
- }
38
- if(!this.prisma){
39
- Logger.error('Prisma client could not be initialized.');
40
- return false;
40
+ this.prismaModules.client = this.prisma;
41
41
  }
42
42
  await this.prisma.$connect();
43
43
  let dbTest = await this.prisma.$queryRaw`SELECT DATABASE() as current_db`;
@@ -50,20 +50,6 @@ class PrismaDataServer extends BaseDataServer
50
50
  return false;
51
51
  }
52
52
 
53
- defaultClientIsGenerated()
54
- {
55
- let existsFromProjectRoot = false;
56
- if(this.projectRoot){
57
- existsFromProjectRoot = FileHandler.exists(FileHandler.joinPaths(
58
- this.projectRoot, 'node_modules', '@prisma', 'client', 'index.js'
59
- ));
60
- }
61
- let existsFromRelativePath = FileHandler.exists(FileHandler.joinPaths(
62
- __dirname, '..', '..', '..', '@prisma', 'client', 'index.js'
63
- ));
64
- return existsFromProjectRoot || existsFromRelativePath;
65
- }
66
-
67
53
  generateEntities()
68
54
  {
69
55
  if(!this.initialized){
@@ -93,7 +79,7 @@ class PrismaDataServer extends BaseDataServer
93
79
  model: prismaModel,
94
80
  server: this,
95
81
  allRelationMappings: this.allRelationMappings,
96
- prismaDbNull: Prisma.DbNull
82
+ prismaDbNull: this.prismaModules.Prisma.DbNull
97
83
  });
98
84
  }
99
85
  this.entityManager.setEntities(this.entities);
@@ -0,0 +1,63 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaModulesValidator
4
+ *
5
+ */
6
+
7
+ const { Logger, sc } = require('@reldens/utils');
8
+
9
+ class PrismaModulesValidator
10
+ {
11
+
12
+ static validate(prismaModules)
13
+ {
14
+ if(!sc.isObject(prismaModules)){
15
+ Logger.critical('Missing "prismaModules" object.');
16
+ return false;
17
+ }
18
+ if(!sc.isObject(prismaModules.Prisma) || !sc.hasOwn(prismaModules.Prisma, 'DbNull')){
19
+ Logger.critical('Invalid "prismaModules.Prisma", "DbNull" not found.');
20
+ return false;
21
+ }
22
+ if(prismaModules.client){
23
+ return PrismaModulesValidator.validateClientInstance(prismaModules.client);
24
+ }
25
+ if(!sc.isFunction(prismaModules.PrismaClient)){
26
+ Logger.critical('Invalid "prismaModules.PrismaClient".');
27
+ return false;
28
+ }
29
+ if(sc.isObject(prismaModules.adapter)){
30
+ return true;
31
+ }
32
+ if(!sc.isFunction(prismaModules.PrismaAdapter)){
33
+ Logger.critical('Missing "prismaModules.adapter" instance or "prismaModules.PrismaAdapter" class.');
34
+ return false;
35
+ }
36
+ return true;
37
+ }
38
+
39
+ static validateClientInstance(client)
40
+ {
41
+ let requiredMethods = [
42
+ '$connect',
43
+ '$disconnect',
44
+ '$queryRaw',
45
+ '$queryRawUnsafe',
46
+ '$executeRawUnsafe',
47
+ '$transaction'
48
+ ];
49
+ let missingMethods = requiredMethods.filter(method => !sc.isFunction(client[method]));
50
+ if(0 < missingMethods.length){
51
+ Logger.critical('Invalid Prisma client instance, missing: '+missingMethods.join(', '));
52
+ return false;
53
+ }
54
+ if(!sc.isObject(sc.get(client, '_runtimeDataModel', false))){
55
+ Logger.critical('Invalid Prisma client instance, "_runtimeDataModel" not found.');
56
+ return false;
57
+ }
58
+ return true;
59
+ }
60
+
61
+ }
62
+
63
+ module.exports.PrismaModulesValidator = PrismaModulesValidator;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/storage",
3
3
  "scope": "@reldens",
4
- "version": "0.111.0",
4
+ "version": "0.113.0",
5
5
  "description": "Reldens - Storage",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -51,17 +51,14 @@
51
51
  "test:driver": "node tests/run-tests.js --driver="
52
52
  },
53
53
  "dependencies": {
54
- "@mikro-orm/core": "7.1.11",
55
- "@mikro-orm/mongodb": "7.1.11",
56
- "@mikro-orm/mysql": "7.1.11",
57
- "@prisma/adapter-mariadb": "7.9.1",
58
- "@prisma/client": "7.9.1",
59
- "@reldens/server-utils": "^0.56.0",
54
+ "@mikro-orm/core": "7.1.14",
55
+ "@mikro-orm/mongodb": "7.1.14",
56
+ "@mikro-orm/mysql": "7.1.14",
57
+ "@reldens/server-utils": "^0.57.0",
60
58
  "@reldens/utils": "^0.57.0",
61
59
  "knex": "3.3.0",
62
60
  "mysql": "2.18.1",
63
- "mysql2": "3.23.3",
64
- "objection": "3.1.5",
65
- "prisma": "7.9.1"
61
+ "mysql2": "3.24.3",
62
+ "objection": "3.1.5"
66
63
  }
67
64
  }
@@ -5,3 +5,4 @@ RELDENS_TEST_DB_PASSWORD=test_password
5
5
  RELDENS_TEST_DB_NAME=reldens_storage_test
6
6
  RELDENS_TEST_DB_CLIENT=mysql
7
7
  RELDENS_DB_URL=mysql://test_user:test_password@localhost:3306/reldens_storage_test
8
+ RELDENS_TEST_PRISMA_ENABLED=0
@@ -20,7 +20,7 @@ const EntitiesGenerationTest = require('./unit/test-entities-generation');
20
20
  if(!process.env.RELDENS_TEST_DB_HOST){
21
21
  let envPath = FileHandler.joinPaths(__dirname, '.env.test');
22
22
  if(FileHandler.exists(envPath)){
23
- require('dotenv').config({path: envPath});
23
+ process.loadEnvFile(envPath);
24
24
  }
25
25
  }
26
26
 
@@ -117,7 +117,7 @@ class RunTests
117
117
 
118
118
  async runIntegrationTests()
119
119
  {
120
- let driverNames = ['objection-js', 'mikro-orm', 'prisma'];
120
+ let driverNames = TestHelpers.activeDriverNames();
121
121
  if(this.driver){
122
122
  driverNames = [this.driver];
123
123
  }
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  const { TestRunner, assert } = require('../utils/test-runner');
9
+ const { TestHelpers } = require('../utils/test-helpers');
9
10
  const { ObjectionJsDriver } = require('../../lib/objection-js/objection-js-driver');
10
11
  const { MikroOrmDriver } = require('../../lib/mikro-orm/mikro-orm-driver');
11
12
  const { PrismaDriver } = require('../../lib/prisma/prisma-driver');
@@ -18,9 +19,11 @@ class DriversUnitTest
18
19
  this.runner = new TestRunner();
19
20
  this.DRIVERS = [
20
21
  {name: 'objection-js', class: ObjectionJsDriver},
21
- {name: 'mikro-orm', class: MikroOrmDriver},
22
- {name: 'prisma', class: PrismaDriver}
22
+ {name: 'mikro-orm', class: MikroOrmDriver}
23
23
  ];
24
+ if(TestHelpers.isPrismaEnabled()){
25
+ this.DRIVERS.push({name: 'prisma', class: PrismaDriver});
26
+ }
24
27
  this.SHARED_PUBLIC_METHODS = [
25
28
  'databaseName',
26
29
  'id',
@@ -23,7 +23,7 @@ class DriverRegistry
23
23
  };
24
24
  this.schemaPath = FileHandler.joinPaths(__dirname, '..', 'fixtures', 'sql', 'test-schema.sql');
25
25
  this.repoNames = ['testCategories', 'testProducts', 'testReviews'];
26
- this.driverNames = ['objection-js', 'mikro-orm', 'prisma'];
26
+ this.driverNames = TestHelpers.activeDriverNames();
27
27
  this.skipGeneration = false;
28
28
  }
29
29
 
@@ -6,8 +6,10 @@
6
6
 
7
7
  const { FileHandler } = require('@reldens/server-utils');
8
8
  const { Logger, sc } = require('@reldens/utils');
9
- const { exec } = require('child_process');
9
+ const { exec, execSync } = require('child_process');
10
10
  const { promisify } = require('util');
11
+ const { delimiter } = require('path');
12
+ const NodeModule = require('module');
11
13
  const execAsync = promisify(exec);
12
14
 
13
15
  class TestHelpers
@@ -44,11 +46,11 @@ class TestHelpers
44
46
  rawEntities: rawEntities
45
47
  };
46
48
  if('prisma' === driverName){
47
- let prismaClient = await this.loadPrismaClient(process.cwd(), config);
48
- if(!prismaClient){
49
- throw new Error('Failed to load Prisma client');
49
+ let prismaModules = await this.loadPrismaModules(process.cwd(), config);
50
+ if(!prismaModules){
51
+ throw new Error('Failed to load Prisma modules');
50
52
  }
51
- serverConfig.prismaClient = prismaClient;
53
+ serverConfig.prismaModules = prismaModules;
52
54
  }
53
55
  let DataServerClass = this.getDataServerClass(driverName);
54
56
  let dataServer = new DataServerClass(serverConfig);
@@ -253,7 +255,7 @@ class TestHelpers
253
255
  return true;
254
256
  }
255
257
 
256
- static verifyPackageInstallation(packageName, version)
258
+ static verifyPackageInstallation(packageName, version, isOptional)
257
259
  {
258
260
  let packagePath = FileHandler.joinPaths(
259
261
  process.cwd(),
@@ -261,7 +263,18 @@ class TestHelpers
261
263
  packageName,
262
264
  'package.json'
263
265
  );
266
+ if(!FileHandler.exists(packagePath) && isOptional && process.env.RELDENS_TEST_NPM_GLOBAL_ROOT){
267
+ packagePath = FileHandler.joinPaths(
268
+ process.env.RELDENS_TEST_NPM_GLOBAL_ROOT,
269
+ packageName,
270
+ 'package.json'
271
+ );
272
+ }
264
273
  if(!FileHandler.exists(packagePath)){
274
+ if(isOptional){
275
+ Logger.warning('Optional package not installed: '+packageName);
276
+ return false;
277
+ }
265
278
  Logger.critical('Required package not installed: '+packageName);
266
279
  return false;
267
280
  }
@@ -278,26 +291,104 @@ class TestHelpers
278
291
  static verifyAllPackages()
279
292
  {
280
293
  let required = [
281
- {name: '@mikro-orm/core', version: '7.1.11'},
282
- {name: '@mikro-orm/mongodb', version: '7.1.11'},
283
- {name: '@mikro-orm/mysql', version: '7.1.11'},
284
- {name: '@prisma/client', version: '7.9.1'},
285
- {name: '@prisma/adapter-mariadb', version: '7.9.1'},
294
+ {name: '@mikro-orm/core', version: '7.1.14'},
295
+ {name: '@mikro-orm/mongodb', version: '7.1.14'},
296
+ {name: '@mikro-orm/mysql', version: '7.1.14'},
286
297
  {name: 'knex', version: '3.3.0'},
287
298
  {name: 'mysql', version: '2.18.1'},
288
- {name: 'mysql2', version: '3.23.3'},
289
- {name: 'objection', version: '3.1.5'},
290
- {name: 'prisma', version: '7.9.1'}
299
+ {name: 'mysql2', version: '3.24.3'},
300
+ {name: 'objection', version: '3.1.5'}
291
301
  ];
302
+ if(this.isPrismaFlagEnabled()){
303
+ required.push({name: 'prisma', version: '7.9.1', optional: true});
304
+ required.push({name: '@prisma/client', version: '7.9.1', optional: true});
305
+ required.push({name: '@prisma/adapter-mariadb', version: '7.9.1', optional: true});
306
+ }
292
307
  let allVerified = true;
293
308
  for(let pkg of required){
294
- if(!this.verifyPackageInstallation(pkg.name, pkg.version)){
309
+ let isOptional = sc.get(pkg, 'optional', false);
310
+ if(!this.verifyPackageInstallation(pkg.name, pkg.version, isOptional) && !isOptional){
295
311
  allVerified = false;
296
312
  }
297
313
  }
298
314
  return allVerified;
299
315
  }
300
316
 
317
+ static isPrismaAvailable()
318
+ {
319
+ let resolvableEntries = {
320
+ 'prisma': 'prisma/package.json',
321
+ '@prisma/client': '@prisma/client',
322
+ '@prisma/adapter-mariadb': '@prisma/adapter-mariadb'
323
+ };
324
+ for(let packageName of Object.keys(resolvableEntries)){
325
+ if(!this.canResolvePackage(resolvableEntries[packageName])){
326
+ Logger.warning('Prisma package not installed, Prisma driver tests skipped: '+packageName);
327
+ return false;
328
+ }
329
+ }
330
+ return true;
331
+ }
332
+
333
+ static canResolvePackage(specifier)
334
+ {
335
+ try {
336
+ require.resolve(specifier);
337
+ return true;
338
+ } catch(error) {
339
+ this.registerNpmGlobalPaths();
340
+ }
341
+ try {
342
+ require.resolve(specifier);
343
+ return true;
344
+ } catch(error) {
345
+ return false;
346
+ }
347
+ }
348
+
349
+ static registerNpmGlobalPaths()
350
+ {
351
+ if(process.env.RELDENS_TEST_NPM_GLOBAL_ROOT){
352
+ return process.env.RELDENS_TEST_NPM_GLOBAL_ROOT;
353
+ }
354
+ let globalRoot = '';
355
+ try {
356
+ globalRoot = execSync('npm root -g', {encoding: 'utf8'}).trim();
357
+ } catch(error) {
358
+ Logger.warning('Could not resolve the npm global root: '+error.message);
359
+ return '';
360
+ }
361
+ let extraPaths = [globalRoot, FileHandler.joinPaths(globalRoot, '@prisma', 'client', 'node_modules')];
362
+ let currentPaths = process.env.NODE_PATH ? process.env.NODE_PATH.split(delimiter) : [];
363
+ process.env.NODE_PATH = [...currentPaths, ...extraPaths].join(delimiter);
364
+ NodeModule._initPaths();
365
+ process.env.RELDENS_TEST_NPM_GLOBAL_ROOT = globalRoot;
366
+ Logger.info('Registered npm global root for Prisma packages: '+globalRoot);
367
+ return globalRoot;
368
+ }
369
+
370
+ static isPrismaFlagEnabled()
371
+ {
372
+ return '1' === String(process.env.RELDENS_TEST_PRISMA_ENABLED);
373
+ }
374
+
375
+ static isPrismaEnabled()
376
+ {
377
+ if(!this.isPrismaFlagEnabled()){
378
+ return false;
379
+ }
380
+ return this.isPrismaAvailable();
381
+ }
382
+
383
+ static activeDriverNames()
384
+ {
385
+ let driverNames = ['objection-js', 'mikro-orm'];
386
+ if(this.isPrismaEnabled()){
387
+ driverNames.push('prisma');
388
+ }
389
+ return driverNames;
390
+ }
391
+
301
392
  static async prismaClientExists()
302
393
  {
303
394
  return FileHandler.exists(FileHandler.joinPaths(
@@ -372,7 +463,7 @@ class TestHelpers
372
463
  return subprocessSuccess;
373
464
  }
374
465
 
375
- static async loadPrismaClient(projectRoot, config)
466
+ static async loadPrismaModules(projectRoot, config)
376
467
  {
377
468
  try {
378
469
  let clientPath = FileHandler.joinPaths(projectRoot, 'prisma', 'client');
@@ -380,20 +471,25 @@ class TestHelpers
380
471
  Logger.critical('Prisma client path does not exist: '+clientPath);
381
472
  return false;
382
473
  }
383
- let { PrismaClient } = require(clientPath);
384
- if(!PrismaClient){
474
+ let prismaModule = require(clientPath);
475
+ if(!prismaModule.PrismaClient){
385
476
  Logger.critical('PrismaClient not found in module.');
386
477
  return false;
387
478
  }
388
- let { PrismaMariaDb } = require('@prisma/adapter-mariadb');
479
+ let adapterModule = require('@prisma/adapter-mariadb');
389
480
  let adapterConfig = config
390
481
  ? { host: config.host, port: config.port, user: config.user, password: config.password, database: config.database }
391
482
  : process.env.RELDENS_DB_URL;
392
- let client = new PrismaClient({ adapter: new PrismaMariaDb(adapterConfig) });
483
+ let client = new prismaModule.PrismaClient({ adapter: new adapterModule.PrismaMariaDb(adapterConfig) });
393
484
  await client.$connect();
394
- return client;
485
+ return {
486
+ PrismaClient: prismaModule.PrismaClient,
487
+ Prisma: prismaModule.Prisma,
488
+ PrismaAdapter: adapterModule.PrismaMariaDb,
489
+ client
490
+ };
395
491
  } catch(error) {
396
- Logger.critical('Failed to load Prisma client: '+error.message);
492
+ Logger.critical('Failed to load Prisma modules: '+error.message);
397
493
  return false;
398
494
  }
399
495
  }
@@ -562,11 +658,10 @@ class TestHelpers
562
658
  server: dataServer
563
659
  };
564
660
  if('prisma' === driverName){
565
- let prismaClient = dataServer.prisma;
566
- if(!prismaClient){
661
+ if(!dataServer.prisma){
567
662
  throw new Error('Prisma client not available on dataServer');
568
663
  }
569
- generatorProps.prismaClient = prismaClient;
664
+ generatorProps.prismaModules = dataServer.prismaModules;
570
665
  }
571
666
  let generator = new EntitiesGenerator(generatorProps);
572
667
  let result = await generator.generate();