@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.
- package/.claude/prisma-setup.md +162 -0
- package/.claude/test-architecture.md +27 -18
- package/CLAUDE.md +61 -12
- package/README.md +35 -8
- package/bin/reldens-storage.js +42 -8
- package/index.js +2 -0
- package/lib/entities-generator.js +6 -6
- package/lib/prisma/prisma-client-loader.js +32 -8
- package/lib/prisma/prisma-data-server.js +12 -26
- package/lib/prisma/prisma-modules-validator.js +63 -0
- package/package.json +7 -10
- package/tests/.env.test.example +1 -0
- package/tests/run-tests.js +2 -2
- package/tests/unit/test-drivers.js +5 -2
- package/tests/utils/driver-registry.js +1 -1
- package/tests/utils/test-helpers.js +120 -25
|
@@ -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
|
|
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
|
|
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
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
let
|
|
36
|
-
|
|
37
|
-
// 3. Pass
|
|
38
|
-
serverConfig.
|
|
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
|
-
**
|
|
43
|
-
- `
|
|
44
|
-
- `
|
|
45
|
-
-
|
|
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
|
|
50
|
-
-
|
|
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
|
|
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.
|
|
475
|
-
- If `connectionData` is provided: Builds connection string and creates
|
|
476
|
-
-
|
|
477
|
-
|
|
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
|
-
|
|
486
|
-
if(!
|
|
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
|
-
|
|
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(!
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
194
|
-
if(!
|
|
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
|
-
|
|
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(!
|
|
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
|
|
package/bin/reldens-storage.js
CHANGED
|
@@ -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
|
-
|
|
127
|
+
loadPrismaModules(connectionData)
|
|
115
128
|
{
|
|
116
|
-
let
|
|
117
|
-
if(!
|
|
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
|
|
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
|
|
139
|
-
if(!
|
|
172
|
+
let prismaModules = this.loadPrismaModules(connectionData);
|
|
173
|
+
if(!prismaModules){
|
|
140
174
|
return false;
|
|
141
175
|
}
|
|
142
|
-
generatorProps.
|
|
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.
|
|
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.
|
|
268
|
-
this.
|
|
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.
|
|
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.
|
|
361
|
-
serverConfig.
|
|
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 {
|
|
7
|
+
const { PrismaModulesValidator } = require('./prisma-modules-validator');
|
|
8
8
|
const { FileHandler } = require('@reldens/server-utils');
|
|
9
|
-
const {
|
|
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(
|
|
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(
|
|
45
|
+
return PrismaClientLoader.createWithAdapter(completedModules, connectionString);
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
/**
|
|
45
|
-
* @param {
|
|
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
|
|
70
|
+
static resolveAdapter(prismaModules, connectionUrl)
|
|
50
71
|
{
|
|
51
|
-
|
|
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 {
|
|
11
|
-
const {
|
|
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.
|
|
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
|
|
33
|
-
this.prisma = new PrismaClient({
|
|
34
|
-
adapter:
|
|
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.
|
|
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.
|
|
55
|
-
"@mikro-orm/mongodb": "7.1.
|
|
56
|
-
"@mikro-orm/mysql": "7.1.
|
|
57
|
-
"@
|
|
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.
|
|
64
|
-
"objection": "3.1.5"
|
|
65
|
-
"prisma": "7.9.1"
|
|
61
|
+
"mysql2": "3.24.3",
|
|
62
|
+
"objection": "3.1.5"
|
|
66
63
|
}
|
|
67
64
|
}
|
package/tests/.env.test.example
CHANGED
package/tests/run-tests.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
48
|
-
if(!
|
|
49
|
-
throw new Error('Failed to load Prisma
|
|
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.
|
|
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.
|
|
282
|
-
{name: '@mikro-orm/mongodb', version: '7.1.
|
|
283
|
-
{name: '@mikro-orm/mysql', version: '7.1.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
566
|
-
if(!prismaClient){
|
|
661
|
+
if(!dataServer.prisma){
|
|
567
662
|
throw new Error('Prisma client not available on dataServer');
|
|
568
663
|
}
|
|
569
|
-
generatorProps.
|
|
664
|
+
generatorProps.prismaModules = dataServer.prismaModules;
|
|
570
665
|
}
|
|
571
666
|
let generator = new EntitiesGenerator(generatorProps);
|
|
572
667
|
let result = await generator.generate();
|