@reldens/storage 0.33.0 → 0.34.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  # Reldens - Storage
4
4
 
5
5
  ## About this package
6
- This package provides standardized database drivers for Reldens projects.
6
+ This package provides standardized database drivers for Reldens projects.
7
7
  It ensures consistent data access methods across different database types and ORM implementations.
8
8
 
9
9
  ## Features
@@ -11,6 +11,7 @@ It ensures consistent data access methods across different database types and OR
11
11
  ### ORM Support
12
12
  - **Objection JS** (via Knex) - For SQL databases (recommended)
13
13
  - **Mikro-ORM** - For MongoDB/NoSQL support
14
+ - **Prisma** - Modern database toolkit
14
15
 
15
16
  ### Entity Management
16
17
  - Standardized CRUD operations
@@ -35,6 +36,20 @@ Options:
35
36
  - `--client` - Database client (mysql|mysql2|mongodb)
36
37
  - `--path` - Project path for output files
37
38
 
39
+ Generate Prisma schema for Prisma database:
40
+ ```bash
41
+ npx reldens-storage-prisma --user=dbuser --pass=dbpass --database=dbname
42
+ ```
43
+
44
+ Options:
45
+ - `--user` - Database username
46
+ - `--pass` - Database password
47
+ - `--host` - Database host (default: localhost)
48
+ - `--port` - Database port (default: 3306)
49
+ - `--database` - Database name
50
+ - `--client` - Database client (mysql|mysql2|postgresql|mongodb)
51
+ - `--path` - Project path for output files
52
+
38
53
  ## Usage Examples
39
54
 
40
55
  ### SQL with Objection JS
@@ -77,6 +92,35 @@ await server.connect();
77
92
  const entities = server.generateEntities();
78
93
  ```
79
94
 
95
+ ### Using Prisma
96
+
97
+ First, generate your Prisma schema:
98
+ ```bash
99
+ npx reldens-storage-prisma --user=dbuser --pass=dbpass --database=dbname
100
+ ```
101
+
102
+ Then, use the PrismaDataServer in your code:
103
+ ```javascript
104
+ const { PrismaDataServer } = require('@reldens/storage');
105
+
106
+ const server = new PrismaDataServer({
107
+ client: 'mysql2',
108
+ config: {
109
+ user: 'reldens',
110
+ password: 'reldens',
111
+ database: 'reldens',
112
+ host: 'localhost',
113
+ port: 3306
114
+ },
115
+ rawEntities: yourEntities
116
+ });
117
+
118
+ await server.connect();
119
+ const entities = server.generateEntities();
120
+ ```
121
+
122
+ Note: The PrismaDataServer requires the Prisma schema to be generated first. Make sure to run the `reldens-storage-prisma` command before using PrismaDataServer.
123
+
80
124
  ## Custom Drivers
81
125
 
82
126
  You can create custom storage drivers by extending the base classes:
@@ -102,4 +146,3 @@ const appServer = new ServerManager(serverConfig, eventsManager, customDriver);
102
146
  ### [Reldens](https://www.reldens.com/ "Reldens")
103
147
 
104
148
  ##### [By DwDeveloper](https://www.dwdeveloper.com/ "DwDeveloper")
105
-
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ *
5
+ * Reldens - Generate Prisma Schema CLI
6
+ *
7
+ */
8
+
9
+ const { PrismaSchemaGenerator } = require('../lib/prisma/prisma-schema-generator');
10
+ const { Logger } = require('@reldens/utils');
11
+
12
+ let args = process.argv.slice(2);
13
+
14
+ let connectionData = {
15
+ client: 'mysql2',
16
+ config: {
17
+ user: '',
18
+ password: '',
19
+ host: 'localhost',
20
+ database: '',
21
+ port: 3306
22
+ }
23
+ };
24
+
25
+ let projectPath = process.cwd();
26
+ for(let i = 0; i < args.length; i++){
27
+ let arg = args[i];
28
+ if(!arg.startsWith('--')){
29
+ continue;
30
+ }
31
+ let [key, value] = arg.substring(2).split('=');
32
+ if(!key || !value){
33
+ continue;
34
+ }
35
+ if('pass' === key){
36
+ connectionData.config.password = value;
37
+ continue;
38
+ }
39
+ if('path' === key){
40
+ projectPath = value;
41
+ continue;
42
+ }
43
+ if('user' === key){
44
+ connectionData.config.user = value;
45
+ continue;
46
+ }
47
+ if('database' === key){
48
+ connectionData.config.database = value;
49
+ continue;
50
+ }
51
+ if('host' === key){
52
+ connectionData.config.host = value;
53
+ continue;
54
+ }
55
+ if('port' === key){
56
+ connectionData.config.port = value;
57
+ continue;
58
+ }
59
+ if('client' === key){
60
+ connectionData.client = value;
61
+ }
62
+ }
63
+
64
+ if(!connectionData.config.user || !connectionData.config.database){
65
+ Logger.error('Required parameters missing.');
66
+ Logger.error(
67
+ 'Usage:',
68
+ 'npx reldens-storage generatePrismaSchema --user=[db-username] --pass=[db-password] --host=[db-host] --database=[db-name] --client=[db-client] --path=[project-path]'
69
+ );
70
+ process.exit();
71
+ }
72
+
73
+ let generator = new PrismaSchemaGenerator({
74
+ ...connectionData,
75
+ prismaSchemaPath: projectPath+'/prisma'
76
+ });
77
+
78
+ generator.generate().then((success) => {
79
+ if(!success){
80
+ Logger.error('Prisma schema generation failed.');
81
+ process.exit();
82
+ }
83
+ Logger.info('Prisma schema generation completed successfully!');
84
+ process.exit();
85
+ }).catch((error) => {
86
+ Logger.error('Error during Prisma schema generation: '+error.message);
87
+ process.exit();
88
+ });
@@ -65,7 +65,7 @@ let generator = new EntitiesGenerator({connectionData, projectPath});
65
65
  generator.generate().then((success) => {
66
66
  if(!success){
67
67
  Logger.error('Entity generation failed.');
68
- return;
68
+ process.exit();
69
69
  }
70
70
  Logger.info('Entity generation completed successfully!');
71
71
  process.exit();
package/index.js CHANGED
@@ -16,6 +16,9 @@ const { EntitiesGenerator } = require('./lib/entities-generator');
16
16
  const { EntityProperties } = require('./lib/entity-properties');
17
17
  const { TypeMapper } = require('./lib/type-mapper');
18
18
  const { MySQLTablesProvider } = require('./lib/mysql-tables-provider');
19
+ const { PrismaDriver } = require('./lib/prisma/prisma-driver');
20
+ const { PrismaDataServer } = require('./lib/prisma/prisma-data-server');
21
+ const { PrismaSchemaGenerator } = require('./lib/prisma/prisma-schema-generator');
19
22
 
20
23
  module.exports = {
21
24
  // base:
@@ -23,7 +26,8 @@ module.exports = {
23
26
  BaseDriver,
24
27
  DriversMap: {
25
28
  'objection-js': ObjectionJsDataServer,
26
- 'mikro-orm': MikroOrmDataServer
29
+ 'mikro-orm': MikroOrmDataServer,
30
+ 'prisma': PrismaDataServer
27
31
  },
28
32
  // objection-js:
29
33
  ObjectionJsDataServer,
@@ -33,6 +37,10 @@ module.exports = {
33
37
  MikroOrmCore,
34
38
  MikroOrmDataServer,
35
39
  MikroOrmDriver,
40
+ // prisma:
41
+ PrismaDataServer,
42
+ PrismaDriver,
43
+ PrismaSchemaGenerator,
36
44
  // entities:
37
45
  EntitiesGenerator,
38
46
  EntityProperties,
@@ -8,6 +8,7 @@ const { FileHandler } = require('@reldens/server-utils');
8
8
  const { ErrorManager, Logger, sc } = require('@reldens/utils');
9
9
  const { ObjectionJsDataServer } = require('./objection-js/objection-js-data-server');
10
10
  const { MikroOrmDataServer } = require('./mikro-orm/mikro-orm-data-server');
11
+ const { PrismaDataServer } = require('./prisma/prisma-data-server');
11
12
  const { TypeMapper } = require('./type-mapper');
12
13
 
13
14
  class EntitiesGenerator
@@ -17,8 +18,9 @@ class EntitiesGenerator
17
18
  {
18
19
  this.templatesFolderPath = FileHandler.joinPaths(__dirname, 'entity-templates');
19
20
  this.templates = {
20
- 'objection-js': FileHandler.joinPaths(this.templatesFolderPath, 'objection-js-entity.template'),
21
- 'mikro-orm': FileHandler.joinPaths(this.templatesFolderPath, 'mikro-orm-entity.template'),
21
+ 'objection-js': FileHandler.joinPaths(this.templatesFolderPath, 'objection-js-model.template'),
22
+ 'mikro-orm': FileHandler.joinPaths(this.templatesFolderPath, 'mikro-orm-model.template'),
23
+ 'prisma': FileHandler.joinPaths(this.templatesFolderPath, 'prisma-model.template'),
22
24
  'entity': FileHandler.joinPaths(this.templatesFolderPath, 'entity.template'),
23
25
  'entities-config': FileHandler.joinPaths(this.templatesFolderPath, 'entities-config.template'),
24
26
  'entities-translations': FileHandler.joinPaths(this.templatesFolderPath, 'entities-translations.template'),
@@ -32,7 +34,8 @@ class EntitiesGenerator
32
34
  this.entitiesTranslationsPath = FileHandler.joinPaths(this.generationFolder, 'entities-translations.js');
33
35
  this.driverMap = {
34
36
  'objection-js': ObjectionJsDataServer,
35
- 'mikro-orm': MikroOrmDataServer
37
+ 'mikro-orm': MikroOrmDataServer,
38
+ 'prisma': PrismaDataServer
36
39
  };
37
40
  this.server = sc.get(props, 'server', false);
38
41
  this.connectionData = sc.get(props, 'connectionData', false);
@@ -324,7 +327,7 @@ class EntitiesGenerator
324
327
  let entityPropertiesDefinition = this.getEntityPropertiesDefinition(tableData.columns, driverKey);
325
328
  let replacements = {
326
329
  modelClassName,
327
- tableName,
330
+ tableName: driverKey === 'prisma' ? tableName.toLowerCase() : tableName,
328
331
  modelPropertiesList,
329
332
  modelPropertiesConstructor,
330
333
  modelRelations,
@@ -348,25 +351,43 @@ class EntitiesGenerator
348
351
 
349
352
  getEntityPropertiesDefinition(columns, driverKey)
350
353
  {
351
- if('mikro-orm' !== driverKey){
352
- return '';
353
- }
354
- let entityProps = [];
355
- for(let columnName in columns){
356
- let column = columns[columnName];
357
- let isPrimary = 'PRI' === column.key;
358
- let type = TypeMapper.mapDbTypeToJsType(column.type);
359
- let propDef = columnName+': { type: \''+type+'\'';
360
- if(isPrimary){
361
- propDef += ', primary: true';
354
+ if('mikro-orm' === driverKey){
355
+ let entityProps = [];
356
+ for(let columnName in columns){
357
+ let column = columns[columnName];
358
+ let isPrimary = 'PRI' === column.key;
359
+ let type = TypeMapper.mapDbTypeToJsType(column.type);
360
+ let propDef = columnName+': { type: \''+type+'\'';
361
+ if(isPrimary){
362
+ propDef += ', primary: true';
363
+ }
364
+ if(column.nullable){
365
+ propDef += ', nullable: true';
366
+ }
367
+ propDef += ' }';
368
+ entityProps.push(propDef);
362
369
  }
363
- if(column.nullable){
364
- propDef += ', nullable: true';
370
+ return entityProps.join(',\n ');
371
+ }
372
+ if('prisma' === driverKey){
373
+ let prismaProps = [];
374
+ for(let columnName in columns){
375
+ let column = columns[columnName];
376
+ let prismaType = TypeMapper.mapDbTypeToPrismaType(column.type);
377
+ let isPrimary = 'PRI' === column.key;
378
+ let propDef = columnName+': {\n type: \''+prismaType+'\'';
379
+ if(isPrimary){
380
+ propDef += ',\n id: true';
381
+ }
382
+ if(column.nullable){
383
+ propDef += ',\n optional: true';
384
+ }
385
+ propDef += '\n }';
386
+ prismaProps.push(propDef);
365
387
  }
366
- propDef += ' }';
367
- entityProps.push(propDef);
388
+ return prismaProps.join(',\n ');
368
389
  }
369
- return entityProps.join(',\n ');
390
+ return '';
370
391
  }
371
392
 
372
393
  generateEntitiesConfigFile()
@@ -0,0 +1,32 @@
1
+ /**
2
+ *
3
+ * Reldens - {{modelClassName}}
4
+ *
5
+ */
6
+
7
+ const { PrismaClient } = require('@prisma/client');
8
+
9
+ class {{modelClassName}}
10
+ {
11
+
12
+ constructor({{modelPropertiesList}})
13
+ {
14
+ {{modelPropertiesConstructor}}
15
+ }
16
+
17
+ static get client()
18
+ {
19
+ if(!this._client){
20
+ this._client = new PrismaClient();
21
+ }
22
+ return this._client;
23
+ }
24
+
25
+ static get model()
26
+ {
27
+ return this.client['{{tableName}}'];
28
+ }
29
+ {{modelRelations}}
30
+ }
31
+
32
+ module.exports.{{modelClassName}} = {{modelClassName}};
@@ -123,7 +123,7 @@ class MikroOrmDriver extends BaseDriver
123
123
  if (!entries){
124
124
  return false;
125
125
  }
126
- for(let entry of Object.keys(entries)){
126
+ for(let entry of entries){
127
127
  await this.repository.nativeDelete(entry.id);
128
128
  }
129
129
  }
@@ -0,0 +1,114 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaDataServer
4
+ *
5
+ */
6
+
7
+ const { BaseDataServer } = require('../base-data-server');
8
+ const { PrismaDriver } = require('./prisma-driver');
9
+ const { MySQLTablesProvider } = require('../mysql-tables-provider');
10
+ const { ErrorManager, Logger, sc } = require('@reldens/utils');
11
+
12
+ class PrismaDataServer extends BaseDataServer
13
+ {
14
+
15
+ constructor(props)
16
+ {
17
+ super(props);
18
+ this.prisma = false;
19
+ }
20
+
21
+ async connect()
22
+ {
23
+ if(this.initialized){
24
+ return this.initialized;
25
+ }
26
+ try {
27
+ let { PrismaClient } = require('@prisma/client');
28
+ this.prisma = new PrismaClient({
29
+ log: this.debug ? ['query', 'info', 'warn', 'error'] : ['error']
30
+ });
31
+ await this.prisma.$connect();
32
+ this.initialized = Date.now();
33
+ return this.initialized;
34
+ } catch(error) {
35
+ Logger.critical('Connection failed, Prisma error: '+error.message);
36
+ }
37
+ return false;
38
+ }
39
+
40
+ generateEntities()
41
+ {
42
+ if(!this.initialized){
43
+ Logger.warning('Connection was not initialized, please use the connect method first.');
44
+ return {};
45
+ }
46
+ if(!this.rawEntities){
47
+ Logger.warning('Empty raw entities array, none entities generated.');
48
+ return {};
49
+ }
50
+ this.entities = {};
51
+ for(let i of Object.keys(this.rawEntities)){
52
+ let rawEntity = this.rawEntities[i];
53
+ let modelName = i.toLowerCase();
54
+ if(!this.prisma[modelName]){
55
+ Logger.critical('Invalid raw entity "'+i+'". No matching Prisma model found.');
56
+ continue;
57
+ }
58
+ this.entities[i] = new PrismaDriver({
59
+ rawModel: rawEntity,
60
+ id: i,
61
+ name: i,
62
+ config: this.config,
63
+ prisma: this.prisma,
64
+ model: this.prisma[modelName],
65
+ server: this
66
+ });
67
+ }
68
+ this.entityManager.setEntities(this.entities);
69
+ return this.entities;
70
+ }
71
+
72
+ name()
73
+ {
74
+ return this.name || 'Prisma Data Server Driver';
75
+ }
76
+
77
+ async rawQuery(content)
78
+ {
79
+ try {
80
+ let queryResult = await this.prisma.$queryRawUnsafe(content);
81
+ if(!sc.isArray(queryResult) || 0 === queryResult.length){
82
+ return false;
83
+ }
84
+ return queryResult;
85
+ } catch(error) {
86
+ Logger.error('Raw query failed: '+error.message);
87
+ return false;
88
+ }
89
+ }
90
+
91
+ async fetchEntitiesFromDatabase()
92
+ {
93
+ if(!this.initialized){
94
+ Logger.error('Connection was not initialized, please use the connect method first.');
95
+ return false;
96
+ }
97
+ try {
98
+ return await MySQLTablesProvider.fetchTables(this);
99
+ } catch(error) {
100
+ ErrorManager.error('Failed to fetch tables from database: '+error.message);
101
+ }
102
+ }
103
+
104
+ async disconnect()
105
+ {
106
+ if(this.prisma){
107
+ await this.prisma.$disconnect();
108
+ }
109
+ this.initialized = false;
110
+ }
111
+
112
+ }
113
+
114
+ module.exports.PrismaDataServer = PrismaDataServer;
@@ -0,0 +1,523 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaDriver
4
+ *
5
+ */
6
+
7
+ const { BaseDriver } = require('../base-driver');
8
+ const { ErrorManager, Logger, sc } = require('@reldens/utils');
9
+
10
+ class PrismaDriver extends BaseDriver
11
+ {
12
+
13
+ constructor(props)
14
+ {
15
+ super(props);
16
+ if(!props.prisma){
17
+ ErrorManager.error('Missing Prisma client on Prisma driver.');
18
+ }
19
+ if(!props.model){
20
+ ErrorManager.error('Missing Prisma model on Prisma driver.');
21
+ }
22
+ if(!props.server){
23
+ ErrorManager.error('Missing Server Driver on Prisma driver.');
24
+ }
25
+ this.prisma = props.prisma;
26
+ this.model = props.model;
27
+ this.server = props.server;
28
+ this.requiredFields = [];
29
+ this.fieldTypes = {};
30
+ this.initFieldMetadata();
31
+ }
32
+
33
+ async initFieldMetadata()
34
+ {
35
+ try {
36
+ let dmmf = this.prisma._baseDmmf;
37
+ if(!dmmf){
38
+ return;
39
+ }
40
+ let modelInfo = dmmf.modelMap[this.tableName().toLowerCase()];
41
+ if(!modelInfo){
42
+ return;
43
+ }
44
+ for(let field of modelInfo.fields){
45
+ if(field.isRequired && !field.hasDefaultValue && !field.isId){
46
+ this.requiredFields.push(field.name);
47
+ }
48
+ this.fieldTypes[field.name] = field.type;
49
+ }
50
+ } catch(error) {
51
+ Logger.warning('Could not initialize field metadata: '+error.message);
52
+ }
53
+ }
54
+
55
+ databaseName()
56
+ {
57
+ return this.config.database || '';
58
+ }
59
+
60
+ id()
61
+ {
62
+ return this.name || '';
63
+ }
64
+
65
+ name()
66
+ {
67
+ return this.rawName || '';
68
+ }
69
+
70
+ tableName()
71
+ {
72
+ return this.model._name || '';
73
+ }
74
+
75
+ property(propertyName)
76
+ {
77
+ return this.rawModel[propertyName] || null;
78
+ }
79
+
80
+ prepareData(params)
81
+ {
82
+ let data = {...params};
83
+ for(let key in data){
84
+ if('status' === key && 'number' === typeof data[key]){
85
+ data[key] = String(data[key]);
86
+ }
87
+ }
88
+ return data;
89
+ }
90
+
91
+ ensureRequiredFields(data)
92
+ {
93
+ let missingFields = [];
94
+ for(let field of this.requiredFields){
95
+ if(!sc.hasOwn(data, field)){
96
+ missingFields.push(field);
97
+ }
98
+ }
99
+ if(0 < missingFields.length){
100
+ Logger.warning('Missing required fields for '+this.tableName()+': '+missingFields.join(', '));
101
+ }
102
+ return data;
103
+ }
104
+
105
+ async create(params)
106
+ {
107
+ try {
108
+ let preparedData = this.prepareData(params);
109
+ this.ensureRequiredFields(preparedData);
110
+ return await this.model.create({
111
+ data: preparedData
112
+ });
113
+ } catch(error) {
114
+ Logger.error('Create error: '+error.message);
115
+ return false;
116
+ }
117
+ }
118
+
119
+ async createWithRelations(params, relations)
120
+ {
121
+ try {
122
+ let preparedData = this.prepareData(params);
123
+ this.ensureRequiredFields(preparedData);
124
+ let createData = {data: preparedData};
125
+ if(sc.isArray(relations) && 0 < relations.length){
126
+ for(let relation of relations){
127
+ if(!sc.hasOwn(params, relation)){
128
+ continue;
129
+ }
130
+ let relationData = params[relation];
131
+ if(sc.isArray(relationData)){
132
+ createData.data[relation] = {
133
+ connect: relationData.map(item => ({id: item.id}))
134
+ };
135
+ continue;
136
+ }
137
+ createData.data[relation] = {
138
+ connect: {id: relationData.id}
139
+ };
140
+ }
141
+ }
142
+ return await this.model.create(createData);
143
+ } catch(error) {
144
+ Logger.error('Create with relations error: '+error.message);
145
+ return false;
146
+ }
147
+ }
148
+
149
+ async update(filters, updatePatch)
150
+ {
151
+ try {
152
+ let preparedData = this.prepareData(updatePatch);
153
+ return await this.model.updateMany({
154
+ where: filters,
155
+ data: preparedData
156
+ });
157
+ } catch(error) {
158
+ Logger.error('Update error: '+error.message);
159
+ return false;
160
+ }
161
+ }
162
+
163
+ async updateBy(field, fieldValue, updatePatch, operator = null)
164
+ {
165
+ let filter = this.createSingleFilter(field, fieldValue, operator);
166
+ try {
167
+ let preparedData = this.prepareData(updatePatch);
168
+ return await this.model.updateMany({
169
+ where: filter,
170
+ data: preparedData
171
+ });
172
+ } catch(error) {
173
+ Logger.error('Update by error: '+error.message);
174
+ return false;
175
+ }
176
+ }
177
+
178
+ async updateById(id, params)
179
+ {
180
+ try {
181
+ let found = await this.loadById(id);
182
+ if(!found){
183
+ return false;
184
+ }
185
+ let preparedData = this.prepareData(params);
186
+ return await this.model.update({
187
+ where: {id},
188
+ data: preparedData
189
+ });
190
+ } catch(error) {
191
+ Logger.error('Update by ID error: '+error.message);
192
+ return false;
193
+ }
194
+ }
195
+
196
+ async upsert(params, filters)
197
+ {
198
+ try {
199
+ let preparedData = this.prepareData(params);
200
+ let existing = false;
201
+ if(params.id){
202
+ existing = await this.loadById(params.id);
203
+ if(existing){
204
+ let patch = Object.assign({}, preparedData);
205
+ delete patch.id;
206
+ return await this.model.update({
207
+ where: {id: params.id},
208
+ data: patch
209
+ });
210
+ }
211
+ }
212
+ if(filters){
213
+ let existing = await this.loadOne(filters);
214
+ if(existing){
215
+ return await this.model.update({
216
+ where: {id: existing.id},
217
+ data: preparedData
218
+ });
219
+ }
220
+ }
221
+ return await this.create(preparedData);
222
+ } catch(error) {
223
+ Logger.error('Upsert error: '+error.message);
224
+ return false;
225
+ }
226
+ }
227
+
228
+ async delete(filters = {})
229
+ {
230
+ try {
231
+ return await this.model.deleteMany({
232
+ where: filters
233
+ });
234
+ } catch(error) {
235
+ Logger.error('Delete error: '+error.message);
236
+ return false;
237
+ }
238
+ }
239
+
240
+ async deleteById(id)
241
+ {
242
+ try {
243
+ let found = await this.loadById(id);
244
+ if(!found){
245
+ return false;
246
+ }
247
+ return await this.model.delete({
248
+ where: {id}
249
+ });
250
+ } catch(error) {
251
+ Logger.error('Delete by ID error: '+error.message);
252
+ return false;
253
+ }
254
+ }
255
+
256
+ async count(filters)
257
+ {
258
+ try {
259
+ return await this.model.count({
260
+ where: filters
261
+ });
262
+ } catch(error) {
263
+ Logger.error('Count error: '+error.message);
264
+ return 0;
265
+ }
266
+ }
267
+
268
+ async countWithRelations(filters, relations)
269
+ {
270
+ try {
271
+ let query = {
272
+ where: filters
273
+ };
274
+ if(sc.isArray(relations) && 0 < relations.length){
275
+ query.include = this.buildIncludeObject(relations);
276
+ }
277
+ return await this.model.count(query);
278
+ } catch(error) {
279
+ Logger.error('Count with relations error: '+error.message);
280
+ return 0;
281
+ }
282
+ }
283
+
284
+ async loadAll()
285
+ {
286
+ try {
287
+ return await this.model.findMany(this.buildQueryOptions());
288
+ } catch(error) {
289
+ Logger.error('Load all error: '+error.message);
290
+ return [];
291
+ }
292
+ }
293
+
294
+ async loadAllWithRelations(relations)
295
+ {
296
+ try {
297
+ let query = this.buildQueryOptions();
298
+ if(sc.isArray(relations) && 0 < relations.length){
299
+ query.include = this.buildIncludeObject(relations);
300
+ }
301
+ return await this.model.findMany(query);
302
+ } catch(error) {
303
+ Logger.error('Load all with relations error: '+error.message);
304
+ return [];
305
+ }
306
+ }
307
+
308
+ async load(filters)
309
+ {
310
+ try {
311
+ let query = this.buildQueryOptions();
312
+ query.where = filters;
313
+ return await this.model.findMany(query);
314
+ } catch(error) {
315
+ Logger.error('Load error: '+error.message);
316
+ return [];
317
+ }
318
+ }
319
+
320
+ async loadWithRelations(filters, relations)
321
+ {
322
+ try {
323
+ let query = this.buildQueryOptions();
324
+ query.where = filters;
325
+ if(sc.isArray(relations) && 0 < relations.length){
326
+ query.include = this.buildIncludeObject(relations);
327
+ }
328
+ return await this.model.findMany(query);
329
+ } catch(error) {
330
+ Logger.error('Load with relations error: '+error.message);
331
+ return [];
332
+ }
333
+ }
334
+
335
+ async loadBy(field, fieldValue, operator = null)
336
+ {
337
+ try {
338
+ let filter = this.createSingleFilter(field, fieldValue, operator);
339
+ let query = this.buildQueryOptions();
340
+ query.where = filter;
341
+ return await this.model.findMany(query);
342
+ } catch(error) {
343
+ Logger.error('Load by error: '+error.message);
344
+ return [];
345
+ }
346
+ }
347
+
348
+ async loadByWithRelations(field, fieldValue, relations, operator = null)
349
+ {
350
+ try {
351
+ let filter = this.createSingleFilter(field, fieldValue, operator);
352
+ let query = this.buildQueryOptions();
353
+ query.where = filter;
354
+ if(sc.isArray(relations) && 0 < relations.length){
355
+ query.include = this.buildIncludeObject(relations);
356
+ }
357
+ return await this.model.findMany(query);
358
+ } catch(error) {
359
+ Logger.error('Load by with relations error: '+error.message);
360
+ return [];
361
+ }
362
+ }
363
+
364
+ async loadById(id)
365
+ {
366
+ try {
367
+ return await this.model.findUnique({
368
+ where: {id}
369
+ });
370
+ } catch(error) {
371
+ Logger.error('Load by ID error: '+error.message);
372
+ return null;
373
+ }
374
+ }
375
+
376
+ async loadByIdWithRelations(id, relations)
377
+ {
378
+ try {
379
+ let query = {
380
+ where: {id}
381
+ };
382
+ if(sc.isArray(relations) && 0 < relations.length){
383
+ query.include = this.buildIncludeObject(relations);
384
+ }
385
+ return await this.model.findUnique(query);
386
+ } catch(error) {
387
+ Logger.error('Load by ID with relations error: '+error.message);
388
+ return null;
389
+ }
390
+ }
391
+
392
+ async loadByIds(ids)
393
+ {
394
+ try {
395
+ return await this.model.findMany({
396
+ where: {
397
+ id: {in: ids}
398
+ }
399
+ });
400
+ } catch(error) {
401
+ Logger.error('Load by IDs error: '+error.message);
402
+ return [];
403
+ }
404
+ }
405
+
406
+ async loadOne(filters)
407
+ {
408
+ try {
409
+ let query = this.buildQueryOptions(false);
410
+ query.where = filters;
411
+ return await this.model.findFirst(query);
412
+ } catch(error) {
413
+ Logger.error('Load one error: '+error.message);
414
+ return null;
415
+ }
416
+ }
417
+
418
+ async loadOneWithRelations(filters, relations)
419
+ {
420
+ try {
421
+ let query = this.buildQueryOptions(false);
422
+ query.where = filters;
423
+ if(sc.isArray(relations) && 0 < relations.length){
424
+ query.include = this.buildIncludeObject(relations);
425
+ }
426
+ return await this.model.findFirst(query);
427
+ } catch(error) {
428
+ Logger.error('Load one with relations error: '+error.message);
429
+ return null;
430
+ }
431
+ }
432
+
433
+ async loadOneBy(field, fieldValue, operator = null)
434
+ {
435
+ try {
436
+ let filter = this.createSingleFilter(field, fieldValue, operator);
437
+ let query = this.buildQueryOptions(false);
438
+ query.where = filter;
439
+ return await this.model.findFirst(query);
440
+ } catch(error) {
441
+ Logger.error('Load one by error: '+error.message);
442
+ return null;
443
+ }
444
+ }
445
+
446
+ async loadOneByWithRelations(field, fieldValue, relations, operator = null)
447
+ {
448
+ try {
449
+ let filter = this.createSingleFilter(field, fieldValue, operator);
450
+ let query = this.buildQueryOptions(false);
451
+ query.where = filter;
452
+ if(sc.isArray(relations) && 0 < relations.length){
453
+ query.include = this.buildIncludeObject(relations);
454
+ }
455
+ return await this.model.findFirst(query);
456
+ } catch(error) {
457
+ Logger.error('Load one by with relations error: '+error.message);
458
+ return null;
459
+ }
460
+ }
461
+
462
+ buildQueryOptions(useLimit = true)
463
+ {
464
+ let queryOptions = {};
465
+ if(0 < this.select.length){
466
+ queryOptions.select = {};
467
+ for(let field of this.select){
468
+ queryOptions.select[field] = true;
469
+ }
470
+ }
471
+ if(useLimit && 0 !== this.limit){
472
+ queryOptions.take = this.limit;
473
+ }
474
+ if(0 !== this.offset){
475
+ queryOptions.skip = this.offset;
476
+ }
477
+ if(false !== this.sortBy && false !== this.sortDirection){
478
+ queryOptions.orderBy = {
479
+ [this.sortBy]: this.sortDirection.toLowerCase()
480
+ };
481
+ }
482
+ return queryOptions;
483
+ }
484
+
485
+ createSingleFilter(field, fieldValue, operator = null)
486
+ {
487
+ let filter = {};
488
+ if(null === operator){
489
+ filter[field] = fieldValue;
490
+ return filter;
491
+ }
492
+ filter[field] = this.applyOperator(fieldValue, operator);
493
+ return filter;
494
+ }
495
+
496
+ applyOperator(value, operator)
497
+ {
498
+ let operatorsMap = {
499
+ '=': value,
500
+ '!=': {not: value},
501
+ '>': {gt: value},
502
+ '>=': {gte: value},
503
+ '<': {lt: value},
504
+ '<=': {lte: value},
505
+ 'LIKE': {contains: value},
506
+ 'IN': {in: value},
507
+ 'NOT IN': {notIn: value}
508
+ };
509
+ return operatorsMap[operator] || value;
510
+ }
511
+
512
+ buildIncludeObject(relations)
513
+ {
514
+ let include = {};
515
+ for(let relation of relations){
516
+ include[relation] = true;
517
+ }
518
+ return include;
519
+ }
520
+
521
+ }
522
+
523
+ module.exports.PrismaDriver = PrismaDriver;
@@ -0,0 +1,71 @@
1
+ /**
2
+ *
3
+ * Reldens - PrismaSchemaGenerator
4
+ *
5
+ */
6
+
7
+ const { ErrorManager, Logger, sc } = require('@reldens/utils');
8
+ const { FileHandler } = require('@reldens/server-utils');
9
+ const { execSync } = require('child_process');
10
+
11
+ class PrismaSchemaGenerator
12
+ {
13
+
14
+ constructor(props)
15
+ {
16
+ this.config = sc.get(props, 'config', {});
17
+ this.client = sc.get(props, 'client', 'mysql');
18
+ this.debug = sc.get(props, 'debug', false);
19
+ this.prismaSchemaPath = sc.get(props, 'prismaSchemaPath',
20
+ FileHandler.joinPaths(process.cwd(), 'prisma'));
21
+ this.schemaFilePath = FileHandler.joinPaths(this.prismaSchemaPath,
22
+ 'schema.prisma');
23
+ }
24
+
25
+ async generate()
26
+ {
27
+ try {
28
+ FileHandler.createFolder(this.prismaSchemaPath, { recursive: true });
29
+ this.generateSchemaFile();
30
+ Logger.info('Running prisma introspect "npx prisma db pull"...');
31
+ execSync('npx prisma db pull', { stdio: 'inherit' });
32
+ Logger.info('Running prisma generate "npx prisma generate"...');
33
+ execSync('npx prisma generate', { stdio: 'inherit' });
34
+ return true;
35
+ } catch(error) {
36
+ ErrorManager.error('Failed to generate Prisma schema. '+error.message);
37
+ return false;
38
+ }
39
+ }
40
+
41
+ generateSchemaFile()
42
+ {
43
+ let datasourceProvider = 'mysql';
44
+ if('postgresql' === this.client || 'postgres' === this.client){
45
+ datasourceProvider = 'postgresql';
46
+ }
47
+ if('mongodb' === this.client){
48
+ datasourceProvider = 'mongodb';
49
+ }
50
+ let connectionString = datasourceProvider + '://'
51
+ + this.config.user + ':' + this.config.password
52
+ + '@' + this.config.host + ':' + this.config.port + '/'
53
+ + this.config.database;
54
+ let schemaContent = `
55
+ generator client {
56
+ provider = "prisma-client-js"
57
+ output = "../node_modules/.prisma/client"
58
+ }
59
+
60
+ datasource db {
61
+ provider = "${datasourceProvider}"
62
+ url = "${connectionString}"
63
+ }
64
+ `;
65
+ FileHandler.writeFile(this.schemaFilePath, schemaContent);
66
+ Logger.info('Generated Prisma schema file at: '+this.schemaFilePath);
67
+ }
68
+
69
+ }
70
+
71
+ module.exports.PrismaSchemaGenerator = PrismaSchemaGenerator;
@@ -44,6 +44,43 @@ class TypeMapper
44
44
  return typeMap[dbType.toLowerCase()];
45
45
  }
46
46
 
47
+ mapDbTypeToPrismaType(dbType)
48
+ {
49
+ let typeMap = {
50
+ 'int': 'Int',
51
+ 'integer': 'Int',
52
+ 'tinyint': 'Int',
53
+ 'smallint': 'Int',
54
+ 'mediumint': 'Int',
55
+ 'bigint': 'BigInt',
56
+ 'decimal': 'Decimal',
57
+ 'float': 'Float',
58
+ 'double': 'Float',
59
+ 'varchar': 'String',
60
+ 'char': 'String',
61
+ 'text': 'String',
62
+ 'tinytext': 'String',
63
+ 'mediumtext': 'String',
64
+ 'longtext': 'String',
65
+ 'date': 'DateTime',
66
+ 'datetime': 'DateTime',
67
+ 'timestamp': 'DateTime',
68
+ 'time': 'String',
69
+ 'year': 'Int',
70
+ 'boolean': 'Boolean',
71
+ 'bool': 'Boolean',
72
+ 'bit': 'Boolean',
73
+ 'json': 'Json',
74
+ 'binary': 'Bytes',
75
+ 'varbinary': 'Bytes',
76
+ 'blob': 'Bytes',
77
+ 'tinyblob': 'Bytes',
78
+ 'mediumblob': 'Bytes',
79
+ 'longblob': 'Bytes'
80
+ };
81
+ return typeMap[dbType.toLowerCase()] || 'String';
82
+ }
83
+
47
84
  }
48
85
 
49
86
  module.exports.TypeMapper = new TypeMapper();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/storage",
3
3
  "scope": "@reldens",
4
- "version": "0.33.0",
4
+ "version": "0.34.0",
5
5
  "description": "Reldens - Storage",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -9,7 +9,8 @@
9
9
  "source": true,
10
10
  "main": "index.js",
11
11
  "bin": {
12
- "reldens-storage": "./bin/reldens-storage.js"
12
+ "reldens-storage": "./bin/reldens-storage.js",
13
+ "reldens-storage-prisma": "./bin/generate-prisma-schema.js"
13
14
  },
14
15
  "repository": {
15
16
  "type": "git",
@@ -50,11 +51,13 @@
50
51
  "@mikro-orm/core": "^6.4.15",
51
52
  "@mikro-orm/mongodb": "^6.4.15",
52
53
  "@mikro-orm/mysql": "^6.4.15",
53
- "@reldens/utils": "^0.47.0",
54
+ "@prisma/client": "^6.7.0",
54
55
  "@reldens/server-utils": "^0.15.0",
56
+ "@reldens/utils": "^0.47.0",
55
57
  "knex": "^3.1.0",
56
58
  "mysql": "^2.18.1",
57
59
  "mysql2": "^3.14.1",
58
- "objection": "^3.1.5"
60
+ "objection": "^3.1.5",
61
+ "prisma": "^6.7.0"
59
62
  }
60
63
  }