core-services-sdk 1.3.64 → 1.3.65

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 (32) hide show
  1. package/package.json +1 -1
  2. package/src/postgresql/core/get-table-name.js +10 -0
  3. package/src/postgresql/filters/apply-filter-object.js +19 -0
  4. package/src/postgresql/filters/apply-filter-snake-case.js +16 -0
  5. package/src/postgresql/filters/apply-filter.js +33 -0
  6. package/src/postgresql/filters/operators.js +32 -0
  7. package/src/postgresql/index.js +5 -2
  8. package/src/postgresql/modifiers/apply-order-by.js +19 -0
  9. package/src/postgresql/modifiers/apply-pagination.js +12 -0
  10. package/src/postgresql/pagination/paginate.js +48 -0
  11. package/tests/core/normalize-phone-number.unit.test.js +1 -1
  12. package/tests/postgresql/apply-filter-snake-case.integration.test.js +25 -354
  13. package/tests/postgresql/apply-filter.integration.test.js +28 -78
  14. package/tests/postgresql/core/get-table-name.unit.test.js +20 -0
  15. package/tests/postgresql/filters/apply-filter-object.test.js +23 -0
  16. package/tests/postgresql/filters/operators.unit.test.js +23 -0
  17. package/tests/postgresql/modifiers/apply-order-by.test.js +80 -0
  18. package/tests/postgresql/modifiers/apply-pagination.unit.test.js +18 -0
  19. package/tests/postgresql/paginate.integration.test.js +8 -16
  20. package/tests/postgresql/pagination/paginate.js +48 -0
  21. package/tests/postgresql/validate-schema.integration.test.js +7 -3
  22. package/types/postgresql/core/get-table-name.d.ts +10 -0
  23. package/types/postgresql/filters/apply-filter-object.d.ts +15 -0
  24. package/types/postgresql/filters/apply-filter-snake-case.d.ts +14 -0
  25. package/types/postgresql/filters/apply-filter.d.ts +15 -0
  26. package/types/postgresql/filters/operators.d.ts +14 -0
  27. package/types/postgresql/index.d.ts +5 -2
  28. package/types/postgresql/modifiers/apply-order-by.d.ts +17 -0
  29. package/types/postgresql/modifiers/apply-pagination.d.ts +17 -0
  30. package/types/postgresql/pagination/paginate.d.ts +29 -0
  31. package/src/postgresql/apply-filter.js +0 -401
  32. package/src/postgresql/paginate.js +0 -61
@@ -8,7 +8,7 @@ import {
8
8
  buildPostgresUri,
9
9
  } from '../../src/postgresql/start-stop-postgres-docker.js'
10
10
 
11
- import { applyFilterSnakeCase } from '../../src/postgresql/apply-filter.js'
11
+ import { applyFilterSnakeCase } from '../../src/postgresql/filters/apply-filter-snake-case.js'
12
12
 
13
13
  const PG_OPTIONS = {
14
14
  port: 5444,
@@ -69,7 +69,7 @@ beforeEach(async () => {
69
69
  name: 'Asset One',
70
70
  status: 'active',
71
71
  type: 'invoice',
72
- price: 100.0,
72
+ price: 100,
73
73
  created_at: new Date('2024-01-01'),
74
74
  deleted_at: null,
75
75
  },
@@ -78,7 +78,7 @@ beforeEach(async () => {
78
78
  name: 'Asset Two',
79
79
  status: 'active',
80
80
  type: 'receipt',
81
- price: 200.0,
81
+ price: 200,
82
82
  created_at: new Date('2024-01-02'),
83
83
  deleted_at: null,
84
84
  },
@@ -87,7 +87,7 @@ beforeEach(async () => {
87
87
  name: 'Asset Three',
88
88
  status: 'pending',
89
89
  type: 'invoice',
90
- price: 150.0,
90
+ price: 150,
91
91
  created_at: new Date('2024-01-03'),
92
92
  deleted_at: null,
93
93
  },
@@ -96,7 +96,7 @@ beforeEach(async () => {
96
96
  name: 'Deleted Asset',
97
97
  status: 'deleted',
98
98
  type: 'receipt',
99
- price: 50.0,
99
+ price: 50,
100
100
  created_at: new Date('2024-01-04'),
101
101
  deleted_at: new Date('2024-01-05'),
102
102
  },
@@ -105,7 +105,7 @@ beforeEach(async () => {
105
105
  name: 'Expensive Asset',
106
106
  status: 'active',
107
107
  type: 'invoice',
108
- price: 500.0,
108
+ price: 500,
109
109
  created_at: new Date('2024-01-05'),
110
110
  deleted_at: null,
111
111
  },
@@ -115,7 +115,7 @@ beforeEach(async () => {
115
115
  {
116
116
  id: '00000000-0000-0000-0000-000000000101',
117
117
  invoice_number: 'INV-001',
118
- amount: 1000.0,
118
+ amount: 1000,
119
119
  status: 'paid',
120
120
  customer_id: '00000000-0000-0000-0000-000000000201',
121
121
  created_at: new Date('2024-01-01'),
@@ -125,7 +125,7 @@ beforeEach(async () => {
125
125
  {
126
126
  id: '00000000-0000-0000-0000-000000000102',
127
127
  invoice_number: 'INV-002',
128
- amount: 2500.0,
128
+ amount: 2500,
129
129
  status: 'pending',
130
130
  customer_id: '00000000-0000-0000-0000-000000000202',
131
131
  created_at: new Date('2024-01-02'),
@@ -135,7 +135,7 @@ beforeEach(async () => {
135
135
  {
136
136
  id: '00000000-0000-0000-0000-000000000103',
137
137
  invoice_number: 'INV-003',
138
- amount: 500.0,
138
+ amount: 500,
139
139
  status: 'paid',
140
140
  customer_id: '00000000-0000-0000-0000-000000000201',
141
141
  created_at: new Date('2024-01-03'),
@@ -145,7 +145,7 @@ beforeEach(async () => {
145
145
  {
146
146
  id: '00000000-0000-0000-0000-000000000104',
147
147
  invoice_number: 'INV-004',
148
- amount: 3000.0,
148
+ amount: 3000,
149
149
  status: 'overdue',
150
150
  customer_id: '00000000-0000-0000-0000-000000000203',
151
151
  created_at: new Date('2024-01-04'),
@@ -155,7 +155,7 @@ beforeEach(async () => {
155
155
  {
156
156
  id: '00000000-0000-0000-0000-000000000105',
157
157
  invoice_number: 'INV-005',
158
- amount: 750.0,
158
+ amount: 750,
159
159
  status: 'cancelled',
160
160
  customer_id: '00000000-0000-0000-0000-000000000201',
161
161
  created_at: new Date('2024-01-05'),
@@ -166,384 +166,55 @@ beforeEach(async () => {
166
166
  })
167
167
 
168
168
  describe('applyFilterSnakeCase integration', () => {
169
- it('applies simple equality filter (eq)', async () => {
169
+ it('applies equality filter', async () => {
170
170
  const query = db('assets').select('*')
171
- const filteredQuery = applyFilterSnakeCase({
171
+
172
+ applyFilterSnakeCase({
172
173
  query,
173
174
  filter: { status: 'active' },
174
- tableName: 'assets',
175
175
  })
176
176
 
177
- const results = await filteredQuery
178
-
177
+ const results = await query
179
178
  expect(results).toHaveLength(3)
180
- expect(results.every((r) => r.status === 'active')).toBe(true)
181
179
  })
182
180
 
183
181
  it('converts camelCase keys to snake_case', async () => {
184
182
  const query = db('assets').select('*')
185
- const filteredQuery = applyFilterSnakeCase({
183
+
184
+ applyFilterSnakeCase({
186
185
  query,
187
186
  filter: { deletedAt: { isNull: true }, name: 'Asset One' },
188
- tableName: 'assets',
189
187
  })
190
188
 
191
- const results = await filteredQuery
192
-
189
+ const results = await query
193
190
  expect(results).toHaveLength(1)
194
191
  expect(results[0].name).toBe('Asset One')
195
192
  })
196
193
 
197
- it('applies not equal filter (ne)', async () => {
198
- const query = db('assets').select('*')
199
- const filteredQuery = applyFilterSnakeCase({
200
- query,
201
- filter: { status: { ne: 'deleted' } },
202
- tableName: 'assets',
203
- })
204
-
205
- const results = await filteredQuery
206
-
207
- expect(results).toHaveLength(4)
208
- expect(results.every((r) => r.status !== 'deleted')).toBe(true)
209
- })
210
-
211
- it('applies not equal filter (neq alias)', async () => {
212
- const query = db('assets').select('*')
213
- const filteredQuery = applyFilterSnakeCase({
214
- query,
215
- filter: { status: { neq: 'deleted' } },
216
- tableName: 'assets',
217
- })
218
-
219
- const results = await filteredQuery
220
-
221
- expect(results).toHaveLength(4)
222
- expect(results.every((r) => r.status !== 'deleted')).toBe(true)
223
- })
224
-
225
- it('applies IN filter with array directly', async () => {
226
- const query = db('assets').select('*')
227
- const filteredQuery = applyFilterSnakeCase({
228
- query,
229
- filter: { status: ['active', 'pending'] },
230
- tableName: 'assets',
231
- })
232
-
233
- const results = await filteredQuery
234
-
235
- expect(results).toHaveLength(4)
236
- expect(
237
- results.every((r) => r.status === 'active' || r.status === 'pending'),
238
- ).toBe(true)
239
- })
240
-
241
- it('applies IN filter with operator', async () => {
242
- const query = db('assets').select('*')
243
- const filteredQuery = applyFilterSnakeCase({
244
- query,
245
- filter: { status: { in: ['active', 'pending'] } },
246
- tableName: 'assets',
247
- })
248
-
249
- const results = await filteredQuery
250
-
251
- expect(results).toHaveLength(4)
252
- expect(
253
- results.every((r) => r.status === 'active' || r.status === 'pending'),
254
- ).toBe(true)
255
- })
256
-
257
- it('applies NOT IN filter (nin)', async () => {
258
- const query = db('assets').select('*')
259
- const filteredQuery = applyFilterSnakeCase({
260
- query,
261
- filter: { status: { nin: ['deleted', 'archived'] } },
262
- tableName: 'assets',
263
- })
264
-
265
- const results = await filteredQuery
266
-
267
- expect(results).toHaveLength(4)
268
- expect(
269
- results.every((r) => r.status !== 'deleted' && r.status !== 'archived'),
270
- ).toBe(true)
271
- })
272
-
273
- it('applies greater than filter (gt)', async () => {
274
- const query = db('assets').select('*')
275
- const filteredQuery = applyFilterSnakeCase({
276
- query,
277
- filter: { price: { gt: 150 } },
278
- tableName: 'assets',
279
- })
280
-
281
- const results = await filteredQuery
282
-
283
- expect(results).toHaveLength(2)
284
- expect(results.every((r) => parseFloat(r.price) > 150)).toBe(true)
285
- })
286
-
287
- it('applies greater than or equal filter (gte)', async () => {
288
- const query = db('assets').select('*')
289
- const filteredQuery = applyFilterSnakeCase({
290
- query,
291
- filter: { price: { gte: 150 } },
292
- tableName: 'assets',
293
- })
294
-
295
- const results = await filteredQuery
296
-
297
- expect(results).toHaveLength(3)
298
- expect(results.every((r) => parseFloat(r.price) >= 150)).toBe(true)
299
- })
300
-
301
- it('applies less than filter (lt)', async () => {
302
- const query = db('assets').select('*')
303
- const filteredQuery = applyFilterSnakeCase({
304
- query,
305
- filter: { price: { lt: 150 } },
306
- tableName: 'assets',
307
- })
308
-
309
- const results = await filteredQuery
310
-
311
- expect(results).toHaveLength(2)
312
- expect(results.every((r) => parseFloat(r.price) < 150)).toBe(true)
313
- })
314
-
315
- it('applies less than or equal filter (lte)', async () => {
316
- const query = db('assets').select('*')
317
- const filteredQuery = applyFilterSnakeCase({
318
- query,
319
- filter: { price: { lte: 150 } },
320
- tableName: 'assets',
321
- })
322
-
323
- const results = await filteredQuery
324
-
325
- expect(results).toHaveLength(3)
326
- expect(results.every((r) => parseFloat(r.price) <= 150)).toBe(true)
327
- })
328
-
329
- it('applies range filter with gte and lte', async () => {
330
- const query = db('assets').select('*')
331
- const filteredQuery = applyFilterSnakeCase({
332
- query,
333
- filter: { price: { gte: 100, lte: 200 } },
334
- tableName: 'assets',
335
- })
336
-
337
- const results = await filteredQuery
338
-
339
- expect(results).toHaveLength(3)
340
- expect(
341
- results.every(
342
- (r) => parseFloat(r.price) >= 100 && parseFloat(r.price) <= 200,
343
- ),
344
- ).toBe(true)
345
- })
346
-
347
- it('applies case-sensitive LIKE filter', async () => {
348
- const query = db('assets').select('*')
349
- const filteredQuery = applyFilterSnakeCase({
350
- query,
351
- filter: { name: { like: '%Asset%' } },
352
- tableName: 'assets',
353
- })
354
-
355
- const results = await filteredQuery
356
-
357
- expect(results.length).toBeGreaterThan(0)
358
- expect(results.every((r) => r.name.includes('Asset'))).toBe(true)
359
- })
360
-
361
- it('applies case-insensitive ILIKE filter', async () => {
362
- const query = db('assets').select('*')
363
- const filteredQuery = applyFilterSnakeCase({
364
- query,
365
- filter: { name: { ilike: '%asset%' } },
366
- tableName: 'assets',
367
- })
368
-
369
- const results = await filteredQuery
370
-
371
- expect(results.length).toBeGreaterThan(0)
372
- expect(
373
- results.every((r) =>
374
- r.name.toLowerCase().includes('asset'.toLowerCase()),
375
- ),
376
- ).toBe(true)
377
- })
378
-
379
- it('applies isNull filter when value is true', async () => {
380
- const query = db('assets').select('*')
381
- const filteredQuery = applyFilterSnakeCase({
382
- query,
383
- filter: { deletedAt: { isNull: true } },
384
- tableName: 'assets',
385
- })
386
-
387
- const results = await filteredQuery
388
-
389
- expect(results).toHaveLength(4)
390
- expect(results.every((r) => r.deleted_at === null)).toBe(true)
391
- })
392
-
393
- it('applies isNotNull filter when isNull value is false', async () => {
394
- const query = db('assets').select('*')
395
- const filteredQuery = applyFilterSnakeCase({
396
- query,
397
- filter: { deletedAt: { isNull: false } },
398
- tableName: 'assets',
399
- })
400
-
401
- const results = await filteredQuery
402
-
403
- expect(results).toHaveLength(1)
404
- expect(results[0].deleted_at).not.toBe(null)
405
- })
406
-
407
- it('applies isNotNull filter when value is true', async () => {
408
- const query = db('assets').select('*')
409
- const filteredQuery = applyFilterSnakeCase({
410
- query,
411
- filter: { deletedAt: { isNotNull: true } },
412
- tableName: 'assets',
413
- })
414
-
415
- const results = await filteredQuery
416
-
417
- expect(results).toHaveLength(1)
418
- expect(results[0].deleted_at).not.toBe(null)
419
- })
420
-
421
- it('applies isNull filter when isNotNull value is false', async () => {
422
- const query = db('assets').select('*')
423
- const filteredQuery = applyFilterSnakeCase({
424
- query,
425
- filter: { deletedAt: { isNotNull: false } },
426
- tableName: 'assets',
427
- })
428
-
429
- const results = await filteredQuery
430
-
431
- expect(results).toHaveLength(4)
432
- expect(results.every((r) => r.deleted_at === null)).toBe(true)
433
- })
434
-
435
- it('applies multiple filters together', async () => {
436
- const query = db('assets').select('*')
437
- const filteredQuery = applyFilterSnakeCase({
438
- query,
439
- filter: {
440
- status: 'active',
441
- type: 'invoice',
442
- price: { gte: 100 },
443
- },
444
- tableName: 'assets',
445
- })
446
-
447
- const results = await filteredQuery
448
-
449
- expect(results).toHaveLength(2)
450
- expect(results.every((r) => r.status === 'active')).toBe(true)
451
- expect(results.every((r) => r.type === 'invoice')).toBe(true)
452
- expect(results.every((r) => parseFloat(r.price) >= 100)).toBe(true)
453
- })
454
-
455
- it('returns empty results when filter matches nothing', async () => {
456
- const query = db('assets').select('*')
457
- const filteredQuery = applyFilterSnakeCase({
458
- query,
459
- filter: { status: 'non-existing' },
460
- tableName: 'assets',
461
- })
462
-
463
- const results = await filteredQuery
464
-
465
- expect(results).toHaveLength(0)
466
- })
467
-
468
- it('uses qualified column names with tableName', async () => {
469
- const query = db('assets').select('assets.*')
470
- const filteredQuery = applyFilterSnakeCase({
471
- query,
472
- filter: { status: 'active' },
473
- tableName: 'assets',
474
- })
475
-
476
- const results = await filteredQuery
477
-
478
- expect(results).toHaveLength(3)
479
- expect(results.every((r) => r.status === 'active')).toBe(true)
480
- })
481
-
482
194
  it('ignores unknown operators', async () => {
483
195
  const query = db('assets').select('*')
484
- const filteredQuery = applyFilterSnakeCase({
196
+
197
+ applyFilterSnakeCase({
485
198
  query,
486
- filter: { status: { unknownOperator: 'value' } },
487
- tableName: 'assets',
199
+ filter: { status: { unknown: 'x' } },
488
200
  })
489
201
 
490
- const results = await filteredQuery
491
-
492
- // Should return all records since unknown operator is ignored
202
+ const results = await query
493
203
  expect(results).toHaveLength(5)
494
204
  })
495
205
 
496
- it('works with invoices table', async () => {
206
+ it('works with invoices table and camelCase conversion', async () => {
497
207
  const query = db('invoices').select('*')
498
- const filteredQuery = applyFilterSnakeCase({
499
- query,
500
- filter: { status: 'paid', deletedAt: { isNull: true } },
501
- tableName: 'invoices',
502
- })
503
-
504
- const results = await filteredQuery
505
-
506
- expect(results).toHaveLength(2)
507
- expect(results.every((r) => r.status === 'paid')).toBe(true)
508
- expect(results.every((r) => r.deleted_at === null)).toBe(true)
509
- })
510
208
 
511
- it('works with invoices table using camelCase conversion', async () => {
512
- const query = db('invoices').select('*')
513
- const filteredQuery = applyFilterSnakeCase({
209
+ applyFilterSnakeCase({
514
210
  query,
515
211
  filter: {
516
212
  customerId: '00000000-0000-0000-0000-000000000201',
517
213
  amount: { gte: 500 },
518
214
  },
519
- tableName: 'invoices',
520
- })
521
-
522
- const results = await filteredQuery
523
-
524
- expect(results).toHaveLength(3)
525
- expect(
526
- results.every(
527
- (r) =>
528
- r.customer_id === '00000000-0000-0000-0000-000000000201' &&
529
- parseFloat(r.amount) >= 500,
530
- ),
531
- ).toBe(true)
532
- })
533
-
534
- it('works with invoices table using IN filter', async () => {
535
- const query = db('invoices').select('*')
536
- const filteredQuery = applyFilterSnakeCase({
537
- query,
538
- filter: { status: ['paid', 'pending'] },
539
- tableName: 'invoices',
540
215
  })
541
216
 
542
- const results = await filteredQuery
543
-
217
+ const results = await query
544
218
  expect(results).toHaveLength(3)
545
- expect(
546
- results.every((r) => r.status === 'paid' || r.status === 'pending'),
547
- ).toBe(true)
548
219
  })
549
220
  })
@@ -1,6 +1,4 @@
1
- // apply-filter.integration.test.js
2
1
  // @ts-nocheck
3
-
4
2
  import { describe, it, beforeAll, afterAll, beforeEach, expect } from 'vitest'
5
3
  import knex from 'knex'
6
4
 
@@ -10,7 +8,7 @@ import {
10
8
  buildPostgresUri,
11
9
  } from '../../src/postgresql/start-stop-postgres-docker.js'
12
10
 
13
- import { applyFilter } from '../../src/postgresql/apply-filter.js'
11
+ import { applyFilter } from '../../src/postgresql/filters/apply-filter.js'
14
12
 
15
13
  const PG_OPTIONS = {
16
14
  port: 5443,
@@ -169,112 +167,64 @@ beforeEach(async () => {
169
167
 
170
168
  describe('applyFilter integration (snake_case only)', () => {
171
169
  it('applies equality filter', async () => {
172
- const results = await applyFilter({
173
- query: db('assets').select('*'),
170
+ const query = db('assets').select('*')
171
+
172
+ applyFilter({
173
+ query,
174
174
  filter: { status: 'active' },
175
- tableName: 'assets',
176
175
  })
177
176
 
177
+ const results = await query
178
178
  expect(results).toHaveLength(3)
179
179
  })
180
180
 
181
181
  it('applies IN filter', async () => {
182
- const results = await applyFilter({
183
- query: db('assets').select('*'),
184
- filter: { status: ['active', 'pending'] },
185
- tableName: 'assets',
186
- })
182
+ const query = db('assets').select('*')
187
183
 
188
- expect(results).toHaveLength(4)
189
- })
190
-
191
- it('applies NOT IN filter', async () => {
192
- const results = await applyFilter({
193
- query: db('assets').select('*'),
194
- filter: { status: { nin: ['deleted'] } },
195
- tableName: 'assets',
184
+ applyFilter({
185
+ query,
186
+ filter: { status: ['active', 'pending'] },
196
187
  })
197
188
 
189
+ const results = await query
198
190
  expect(results).toHaveLength(4)
199
191
  })
200
192
 
201
193
  it('applies numeric range filter', async () => {
202
- const results = await applyFilter({
203
- query: db('assets').select('*'),
194
+ const query = db('assets').select('*')
195
+
196
+ applyFilter({
197
+ query,
204
198
  filter: { price: { gte: 100, lte: 200 } },
205
- tableName: 'assets',
206
199
  })
207
200
 
201
+ const results = await query
208
202
  expect(results).toHaveLength(3)
209
203
  })
210
204
 
211
- it('applies isNull filter', async () => {
212
- const results = await applyFilter({
213
- query: db('assets').select('*'),
214
- filter: { deleted_at: { isNull: true } },
215
- tableName: 'assets',
216
- })
217
-
218
- expect(results).toHaveLength(4)
219
- })
220
-
221
- it('applies isNotNull filter', async () => {
222
- const results = await applyFilter({
223
- query: db('assets').select('*'),
224
- filter: { deleted_at: { isNotNull: true } },
225
- tableName: 'assets',
226
- })
227
-
228
- expect(results).toHaveLength(1)
229
- })
230
-
231
- it('applies multiple filters together', async () => {
232
- const results = await applyFilter({
233
- query: db('assets').select('*'),
234
- filter: {
235
- status: 'active',
236
- type: 'invoice',
237
- price: { gte: 100 },
238
- },
239
- tableName: 'assets',
240
- })
241
-
242
- expect(results).toHaveLength(2)
243
- })
244
-
245
205
  it('works with invoices table', async () => {
246
- const results = await applyFilter({
247
- query: db('invoices').select('*'),
206
+ const query = db('invoices').select('*')
207
+
208
+ applyFilter({
209
+ query,
248
210
  filter: {
249
211
  status: 'paid',
250
212
  deleted_at: { isNull: true },
251
213
  },
252
- tableName: 'invoices',
253
214
  })
254
215
 
216
+ const results = await query
255
217
  expect(results).toHaveLength(2)
256
218
  })
257
219
 
258
- it('works with invoices using snake_case foreign key', async () => {
259
- const results = await applyFilter({
260
- query: db('invoices').select('*'),
261
- filter: {
262
- customer_id: '00000000-0000-0000-0000-000000000201',
263
- amount: { gte: 500 },
264
- },
265
- tableName: 'invoices',
266
- })
220
+ it('throws when using camelCase keys', async () => {
221
+ const query = db('assets').select('*')
267
222
 
268
- expect(results).toHaveLength(3)
269
- })
223
+ applyFilter({
224
+ query,
225
+ filter: { deletedAt: { isNull: true } },
226
+ })
270
227
 
271
- it('throws when using camelCase keys (no automatic conversion)', async () => {
272
- await expect(
273
- applyFilter({
274
- query: db('assets').select('*'),
275
- filter: { deletedAt: { isNull: true } },
276
- tableName: 'assets',
277
- }),
278
- ).rejects.toThrow(/column .*deletedAt.* does not exist/i)
228
+ await expect(query).rejects.toThrow(/deletedat/i)
279
229
  })
280
230
  })
@@ -0,0 +1,20 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getTableNameFromQuery } from '../../../src/postgresql/core/get-table-name.js'
3
+
4
+ describe('getTableNameFromQuery', () => {
5
+ it('extracts table name from knex query mock', () => {
6
+ const mockQuery = {
7
+ _single: {
8
+ table: 'assets',
9
+ },
10
+ }
11
+
12
+ // @ts-ignore
13
+ expect(getTableNameFromQuery(mockQuery)).toBe('assets')
14
+ })
15
+
16
+ it('returns undefined when table is missing', () => {
17
+ // @ts-ignore
18
+ expect(getTableNameFromQuery({})).toBeUndefined()
19
+ })
20
+ })
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { applyFilterObject } from '../../../src/postgresql/filters/apply-filter-object.js'
3
+ import { OPERATORS } from '../../../src/postgresql/filters/operators.js'
4
+
5
+ describe('applyFilterObject', () => {
6
+ it('applies multiple operators', () => {
7
+ const q = {}
8
+ // @ts-ignore
9
+ const spy = vi.spyOn(OPERATORS, 'gte').mockReturnValue(q)
10
+
11
+ // @ts-ignore
12
+ applyFilterObject(q, 'price', { gte: 100 })
13
+
14
+ expect(spy).toHaveBeenCalledWith(q, 'price', 100)
15
+ })
16
+
17
+ it('ignores unknown operators', () => {
18
+ const q = {}
19
+ // @ts-ignore
20
+ const result = applyFilterObject(q, 'x', { unknown: 1 })
21
+ expect(result).toBe(q)
22
+ })
23
+ })