sql-flex-query 1.0.0 → 1.0.1

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.
Files changed (2) hide show
  1. package/README.md +471 -40
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -17,81 +17,512 @@ A lightweight, dialect-aware SQL query builder that enhances base query template
17
17
  npm install sql-flex-query
18
18
  ```
19
19
 
20
- ## Quick Start
20
+ ---
21
21
 
22
- ### Functional API
22
+ ## Basic Examples
23
+
24
+ ### 1. Simple SELECT with WHERE and Pagination
23
25
 
24
26
  ```javascript
25
27
  const { buildQueries } = require('sql-flex-query');
26
28
 
27
- const BASE = \`
29
+ const BASE = `
28
30
  SELECT /*SELECT_COLUMNS*/
29
31
  FROM users u
30
32
  /*WHERE_CLAUSE*/
31
33
  /*ORDER_BY*/
32
34
  /*LIMIT_CLAUSE*/
33
- \`;
35
+ `;
34
36
 
35
- // Postgres (default)
36
37
  const result = buildQueries(
37
38
  BASE,
38
- [{ key: 'status', operation: 'EQ', value: 'ACTIVE' }],
39
- [], [], 1, 10, {}, []
39
+ [
40
+ { key: 'status', operation: 'EQ', value: 'ACTIVE' },
41
+ { key: 'age', operation: 'GTE', value: 18 },
42
+ ],
43
+ [],
44
+ [{ key: 'createdAt', direction: 'DESC' }],
45
+ 1, 10,
46
+ { createdAt: 'u.created_at' },
47
+ ['id', 'name', 'email', 'createdAt']
40
48
  );
49
+ // searchQuery: SELECT u.id AS "id", ... WHERE "status" = $1 AND u.created_at >= $2 ORDER BY ... LIMIT 10 OFFSET 0
50
+ // params: ['ACTIVE', 18]
51
+ ```
41
52
 
42
- // MySQL
43
- const mysqlResult = buildQueries(
44
- BASE,
45
- [{ key: 'status', operation: 'EQ', value: 'ACTIVE' }],
46
- [], [], 1, 10, {}, [], false, null,
47
- { dialect: 'mysql' }
48
- );
53
+ ### 2. Text Search (OR) + Filters (AND)
54
+
55
+ ```javascript
56
+ const result = buildQueries({
57
+ baseQueryTemplate: `
58
+ SELECT /*SELECT_COLUMNS*/
59
+ FROM products p
60
+ /*WHERE_CLAUSE*/
61
+ /*ORDER_BY*/
62
+ /*LIMIT_CLAUSE*/
63
+ `,
64
+ textSearchParams: [
65
+ { key: 'name', operation: 'LIKE', value: '%laptop%', ignoreCase: true },
66
+ { key: 'description', operation: 'LIKE', value: '%laptop%', ignoreCase: true },
67
+ ],
68
+ whereParams: [
69
+ { key: 'status', operation: 'EQ', value: 'PUBLISHED' },
70
+ { key: 'price', operation: 'LTE', value: 2000 },
71
+ { key: 'deleted_at', operation: 'NULL' },
72
+ ],
73
+ sortBy: [{ key: 'price', direction: 'ASC' }],
74
+ page: 1,
75
+ size: 20,
76
+ dialect: 'postgres',
77
+ });
78
+ // WHERE (LOWER("name") LIKE $1 OR LOWER("description") LIKE $2)
79
+ // AND "status" = $3 AND "price" <= $4 AND "deleted_at" IS NULL
80
+ // params: ['%laptop%', '%laptop%', 'PUBLISHED', 2000]
49
81
  ```
50
82
 
51
- ### Options Object API
83
+ ---
84
+
85
+ ## Complex Examples
86
+
87
+ ### 3. Multi-table JOIN with Column Mapping
52
88
 
53
89
  ```javascript
90
+ const { buildQueries } = require('sql-flex-query');
91
+
92
+ const BASE = `
93
+ SELECT /*SELECT_COLUMNS*/
94
+ FROM orders o
95
+ JOIN customers c ON c.id = o.customer_id
96
+ JOIN order_items oi ON oi.order_id = o.id
97
+ JOIN products p ON p.id = oi.product_id
98
+ /*WHERE_CLAUSE*/
99
+ /*ORDER_BY*/
100
+ /*LIMIT_CLAUSE*/
101
+ `;
102
+
103
+ const columnMapper = {
104
+ orderId: 'o.id',
105
+ orderDate: 'o.created_at',
106
+ orderStatus: 'o.status',
107
+ customerName: 'c.name',
108
+ customerEmail: 'c.email',
109
+ productName: 'p.name',
110
+ quantity: 'oi.quantity',
111
+ unitPrice: 'oi.unit_price',
112
+ };
113
+
54
114
  const result = buildQueries({
55
115
  baseQueryTemplate: BASE,
56
- whereParams: [{ key: 'status', operation: 'EQ', value: 'ACTIVE' }],
57
- sortBy: [{ key: 'name', direction: 'ASC' }],
116
+ columnMapper,
117
+ selectColumns: [
118
+ 'orderId', 'orderDate', 'orderStatus',
119
+ 'customerName', 'customerEmail',
120
+ 'productName', 'quantity', 'unitPrice',
121
+ ],
122
+ whereParams: [
123
+ { key: 'orderStatus', operation: 'IN', value: ['SHIPPED', 'DELIVERED'] },
124
+ { key: 'orderDate', operation: 'GTE', value: '2024-01-01' },
125
+ { key: 'orderDate', operation: 'LTE', value: '2024-12-31' },
126
+ { key: 'unitPrice', operation: 'GT', value: 0 },
127
+ ],
128
+ textSearchParams: [
129
+ { key: 'customerName', operation: 'LIKE', value: '%john%', ignoreCase: true },
130
+ { key: 'customerEmail', operation: 'LIKE', value: '%john%', ignoreCase: true },
131
+ { key: 'productName', operation: 'LIKE', value: '%john%', ignoreCase: true },
132
+ ],
133
+ sortBy: [
134
+ { key: 'orderDate', direction: 'DESC' },
135
+ { key: 'customerName', direction: 'ASC' },
136
+ ],
137
+ page: 1,
138
+ size: 25,
139
+ dialect: 'postgres',
140
+ });
141
+
142
+ // searchQuery:
143
+ // SELECT o.id AS "orderId", o.created_at AS "orderDate", o.status AS "orderStatus",
144
+ // c.name AS "customerName", c.email AS "customerEmail",
145
+ // p.name AS "productName", oi.quantity AS "quantity", oi.unit_price AS "unitPrice"
146
+ // FROM orders o
147
+ // JOIN customers c ON c.id = o.customer_id
148
+ // JOIN order_items oi ON oi.order_id = o.id
149
+ // JOIN products p ON p.id = oi.product_id
150
+ // WHERE (LOWER(c.name) LIKE $1 OR LOWER(c.email) LIKE $2 OR LOWER(p.name) LIKE $3)
151
+ // AND o.status IN ($4, $5)
152
+ // AND o.created_at >= $6 AND o.created_at <= $7
153
+ // AND oi.unit_price > $8
154
+ // ORDER BY o.created_at DESC, c.name ASC
155
+ // LIMIT 25 OFFSET 0
156
+ //
157
+ // params: ['%john%', '%john%', '%john%', 'SHIPPED', 'DELIVERED', '2024-01-01', '2024-12-31', 0]
158
+ ```
159
+
160
+ ### 4. GROUP BY + HAVING (Aggregation Reports)
161
+
162
+ ```javascript
163
+ const { buildQueries } = require('sql-flex-query');
164
+
165
+ // GROUP BY is part of the template.
166
+ // The builder handles WHERE (before GROUP BY) and HAVING (after GROUP BY) automatically.
167
+ const BASE = `
168
+ SELECT /*SELECT_COLUMNS*/
169
+ FROM orders o
170
+ JOIN customers c ON c.id = o.customer_id
171
+ JOIN order_items oi ON oi.order_id = o.id
172
+ /*WHERE_CLAUSE*/
173
+ GROUP BY c.id, c.name, c.email
174
+ /*HAVING_CLAUSE*/
175
+ /*ORDER_BY*/
176
+ /*LIMIT_CLAUSE*/
177
+ `;
178
+
179
+ const columnMapper = {
180
+ customerName: 'c.name',
181
+ customerEmail: 'c.email',
182
+ orderCount: 'COUNT(DISTINCT o.id)',
183
+ totalSpent: 'SUM(oi.quantity * oi.unit_price)',
184
+ avgOrderValue: 'AVG(oi.quantity * oi.unit_price)',
185
+ orderDate: 'o.created_at',
186
+ orderStatus: 'o.status',
187
+ };
188
+
189
+ const result = buildQueries({
190
+ baseQueryTemplate: BASE,
191
+ columnMapper,
192
+ selectColumns: ['customerName', 'customerEmail', 'orderCount', 'totalSpent', 'avgOrderValue'],
193
+ whereParams: [
194
+ // WHERE filters — applied BEFORE GROUP BY
195
+ { key: 'orderStatus', operation: 'IN', value: ['COMPLETED', 'DELIVERED'] },
196
+ { key: 'orderDate', operation: 'GTE', value: '2024-01-01' },
197
+ { key: 'orderDate', operation: 'LTE', value: '2024-12-31' },
198
+
199
+ // HAVING filters — set having: true — applied AFTER GROUP BY
200
+ { key: 'orderCount', operation: 'GTE', value: 5, having: true },
201
+ { key: 'totalSpent', operation: 'GTE', value: 1000, having: true },
202
+ ],
203
+ sortBy: [{ key: 'totalSpent', direction: 'DESC' }],
58
204
  page: 1,
59
205
  size: 10,
60
- dialect: 'mysql',
206
+ dialect: 'postgres',
207
+ });
208
+
209
+ // searchQuery:
210
+ // SELECT c.name AS "customerName", c.email AS "customerEmail",
211
+ // COUNT(DISTINCT o.id) AS "orderCount",
212
+ // SUM(oi.quantity * oi.unit_price) AS "totalSpent",
213
+ // AVG(oi.quantity * oi.unit_price) AS "avgOrderValue"
214
+ // FROM orders o
215
+ // JOIN customers c ON c.id = o.customer_id
216
+ // JOIN order_items oi ON oi.order_id = o.id
217
+ // WHERE o.status IN ($1, $2) AND o.created_at >= $3 AND o.created_at <= $4
218
+ // GROUP BY c.id, c.name, c.email
219
+ // HAVING COUNT(DISTINCT o.id) >= $5 AND SUM(oi.quantity * oi.unit_price) >= $6
220
+ // ORDER BY SUM(oi.quantity * oi.unit_price) DESC
221
+ // LIMIT 10 OFFSET 0
222
+ //
223
+ // params: ['COMPLETED', 'DELIVERED', '2024-01-01', '2024-12-31', 5, 1000]
224
+ ```
225
+
226
+ ### 5. GROUP BY with modifyCountQuery
227
+
228
+ When using GROUP BY, the default COUNT gives wrong results. Use modifyCountQuery to wrap the count:
229
+
230
+ ```javascript
231
+ const BASE_WITH_GROUP = `
232
+ SELECT /*SELECT_COLUMNS*/
233
+ FROM r2r_emails em
234
+ LEFT JOIN r2r_regions r ON em.region_id = r.id
235
+ JOIN r2r_work_queue wq ON wq.id = em.wq_id
236
+ JOIN r2r_work_queue_case_mapping wqcm ON wqcm.wq_id = wq.id
237
+ JOIN (
238
+ SELECT DISTINCT wq_id, case_id, company_code
239
+ FROM r2r_process_output_model WHERE is_latest = true
240
+ ) pom ON pom.wq_id = wqcm.wq_id AND pom.case_id = wqcm.case_id
241
+ /*WHERE_CLAUSE*/
242
+ GROUP BY DATE(wq.created_date), pom.company_code, em.structured
243
+ /*HAVING_CLAUSE*/
244
+ /*ORDER_BY*/ /*LIMIT_CLAUSE*/
245
+ `;
246
+
247
+ const columnMapper = {
248
+ createdDate: 'DATE(wq.created_date)',
249
+ companyCode: 'pom.company_code',
250
+ structured: 'em.structured',
251
+ emailCount: 'COUNT(em.id)',
252
+ regionName: 'r.name',
253
+ status: 'em.status',
254
+ };
255
+
256
+ const result = buildQueries({
257
+ baseQueryTemplate: BASE_WITH_GROUP,
258
+ columnMapper,
259
+ selectColumns: ['createdDate', 'companyCode', 'structured', 'emailCount'],
260
+ whereParams: [
261
+ { key: 'status', operation: 'EQ', value: 'PROCESSED' },
262
+ { key: 'regionName', operation: 'NOT_NULL' },
263
+ { key: 'emailCount', operation: 'GT', value: 10, having: true },
264
+ ],
265
+ sortBy: [
266
+ { key: 'createdDate', direction: 'DESC' },
267
+ { key: 'emailCount', direction: 'DESC' },
268
+ ],
269
+ page: 1,
270
+ size: 20,
271
+ dialect: 'postgres',
272
+ // Wrap count query to count groups, not rows within groups
273
+ modifyCountQuery: (query) => `SELECT COUNT(*) AS count FROM (${query}) AS grouped_count`,
61
274
  });
275
+
276
+ // countQuery:
277
+ // SELECT COUNT(*) AS count FROM (
278
+ // SELECT 1 FROM r2r_emails em ...
279
+ // WHERE em.status = $1 AND r.name IS NOT NULL
280
+ // GROUP BY DATE(wq.created_date), pom.company_code, em.structured
281
+ // HAVING COUNT(em.id) > $2
282
+ // ) AS grouped_count
62
283
  ```
63
284
 
64
- ### Fluent API
285
+ ### 6. LEFT JOIN + DISTINCT (Fluent API)
65
286
 
66
287
  ```javascript
67
288
  const { QueryBuilder } = require('sql-flex-query');
68
289
 
290
+ const BASE = `
291
+ SELECT /*SELECT_COLUMNS*/
292
+ FROM employees e
293
+ LEFT JOIN departments d ON d.id = e.department_id
294
+ LEFT JOIN employee_skills es ON es.employee_id = e.id
295
+ LEFT JOIN skills s ON s.id = es.skill_id
296
+ /*WHERE_CLAUSE*/
297
+ /*ORDER_BY*/
298
+ /*LIMIT_CLAUSE*/
299
+ `;
300
+
301
+ const result = new QueryBuilder('postgres')
302
+ .baseQuery(BASE)
303
+ .columnMapper({
304
+ employeeId: 'e.id',
305
+ employeeName: 'e.name',
306
+ employeeEmail: 'e.email',
307
+ departmentName: 'd.name',
308
+ hireDate: 'e.hire_date',
309
+ salary: 'e.salary',
310
+ skillName: 's.name',
311
+ })
312
+ .select(['employeeId', 'employeeName', 'employeeEmail', 'departmentName'])
313
+ .distinct()
314
+ .where([
315
+ { key: 'departmentName', operation: 'IN', value: ['Engineering', 'Product', 'Design'] },
316
+ { key: 'hireDate', operation: 'GTE', value: '2023-01-01' },
317
+ { key: 'salary', operation: 'GTE', value: 50000 },
318
+ { key: 'salary', operation: 'LTE', value: 150000 },
319
+ ])
320
+ .textSearch([
321
+ { key: 'employeeName', operation: 'LIKE', value: '%sarah%', ignoreCase: true },
322
+ { key: 'employeeEmail', operation: 'LIKE', value: '%sarah%', ignoreCase: true },
323
+ { key: 'skillName', operation: 'LIKE', value: '%sarah%', ignoreCase: true },
324
+ ])
325
+ .orderBy([
326
+ { key: 'departmentName', direction: 'ASC' },
327
+ { key: 'employeeName', direction: 'ASC' },
328
+ ])
329
+ .paginate(2, 15)
330
+ .build();
331
+
332
+ // searchQuery:
333
+ // SELECT DISTINCT e.id AS "employeeId", e.name AS "employeeName",
334
+ // e.email AS "employeeEmail", d.name AS "departmentName"
335
+ // FROM employees e
336
+ // LEFT JOIN departments d ON d.id = e.department_id
337
+ // LEFT JOIN employee_skills es ON es.employee_id = e.id
338
+ // LEFT JOIN skills s ON s.id = es.skill_id
339
+ // WHERE (LOWER(e.name) LIKE $1 OR LOWER(e.email) LIKE $2 OR LOWER(s.name) LIKE $3)
340
+ // AND d.name IN ($4, $5, $6)
341
+ // AND e.hire_date >= $7 AND e.salary >= $8 AND e.salary <= $9
342
+ // ORDER BY d.name ASC, e.name ASC
343
+ // LIMIT 15 OFFSET 15
344
+ //
345
+ // params: ['%sarah%', '%sarah%', '%sarah%', 'Engineering', 'Product', 'Design',
346
+ // '2023-01-01', 50000, 150000]
347
+ ```
348
+
349
+ ### 7. Sales Dashboard (Fluent API + GROUP BY + HAVING + MSSQL)
350
+
351
+ ```javascript
352
+ const { QueryBuilder } = require('sql-flex-query');
353
+
354
+ const BASE = `
355
+ SELECT /*SELECT_COLUMNS*/
356
+ FROM sales s
357
+ JOIN sales_reps sr ON sr.id = s.rep_id
358
+ JOIN regions r ON r.id = sr.region_id
359
+ JOIN products p ON p.id = s.product_id
360
+ JOIN product_categories pc ON pc.id = p.category_id
361
+ /*WHERE_CLAUSE*/
362
+ GROUP BY r.name, sr.name, pc.name, YEAR(s.sale_date), MONTH(s.sale_date)
363
+ /*HAVING_CLAUSE*/
364
+ /*ORDER_BY*/ /*LIMIT_CLAUSE*/
365
+ `;
366
+
367
+ const result = new QueryBuilder('mssql')
368
+ .baseQuery(BASE)
369
+ .columnMapper({
370
+ regionName: 'r.name',
371
+ repName: 'sr.name',
372
+ categoryName: 'pc.name',
373
+ saleYear: 'YEAR(s.sale_date)',
374
+ saleMonth: 'MONTH(s.sale_date)',
375
+ totalRevenue: 'SUM(s.amount)',
376
+ totalUnits: 'SUM(s.quantity)',
377
+ dealCount: 'COUNT(s.id)',
378
+ avgDealSize: 'AVG(s.amount)',
379
+ saleDate: 's.sale_date',
380
+ saleStatus: 's.status',
381
+ })
382
+ .select([
383
+ 'regionName', 'repName', 'categoryName',
384
+ 'saleYear', 'saleMonth',
385
+ 'totalRevenue', 'totalUnits', 'dealCount', 'avgDealSize',
386
+ ])
387
+ .where([
388
+ { key: 'saleDate', operation: 'GTE', value: '2024-01-01' },
389
+ { key: 'saleDate', operation: 'LTE', value: '2024-12-31' },
390
+ { key: 'saleStatus', operation: 'EQ', value: 'CLOSED_WON' },
391
+ { key: 'regionName', operation: 'IN', value: ['North America', 'Europe', 'APAC'] },
392
+ ])
393
+ .having([
394
+ { key: 'totalRevenue', operation: 'GTE', value: 50000 },
395
+ { key: 'dealCount', operation: 'GTE', value: 3 },
396
+ ])
397
+ .orderBy([
398
+ { key: 'totalRevenue', direction: 'DESC' },
399
+ { key: 'regionName', direction: 'ASC' },
400
+ ])
401
+ .paginate(1, 20)
402
+ .modifyCountQuery((query) => `SELECT COUNT(*) AS count FROM (${query}) AS t`)
403
+ .build();
404
+
405
+ // searchQuery (SQL Server):
406
+ // SELECT r.name AS [regionName], sr.name AS [repName], pc.name AS [categoryName],
407
+ // YEAR(s.sale_date) AS [saleYear], MONTH(s.sale_date) AS [saleMonth],
408
+ // SUM(s.amount) AS [totalRevenue], SUM(s.quantity) AS [totalUnits],
409
+ // COUNT(s.id) AS [dealCount], AVG(s.amount) AS [avgDealSize]
410
+ // FROM sales s
411
+ // JOIN sales_reps sr ON sr.id = s.rep_id
412
+ // JOIN regions r ON r.id = sr.region_id
413
+ // JOIN products p ON p.id = s.product_id
414
+ // JOIN product_categories pc ON pc.id = p.category_id
415
+ // WHERE s.sale_date >= @p1 AND s.sale_date <= @p2
416
+ // AND s.status = @p3 AND r.name IN (@p4, @p5, @p6)
417
+ // GROUP BY r.name, sr.name, pc.name, YEAR(s.sale_date), MONTH(s.sale_date)
418
+ // HAVING SUM(s.amount) >= @p7 AND COUNT(s.id) >= @p8
419
+ // ORDER BY SUM(s.amount) DESC, r.name ASC OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY
420
+ //
421
+ // params: ['2024-01-01', '2024-12-31', 'CLOSED_WON',
422
+ // 'North America', 'Europe', 'APAC', 50000, 3]
423
+ ```
424
+
425
+ ### 8. Subquery JOIN with MySQL (Fluent API)
426
+
427
+ ```javascript
428
+ const { QueryBuilder } = require('sql-flex-query');
429
+
430
+ const BASE = `
431
+ SELECT /*SELECT_COLUMNS*/
432
+ FROM users u
433
+ JOIN (
434
+ SELECT user_id,
435
+ COUNT(*) as login_count,
436
+ MAX(login_at) as last_login
437
+ FROM login_history
438
+ WHERE login_at >= '2024-01-01'
439
+ GROUP BY user_id
440
+ ) lh ON lh.user_id = u.id
441
+ LEFT JOIN user_subscriptions us ON us.user_id = u.id AND us.is_active = true
442
+ LEFT JOIN plans p ON p.id = us.plan_id
443
+ /*WHERE_CLAUSE*/
444
+ /*ORDER_BY*/
445
+ /*LIMIT_CLAUSE*/
446
+ `;
447
+
69
448
  const result = new QueryBuilder('mysql')
70
449
  .baseQuery(BASE)
71
- .columnMapper({ name: 'u.name' })
72
- .select(['name', 'email'])
73
- .where({ key: 'status', operation: 'EQ', value: 'ACTIVE' })
74
- .textSearch([{ key: 'name', operation: 'LIKE', value: '%john%', ignoreCase: true }])
75
- .orderBy({ key: 'name', direction: 'ASC' })
76
- .paginate(1, 10)
450
+ .columnMapper({
451
+ userId: 'u.id',
452
+ userName: 'u.name',
453
+ userEmail: 'u.email',
454
+ loginCount: 'lh.login_count',
455
+ lastLogin: 'lh.last_login',
456
+ planName: 'p.name',
457
+ userStatus: 'u.status',
458
+ userRole: 'u.role',
459
+ })
460
+ .select(['userId', 'userName', 'userEmail', 'loginCount', 'lastLogin', 'planName'])
461
+ .where([
462
+ { key: 'userStatus', operation: 'EQ', value: 'ACTIVE' },
463
+ { key: 'loginCount', operation: 'GTE', value: 10 },
464
+ { key: 'userRole', operation: 'IN', value: ['ADMIN', 'PREMIUM', 'ENTERPRISE'] },
465
+ { key: 'planName', operation: 'NOT_NULL' },
466
+ ])
467
+ .textSearch([
468
+ { key: 'userName', operation: 'LIKE', value: '%search_term%', ignoreCase: true },
469
+ { key: 'userEmail', operation: 'LIKE', value: '%search_term%', ignoreCase: true },
470
+ ])
471
+ .orderBy([
472
+ { key: 'loginCount', direction: 'DESC' },
473
+ { key: 'lastLogin', direction: 'DESC' },
474
+ ])
475
+ .paginate(1, 50)
77
476
  .build();
477
+
478
+ // searchQuery (MySQL):
479
+ // SELECT u.id AS \`userId\`, u.name AS \`userName\`, u.email AS \`userEmail\`,
480
+ // lh.login_count AS \`loginCount\`, lh.last_login AS \`lastLogin\`,
481
+ // p.name AS \`planName\`
482
+ // FROM users u
483
+ // JOIN (...) lh ON lh.user_id = u.id
484
+ // LEFT JOIN user_subscriptions us ON us.user_id = u.id AND us.is_active = true
485
+ // LEFT JOIN plans p ON p.id = us.plan_id
486
+ // WHERE (\`userName\` LIKE ? OR \`userEmail\` LIKE ?)
487
+ // AND u.status = ? AND lh.login_count >= ?
488
+ // AND u.role IN (?, ?, ?) AND p.name IS NOT NULL
489
+ // ORDER BY lh.login_count DESC, lh.last_login DESC
490
+ // LIMIT 50 OFFSET 0
491
+ //
492
+ // params: ['%search_term%', '%search_term%', 'ACTIVE', 10, 'ADMIN', 'PREMIUM', 'ENTERPRISE']
78
493
  ```
79
494
 
80
- ## Operations
81
-
82
- | Operation | SQL Output |
83
- |-----------|-----------|
84
- | EQ | `column = ?` |
85
- | NEQ | `column <> ?` |
86
- | LIKE | `column LIKE ?` |
87
- | NOT_LIKE | `column NOT LIKE ?` |
88
- | GT | `column > ?` |
89
- | LT | `column < ?` |
90
- | GTE | `column >= ?` |
91
- | LTE | `column <= ?` |
92
- | IN | `column IN (?, ?)` |
93
- | NULL | `column IS NULL` |
94
- | NOT_NULL | `column IS NOT NULL` |
495
+ ---
496
+
497
+ ## Template Placeholders Reference
498
+
499
+ | Placeholder | Required | Description |
500
+ |-------------|----------|-------------|
501
+ | `/*SELECT_COLUMNS*/` | Yes | Replaced with column list or `*` |
502
+ | `/*WHERE_CLAUSE*/` | Yes | Replaced with `WHERE ...` or empty string |
503
+ | `/*ORDER_BY*/` | Yes | Replaced with `ORDER BY ...` or empty string |
504
+ | `/*LIMIT_CLAUSE*/` | Yes | Replaced with pagination clause or empty string |
505
+ | `/*HAVING_CLAUSE*/` | Optional | Replaced with `HAVING ...` (use with GROUP BY) |
506
+
507
+ ---
508
+
509
+ ## Operations Reference
510
+
511
+ | Operation | SQL | Needs Value | Example |
512
+ |-----------|-----|-------------|---------|
513
+ | `EQ` | `col = ?` | Yes | `{ key: 'status', operation: 'EQ', value: 'ACTIVE' }` |
514
+ | `NEQ` | `col <> ?` | Yes | `{ key: 'role', operation: 'NEQ', value: 'GUEST' }` |
515
+ | `LIKE` | `col LIKE ?` | Yes | `{ key: 'name', operation: 'LIKE', value: '%john%' }` |
516
+ | `NOT_LIKE` | `col NOT LIKE ?` | Yes | `{ key: 'email', operation: 'NOT_LIKE', value: '%spam%' }` |
517
+ | `GT` | `col > ?` | Yes | `{ key: 'age', operation: 'GT', value: 18 }` |
518
+ | `LT` | `col < ?` | Yes | `{ key: 'price', operation: 'LT', value: 100 }` |
519
+ | `GTE` | `col >= ?` | Yes | `{ key: 'score', operation: 'GTE', value: 90 }` |
520
+ | `LTE` | `col <= ?` | Yes | `{ key: 'weight', operation: 'LTE', value: 80 }` |
521
+ | `IN` | `col IN (?, ?)` | Yes (array) | `{ key: 'status', operation: 'IN', value: ['A', 'B'] }` |
522
+ | `NULL` | `col IS NULL` | No | `{ key: 'deleted_at', operation: 'NULL' }` |
523
+ | `NOT_NULL` | `col IS NOT NULL` | No | `{ key: 'verified_at', operation: 'NOT_NULL' }` |
524
+
525
+ ---
95
526
 
96
527
  ## License
97
528
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sql-flex-query",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A lightweight, dialect-aware SQL query builder that enhances base query templates with dynamic WHERE, HAVING, ORDER BY, pagination, and more.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",