core-services-sdk 1.3.64 → 1.3.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/postgresql/core/get-table-name.js +10 -0
- package/src/postgresql/filters/apply-filter-object.js +19 -0
- package/src/postgresql/filters/apply-filter-snake-case.js +16 -0
- package/src/postgresql/filters/apply-filter.js +33 -0
- package/src/postgresql/filters/operators.js +32 -0
- package/src/postgresql/index.js +5 -2
- package/src/postgresql/modifiers/apply-order-by.js +19 -0
- package/src/postgresql/modifiers/apply-pagination.js +12 -0
- package/src/postgresql/pagination/paginate.js +48 -0
- package/tests/core/normalize-phone-number.unit.test.js +1 -1
- package/tests/postgresql/apply-filter-snake-case.integration.test.js +27 -356
- package/tests/postgresql/apply-filter.integration.test.js +31 -81
- package/tests/postgresql/core/get-table-name.unit.test.js +20 -0
- package/tests/postgresql/filters/apply-filter-object.test.js +23 -0
- package/tests/postgresql/filters/operators.unit.test.js +23 -0
- package/tests/postgresql/modifiers/apply-order-by.test.js +80 -0
- package/tests/postgresql/modifiers/apply-pagination.unit.test.js +18 -0
- package/tests/postgresql/paginate.integration.test.js +55 -36
- package/tests/postgresql/pagination/paginate.js +49 -0
- package/tests/postgresql/validate-schema.integration.test.js +9 -5
- package/types/postgresql/core/get-table-name.d.ts +10 -0
- package/types/postgresql/filters/apply-filter-object.d.ts +15 -0
- package/types/postgresql/filters/apply-filter-snake-case.d.ts +14 -0
- package/types/postgresql/filters/apply-filter.d.ts +15 -0
- package/types/postgresql/filters/operators.d.ts +14 -0
- package/types/postgresql/index.d.ts +5 -2
- package/types/postgresql/modifiers/apply-order-by.d.ts +17 -0
- package/types/postgresql/modifiers/apply-pagination.d.ts +17 -0
- package/types/postgresql/pagination/paginate.d.ts +29 -0
- package/src/postgresql/apply-filter.js +0 -401
- package/src/postgresql/paginate.js +0 -61
|
@@ -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
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { OPERATORS } from '../../../src/postgresql/filters/operators.js'
|
|
4
|
+
|
|
5
|
+
describe('OPERATORS', () => {
|
|
6
|
+
it('eq calls where with "="', () => {
|
|
7
|
+
const q = { where: vi.fn(() => q) }
|
|
8
|
+
OPERATORS.eq(q, 'a', 1)
|
|
9
|
+
expect(q.where).toHaveBeenCalledWith('a', '=', 1)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('in calls whereIn', () => {
|
|
13
|
+
const q = { whereIn: vi.fn(() => q) }
|
|
14
|
+
OPERATORS.in(q, 'a', [1, 2])
|
|
15
|
+
expect(q.whereIn).toHaveBeenCalledWith('a', [1, 2])
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('isNull true calls whereNull', () => {
|
|
19
|
+
const q = { whereNull: vi.fn(() => q) }
|
|
20
|
+
OPERATORS.isNull(q, 'a', true)
|
|
21
|
+
expect(q.whereNull).toHaveBeenCalledWith('a')
|
|
22
|
+
})
|
|
23
|
+
})
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
3
|
+
import { applyOrderBy } from '../../../src/postgresql/modifiers/apply-order-by.js'
|
|
4
|
+
|
|
5
|
+
describe('applyOrderBy', () => {
|
|
6
|
+
it('does nothing when orderBy is not provided', () => {
|
|
7
|
+
const query = {}
|
|
8
|
+
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
const result = applyOrderBy({ query })
|
|
11
|
+
|
|
12
|
+
expect(result).toBe(query)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('does nothing when orderBy.column is missing', () => {
|
|
16
|
+
const query = {}
|
|
17
|
+
|
|
18
|
+
const result = applyOrderBy({
|
|
19
|
+
query,
|
|
20
|
+
orderBy: {},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(result).toBe(query)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('applies ORDER BY with default direction asc', () => {
|
|
27
|
+
const query = {
|
|
28
|
+
orderBy: vi.fn(() => query),
|
|
29
|
+
_single: { table: 'assets' },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
applyOrderBy({
|
|
33
|
+
query,
|
|
34
|
+
orderBy: { column: 'created_at' },
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(query.orderBy).toHaveBeenCalledWith('assets.created_at', 'asc')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('applies ORDER BY with explicit direction', () => {
|
|
41
|
+
const query = {
|
|
42
|
+
orderBy: vi.fn(() => query),
|
|
43
|
+
_single: { table: 'assets' },
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
applyOrderBy({
|
|
47
|
+
query,
|
|
48
|
+
orderBy: { column: 'created_at', direction: 'desc' },
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
expect(query.orderBy).toHaveBeenCalledWith('assets.created_at', 'desc')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('uses unqualified column when table name cannot be resolved', () => {
|
|
55
|
+
const query = {
|
|
56
|
+
orderBy: vi.fn(() => query),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
applyOrderBy({
|
|
60
|
+
query,
|
|
61
|
+
orderBy: { column: 'created_at' },
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
expect(query.orderBy).toHaveBeenCalledWith('created_at', 'asc')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('returns the same query instance for chaining', () => {
|
|
68
|
+
const query = {
|
|
69
|
+
orderBy: vi.fn(() => query),
|
|
70
|
+
_single: { table: 'assets' },
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const result = applyOrderBy({
|
|
74
|
+
query,
|
|
75
|
+
orderBy: { column: 'id' },
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
expect(result).toBe(query)
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { applyPagination } from '../../../src/postgresql/modifiers/apply-pagination.js'
|
|
4
|
+
|
|
5
|
+
describe('applyPagination', () => {
|
|
6
|
+
it('applies limit and offset', () => {
|
|
7
|
+
const q = {
|
|
8
|
+
limit: vi.fn(() => q),
|
|
9
|
+
offset: vi.fn(() => q),
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
applyPagination({ query: q, page: 2, limit: 10 })
|
|
14
|
+
|
|
15
|
+
expect(q.limit).toHaveBeenCalledWith(10)
|
|
16
|
+
expect(q.offset).toHaveBeenCalledWith(10)
|
|
17
|
+
})
|
|
18
|
+
})
|
|
@@ -8,14 +8,15 @@ import {
|
|
|
8
8
|
buildPostgresUri,
|
|
9
9
|
} from '../../src/postgresql/start-stop-postgres-docker.js'
|
|
10
10
|
|
|
11
|
-
import { sqlPaginate } from '../../src/postgresql/paginate.js'
|
|
11
|
+
import { sqlPaginate } from '../../src/postgresql/pagination/paginate.js'
|
|
12
|
+
import { sub } from 'date-fns'
|
|
12
13
|
|
|
13
14
|
const PG_OPTIONS = {
|
|
14
15
|
port: 5442,
|
|
15
|
-
|
|
16
|
+
db: 'testdb',
|
|
16
17
|
user: 'testuser',
|
|
17
18
|
pass: 'testpass',
|
|
18
|
-
|
|
19
|
+
containerName: 'postgres-paginate-test-5442',
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const DATABASE_URI = buildPostgresUri(PG_OPTIONS)
|
|
@@ -34,6 +35,7 @@ beforeAll(async () => {
|
|
|
34
35
|
table.uuid('id').primary()
|
|
35
36
|
table.string('name').notNullable()
|
|
36
37
|
table.string('type').notNullable()
|
|
38
|
+
table.bigInteger('age').notNullable()
|
|
37
39
|
table.timestamp('created_at').notNullable()
|
|
38
40
|
})
|
|
39
41
|
})
|
|
@@ -47,70 +49,88 @@ afterAll(async () => {
|
|
|
47
49
|
|
|
48
50
|
beforeEach(async () => {
|
|
49
51
|
await db('tenants').truncate()
|
|
50
|
-
|
|
51
|
-
await db('tenants').insert([
|
|
52
|
+
const records = [
|
|
52
53
|
{
|
|
53
54
|
id: '00000000-0000-0000-0000-000000000001',
|
|
54
55
|
name: 'Tenant A',
|
|
55
56
|
type: 'business',
|
|
56
|
-
|
|
57
|
+
age: 2,
|
|
58
|
+
created_at: new Date('2025-01-01'),
|
|
57
59
|
},
|
|
58
60
|
{
|
|
59
61
|
id: '00000000-0000-0000-0000-000000000002',
|
|
60
62
|
name: 'Tenant B',
|
|
63
|
+
age: 3,
|
|
61
64
|
type: 'business',
|
|
62
65
|
created_at: new Date('2024-01-02'),
|
|
63
66
|
},
|
|
64
67
|
{
|
|
65
68
|
id: '00000000-0000-0000-0000-000000000003',
|
|
66
69
|
name: 'Tenant C',
|
|
70
|
+
age: 1,
|
|
71
|
+
type: 'cpa',
|
|
72
|
+
created_at: new Date('2023-01-03'),
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
id: '00000000-0000-0000-0000-000000000004',
|
|
77
|
+
name: 'Tenant D',
|
|
78
|
+
age: 7,
|
|
79
|
+
type: 'cpa',
|
|
80
|
+
created_at: new Date('2022-01-03'),
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
id: '00000000-0000-0000-0000-000000000005',
|
|
85
|
+
name: 'Tenant E',
|
|
86
|
+
age: 0,
|
|
67
87
|
type: 'cpa',
|
|
68
|
-
created_at: new Date('
|
|
88
|
+
created_at: new Date('2021-01-03'),
|
|
69
89
|
},
|
|
70
|
-
]
|
|
90
|
+
]
|
|
91
|
+
await db('tenants').insert(records)
|
|
71
92
|
})
|
|
72
93
|
|
|
73
94
|
describe('paginate integration', () => {
|
|
74
95
|
it('returns first page without ordering guarantees', async () => {
|
|
75
96
|
const result = await sqlPaginate({
|
|
76
|
-
db,
|
|
77
|
-
|
|
78
|
-
page: 1,
|
|
97
|
+
baseQuery: db('tenants'),
|
|
98
|
+
page: 2,
|
|
79
99
|
limit: 2,
|
|
80
100
|
})
|
|
81
101
|
|
|
82
|
-
expect(result.totalCount).toBe(
|
|
83
|
-
expect(result.
|
|
84
|
-
expect(result.
|
|
85
|
-
expect(result.hasPrevious).toBe(
|
|
102
|
+
expect(result.totalCount).toBe(5)
|
|
103
|
+
expect(result.pages).toBe(3)
|
|
104
|
+
expect(result.page).toBe(2)
|
|
105
|
+
expect(result.hasPrevious).toBe(true)
|
|
86
106
|
expect(result.hasNext).toBe(true)
|
|
87
|
-
|
|
88
107
|
expect(result.list).toHaveLength(2)
|
|
89
108
|
})
|
|
90
109
|
|
|
91
110
|
it('returns second page without ordering guarantees', async () => {
|
|
92
111
|
const result = await sqlPaginate({
|
|
93
|
-
db,
|
|
94
|
-
tableName: 'tenants',
|
|
112
|
+
baseQuery: db('tenants'),
|
|
95
113
|
page: 2,
|
|
96
114
|
limit: 2,
|
|
97
115
|
})
|
|
98
116
|
|
|
99
|
-
expect(result.totalCount).toBe(
|
|
100
|
-
expect(result.
|
|
101
|
-
expect(result.
|
|
117
|
+
expect(result.totalCount).toBe(5)
|
|
118
|
+
expect(result.pages).toBe(3)
|
|
119
|
+
expect(result.page).toBe(2)
|
|
102
120
|
expect(result.hasPrevious).toBe(true)
|
|
103
|
-
expect(result.hasNext).toBe(
|
|
104
|
-
|
|
105
|
-
expect(result.list).toHaveLength(1)
|
|
121
|
+
expect(result.hasNext).toBe(true)
|
|
122
|
+
expect(result.list).toHaveLength(2)
|
|
106
123
|
})
|
|
107
124
|
|
|
108
|
-
it('applies filters correctly
|
|
125
|
+
it('applies filters correctly', async () => {
|
|
126
|
+
const minDate = sub(new Date(), { years: 2 })
|
|
109
127
|
const result = await sqlPaginate({
|
|
110
|
-
db,
|
|
111
|
-
|
|
112
|
-
|
|
128
|
+
baseQuery: db('tenants'),
|
|
129
|
+
filter: {
|
|
130
|
+
createdAt: { lte: new Date(), gte: minDate },
|
|
131
|
+
},
|
|
113
132
|
limit: 10,
|
|
133
|
+
page: 1,
|
|
114
134
|
})
|
|
115
135
|
|
|
116
136
|
expect(result.totalCount).toBe(2)
|
|
@@ -121,10 +141,9 @@ describe('paginate integration', () => {
|
|
|
121
141
|
|
|
122
142
|
it('supports custom ordering', async () => {
|
|
123
143
|
const result = await sqlPaginate({
|
|
124
|
-
db,
|
|
125
|
-
tableName: 'tenants',
|
|
144
|
+
baseQuery: db('tenants'),
|
|
126
145
|
orderBy: {
|
|
127
|
-
column: '
|
|
146
|
+
column: 'name',
|
|
128
147
|
direction: 'asc',
|
|
129
148
|
},
|
|
130
149
|
})
|
|
@@ -133,13 +152,14 @@ describe('paginate integration', () => {
|
|
|
133
152
|
'Tenant A',
|
|
134
153
|
'Tenant B',
|
|
135
154
|
'Tenant C',
|
|
155
|
+
'Tenant D',
|
|
156
|
+
'Tenant E',
|
|
136
157
|
])
|
|
137
158
|
})
|
|
138
159
|
|
|
139
160
|
it('supports row mapping', async () => {
|
|
140
161
|
const result = await sqlPaginate({
|
|
141
|
-
db,
|
|
142
|
-
tableName: 'tenants',
|
|
162
|
+
baseQuery: db('tenants'),
|
|
143
163
|
mapRow: (row) => ({
|
|
144
164
|
...row,
|
|
145
165
|
mapped: true,
|
|
@@ -152,13 +172,12 @@ describe('paginate integration', () => {
|
|
|
152
172
|
|
|
153
173
|
it('returns empty list when no records match filter', async () => {
|
|
154
174
|
const result = await sqlPaginate({
|
|
155
|
-
db,
|
|
156
|
-
tableName: 'tenants',
|
|
175
|
+
baseQuery: db('tenants'),
|
|
157
176
|
filter: { type: 'non-existing' },
|
|
158
177
|
})
|
|
159
178
|
|
|
160
179
|
expect(result.totalCount).toBe(0)
|
|
161
|
-
expect(result.
|
|
180
|
+
expect(result.pages).toBe(0)
|
|
162
181
|
expect(result.list).toEqual([])
|
|
163
182
|
expect(result.hasNext).toBe(false)
|
|
164
183
|
expect(result.hasPrevious).toBe(false)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { applyFilter } from '../filters/apply-filter.js'
|
|
3
|
+
import { applyFilterSnakeCase } from '../filters/apply-filter-snake-case.js'
|
|
4
|
+
import { applyOrderBy } from '../modifiers/apply-order-by.js'
|
|
5
|
+
import { applyPagination } from '../modifiers/apply-pagination.js'
|
|
6
|
+
import { normalizeNumberOrDefault } from '../../core/normalize-premitives-types-or-default.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Executes paginated query.
|
|
10
|
+
*
|
|
11
|
+
* @async
|
|
12
|
+
*/
|
|
13
|
+
export async function sqlPaginate({
|
|
14
|
+
mapRow,
|
|
15
|
+
orderBy,
|
|
16
|
+
page = 1,
|
|
17
|
+
baseQuery,
|
|
18
|
+
limit = 10,
|
|
19
|
+
filter = {},
|
|
20
|
+
snakeCase = true,
|
|
21
|
+
}) {
|
|
22
|
+
const listQuery = baseQuery.clone()
|
|
23
|
+
const countQuery = baseQuery.clone()
|
|
24
|
+
|
|
25
|
+
const applyFilterFn = snakeCase ? applyFilterSnakeCase : applyFilter
|
|
26
|
+
|
|
27
|
+
applyFilterFn({ query: listQuery, filter })
|
|
28
|
+
applyFilterFn({ query: countQuery, filter })
|
|
29
|
+
|
|
30
|
+
applyOrderBy({ query: listQuery, orderBy })
|
|
31
|
+
applyPagination({ query: listQuery, page, limit })
|
|
32
|
+
|
|
33
|
+
const [rows, countResult] = await Promise.all([
|
|
34
|
+
listQuery.select('*'),
|
|
35
|
+
countQuery.count('* as count').first(),
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
const totalCount = normalizeNumberOrDefault(countResult?.count || 0)
|
|
39
|
+
const pages = Math.ceil(totalCount / limit)
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
totalCount,
|
|
43
|
+
pages,
|
|
44
|
+
currentPage: page,
|
|
45
|
+
hasPrevious: page > 1,
|
|
46
|
+
hasNext: page < pages,
|
|
47
|
+
list: mapRow ? rows.map(mapRow) : rows,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -5,20 +5,21 @@ import {
|
|
|
5
5
|
stopPostgres,
|
|
6
6
|
buildPostgresUri,
|
|
7
7
|
} from '../../src/postgresql/start-stop-postgres-docker.js'
|
|
8
|
+
|
|
8
9
|
import { connectToPg } from '../../src/postgresql/connect-to-pg.js'
|
|
9
10
|
import { validateSchema } from '../../src/postgresql/validate-schema.js'
|
|
10
11
|
|
|
11
12
|
const PG_OPTIONS = {
|
|
12
13
|
port: 5431,
|
|
13
|
-
|
|
14
|
+
db: 'testdb',
|
|
14
15
|
user: 'testuser',
|
|
15
16
|
pass: 'testpass',
|
|
16
|
-
|
|
17
|
+
containerName: 'postgres-validate-schema-test-5431',
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
const DATABASE_URI = buildPostgresUri(PG_OPTIONS)
|
|
20
21
|
|
|
21
|
-
describe('validateSchema', () => {
|
|
22
|
+
describe('validateSchema integration', () => {
|
|
22
23
|
beforeAll(async () => {
|
|
23
24
|
startPostgres(PG_OPTIONS)
|
|
24
25
|
|
|
@@ -32,13 +33,16 @@ describe('validateSchema', () => {
|
|
|
32
33
|
await db.destroy()
|
|
33
34
|
})
|
|
34
35
|
|
|
35
|
-
afterAll(() => {
|
|
36
|
+
afterAll(async () => {
|
|
36
37
|
stopPostgres(PG_OPTIONS.containerName)
|
|
37
38
|
})
|
|
38
39
|
|
|
39
40
|
it('does not throw when all required tables exist', async () => {
|
|
40
41
|
await expect(
|
|
41
|
-
validateSchema({
|
|
42
|
+
validateSchema({
|
|
43
|
+
connection: DATABASE_URI,
|
|
44
|
+
tables: ['files'],
|
|
45
|
+
}),
|
|
42
46
|
).resolves.not.toThrow()
|
|
43
47
|
})
|
|
44
48
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts base table name from Knex QueryBuilder.
|
|
3
|
+
* Relies on Knex internal structure.
|
|
4
|
+
*
|
|
5
|
+
* @param {import('knex').Knex.QueryBuilder} query
|
|
6
|
+
* @returns {string|undefined}
|
|
7
|
+
*/
|
|
8
|
+
export function getTableNameFromQuery(
|
|
9
|
+
query: import('knex').Knex.QueryBuilder,
|
|
10
|
+
): string | undefined
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies multiple operators on the same column.
|
|
3
|
+
*
|
|
4
|
+
* @param {import('knex').Knex.QueryBuilder} query
|
|
5
|
+
* @param {string} qualifiedKey
|
|
6
|
+
* @param {Object<string, *>} value
|
|
7
|
+
* @returns {import('knex').Knex.QueryBuilder}
|
|
8
|
+
*/
|
|
9
|
+
export function applyFilterObject(
|
|
10
|
+
query: import('knex').Knex.QueryBuilder,
|
|
11
|
+
qualifiedKey: string,
|
|
12
|
+
value: {
|
|
13
|
+
[x: string]: any
|
|
14
|
+
},
|
|
15
|
+
): import('knex').Knex.QueryBuilder
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies filters with automatic camelCase to snake_case conversion.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} params
|
|
5
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
6
|
+
* @param {Object} params.filter
|
|
7
|
+
*/
|
|
8
|
+
export function applyFilterSnakeCase({
|
|
9
|
+
query,
|
|
10
|
+
filter,
|
|
11
|
+
}: {
|
|
12
|
+
query: import('knex').Knex.QueryBuilder
|
|
13
|
+
filter: any
|
|
14
|
+
}): import('knex').Knex.QueryBuilder<any, any>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies MongoDB-style filters to a Knex QueryBuilder.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} params
|
|
5
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
6
|
+
* @param {Object} [params.filter]
|
|
7
|
+
* @returns {import('knex').Knex.QueryBuilder}
|
|
8
|
+
*/
|
|
9
|
+
export function applyFilter({
|
|
10
|
+
query,
|
|
11
|
+
filter,
|
|
12
|
+
}: {
|
|
13
|
+
query: import('knex').Knex.QueryBuilder
|
|
14
|
+
filter?: any
|
|
15
|
+
}): import('knex').Knex.QueryBuilder
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Function} OperatorFunction
|
|
3
|
+
* @param {import('knex').Knex.QueryBuilder} q
|
|
4
|
+
* @param {string} key
|
|
5
|
+
* @param {*} value
|
|
6
|
+
* @returns {import('knex').Knex.QueryBuilder}
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* @type {Object<string, OperatorFunction>}
|
|
10
|
+
*/
|
|
11
|
+
export const OPERATORS: {
|
|
12
|
+
[x: string]: Function
|
|
13
|
+
}
|
|
14
|
+
export type OperatorFunction = Function
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export * from './paginate.js'
|
|
2
|
-
export * from './apply-filter.js'
|
|
3
1
|
export * from './connect-to-pg.js'
|
|
4
2
|
export * from './validate-schema.js'
|
|
3
|
+
export * from './pagination/paginate.js'
|
|
4
|
+
export * from './filters/apply-filter.js'
|
|
5
|
+
export * from './modifiers/apply-order-by.js'
|
|
5
6
|
export * from './start-stop-postgres-docker.js'
|
|
7
|
+
export * from './modifiers/apply-pagination.js'
|
|
8
|
+
export * from './filters/apply-filter-snake-case.js'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies ORDER BY clause.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} params
|
|
5
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
6
|
+
* @param {{ column: string, direction?: 'asc'|'desc' }} params.orderBy
|
|
7
|
+
*/
|
|
8
|
+
export function applyOrderBy({
|
|
9
|
+
query,
|
|
10
|
+
orderBy,
|
|
11
|
+
}: {
|
|
12
|
+
query: import('knex').Knex.QueryBuilder
|
|
13
|
+
orderBy: {
|
|
14
|
+
column: string
|
|
15
|
+
direction?: 'asc' | 'desc'
|
|
16
|
+
}
|
|
17
|
+
}): import('knex').Knex.QueryBuilder<any, any>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies LIMIT and OFFSET.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} params
|
|
5
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
6
|
+
* @param {number} [params.page=1]
|
|
7
|
+
* @param {number} [params.limit=10]
|
|
8
|
+
*/
|
|
9
|
+
export function applyPagination({
|
|
10
|
+
query,
|
|
11
|
+
page,
|
|
12
|
+
limit,
|
|
13
|
+
}: {
|
|
14
|
+
query: import('knex').Knex.QueryBuilder
|
|
15
|
+
page?: number
|
|
16
|
+
limit?: number
|
|
17
|
+
}): import('knex').Knex.QueryBuilder<any, any>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executes paginated query.
|
|
3
|
+
*
|
|
4
|
+
* @async
|
|
5
|
+
*/
|
|
6
|
+
export function sqlPaginate({
|
|
7
|
+
mapRow,
|
|
8
|
+
orderBy,
|
|
9
|
+
page,
|
|
10
|
+
baseQuery,
|
|
11
|
+
limit,
|
|
12
|
+
filter,
|
|
13
|
+
snakeCase,
|
|
14
|
+
}: {
|
|
15
|
+
mapRow: any
|
|
16
|
+
orderBy: any
|
|
17
|
+
page?: number
|
|
18
|
+
baseQuery: any
|
|
19
|
+
limit?: number
|
|
20
|
+
filter?: {}
|
|
21
|
+
snakeCase?: boolean
|
|
22
|
+
}): Promise<{
|
|
23
|
+
totalCount: number
|
|
24
|
+
totalPages: number
|
|
25
|
+
currentPage: number
|
|
26
|
+
hasPrevious: boolean
|
|
27
|
+
hasNext: boolean
|
|
28
|
+
list: any
|
|
29
|
+
}>
|