@rapidd/build 1.2.0 → 1.2.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidd/build",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
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,13 +49,13 @@
49
49
  "README.md"
50
50
  ],
51
51
  "dependencies": {
52
- "@prisma/adapter-planetscale": "^7.0.1",
52
+ "@prisma/adapter-mariadb": "^7.0.1",
53
+ "@prisma/adapter-pg": "^7.0.1",
53
54
  "@prisma/client": "^7.0.0",
54
55
  "@prisma/internals": "^7.0.0",
55
56
  "commander": "^11.0.0",
56
57
  "dotenv": "^16.0.0",
57
- "pg": "^8.16.3",
58
- "undici": "^7.16.0"
58
+ "pg": "^8.16.3"
59
59
  },
60
60
  "devDependencies": {
61
61
  "prisma": "^7.0.0"
@@ -7,394 +7,6 @@ const { generateACL } = require('../generators/aclGenerator');
7
7
  const { parseDatasource } = require('../parsers/datasourceParser');
8
8
  const { generateAllRoutes } = require('../generators/routeGenerator');
9
9
 
10
- /**
11
- * Generate src/Model.js base class file
12
- */
13
- function generateBaseModelFile(modelJsPath) {
14
- const content = `const { QueryBuilder, prisma, prismaTransaction } = require("./QueryBuilder");
15
- const {acl} = require('../rapidd/rapidd');
16
- const {ErrorResponse} = require('./Api');
17
-
18
- class Model {
19
- /**
20
- * @param {string} name
21
- * @param {{'user': {}}} options
22
- */
23
- constructor(name, options){
24
- this.modelName = name;
25
- this.queryBuilder = new QueryBuilder(name);
26
- this.acl = acl.model[name] || {};
27
- this.options = options || {}
28
- this.user = this.options.user || {'id': 1, 'role': 'application'};
29
- this.user_id = this.user ? this.user.id : null;
30
- }
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
-
41
- _select = (fields) => this.queryBuilder.select(fields);
42
- _filter = (q) => this.queryBuilder.filter(q);
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);
46
- // ACL METHODS
47
- _canCreate = () => this.acl.canCreate(this.user);
48
- _getAccessFilter = () => this.acl.getAccessFilter?.(this.user);
49
- _getUpdateFilter = () => this.acl.getUpdateFilter(this.user);
50
- _getDeleteFilter = () => this.acl.getDeleteFilter(this.user);
51
- _omit = () => this.queryBuilder.omit(this.user);
52
-
53
- /**
54
- *
55
- * @param {string} q
56
- * @property {string|Object} include
57
- * @param {number} limit
58
- * @param {number} offset
59
- * @param {string} sortBy
60
- * @param {'asc'|'desc'} sortOrder
61
- * @param {{}} [options={}]
62
- * @returns {Promise<Object[]>}
63
- */
64
- _getMany = async (q = {}, include = "", limit = 25, offset = 0, sortBy = this.primaryKey, sortOrder = "asc", options = {})=>{
65
- const take = this.take(Number(limit));
66
- const skip = this.skip(Number(offset));
67
-
68
- sortBy = sortBy?.trim();
69
- sortOrder = sortOrder?.trim();
70
- if (!sortBy.includes('.') && this.fields[sortBy] == undefined) {
71
- throw new ErrorResponse(400, "invalid_sort_field", {sortBy, modelName: this.constructor.name});
72
- }
73
-
74
- // Query the database using Prisma with filters, pagination, and limits
75
- const [data, total] = await prismaTransaction([
76
- (tx) => tx[this.name].findMany({
77
- 'where': this.filter(q),
78
- 'include': this.include(include),
79
- 'take': take,
80
- 'skip': skip,
81
- 'orderBy': this.sort(sortBy, sortOrder),
82
- 'omit': this._omit(),
83
- ...options
84
- }),
85
- (tx) => tx[this.name].count({
86
- 'where': this.filter(q)
87
- })
88
- ]);
89
- return {data, meta: {take, skip, total}};
90
- }
91
- /**
92
- * @param {number} id
93
- * @param {string | Object} include
94
- * @param {{}} [options={}]
95
- * @returns {Promise<{} | null>}
96
- */
97
- _get = async (id, include, options = {}) =>{
98
- const {omit, ..._options} = options;
99
- console.log(JSON.stringify(this.include(include)));
100
- // To determine if the record is inaccessible, either due to non-existence or insufficient permissions, two simultaneous queries are performed.
101
- const _response = this.prisma.findUnique({
102
- 'where': {
103
- [this.primaryKey]: id,
104
- },
105
- 'include': this.include(include),
106
- 'omit': {...this._omit(), ...omit},
107
- ..._options
108
- });
109
-
110
- const _checkPermission = this.prisma.findUnique({
111
- 'where': {
112
- [this.primaryKey]: id,
113
- ...this.getAccessFilter()
114
- },
115
- 'select': {
116
- 'id': true
117
- }
118
- });
119
-
120
- const [response, checkPermission] = await Promise.all([_response, _checkPermission]);
121
- if(response){
122
- if(checkPermission){
123
- if(response.id != checkPermission?.id){ // IN CASE access_filter CONTAINS id FIELD
124
- throw new ErrorResponse(403, "no_permission");
125
- }
126
- }
127
- else{
128
- throw new ErrorResponse(403, "no_permission");
129
- }
130
- }
131
- else{
132
- throw new ErrorResponse(404, "record_not_found");
133
- }
134
- return response;
135
- }
136
- /**
137
- * @param {{}} data
138
- * @param {{}} [options={}]
139
- * @returns {Promise<Object>}
140
- */
141
- _create = async (data, options = {}) => {
142
- // CHECK CREATE PERMISSION
143
- if (!this.canCreate()) {
144
- throw new ErrorResponse(403, "no_permission_to_create");
145
- }
146
-
147
- // VALIDATE PASSED FIELDS AND RELATIONSHIPS
148
- this._queryCreate(data);
149
-
150
- // CREATE
151
- return await this.prisma.create({
152
- 'data': data,
153
- 'include': this.include('ALL'),
154
- ...options
155
- });
156
- }
157
-
158
- /**
159
- * @param {number} id
160
- * @param {{}} data
161
- * @param {{}} [options={}]
162
- * @returns {Promise<Object>}
163
- */
164
- _update = async (id, data, options = {}) => {
165
- delete data.createdAt;
166
- delete data.createdBy;
167
- // CHECK UPDATE PERMISSION
168
- const updateFilter = this.getUpdateFilter();
169
- if (updateFilter === false) {
170
- throw new ErrorResponse(403, "no_permission_to_update");
171
- }
172
-
173
- // VALIDATE PASSED FIELDS AND RELATIONSHIPS
174
- this._queryUpdate(id, data);
175
- const response = await this.prisma.update({
176
- 'where': {
177
- [this.primaryKey]: id,
178
- ...updateFilter
179
- },
180
- 'data': data,
181
- 'include': this.include('ALL'),
182
- ...options
183
- });
184
- if(response){
185
- return response;
186
- }
187
- throw new ErrorResponse(403, "no_permission");
188
- }
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
-
212
- /**
213
- *
214
- * @param {string} q
215
- * @returns {Promise<number>}
216
- */
217
- _count = async (q = {}) => {
218
- return await this.prisma.count({
219
- 'where': this.filter(q)
220
- });
221
- }
222
-
223
- /**
224
- * @param {number} id
225
- * @param {{}} [options={}]
226
- * @returns {Promise<Object>}
227
- */
228
- _delete = async (id, options = {}) => {
229
- // CHECK DELETE PERMISSION
230
- const deleteFilter = this.getDeleteFilter();
231
- if (deleteFilter === false) {
232
- throw new ErrorResponse(403, "no_permission_to_delete");
233
- }
234
-
235
- const response = await this.prisma.delete({
236
- 'where': {
237
- [this.primaryKey]: id,
238
- ...deleteFilter
239
- },
240
- 'select': this.select(),
241
- ...options
242
- });
243
- if(response){
244
- return response;
245
- }
246
- throw new ErrorResponse(403, "no_permission");
247
- }
248
-
249
- /**
250
- *
251
- * @param {string} q
252
- * @property {string|Object} include
253
- * @param {number} limit
254
- * @param {number} offset
255
- * @param {string} sortBy
256
- * @param {'asc'|'desc'} sortOrder
257
- * @returns {Promise<Object[]>}
258
- */
259
- async getMany(q = {}, include = "", limit = 25, offset = 0, sortBy = "id", sortOrder = "asc"){
260
- return await this._getMany(q, include, Number(limit), Number(offset), sortBy, sortOrder);
261
- }
262
- /**
263
- * @param {number} id
264
- * @param {string | Object} include
265
- * @param {{}} [options={}]
266
- * @returns {Promise<{} | null>}
267
- */
268
- async get(id, include, options = {}){
269
- return await this._get(id, include, options);
270
- }
271
-
272
- /**
273
- * @param {number} id
274
- * @param {{}} data
275
- * @param {{}} [options={}]
276
- * @returns {Promise<Object>}
277
- */
278
- async update(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);
290
- }
291
-
292
- /**
293
- *
294
- * @param {string} q
295
- * @returns {Promise<number>}
296
- */
297
- async count(q = {}) {
298
- return await this._count(q);
299
- }
300
-
301
- /**
302
- * @param {number} id
303
- * @returns {Promise<Object>}
304
- */
305
- async delete(id, data, options = {}){
306
- return await this._delete(id, data, options);
307
- }
308
-
309
- select(fields){
310
- return this._select(fields);
311
- }
312
- filter(include){
313
- return {...this._filter(include), ...this.getAccessFilter()};
314
- }
315
- include(include){
316
- return this._include(include);
317
- }
318
- sort(sortBy, sortOrder) {
319
- return this.queryBuilder.sort(sortBy, sortOrder);
320
- }
321
- take(limit){
322
- return this.queryBuilder.take(Number(limit));
323
- }
324
- skip(offset){
325
- const parsed = parseInt(offset);
326
- if(isNaN(parsed) || parsed < 0){
327
- return 0;
328
- }
329
- return parsed;
330
- }
331
-
332
- /**
333
- *
334
- * @returns {Object}
335
- */
336
- getAccessFilter(){
337
- const filter = this._getAccessFilter()
338
- if(this.user.role == "application" || filter === true){
339
- return {};
340
- }
341
- return this._getAccessFilter();
342
- }
343
-
344
- /**
345
- * Check if user can create records
346
- * @returns {boolean}
347
- */
348
- canCreate() {
349
- if(this.user.role == "application") return true;
350
- return this._canCreate();
351
- }
352
-
353
- /**
354
- * Get update filter for ACL
355
- * @returns {Object|false}
356
- */
357
- getUpdateFilter(){
358
- const filter = this._getUpdateFilter();
359
- if(this.user.role == "application" || filter === true){
360
- return {};
361
- }
362
- return filter;
363
- }
364
-
365
- /**
366
- * Get delete filter for ACL
367
- * @returns {Object|false}
368
- */
369
- getDeleteFilter(){
370
- const filter = this._getDeleteFilter();
371
- if(this.user.role == "application" || filter === true){
372
- return {};
373
- }
374
- return filter;
375
- }
376
-
377
- set modelName (name){
378
- this.name = name;
379
- this.prisma = prisma[name];
380
- }
381
-
382
- static relatedObjects = [];
383
- static Error = ErrorResponse;
384
- }
385
-
386
- module.exports = {Model, QueryBuilder, prisma};`;
387
-
388
- // Ensure src directory exists
389
- const srcDir = path.dirname(modelJsPath);
390
- if (!fs.existsSync(srcDir)) {
391
- fs.mkdirSync(srcDir, { recursive: true });
392
- }
393
-
394
- fs.writeFileSync(modelJsPath, content);
395
- console.log('✓ Generated src/Model.js');
396
- }
397
-
398
10
  /**
399
11
  * Generate rapidd/rapidd.js file
400
12
  * @param {string} rapiddJsPath - Path to rapidd.js
@@ -930,12 +542,6 @@ async function buildModels(options) {
930
542
  generateAllModels(filteredModels, modelDir, modelJsPath);
931
543
  }
932
544
 
933
- // Generate src/Model.js (base Model class) if it doesn't exist
934
- if (!fs.existsSync(modelJsPath)) {
935
- console.log('\nGenerating src/Model.js...');
936
- generateBaseModelFile(modelJsPath);
937
- }
938
-
939
545
  // Parse datasource to determine database type
940
546
  let datasource = { isPostgreSQL: true, url: null }; // Default to PostgreSQL
941
547
  try {
@@ -9,13 +9,16 @@ const path = require('path');
9
9
  */
10
10
  function generateModelFile(modelName, modelInfo) {
11
11
  // Capitalize first letter for class name
12
- const className = modelName.charAt(0).toUpperCase() + modelName.slice(1);
12
+ const className = modelName.split(/[^a-zA-Z0-9]+/) // split on any non-alphanumeric char
13
+ .filter(Boolean) // remove empty parts
14
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
15
+ .join('');
13
16
 
14
17
  return `const {Model, QueryBuilder, prisma} = require('../Model');
15
18
 
16
19
  class ${className} extends Model {
17
20
  constructor(options){
18
- super('${className}', options);
21
+ super('${modelName}', options);
19
22
  }
20
23
 
21
24
  /**
@@ -57,6 +60,15 @@ class ${className} extends Model {
57
60
  return await this._update(id, data);
58
61
  }
59
62
 
63
+ /**
64
+ * @param {{}} data
65
+ * @param {string} [unique_key=this.primaryKey]
66
+ * @returns {Promise<Object>}
67
+ */
68
+ async upsert(data, unique_key = this.primaryKey){
69
+ return await this._upsert(data, unique_key);
70
+ }
71
+
60
72
  /**
61
73
  * @param {number} id
62
74
  * @returns {Object}
@@ -122,7 +134,10 @@ function generateAllModels(models, modelDir, modelJsPath) {
122
134
  for (const [modelName, modelInfo] of Object.entries(models)) {
123
135
  const modelCode = generateModelFile(modelName, modelInfo);
124
136
  // Capitalize first letter for filename
125
- const className = modelName.charAt(0).toUpperCase() + modelName.slice(1);
137
+ const className = modelName.split(/[^a-zA-Z0-9]+/) // split on any non-alphanumeric char
138
+ .filter(Boolean) // remove empty parts
139
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
140
+ .join('');
126
141
  const modelPath = path.join(modelDir, `${className}.js`);
127
142
  fs.writeFileSync(modelPath, modelCode);
128
143
  console.log(`Generated model: ${className}.js`);
@@ -7,7 +7,10 @@ const path = require('path');
7
7
  * @returns {string} - Generated route code
8
8
  */
9
9
  function generateRouteFile(modelName) {
10
- const className = modelName.charAt(0).toUpperCase() + modelName.slice(1);
10
+ const className = modelName.split(/[^a-zA-Z0-9]+/) // split on any non-alphanumeric char
11
+ .filter(Boolean) // remove empty parts
12
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
13
+ .join('');
11
14
 
12
15
  return `const router = require('express').Router();
13
16
  const {${className}, QueryBuilder, prisma} = require('../../../src/Model/${className}');
@@ -104,9 +107,9 @@ function generateAllRoutes(models, routesDir) {
104
107
  // Generate individual route files
105
108
  for (const modelName of Object.keys(models)) {
106
109
  const routeCode = generateRouteFile(modelName);
107
- const routePath = path.join(routesDir, `${modelName.toLowerCase()}.js`);
110
+ const routePath = path.join(routesDir, `${modelName}.js`);
108
111
  fs.writeFileSync(routePath, routeCode);
109
- console.log(`Generated route: ${modelName.toLowerCase()}.js`);
112
+ console.log(`Generated route: ${modelName}.js`);
110
113
  }
111
114
  }
112
115