express-project-builder 1.0.37 → 1.0.39

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
@@ -137,6 +137,7 @@ my-test-project/
137
137
  ├── eslint.config.mjs
138
138
  ├── package-lock.json
139
139
  ├── package.json
140
+ ├── prisma.config.ts
140
141
  └── tsconfig.json
141
142
  ```
142
143
 
@@ -152,11 +153,13 @@ my-test-project/
152
153
  - [eslint-plugin-prettier (5.5.4)](https://www.npmjs.com/package/eslint-plugin-prettier)
153
154
  - [express (5.1.0)](https://www.npmjs.com/package/express)
154
155
  - [jsonwebtoken (9.0.2)](https://www.npmjs.com/package/jsonwebtoken)
155
- - [mongoose (8.19.1)](https://www.npmjs.com/package/mongoose)
156
+ - [mongoose (9.1.5)](https://www.npmjs.com/package/mongoose)
156
157
  - [multer (2.0.2-lts.1)](https://www.npmjs.com/package/multer)
157
158
  - [node-cache (5.1.2)](https://www.npmjs.com/package/node-cache)
158
159
  - [nodemailer (7.0.6)](https://www.npmjs.com/package/nodemailer)
159
- - [@prisma/client (6.16.1)](https://www.npmjs.com/package/@prisma/client)
160
+ - [@prisma/client (7.3.0)](https://www.npmjs.com/package/@prisma/client)
161
+ - [@prisma/adapter-pg (7.3.0)](https://www.npmjs.com/package/@prisma/adapter-ppg)
162
+ - [pg (8.18.0)](https://www.npmjs.com/package/pg)
160
163
  - [zod (3.24.1)](https://www.npmjs.com/package/zod)
161
164
 
162
165
  ### Development Dependencies
@@ -173,7 +176,7 @@ my-test-project/
173
176
  - [eslint (9.35.0)](https://www.npmjs.com/package/eslint)
174
177
  - [eslint-config-prettier (10.1.8)](https://www.npmjs.com/package/eslint-config-prettier)
175
178
  - [globals (16.4.0)](https://www.npmjs.com/package/globals)
176
- - [prisma (6.16.1)](https://www.npmjs.com/package/prisma)
179
+ - [prisma (7.3.0)](https://www.npmjs.com/package/prisma)
177
180
  - [prettier (3.6.2)](https://www.npmjs.com/package/prettier)
178
181
  - [ts-node-dev (2.0.0)](https://www.npmjs.com/package/ts-node-dev)
179
182
  - [typescript (5.9.2)](https://www.npmjs.com/package/typescript)
@@ -225,43 +228,165 @@ my-test-project/
225
228
  - /src/app/builder/**PrismaQueryBuilder.ts** <br/>
226
229
  A fluent API for building complex SQL queries with Prisma. Simplifies dynamic query construction for search, filtering, sorting, pagination, and field selection in a chainable interface.
227
230
 
228
- ```typescript
229
- // In your controller/service:
230
- import PrismaQueryBuilder from "../../builder/PrismaQueryBuilder";
231
-
232
- // Extract query parameters from request (e.g., ?search=keyword&page=1&limit=10)
233
- const { page, limit, sort, search, fields, ...filters } = req.query;
234
-
235
- // Create a new query builder instance
236
- const docQuery = new PrismaQueryBuilder(YourModel.find(), req.query)
237
-
238
- // Search in specified text fields (e.g., name, description, category)
239
- .search(["name", "description", "category"])
240
-
241
- // Apply filters dynamically (e.g., status=active, category=tech)
242
- .filter()
243
-
244
- // Sort results (e.g., ?sort=name asc,sort=-createdAt desc)
245
- .sort()
246
-
247
- // Paginate results (e.g., ?page=1&limit=10)
248
- .paginate()
249
-
250
- // Select specific fields (e.g., ?fields=name,price,description)
251
- .fields();
252
-
253
- // Execute the query and get results
254
- const result = await docQuery.modelQuery;
255
-
256
- // Get pagination metadata (total count, page, limit, total pages)
257
- const meta = await docQuery.countTotal();
231
+ ```typescript
232
+ // ===============================
233
+ // Example Usage: PrismaQueryBuilder
234
+ // ===============================
235
+
236
+ import PrismaQueryBuilder from "../../builder/PrismaQueryBuilder";
237
+ import { prisma } from "../../shared/prisma";
238
+
239
+ // -------------------------------------------------
240
+ // Controller / Service Layer Example
241
+ // -------------------------------------------------
242
+
243
+ // Extract query parameters from request
244
+ // Example request:
245
+ // /api/docs?search=node&page=1&limit=10&sort=-createdAt&fields=name,price
246
+ const { page, limit, sort, search, fields, ...filters } = req.query;
247
+
248
+ /**
249
+ * Initialize Query Builder
250
+ *
251
+ * Parameters:
252
+ * 1. prisma.youModel -> Prisma model instance (table)
253
+ * 2. filters -> dynamic filters from query params
254
+ */
255
+ const docQuery = new PrismaQueryBuilder(prisma.youModel, filters)
256
+
257
+ /**
258
+ * setBaseQuery()
259
+ * ------------------------------------
260
+ * Adds default conditions that will always apply
261
+ * to the query.
262
+ *
263
+ * Example use cases:
264
+ * - Soft delete filtering
265
+ * - Tenant based filtering
266
+ * - Default status filtering
267
+ */
268
+ .setBaseQuery({
269
+ isDeleted: false,
270
+ })
271
+
272
+ /**
273
+ * setSecretFields()
274
+ * ------------------------------------
275
+ * Prevents sensitive fields from being returned
276
+ * in API responses.
277
+ *
278
+ * Example:
279
+ * password, tokens, internal flags etc.
280
+ */
281
+ .setSecretFields(["isDeleted", "password", "sensetiveFileds"])
282
+
283
+ /**
284
+ * search()
285
+ * ------------------------------------
286
+ * Enables text search across multiple fields.
287
+ *
288
+ * Example request:
289
+ * ?search=node
290
+ *
291
+ * This will search "node" in:
292
+ * name OR description OR category
293
+ */
294
+ .search(["name", "description", "category"])
295
+
296
+ /**
297
+ * filter()
298
+ * ------------------------------------
299
+ * Applies dynamic filters from query params.
300
+ *
301
+ * Example request:
302
+ * ?status=active&category=tech
303
+ *
304
+ * Automatically converts query params
305
+ * into Prisma "where" conditions.
306
+ */
307
+ .filter()
308
+
309
+ /**
310
+ * sort()
311
+ * ------------------------------------
312
+ * Enables sorting of results.
313
+ *
314
+ * Example requests:
315
+ * ?sort=name
316
+ * ?sort=-createdAt
317
+ *
318
+ * "-" indicates descending order.
319
+ */
320
+ .sort()
321
+
322
+ /**
323
+ * paginate()
324
+ * ------------------------------------
325
+ * Applies pagination.
326
+ *
327
+ * Example request:
328
+ * ?page=2&limit=10
329
+ *
330
+ * Converts into:
331
+ * skip + take (Prisma pagination)
332
+ */
333
+ .paginate()
334
+
335
+ /**
336
+ * fields()
337
+ * ------------------------------------
338
+ * Allows selecting specific fields.
339
+ *
340
+ * Example request:
341
+ * ?fields=name,price,description
342
+ *
343
+ * Only these fields will be returned
344
+ * in the response.
345
+ */
346
+ .fields()
347
+
348
+ /**
349
+ * include()
350
+ * ------------------------------------
351
+ * Allows joining related tables using
352
+ * Prisma's include/select functionality.
353
+ *
354
+ * Example:
355
+ * Including related user/profile data
356
+ */
357
+ .include({
358
+ table_name: {
359
+ select: {
360
+ row_1: true,
361
+ row_2: true,
362
+ },
363
+ },
364
+ });
258
365
 
259
- // Return the results and metadata from service
260
- return {
261
- result,
262
- meta,
263
- };
264
- ```
366
+ /**
367
+ * Execute the final query
368
+ */
369
+ const result = await docQuery.execute();
370
+
371
+ /**
372
+ * Get pagination metadata
373
+ *
374
+ * Returns:
375
+ * total records
376
+ * current page
377
+ * limit
378
+ * total pages
379
+ */
380
+ const meta = await docQuery.countTotal();
381
+
382
+ /**
383
+ * Final response format
384
+ */
385
+ return {
386
+ result,
387
+ meta,
388
+ };
389
+ ```
265
390
 
266
391
  - /src/**config/index.ts** <br/> Central configuration file that manages environment variables and application settings. This file provides a structured way to access environment variables throughout the application.
267
392
 
@@ -301,14 +426,14 @@ import auth from "../../middlewares/auth";
301
426
  router.get(
302
427
  "/users",
303
428
  auth("user", "admin", "superAdmin", "developer"), // Only users with these roles can access
304
- UsersControllers.getAllUsers
429
+ UsersControllers.getAllUsers,
305
430
  );
306
431
 
307
432
  // Protect a route without role checking (just verify token)
308
433
  router.get(
309
434
  "/profile",
310
435
  auth("user", "admin", "superAdmin", "developer", true), // Any authenticated user can access
311
- UsersController.getUserProfile
436
+ UsersController.getUserProfile,
312
437
  );
313
438
  ```
314
439
 
@@ -380,7 +505,7 @@ router.post(
380
505
  },
381
506
  ]),
382
507
  formDataToSetJSONformatData, // Convert form data to JSON
383
- ProductController.createProduct
508
+ ProductController.createProduct,
384
509
  );
385
510
  ```
386
511
 
@@ -428,7 +553,7 @@ import { ProductValidation } from "../../app/models/product_validationZodSchema"
428
553
  router.post(
429
554
  "/create",
430
555
  validateRequest(ProductValidation.createProduct_ValidationZodSchema),
431
- ProductControllers.createProduct
556
+ ProductControllers.createProduct,
432
557
  );
433
558
 
434
559
  // /src/app/models/product_validationZodSchema.ts
@@ -508,7 +633,7 @@ const get_All_Products = catchAsync(async (req, res) => {
508
633
  // Your async controller logic here
509
634
  const result = await Product_Services.get_All_Products_FromDB(
510
635
  req.body,
511
- req.query
636
+ req.query,
512
637
  );
513
638
 
514
639
  res.status(200).json({
@@ -579,7 +704,7 @@ const barcodeId = generateRandomBarcodeId(); // Returns: "153.04.55.022"
579
704
  const token = createToken(
580
705
  { user_id: "123", role: "admin" },
581
706
  config.jwt_access_token_secret,
582
- config.jwt_access_token_expires_in
707
+ config.jwt_access_token_expires_in,
583
708
  ); // Returns JWT token
584
709
 
585
710
  // JWT Token Verify and Decode
@@ -606,7 +731,7 @@ const isInvalid = isValidDate(payload.date); // Returns: false
606
731
  // Validate and normalize start and end dates
607
732
  const { start, end } = Start_End_DateTime_Validation(
608
733
  payload.start_date,
609
- payload.end_date
734
+ payload.end_date,
610
735
  );
611
736
  // Returns: { start: Date, end: Date } with proper date objects
612
737
 
@@ -621,7 +746,7 @@ const isoDate = formatDateToISOString(new Date());
621
746
  const daysDiff = getDateDifference(
622
747
  payload.start_date,
623
748
  payload.end_date,
624
- "days"
749
+ "days",
625
750
  ); // Returns: 9
626
751
  ```
627
752
 
@@ -729,7 +854,7 @@ import sendResponse from "../../utils/sendResponse";
729
854
  const get_AllProducts = catchAsync(async (req, res) => {
730
855
  const result = await Product_Services.fetch_AllProductsFromDB(
731
856
  req.params.user_id,
732
- req.query
857
+ req.query,
733
858
  );
734
859
 
735
860
  res.status(200).json({
@@ -744,7 +869,7 @@ const get_AllProducts = catchAsync(async (req, res) => {
744
869
  const get_AllProducts = catchAsync(async (req, res) => {
745
870
  const result = await Product_Services.fetch_AllProductsFromDB(
746
871
  req.params.user_id,
747
- req.query
872
+ req.query,
748
873
  );
749
874
 
750
875
  sendResponse(res, {
@@ -1 +1 @@
1
- {"version":3,"file":"create_QueryBuilder_Helpers.d.ts","sourceRoot":"","sources":["../../../../src/lib/src/builders/create_QueryBuilder_Helpers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGhD,eAAO,MAAM,2BAA2B,GACtC,aAAa,MAAM,EACnB,SAAS,QAAQ,KAChB,OAAO,CAAC,IAAI,CA0Sd,CAAC"}
1
+ {"version":3,"file":"create_QueryBuilder_Helpers.d.ts","sourceRoot":"","sources":["../../../../src/lib/src/builders/create_QueryBuilder_Helpers.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGhD,eAAO,MAAM,2BAA2B,GACtC,aAAa,MAAM,EACnB,SAAS,QAAQ,KAChB,OAAO,CAAC,IAAI,CA6ed,CAAC"}
@@ -108,22 +108,54 @@ export default QueryBuilder;
108
108
  // Create Prisma QueryBuilder if PostgreSQL with Prisma is selected
109
109
  if (answers.database === "PostgreSQL with Prisma") {
110
110
  const prismaQueryBuilderTemplate = `/* eslint-disable @typescript-eslint/no-explicit-any */
111
- /**
112
- * Prisma Query Builder class for building complex queries with chaining methods
113
- * Similar pattern to the Mongoose QueryBuilder but adapted for Prisma
114
- */
115
111
  class PrismaQueryBuilder<T extends Record<string, any>> {
116
112
  private prismaModel: any
117
113
  private query: Record<string, unknown>
118
- private whereClause: any = {}
114
+ private baseWhereClause: any = {} // Priority query - cannot be overridden
115
+ private whereClause: any = {} // User filters
119
116
  private orderBy: any[] = []
120
117
  private selectFields: any = null
118
+ private includeRelations: any = null
121
119
  private skipValue: number | undefined
122
120
  private takeValue: number | undefined
121
+ private secretFields: string[] = [] // Fields that can NEVER be shown
122
+
123
123
  constructor(prismaModel: any, query: Record<string, unknown>) {
124
124
  this.prismaModel = prismaModel
125
125
  this.query = query
126
126
  }
127
+
128
+ /**
129
+ * Set base WHERE conditions that have PRIORITY and cannot be overridden
130
+ * These conditions are always applied regardless of user input
131
+ *
132
+ * @param baseWhere - Base WHERE conditions with priority
133
+ * @returns PrismaQueryBuilder instance for chaining
134
+ *
135
+ * @example
136
+ * .setBaseQuery({ ownerId: user.id, isDeleted: false })
137
+ * // User cannot override these conditions
138
+ */
139
+ setBaseQuery(baseWhere: Record<string, any>): this {
140
+ this.baseWhereClause = baseWhere
141
+ return this
142
+ }
143
+
144
+ /**
145
+ * Set fields that are SECRET and can NEVER be shown in output
146
+ * Even if user explicitly requests them via ?fields=password
147
+ *
148
+ * @param secretFields - Array of field names that must always be hidden
149
+ * @returns PrismaQueryBuilder instance for chaining
150
+ *
151
+ * @example
152
+ * .setSecretFields(['password', 'refreshToken', 'isDeleted'])
153
+ */
154
+ setSecretFields(secretFields: string[]): this {
155
+ this.secretFields = secretFields
156
+ return this
157
+ }
158
+
127
159
  /**
128
160
  * Add search functionality to the query
129
161
  * @param searchableFields - Array of fields to search in
@@ -132,37 +164,74 @@ class PrismaQueryBuilder<T extends Record<string, any>> {
132
164
  search(searchableFields: string[]): this {
133
165
  const searchTerm = this.query.search as string
134
166
  if (searchTerm) {
135
- this.whereClause = {
136
- ...this.whereClause,
137
- OR: searchableFields.map(field => ({
138
- [field]: {
139
- contains: searchTerm,
140
- mode: 'insensitive'
141
- }
142
- }))
167
+ const searchConditions = searchableFields.map(field => ({
168
+ [field]: {
169
+ contains: searchTerm,
170
+ mode: 'insensitive' as const
171
+ }
172
+ }))
173
+
174
+ if (Object.keys(this.whereClause).length > 0) {
175
+ this.whereClause = {
176
+ AND: [{ ...this.whereClause }, { OR: searchConditions }]
177
+ }
178
+ } else {
179
+ this.whereClause = {
180
+ OR: searchConditions
181
+ }
143
182
  }
144
183
  }
145
184
  return this
146
185
  }
186
+
147
187
  /**
148
- * Add filtering to the query
188
+ * Add filtering to the query (exact field = value matching)
189
+ * User filters are applied AFTER base query
149
190
  * @returns PrismaQueryBuilder instance for chaining
150
191
  */
151
192
  filter(): this {
152
193
  const queryObj = { ...this.query }
153
- const excludeFields = ['search', 'sort', 'page', 'limit', 'fields']
194
+
195
+ const excludeFields = [
196
+ 'search',
197
+ 'sort',
198
+ 'page',
199
+ 'limit',
200
+ 'fields',
201
+ 'field',
202
+ 'filelds',
203
+ 'include',
204
+ 'populate',
205
+ 'select'
206
+ ]
207
+
154
208
  excludeFields.forEach(el => delete queryObj[el])
155
- // Add the remaining query parameters to the where clause
209
+
210
+ // Remove any keys that match base query (prevent override)
211
+ Object.keys(this.baseWhereClause).forEach(key => {
212
+ delete queryObj[key]
213
+ })
214
+
156
215
  Object.entries(queryObj).forEach(([key, value]) => {
157
216
  if (value !== undefined && value !== null) {
158
- this.whereClause = {
159
- ...this.whereClause,
160
- [key]: value
217
+ let processedValue = value
218
+ if (value === 'true') processedValue = true
219
+ if (value === 'false') processedValue = false
220
+
221
+ if (this.whereClause.AND || this.whereClause.OR) {
222
+ const existingClause = { ...this.whereClause }
223
+ this.whereClause = {
224
+ AND: [existingClause, { [key]: processedValue }]
225
+ }
226
+ } else {
227
+ this.whereClause[key] = processedValue
161
228
  }
162
229
  }
163
230
  })
231
+
164
232
  return this
165
233
  }
234
+
166
235
  /**
167
236
  * Add sorting to the query
168
237
  * @returns PrismaQueryBuilder instance for chaining
@@ -179,11 +248,11 @@ class PrismaQueryBuilder<T extends Record<string, any>> {
179
248
  }
180
249
  })
181
250
  } else {
182
- // Default sort by createdAt in descending order
183
251
  this.orderBy = [{ createdAt: 'desc' }]
184
252
  }
185
253
  return this
186
254
  }
255
+
187
256
  /**
188
257
  * Add pagination to the query
189
258
  * @returns PrismaQueryBuilder instance for chaining
@@ -195,67 +264,171 @@ class PrismaQueryBuilder<T extends Record<string, any>> {
195
264
  this.takeValue = limit
196
265
  return this
197
266
  }
267
+
198
268
  /**
199
269
  * Field selection to include specific fields
270
+ * Secret fields are ALWAYS removed even if user requests them
271
+ *
200
272
  * @returns PrismaQueryBuilder instance for chaining
201
273
  */
202
274
  fields(): this {
203
- const fields = this.query.fields as string
275
+ const fields = (this.query.fields ||
276
+ this.query.field ||
277
+ this.query.filelds) as string
278
+
204
279
  if (fields) {
205
- const fieldArray = fields.split(',')
206
- this.selectFields = fieldArray.reduce((acc, field) => {
207
- // Skip the __v field which doesn't exist in Prisma
208
- if (field !== '__v') {
280
+ const fieldArray = fields.split(',').map(f => f.trim())
281
+
282
+ // Filter out secret fields - user cannot request them
283
+ const allowedFields = fieldArray.filter(
284
+ field =>
285
+ !this.secretFields.includes(field) && field !== '__v' && field !== ''
286
+ )
287
+
288
+ if (allowedFields.length > 0) {
289
+ this.selectFields = allowedFields.reduce((acc, field) => {
209
290
  acc[field] = true
210
- }
211
- return acc
212
- }, {} as any)
291
+ return acc
292
+ }, {} as any)
293
+ }
294
+ }
295
+
296
+ return this
297
+ }
298
+
299
+ /**
300
+ * Add relations to include with automatic secret field removal
301
+ *
302
+ * @param relations - Object defining which relations to include
303
+ * @param nestedSecretFields - Secret fields for nested relations
304
+ * @returns PrismaQueryBuilder instance for chaining
305
+ *
306
+ * @example
307
+ * .include(
308
+ * {
309
+ * owner: { select: { id: true, name: true, phone: true } },
310
+ * posts: true
311
+ * },
312
+ * {
313
+ * owner: ['password', 'refreshToken'],
314
+ * posts: ['isDeleted', 'internalNotes']
315
+ * }
316
+ * )
317
+ */
318
+ include(relations: any, nestedSecretFields?: Record<string, string[]>): this {
319
+ if (typeof relations === 'object' && relations !== null) {
320
+ // Process relations and apply secret field filtering
321
+ const processedRelations = { ...relations }
322
+
323
+ if (nestedSecretFields) {
324
+ Object.keys(processedRelations).forEach(relationName => {
325
+ const relation = processedRelations[relationName]
326
+ const secretFieldsForRelation = nestedSecretFields[relationName]
327
+
328
+ if (secretFieldsForRelation && relation !== true) {
329
+ // If relation has select, filter out secret fields
330
+ if (relation.select) {
331
+ secretFieldsForRelation.forEach(field => {
332
+ delete relation.select[field]
333
+ })
334
+ }
335
+ }
336
+ })
337
+ }
338
+
339
+ this.includeRelations = processedRelations
213
340
  }
214
341
  return this
215
342
  }
343
+
216
344
  /**
217
- * Execute the query and return results with metadata
218
- * @returns Object with data and pagination metadata
345
+ * Execute the query and return data
346
+ * Combines base query with user filters
347
+ * Removes secret fields from output
348
+ *
349
+ * @returns Array of data
219
350
  */
220
- async execute(): Promise<{
221
- data: T[]
222
- meta: {
223
- page: number
224
- limit: number
225
- totalData: number
226
- totalPage: number
351
+ async execute(): Promise<T[]> {
352
+ // Combine base query (priority) with user filters
353
+ let finalWhereClause: any = {}
354
+
355
+ if (
356
+ Object.keys(this.baseWhereClause).length > 0 &&
357
+ Object.keys(this.whereClause).length > 0
358
+ ) {
359
+ // Both base and user filters exist - combine with AND
360
+ finalWhereClause = {
361
+ AND: [this.baseWhereClause, this.whereClause]
362
+ }
363
+ } else if (Object.keys(this.baseWhereClause).length > 0) {
364
+ // Only base query
365
+ finalWhereClause = this.baseWhereClause
366
+ } else if (Object.keys(this.whereClause).length > 0) {
367
+ // Only user filters
368
+ finalWhereClause = this.whereClause
227
369
  }
228
- }> {
229
- const page = Number(this.query.page) || 1
230
- const limit = Number(this.query.limit) || 10
231
- // Build the query parameters
370
+
232
371
  const queryOptions: any = {
233
- where: this.whereClause,
372
+ where:
373
+ Object.keys(finalWhereClause).length > 0 ? finalWhereClause : undefined,
234
374
  orderBy: this.orderBy.length > 0 ? this.orderBy : undefined,
235
375
  skip: this.skipValue,
236
376
  take: this.takeValue
237
377
  }
238
- // Add select if specified
239
- if (this.selectFields) {
378
+
379
+ if (this.selectFields && !this.includeRelations) {
240
380
  queryOptions.select = this.selectFields
241
381
  }
242
- // Get total count
243
- const totalData = await this.prismaModel.count({ where: this.whereClause })
244
- const totalPage = Math.ceil(totalData / limit)
245
- // Get data
246
- const data = await this.prismaModel.findMany(queryOptions)
247
- return {
248
- data,
249
- meta: {
250
- page,
251
- limit,
252
- totalData,
253
- totalPage
254
- }
382
+
383
+ if (this.includeRelations) {
384
+ queryOptions.include = this.includeRelations
385
+ }
386
+
387
+ let data = await this.prismaModel.findMany(queryOptions)
388
+
389
+ // Remove secret fields from output (post-processing for security)
390
+ if (this.secretFields.length > 0) {
391
+ data = this.removeSecretFields(data, this.secretFields)
255
392
  }
393
+
394
+ return data
395
+ }
396
+
397
+ /**
398
+ * Remove secret fields from data recursively (handles nested objects)
399
+ * @param data - Data to process
400
+ * @param secretFields - Fields to remove
401
+ * @returns Cleaned data
402
+ */
403
+ private removeSecretFields(data: any, secretFields: string[]): any {
404
+ if (Array.isArray(data)) {
405
+ return data.map(item => this.removeSecretFields(item, secretFields))
406
+ }
407
+
408
+ // ✅ IMPORTANT: Preserve Date objects
409
+ if (data instanceof Date) {
410
+ return data
411
+ }
412
+
413
+ if (data && typeof data === 'object') {
414
+ const cleaned: any = {}
415
+
416
+ Object.keys(data).forEach(key => {
417
+ if (!secretFields.includes(key)) {
418
+ cleaned[key] = this.removeSecretFields(data[key], secretFields)
419
+ }
420
+ })
421
+
422
+ return cleaned
423
+ }
424
+
425
+ return data
256
426
  }
427
+
257
428
  /**
258
- * Get total count and pagination metadata without fetching data
429
+ * Get pagination metadata
430
+ * Uses combined WHERE clause (base + user filters)
431
+ *
259
432
  * @returns Object with pagination metadata
260
433
  */
261
434
  async countTotal(): Promise<{
@@ -266,8 +439,29 @@ class PrismaQueryBuilder<T extends Record<string, any>> {
266
439
  }> {
267
440
  const page = Number(this.query.page) || 1
268
441
  const limit = Number(this.query.limit) || 10
269
- const totalData = await this.prismaModel.count({ where: this.whereClause })
442
+
443
+ // Combine base query with user filters for counting
444
+ let finalWhereClause: any = {}
445
+
446
+ if (
447
+ Object.keys(this.baseWhereClause).length > 0 &&
448
+ Object.keys(this.whereClause).length > 0
449
+ ) {
450
+ finalWhereClause = {
451
+ AND: [this.baseWhereClause, this.whereClause]
452
+ }
453
+ } else if (Object.keys(this.baseWhereClause).length > 0) {
454
+ finalWhereClause = this.baseWhereClause
455
+ } else if (Object.keys(this.whereClause).length > 0) {
456
+ finalWhereClause = this.whereClause
457
+ }
458
+
459
+ const totalData = await this.prismaModel.count({
460
+ where:
461
+ Object.keys(finalWhereClause).length > 0 ? finalWhereClause : undefined
462
+ })
270
463
  const totalPage = Math.ceil(totalData / limit)
464
+
271
465
  return {
272
466
  page,
273
467
  limit,
@@ -276,6 +470,7 @@ class PrismaQueryBuilder<T extends Record<string, any>> {
276
470
  }
277
471
  }
278
472
  }
473
+
279
474
  export default PrismaQueryBuilder
280
475
  `;
281
476
  await createFile(path.join(projectPath, "src/app/builder", "PrismaQueryBuilder.ts"), prismaQueryBuilderTemplate);
@@ -1 +1 @@
1
- {"version":3,"file":"create_QueryBuilder_Helpers.js","sourceRoot":"","sources":["../../../../src/lib/src/builders/create_QueryBuilder_Helpers.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,mDAAmD;AACnD,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,EAC9C,WAAmB,EACnB,OAAiB,EACF,EAAE;IACjB,IAAI,CAAC;QACH,qDAAqD;QACrD,IAAI,OAAO,CAAC,QAAQ,KAAK,uBAAuB,EAAE,CAAC;YACjD,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgGvC,CAAC;YACI,MAAM,UAAU,CACd,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,EAAE,yBAAyB,CAAC,EACpE,yBAAyB,CAC1B,CAAC;QACJ,CAAC;QAED,mEAAmE;QACnE,IAAI,OAAO,CAAC,QAAQ,KAAK,wBAAwB,EAAE,CAAC;YAClD,MAAM,0BAA0B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0KxC,CAAC;YACI,MAAM,UAAU,CACd,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,EAAE,uBAAuB,CAAC,EAClE,0BAA0B,CAC3B,CAAC;QACJ,CAAC;QAED,gDAAgD;QAChD,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAChE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,wCAAwC;QACxC,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CAAC,6CAA6C,CAAC,EACxD,GAAG,CACJ,CAAC;QACF,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC,CAAC"}
1
+ {"version":3,"file":"create_QueryBuilder_Helpers.js","sourceRoot":"","sources":["../../../../src/lib/src/builders/create_QueryBuilder_Helpers.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,mDAAmD;AACnD,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,EAC9C,WAAmB,EACnB,OAAiB,EACF,EAAE;IACjB,IAAI,CAAC;QACH,qDAAqD;QACrD,IAAI,OAAO,CAAC,QAAQ,KAAK,uBAAuB,EAAE,CAAC;YACjD,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgGvC,CAAC;YACI,MAAM,UAAU,CACd,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,EAAE,yBAAyB,CAAC,EACpE,yBAAyB,CAC1B,CAAC;QACJ,CAAC;QAED,mEAAmE;QACnE,IAAI,OAAO,CAAC,QAAQ,KAAK,wBAAwB,EAAE,CAAC;YAClD,MAAM,0BAA0B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6WxC,CAAC;YACI,MAAM,UAAU,CACd,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,iBAAiB,EAAE,uBAAuB,CAAC,EAClE,0BAA0B,CAC3B,CAAC;QACJ,CAAC;QAED,gDAAgD;QAChD,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAChE,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,wCAAwC;QACxC,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CAAC,6CAA6C,CAAC,EACxD,GAAG,CACJ,CAAC;QACF,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-project-builder",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
4
4
  "description": "A powerful and professional Express.js project generator CLI that instantly scaffolds a production-ready backend with TypeScript, modular architecture, and built-in support for MongoDB (Mongoose) or PostgreSQL (Prisma). Includes authentication, error handling, rate limiting, file upload, caching, and utility functions—so you can focus on building features instead of boilerplate. Perfect for kickstarting your next Express.js API project with best practices and modern tools.",
5
5
  "type": "module",
6
6
  "main": "dist/bin/index.js",