express-project-builder 1.0.38 → 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
|
@@ -228,43 +228,165 @@ my-test-project/
|
|
|
228
228
|
- /src/app/builder/**PrismaQueryBuilder.ts** <br/>
|
|
229
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.
|
|
230
230
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
+
});
|
|
261
365
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
+
```
|
|
268
390
|
|
|
269
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.
|
|
270
392
|
|
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
212
|
-
}
|
|
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
|
|
218
|
-
*
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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:
|
|
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
|
-
|
|
239
|
-
if (this.selectFields) {
|
|
378
|
+
|
|
379
|
+
if (this.selectFields && !this.includeRelations) {
|
|
240
380
|
queryOptions.select = this.selectFields
|
|
241
381
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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",
|