@rapidd/build 1.1.2 β†’ 1.2.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
@@ -13,6 +13,11 @@ Dynamic code generator that transforms Prisma schemas into complete Express.js C
13
13
  - πŸ—ΊοΈ **Relationships JSON** - Generates complete relationship mappings with foreign keys
14
14
  - ⚑ **Selective Generation** - Update only specific models or components
15
15
 
16
+ ## Requirements
17
+
18
+ - **Prisma 7+** (recommended) - Full support for Prisma 7's new architecture
19
+ - Node.js 14.0.0 or higher
20
+
16
21
  ## Installation
17
22
 
18
23
  ```bash
@@ -155,6 +160,27 @@ npx rapidd build --model user --only model
155
160
  npx rapidd build --model user --only acl
156
161
  ```
157
162
 
163
+ ## Migration from Prisma 6 to 7
164
+
165
+ If you're upgrading from Prisma 6, this package now automatically:
166
+
167
+ 1. **Uses `@prisma/internals`** for DMMF access (no longer relies on generated client)
168
+ 2. **Reads database URL** from multiple sources in order:
169
+ - `prisma.config.ts` (Prisma 7 default)
170
+ - Schema file `datasource.url` (Prisma 5/6 style)
171
+ - `DATABASE_URL` environment variable
172
+ 3. **Maintains full compatibility** - no changes needed to your workflow
173
+
174
+ Simply update your dependencies and rebuild:
175
+
176
+ ```bash
177
+ npm install @rapidd/build@latest
178
+ npm install prisma@^7.0.0 @prisma/client@^7.0.0
179
+ npx rapidd build
180
+ ```
181
+
182
+ For more details on Prisma 7 migration, see the [official Prisma upgrade guide](https://www.prisma.io/docs/orm/more/upgrade-guides/upgrading-versions/upgrading-to-prisma-7).
183
+
158
184
  ## License
159
185
 
160
186
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidd/build",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Dynamic code generator that transforms Prisma schemas into Express.js CRUD APIs with PostgreSQL RLS-to-JavaScript translation",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -49,15 +49,18 @@
49
49
  "README.md"
50
50
  ],
51
51
  "dependencies": {
52
- "@prisma/client": "^5.0.0",
52
+ "@prisma/adapter-planetscale": "^7.0.1",
53
+ "@prisma/client": "^7.0.0",
54
+ "@prisma/internals": "^7.0.0",
53
55
  "commander": "^11.0.0",
54
56
  "dotenv": "^16.0.0",
55
- "pg": "^8.16.3"
57
+ "pg": "^8.16.3",
58
+ "undici": "^7.16.0"
56
59
  },
57
60
  "devDependencies": {
58
- "prisma": "^5.0.0"
61
+ "prisma": "^7.0.0"
59
62
  },
60
63
  "peerDependencies": {
61
- "@prisma/client": "^5.0.0"
64
+ "@prisma/client": "^7.0.0"
62
65
  }
63
66
  }
@@ -29,9 +29,20 @@ class Model {
29
29
  this.user_id = this.user ? this.user.id : null;
30
30
  }
31
31
 
32
+ get primaryKey(){
33
+ const pkey = this.queryBuilder.getPrimaryKey();
34
+ return Array.isArray(pkey) ? pkey.join('_') : pkey;
35
+ }
36
+
37
+ get fields(){
38
+ return this.queryBuilder.fields;
39
+ }
40
+
32
41
  _select = (fields) => this.queryBuilder.select(fields);
33
42
  _filter = (q) => this.queryBuilder.filter(q);
34
43
  _include = (include) => this.queryBuilder.include(include, this.user);
44
+ _queryCreate = (data) => this.queryBuilder.create(data, this.user);
45
+ _queryUpdate = (id, data) => this.queryBuilder.update(id, data, this.user);
35
46
  // ACL METHODS
36
47
  _canCreate = () => this.acl.canCreate(this.user);
37
48
  _getAccessFilter = () => this.acl.getAccessFilter?.(this.user);
@@ -47,18 +58,19 @@ class Model {
47
58
  * @param {number} offset
48
59
  * @param {string} sortBy
49
60
  * @param {'asc'|'desc'} sortOrder
61
+ * @param {{}} [options={}]
50
62
  * @returns {Promise<Object[]>}
51
63
  */
52
- _getMany = async (q = {}, include = "", limit = 25, offset = 0, sortBy = "id", sortOrder = "asc", options = {})=>{
64
+ _getMany = async (q = {}, include = "", limit = 25, offset = 0, sortBy = this.primaryKey, sortOrder = "asc", options = {})=>{
53
65
  const take = this.take(Number(limit));
54
66
  const skip = this.skip(Number(offset));
55
67
 
56
- sortBy = sortBy.trim();
57
- sortOrder = sortOrder.trim();
68
+ sortBy = sortBy?.trim();
69
+ sortOrder = sortOrder?.trim();
58
70
  if (!sortBy.includes('.') && this.fields[sortBy] == undefined) {
59
71
  throw new ErrorResponse(400, "invalid_sort_field", {sortBy, modelName: this.constructor.name});
60
72
  }
61
-
73
+
62
74
  // Query the database using Prisma with filters, pagination, and limits
63
75
  const [data, total] = await prismaTransaction([
64
76
  (tx) => tx[this.name].findMany({
@@ -79,15 +91,16 @@ class Model {
79
91
  /**
80
92
  * @param {number} id
81
93
  * @param {string | Object} include
94
+ * @param {{}} [options={}]
82
95
  * @returns {Promise<{} | null>}
83
96
  */
84
97
  _get = async (id, include, options = {}) =>{
85
98
  const {omit, ..._options} = options;
86
- id = Number(id);
99
+ console.log(JSON.stringify(this.include(include)));
87
100
  // To determine if the record is inaccessible, either due to non-existence or insufficient permissions, two simultaneous queries are performed.
88
101
  const _response = this.prisma.findUnique({
89
102
  'where': {
90
- 'id': id
103
+ [this.primaryKey]: id,
91
104
  },
92
105
  'include': this.include(include),
93
106
  'omit': {...this._omit(), ...omit},
@@ -96,7 +109,7 @@ class Model {
96
109
 
97
110
  const _checkPermission = this.prisma.findUnique({
98
111
  'where': {
99
- 'id': id,
112
+ [this.primaryKey]: id,
100
113
  ...this.getAccessFilter()
101
114
  },
102
115
  'select': {
@@ -107,21 +120,22 @@ class Model {
107
120
  const [response, checkPermission] = await Promise.all([_response, _checkPermission]);
108
121
  if(response){
109
122
  if(checkPermission){
110
- if(response.id != checkExistence?.id){ // IN CASE access_filter CONTAINS id FIELD
111
- throw new ErrorResponse(getTranslation("no_permission"), 403);
123
+ if(response.id != checkPermission?.id){ // IN CASE access_filter CONTAINS id FIELD
124
+ throw new ErrorResponse(403, "no_permission");
112
125
  }
113
126
  }
114
127
  else{
115
- throw new ErrorResponse(getTranslation("no_permission"), 403);
128
+ throw new ErrorResponse(403, "no_permission");
116
129
  }
117
130
  }
118
131
  else{
119
- throw new ErrorResponse(getTranslation("record_not_found"), 404);
132
+ throw new ErrorResponse(404, "record_not_found");
120
133
  }
121
134
  return response;
122
135
  }
123
136
  /**
124
137
  * @param {{}} data
138
+ * @param {{}} [options={}]
125
139
  * @returns {Promise<Object>}
126
140
  */
127
141
  _create = async (data, options = {}) => {
@@ -131,7 +145,7 @@ class Model {
131
145
  }
132
146
 
133
147
  // VALIDATE PASSED FIELDS AND RELATIONSHIPS
134
- this.queryBuilder.create(data, this.user_id);
148
+ this._queryCreate(data);
135
149
 
136
150
  // CREATE
137
151
  return await this.prisma.create({
@@ -144,11 +158,12 @@ class Model {
144
158
  /**
145
159
  * @param {number} id
146
160
  * @param {{}} data
161
+ * @param {{}} [options={}]
147
162
  * @returns {Promise<Object>}
148
163
  */
149
164
  _update = async (id, data, options = {}) => {
150
- id = Number(id);
151
-
165
+ delete data.createdAt;
166
+ delete data.createdBy;
152
167
  // CHECK UPDATE PERMISSION
153
168
  const updateFilter = this.getUpdateFilter();
154
169
  if (updateFilter === false) {
@@ -156,10 +171,10 @@ class Model {
156
171
  }
157
172
 
158
173
  // VALIDATE PASSED FIELDS AND RELATIONSHIPS
159
- this.queryBuilder.update(id, data, this.user_id);
174
+ this._queryUpdate(id, data);
160
175
  const response = await this.prisma.update({
161
176
  'where': {
162
- 'id': id,
177
+ [this.primaryKey]: id,
163
178
  ...updateFilter
164
179
  },
165
180
  'data': data,
@@ -172,6 +187,28 @@ class Model {
172
187
  throw new ErrorResponse(403, "no_permission");
173
188
  }
174
189
 
190
+ /**
191
+ * @param {{}} data
192
+ * @param {string} [unique_key=this.primaryKey]
193
+ * @param {{}} [options={}]
194
+ * @returns {Promise<Object>}
195
+ */
196
+ async _upsert(data, unique_key = this.primaryKey, options = {}){
197
+ const createData = data;
198
+ const updateData = JSON.parse(JSON.stringify(data));
199
+ this.queryBuilder.create(createData, this.user);
200
+ this.queryBuilder.update(updateData, this.user);
201
+ return await this.prisma.upsert({
202
+ 'where': {
203
+ [unique_key]: data[unique_key]
204
+ },
205
+ 'create': createData,
206
+ 'update': updateData,
207
+ 'include': this.include('ALL'),
208
+ ...options
209
+ });
210
+ }
211
+
175
212
  /**
176
213
  *
177
214
  * @param {string} q
@@ -185,11 +222,10 @@ class Model {
185
222
 
186
223
  /**
187
224
  * @param {number} id
225
+ * @param {{}} [options={}]
188
226
  * @returns {Promise<Object>}
189
227
  */
190
228
  _delete = async (id, options = {}) => {
191
- id = Number(id);
192
-
193
229
  // CHECK DELETE PERMISSION
194
230
  const deleteFilter = this.getDeleteFilter();
195
231
  if (deleteFilter === false) {
@@ -198,7 +234,7 @@ class Model {
198
234
 
199
235
  const response = await this.prisma.delete({
200
236
  'where': {
201
- id: parseInt(id),
237
+ [this.primaryKey]: id,
202
238
  ...deleteFilter
203
239
  },
204
240
  'select': this.select(),
@@ -226,19 +262,31 @@ class Model {
226
262
  /**
227
263
  * @param {number} id
228
264
  * @param {string | Object} include
265
+ * @param {{}} [options={}]
229
266
  * @returns {Promise<{} | null>}
230
267
  */
231
268
  async get(id, include, options = {}){
232
- return await this._get(Number(id), include, options);
269
+ return await this._get(id, include, options);
233
270
  }
234
271
 
235
272
  /**
236
273
  * @param {number} id
237
274
  * @param {{}} data
275
+ * @param {{}} [options={}]
238
276
  * @returns {Promise<Object>}
239
277
  */
240
278
  async update(id, data, options = {}){
241
- return await this._update(Number(id), data, options);
279
+ return await this._update(id, data, options);
280
+ }
281
+
282
+ /**
283
+ * @param {{}} data
284
+ * @param {string} [unique_key=this.primaryKey]
285
+ * @param {{}} [options={}]
286
+ * @returns {Promise<Object>}
287
+ */
288
+ async upsert(data, unique_key = this.primaryKey, options = {}){
289
+ return await this._upsert(data, unique_key, options);
242
290
  }
243
291
 
244
292
  /**
@@ -255,7 +303,7 @@ class Model {
255
303
  * @returns {Promise<Object>}
256
304
  */
257
305
  async delete(id, data, options = {}){
258
- return await this._delete(Number(id), data, options);
306
+ return await this._delete(id, data, options);
259
307
  }
260
308
 
261
309
  select(fields){
@@ -329,15 +377,13 @@ class Model {
329
377
  set modelName (name){
330
378
  this.name = name;
331
379
  this.prisma = prisma[name];
332
- this.fields = this.prisma.fields;
333
380
  }
334
381
 
335
382
  static relatedObjects = [];
336
383
  static Error = ErrorResponse;
337
384
  }
338
385
 
339
- module.exports = {Model, QueryBuilder, prisma};
340
- `;
386
+ module.exports = {Model, QueryBuilder, prisma};`;
341
387
 
342
388
  // Ensure src directory exists
343
389
  const srcDir = path.dirname(modelJsPath);
@@ -359,7 +405,7 @@ function generateRapiddFile(rapiddJsPath, isPostgreSQL = true) {
359
405
 
360
406
  if (isPostgreSQL) {
361
407
  // PostgreSQL version with RLS support
362
- content = `const { PrismaClient } = require('../prisma/client');
408
+ content = `const { PrismaClient, Prisma } = require('../prisma/client');
363
409
  const { AsyncLocalStorage } = require('async_hooks');
364
410
  const acl = require('./acl');
365
411
 
@@ -373,13 +419,49 @@ const RLS_CONFIG = {
373
419
  userRole: process.env.RLS_USER_ROLE || 'current_user_role',
374
420
  };
375
421
 
376
- // Basis Prisma Client
422
+ // =====================================================
423
+ // BASE PRISMA CLIENTS
424
+ // =====================================================
425
+
426
+ /**
427
+ * ADMIN CLIENT - Bypasses ALL RLS
428
+ * Uses DATABASE_URL_ADMIN connection (e.g., app_auth_proxy user)
429
+ * Use ONLY for authentication operations:
430
+ * - Login
431
+ * - Register
432
+ * - Email Verification
433
+ * - Password Reset
434
+ * - OAuth operations
435
+ */
436
+ const authPrisma = new PrismaClient({
437
+ datasources: {
438
+ db: {
439
+ url: process.env.DATABASE_URL_ADMIN
440
+ }
441
+ },
442
+ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
443
+ });
444
+
445
+ /**
446
+ * BASE CLIENT - Regular user with RLS
447
+ * Uses DATABASE_URL connection
448
+ * Use for all business operations
449
+ */
377
450
  const basePrisma = new PrismaClient({
451
+ datasources: {
452
+ db: {
453
+ url: process.env.DATABASE_URL
454
+ }
455
+ },
378
456
  log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
379
457
  });
380
458
 
459
+ // =====================================================
460
+ // RLS HELPER FUNCTIONS
461
+ // =====================================================
462
+
381
463
  /**
382
- * FIXED: Setze RLS Session Variables in PostgreSQL
464
+ * Set RLS Session Variables in PostgreSQL
383
465
  * Execute each SET command separately to avoid prepared statement error
384
466
  */
385
467
  async function setRLSVariables(tx, userId, userRole) {
@@ -387,27 +469,48 @@ async function setRLSVariables(tx, userId, userRole) {
387
469
  const userIdVar = RLS_CONFIG.userId;
388
470
  const userRoleVar = RLS_CONFIG.userRole;
389
471
 
390
- // Execute SET commands separately (PostgreSQL doesn't allow multiple commands in prepared statements)
391
- await tx.$executeRawUnsafe(\`SET LOCAL "\${namespace}"."\${userIdVar}" = '\${userId}'\`);
392
- await tx.$executeRawUnsafe(\`SET LOCAL "\${namespace}"."\${userRoleVar}" = '\${userRole}'\`);
472
+ // Execute SET commands separately
473
+ await tx.$executeRawUnsafe(\`SET LOCAL \${namespace}.\${userIdVar} = '\${userId}'\`);
474
+ await tx.$executeRawUnsafe(\`SET LOCAL \${namespace}.\${userRoleVar} = '\${userRole}'\`);
393
475
  }
394
476
 
395
- // FIXED: Erweiterter Prisma mit automatischer RLS
477
+ /**
478
+ * Reset RLS Session Variables
479
+ */
480
+ async function resetRLSVariables(tx) {
481
+ const namespace = RLS_CONFIG.namespace;
482
+ const userIdVar = RLS_CONFIG.userId;
483
+ const userRoleVar = RLS_CONFIG.userRole;
484
+
485
+ try {
486
+ await tx.$executeRawUnsafe(\`RESET \${namespace}.\${userIdVar}\`);
487
+ await tx.$executeRawUnsafe(\`RESET \${namespace}.\${userRoleVar}\`);
488
+ } catch (e) {
489
+ // Ignore errors on reset
490
+ console.error('Failed to reset RLS variables:', e);
491
+ }
492
+ }
493
+
494
+ // =====================================================
495
+ // EXTENDED PRISMA WITH AUTOMATIC RLS
496
+ // =====================================================
497
+
498
+ /**
499
+ * Extended Prisma Client with automatic RLS context
500
+ * Automatically wraps all operations in RLS context from AsyncLocalStorage
501
+ */
396
502
  const prisma = basePrisma.$extends({
397
503
  query: {
398
504
  async $allOperations({ operation, args, query, model }) {
399
505
  const context = requestContext.getStore();
400
506
 
401
- // Kein Context = keine RLS (z.B. System-Operationen)
507
+ // No context = no RLS (e.g., system operations)
402
508
  if (!context?.userId || !context?.userRole) {
403
509
  return query(args);
404
510
  }
405
511
 
406
512
  const { userId, userRole } = context;
407
513
 
408
- // IMPORTANT: The entire operation must happen in ONE transaction
409
- // We need to wrap the ENTIRE query execution in a single transaction
410
-
411
514
  // For operations that are already transactions, just set the variables
412
515
  if (operation === '$transaction') {
413
516
  return basePrisma.$transaction(async (tx) => {
@@ -422,7 +525,6 @@ const prisma = basePrisma.$extends({
422
525
  await setRLSVariables(tx, userId, userRole);
423
526
 
424
527
  // Execute the original query using the transaction client
425
- // This is the key: we need to use the transaction client for the query
426
528
  if (model) {
427
529
  // Model query (e.g., user.findMany())
428
530
  return tx[model][operation](args);
@@ -435,70 +537,31 @@ const prisma = basePrisma.$extends({
435
537
  },
436
538
  });
437
539
 
438
- // Helper for batch operations in single transaction
540
+ // =====================================================
541
+ // TRANSACTION HELPERS
542
+ // =====================================================
543
+
544
+ /**
545
+ * Helper for batch operations in single transaction
546
+ */
439
547
  async function prismaTransaction(operations) {
440
548
  const context = requestContext.getStore();
441
-
442
- if (!context?.userId || !context?.userRole) {
443
- return Promise.all(operations);
444
- }
445
-
549
+
446
550
  return basePrisma.$transaction(async (tx) => {
447
- await setRLSVariables(tx, context.userId, context.userRole);
551
+ if (context?.userId && context?.userRole) {
552
+ await setRLSVariables(tx, context.userId, context.userRole);
553
+ }
448
554
  return Promise.all(operations.map(op => op(tx)));
449
555
  });
450
556
  }
451
557
 
452
- // Alternative approach: Manual transaction wrapper
453
- class PrismaWithRLS {
454
- constructor() {
455
- this.client = basePrisma;
456
- }
457
-
458
- /**
459
- * Execute any Prisma operation with RLS context
460
- */
461
- async withRLS(userId, userRole, callback) {
462
- return this.client.$transaction(async (tx) => {
463
- // Execute SET commands separately to avoid prepared statement error
464
- await tx.$executeRawUnsafe(\`SET LOCAL app.current_user_id = '\${userId}'\`);
465
- await tx.$executeRawUnsafe(\`SET LOCAL app.current_user_role = '\${userRole}'\`);
466
-
467
- // Execute callback with transaction client
468
- return callback(tx);
469
- });
470
- }
471
-
472
- /**
473
- * Get a proxy client for a specific user
474
- * This wraps ALL operations in RLS context
475
- */
476
- forUser(userId, userRole) {
477
- const withRLS = this.withRLS.bind(this);
478
- const client = this.client;
479
-
480
- return new Proxy({}, {
481
- get(target, model) {
482
- // Return a proxy for the model
483
- return new Proxy({}, {
484
- get(modelTarget, operation) {
485
- // Return a function that wraps the operation
486
- return async (args) => {
487
- return withRLS(userId, userRole, async (tx) => {
488
- return tx[model][operation](args);
489
- });
490
- };
491
- }
492
- });
493
- }
494
- });
495
- }
496
- }
497
-
498
- const prismaWithRLS = new PrismaWithRLS();
558
+ // =====================================================
559
+ // CONTEXT HELPERS
560
+ // =====================================================
499
561
 
500
562
  /**
501
563
  * Express Middleware: Set RLS context from authenticated user
564
+ * Use this AFTER your authentication middleware
502
565
  */
503
566
  function setRLSContext(req, res, next) {
504
567
  if (req.user) {
@@ -516,74 +579,50 @@ function setRLSContext(req, res, next) {
516
579
  }
517
580
 
518
581
  /**
519
- * Helper: System-Operationen ohne RLS (fΓΌr Cron-Jobs, etc.)
520
- */
521
- async function withSystemAccess(callback) {
522
- // For system access, we might not want RLS at all
523
- // So we use the base client directly
524
- return callback(basePrisma);
525
- }
526
-
527
- /**
528
- * Helper: Als bestimmter User ausfΓΌhren (fΓΌr Tests)
529
- */
530
- async function withUser(userId, userRole, callback) {
531
- return requestContext.run({ userId, userRole }, () => callback());
532
- }
533
-
534
- /**
535
- * Helper: Direct transaction with RLS for complex operations
536
- */
537
- async function transactionWithRLS(userId, userRole, callback) {
538
- return basePrisma.$transaction(async (tx) => {
539
- // Set RLS context for this transaction - execute separately
540
- await tx.$executeRawUnsafe(\`SET LOCAL app.current_user_id = '\${userId}'\`);
541
- await tx.$executeRawUnsafe(\`SET LOCAL app.current_user_role = '\${userRole}'\`);
542
-
543
- // Execute callback with transaction client
544
- return callback(tx);
545
- });
546
- }
547
-
548
- /**
549
- * Helper: Hole RLS Config (fΓΌr SQL Generation)
582
+ * Get RLS Config (for SQL generation)
550
583
  */
551
584
  function getRLSConfig() {
552
585
  return RLS_CONFIG;
553
586
  }
554
587
 
555
- // Example usage in route
556
- /*
557
- app.get('/api/users', authenticateUser, setRLSContext, async (req, res) => {
558
- // Option 1: Using extended prisma (automatic RLS)
559
- const users = await prisma.user.findMany();
560
-
561
- // Option 2: Using manual transaction
562
- const users = await transactionWithRLS(req.user.id, req.user.role, async (tx) => {
563
- return tx.user.findMany();
564
- });
588
+ // =====================================================
589
+ // GRACEFUL SHUTDOWN
590
+ // =====================================================
565
591
 
566
- // Option 3: Using forUser helper
567
- const userPrisma = prismaWithRLS.forUser(req.user.id, req.user.role);
568
- const users = await userPrisma.user.findMany();
592
+ async function disconnectAll() {
593
+ await authPrisma.$disconnect();
594
+ await basePrisma.$disconnect();
595
+ }
569
596
 
570
- res.json(users);
597
+ process.on('beforeExit', async () => {
598
+ await disconnectAll();
571
599
  });
572
- */
600
+
601
+ // =====================================================
602
+ // EXPORTS
603
+ // =====================================================
573
604
 
574
605
  module.exports = {
575
- prisma,
606
+ // Main clients
607
+ prisma, // Use for regular operations with automatic RLS from context
608
+ authPrisma, // Use ONLY for auth operations (login, register, etc.)
609
+
610
+ // Transaction helpers
576
611
  prismaTransaction,
577
- basePrisma, // Export base for auth operations that don't need RLS
578
- PrismaClient,
612
+
613
+ // Context helpers
579
614
  requestContext,
580
615
  setRLSContext,
581
- withSystemAccess,
582
- withUser,
583
- transactionWithRLS,
584
- prismaWithRLS,
585
- getRLSConfig,
616
+
617
+ // RLS utilities
586
618
  setRLSVariables,
619
+ resetRLSVariables,
620
+ getRLSConfig,
621
+
622
+ // Utilities
623
+ disconnectAll,
624
+ PrismaClient,
625
+ Prisma,
587
626
  acl
588
627
  };
589
628
  `;
@@ -623,7 +662,7 @@ module.exports = {
623
662
  /**
624
663
  * Update relationships.json for a specific model
625
664
  */
626
- async function updateRelationshipsForModel(filteredModels, relationshipsPath, prismaClientPath, schemaPath, usedDMMF) {
665
+ async function updateRelationshipsForModel(filteredModels, relationshipsPath, schemaPath, usedDMMF) {
627
666
  let existingRelationships = {};
628
667
 
629
668
  // Load existing relationships if file exists
@@ -641,7 +680,7 @@ async function updateRelationshipsForModel(filteredModels, relationshipsPath, pr
641
680
  // Use DMMF to get relationships for specific model
642
681
  const { generateRelationshipsFromDMMF } = require('../generators/relationshipsGenerator');
643
682
  const tempPath = relationshipsPath + '.tmp';
644
- await generateRelationshipsFromDMMF(prismaClientPath, tempPath);
683
+ await generateRelationshipsFromDMMF(schemaPath, tempPath);
645
684
  const allRelationships = JSON.parse(fs.readFileSync(tempPath, 'utf8'));
646
685
  fs.unlinkSync(tempPath);
647
686
 
@@ -835,15 +874,14 @@ async function buildModels(options) {
835
874
  console.warn('Continuing with schema parsing fallback...\n');
836
875
  }
837
876
 
838
- // Try to use Prisma DMMF first (if prisma generate has been run)
877
+ // Try to use Prisma DMMF first (using @prisma/internals getDMMF)
839
878
  let parsedData = null;
840
- const prismaClientPath = path.join(process.cwd(), 'prisma', 'client');
841
879
  let usedDMMF = false;
842
880
 
843
881
  try {
844
- parsedData = await parsePrismaDMMF(prismaClientPath);
882
+ parsedData = await parsePrismaDMMF(schemaPath);
845
883
  if (parsedData) {
846
- console.log('Using Prisma generated client (DMMF)');
884
+ console.log('Using Prisma DMMF (via @prisma/internals)');
847
885
  usedDMMF = true;
848
886
  }
849
887
  } catch (error) {
@@ -899,11 +937,14 @@ async function buildModels(options) {
899
937
  }
900
938
 
901
939
  // Parse datasource to determine database type
902
- let datasource = { isPostgreSQL: true }; // Default to PostgreSQL
940
+ let datasource = { isPostgreSQL: true, url: null }; // Default to PostgreSQL
903
941
  try {
904
942
  datasource = parseDatasource(schemaPath);
905
943
  } catch (error) {
906
- console.warn('Could not parse datasource, assuming PostgreSQL:', error.message);
944
+ // Only warn if it's not the expected "No url found" error in Prisma 7
945
+ if (!error.message.includes('No url found')) {
946
+ console.warn('Could not parse datasource, assuming PostgreSQL:', error.message);
947
+ }
907
948
  }
908
949
 
909
950
  // Generate rapidd/rapidd.js if it doesn't exist
@@ -919,11 +960,11 @@ async function buildModels(options) {
919
960
  try {
920
961
  if (options.model) {
921
962
  // Update only specific model in relationships.json
922
- await updateRelationshipsForModel(filteredModels, relationshipsPath, prismaClientPath, schemaPath, usedDMMF);
963
+ await updateRelationshipsForModel(filteredModels, relationshipsPath, schemaPath, usedDMMF);
923
964
  } else {
924
965
  // Generate all relationships
925
966
  if (usedDMMF) {
926
- await generateRelationshipsFromDMMF(prismaClientPath, relationshipsPath);
967
+ await generateRelationshipsFromDMMF(schemaPath, relationshipsPath);
927
968
  } else {
928
969
  generateRelationshipsFromSchema(schemaPath, relationshipsPath);
929
970
  }
@@ -37,7 +37,7 @@ class ${className} extends Model {
37
37
  * @returns {{} | null}
38
38
  */
39
39
  async get(id, include){
40
- return await this._get(Number(id), include);
40
+ return await this._get(id, include);
41
41
  }
42
42
 
43
43
  /**
@@ -54,7 +54,7 @@ class ${className} extends Model {
54
54
  * @returns {Object}
55
55
  */
56
56
  async update(id, data){
57
- return await this._update(Number(id), data);
57
+ return await this._update(id, data);
58
58
  }
59
59
 
60
60
  /**
@@ -62,7 +62,7 @@ class ${className} extends Model {
62
62
  * @returns {Object}
63
63
  */
64
64
  async delete(id){
65
- return await this._delete(Number(id));
65
+ return await this._delete(id);
66
66
  }
67
67
 
68
68
  /**
@@ -184,12 +184,12 @@ function generateRelationshipsFromSchema(schemaPath, outputPath) {
184
184
 
185
185
  /**
186
186
  * Generate relationships.json from DMMF
187
- * @param {string} prismaClientPath - Path to Prisma client
187
+ * @param {string} schemaPath - Path to Prisma schema file
188
188
  * @param {string} outputPath - Path to output relationships.json
189
189
  */
190
- async function generateRelationshipsFromDMMF(prismaClientPath, outputPath) {
190
+ async function generateRelationshipsFromDMMF(schemaPath, outputPath) {
191
191
  const { parsePrismaDMMF } = require('../parsers/prismaParser');
192
- const parsedData = await parsePrismaDMMF(prismaClientPath);
192
+ const parsedData = await parsePrismaDMMF(schemaPath);
193
193
  generateRelationships(parsedData.models, outputPath);
194
194
  }
195
195
 
@@ -8,6 +8,39 @@ try {
8
8
  // dotenv not available, skip
9
9
  }
10
10
 
11
+ /**
12
+ * Try to load DATABASE_URL from prisma.config.ts (Prisma 7)
13
+ * @returns {string|null} - Database URL or null if not found
14
+ */
15
+ function loadUrlFromPrismaConfig() {
16
+ const configPath = path.join(process.cwd(), 'prisma.config.ts');
17
+
18
+ if (!fs.existsSync(configPath)) {
19
+ return null;
20
+ }
21
+
22
+ try {
23
+ const configContent = fs.readFileSync(configPath, 'utf-8');
24
+
25
+ // Look for env('DATABASE_URL') or similar patterns
26
+ const envMatch = configContent.match(/env\(['"]([^'"]+)['"]\)/);
27
+ if (envMatch) {
28
+ const envVar = envMatch[1];
29
+ return process.env[envVar] || null;
30
+ }
31
+
32
+ // Look for direct URL assignment
33
+ const urlMatch = configContent.match(/url:\s*['"]([^'"]+)['"]/);
34
+ if (urlMatch) {
35
+ return urlMatch[1];
36
+ }
37
+ } catch (e) {
38
+ // Failed to read config, return null
39
+ }
40
+
41
+ return null;
42
+ }
43
+
11
44
  /**
12
45
  * Parse datasource configuration from Prisma schema
13
46
  * @param {string} schemaPath - Path to Prisma schema file
@@ -30,26 +63,32 @@ function parseDatasource(schemaPath) {
30
63
  const providerMatch = datasourceBlock.match(/provider\s*=\s*"([^"]+)"/);
31
64
  const provider = providerMatch ? providerMatch[1] : null;
32
65
 
33
- // Extract url
66
+ // Try to extract url from schema first
67
+ let url = null;
34
68
  const urlMatch = datasourceBlock.match(/url\s*=\s*(.+)/);
35
- if (!urlMatch) {
36
- throw new Error('No url found in datasource block');
37
- }
38
69
 
39
- let url = urlMatch[1].trim();
70
+ if (urlMatch) {
71
+ url = urlMatch[1].trim();
72
+
73
+ // Handle env() function
74
+ const envMatch = url.match(/env\(["']([^"']+)["']\)/);
75
+ if (envMatch) {
76
+ const envVar = envMatch[1];
77
+ url = process.env[envVar];
78
+ } else {
79
+ // Remove quotes if present
80
+ url = url.replace(/^["']|["']$/g, '');
81
+ }
82
+ }
40
83
 
41
- // Handle env() function
42
- const envMatch = url.match(/env\(["']([^"']+)["']\)/);
43
- if (envMatch) {
44
- const envVar = envMatch[1];
45
- url = process.env[envVar];
84
+ // If no URL in schema, try prisma.config.ts (Prisma 7)
85
+ if (!url) {
86
+ url = loadUrlFromPrismaConfig();
87
+ }
46
88
 
47
- if (!url) {
48
- throw new Error(`Environment variable ${envVar} is not defined`);
49
- }
50
- } else {
51
- // Remove quotes if present
52
- url = url.replace(/^["']|["']$/g, '');
89
+ // If still no URL, check DATABASE_URL environment variable directly
90
+ if (!url) {
91
+ url = process.env.DATABASE_URL || null;
53
92
  }
54
93
 
55
94
  // Detect PostgreSQL from provider OR from the actual connection URL
@@ -1,5 +1,4 @@
1
1
  const fs = require('fs');
2
- const path = require('path');
3
2
 
4
3
  /**
5
4
  * Extract blocks (model or enum) with proper brace matching
@@ -204,16 +203,26 @@ function parseEnumValues(enumBody) {
204
203
  }
205
204
 
206
205
  /**
207
- * Use Prisma's generated DMMF (Data Model Meta Format) to get model information
206
+ * Use Prisma's DMMF (Data Model Meta Format) to get model information
208
207
  * This is an alternative approach that uses Prisma's own abstraction
209
- * @param {string} prismaClientPath - Path to generated Prisma Client
208
+ * In Prisma 7, we use getDMMF from @prisma/internals instead of accessing it from the client
209
+ * @param {string} schemaPath - Path to Prisma schema file
210
210
  * @returns {Object} - Models extracted from DMMF
211
211
  */
212
- async function parsePrismaDMMF(prismaClientPath) {
212
+ async function parsePrismaDMMF(schemaPath) {
213
213
  try {
214
- // Try to load the generated Prisma Client
215
- const prismaClient = require(prismaClientPath);
216
- const dmmf = prismaClient.Prisma.dmmf;
214
+ // In Prisma 7, DMMF is no longer exposed on the client
215
+ // We need to use getDMMF from @prisma/internals instead
216
+ const { getDMMF } = require('@prisma/internals');
217
+ const fs = require('fs');
218
+
219
+ // Read the schema file
220
+ const schemaContent = fs.readFileSync(schemaPath, 'utf-8');
221
+
222
+ // Get DMMF from the schema
223
+ const dmmf = await getDMMF({
224
+ datamodel: schemaContent
225
+ });
217
226
 
218
227
  const models = {};
219
228
 
@@ -266,7 +275,8 @@ async function parsePrismaDMMF(prismaClientPath) {
266
275
 
267
276
  return { models, enums: dmmf.datamodel.enums };
268
277
  } catch (error) {
269
- console.warn('Could not load Prisma Client DMMF, falling back to schema parsing');
278
+ console.warn('Could not use getDMMF from @prisma/internals, falling back to schema parsing');
279
+ console.warn('Error:', error.message);
270
280
  return null;
271
281
  }
272
282
  }