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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-services-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.66",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"@aws-sdk/credential-provider-node": "^3.862.0",
|
|
32
32
|
"@sendgrid/mail": "^8.1.5",
|
|
33
33
|
"amqplib": "^0.10.8",
|
|
34
|
+
"date-fns": "^4.1.0",
|
|
34
35
|
"dot": "^1.1.3",
|
|
35
36
|
"fastify": "^5.4.0",
|
|
36
37
|
"google-libphonenumber": "^3.2.42",
|
|
@@ -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(query) {
|
|
9
|
+
return query?._single?.table
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { OPERATORS } from './operators.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Applies multiple operators on the same column.
|
|
5
|
+
*
|
|
6
|
+
* @param {import('knex').Knex.QueryBuilder} query
|
|
7
|
+
* @param {string} qualifiedKey
|
|
8
|
+
* @param {Object<string, *>} value
|
|
9
|
+
* @returns {import('knex').Knex.QueryBuilder}
|
|
10
|
+
*/
|
|
11
|
+
export function applyFilterObject(query, qualifiedKey, value) {
|
|
12
|
+
return Object.entries(value).reduce((q, [operator, val]) => {
|
|
13
|
+
if (!OPERATORS[operator]) {
|
|
14
|
+
return q
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return OPERATORS[operator](q, qualifiedKey, val)
|
|
18
|
+
}, query)
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { toSnakeCase } from '../../core/case-mapper.js'
|
|
2
|
+
import { applyFilter } from './apply-filter.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Applies filters with automatic camelCase to snake_case conversion.
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} params
|
|
8
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
9
|
+
* @param {Object} params.filter
|
|
10
|
+
*/
|
|
11
|
+
export function applyFilterSnakeCase({ query, filter }) {
|
|
12
|
+
return applyFilter({
|
|
13
|
+
query,
|
|
14
|
+
filter: toSnakeCase(filter),
|
|
15
|
+
})
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getTableNameFromQuery } from '../core/get-table-name.js'
|
|
2
|
+
import { OPERATORS } from './operators.js'
|
|
3
|
+
import { applyFilterObject } from './apply-filter-object.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Applies MongoDB-style filters to a Knex QueryBuilder.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} params
|
|
9
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
10
|
+
* @param {Object} [params.filter]
|
|
11
|
+
* @returns {import('knex').Knex.QueryBuilder}
|
|
12
|
+
*/
|
|
13
|
+
export function applyFilter({ query, filter = {} }) {
|
|
14
|
+
const tableName = getTableNameFromQuery(query)
|
|
15
|
+
|
|
16
|
+
if (!filter || Object.keys(filter).length === 0) {
|
|
17
|
+
return query
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return Object.entries(filter).reduce((q, [key, value]) => {
|
|
21
|
+
const qualifiedKey = tableName ? `${tableName}.${key}` : key
|
|
22
|
+
|
|
23
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
24
|
+
return applyFilterObject(q, qualifiedKey, value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return OPERATORS.in(q, qualifiedKey, value)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return OPERATORS.eq(q, qualifiedKey, value)
|
|
32
|
+
}, query)
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* @type {Object<string, OperatorFunction>}
|
|
11
|
+
*/
|
|
12
|
+
export const OPERATORS = {
|
|
13
|
+
in: (q, key, value) => q.whereIn(key, value),
|
|
14
|
+
nin: (q, key, value) => q.whereNotIn(key, value),
|
|
15
|
+
|
|
16
|
+
eq: (q, key, value) => q.where(key, '=', value),
|
|
17
|
+
ne: (q, key, value) => q.where(key, '!=', value),
|
|
18
|
+
neq: (q, key, value) => q.where(key, '!=', value),
|
|
19
|
+
|
|
20
|
+
gt: (q, key, value) => q.where(key, '>', value),
|
|
21
|
+
gte: (q, key, value) => q.where(key, '>=', value),
|
|
22
|
+
lt: (q, key, value) => q.where(key, '<', value),
|
|
23
|
+
lte: (q, key, value) => q.where(key, '<=', value),
|
|
24
|
+
|
|
25
|
+
like: (q, key, value) => q.where(key, 'like', value),
|
|
26
|
+
ilike: (q, key, value) => q.where(key, 'ilike', value),
|
|
27
|
+
|
|
28
|
+
isNull: (q, key, value) => (value ? q.whereNull(key) : q.whereNotNull(key)),
|
|
29
|
+
|
|
30
|
+
isNotNull: (q, key, value) =>
|
|
31
|
+
value ? q.whereNotNull(key) : q.whereNull(key),
|
|
32
|
+
}
|
package/src/postgresql/index.js
CHANGED
|
@@ -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,19 @@
|
|
|
1
|
+
import { getTableNameFromQuery } from '../core/get-table-name.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Applies ORDER BY clause.
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} params
|
|
7
|
+
* @param {import('knex').Knex.QueryBuilder} params.query
|
|
8
|
+
* @param {{ column: string, direction?: 'asc'|'desc' }} params.orderBy
|
|
9
|
+
*/
|
|
10
|
+
export function applyOrderBy({ query, orderBy }) {
|
|
11
|
+
if (!orderBy?.column) {
|
|
12
|
+
return query
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const tableName = getTableNameFromQuery(query)
|
|
16
|
+
const column = tableName ? `${tableName}.${orderBy.column}` : orderBy.column
|
|
17
|
+
|
|
18
|
+
return query.orderBy(column, orderBy.direction || 'asc')
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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({ query, page = 1, limit = 10 }) {
|
|
10
|
+
const offset = (page - 1) * limit
|
|
11
|
+
return query.limit(limit).offset(offset)
|
|
12
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { applyFilter } from '../filters/apply-filter.js'
|
|
2
|
+
import { applyFilterSnakeCase } from '../filters/apply-filter-snake-case.js'
|
|
3
|
+
import { applyOrderBy } from '../modifiers/apply-order-by.js'
|
|
4
|
+
import { applyPagination } from '../modifiers/apply-pagination.js'
|
|
5
|
+
import { normalizeNumberOrDefault } from '../../core/normalize-premitives-types-or-default.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Executes paginated query.
|
|
9
|
+
*
|
|
10
|
+
* @async
|
|
11
|
+
*/
|
|
12
|
+
export async function sqlPaginate({
|
|
13
|
+
mapRow,
|
|
14
|
+
orderBy,
|
|
15
|
+
page = 1,
|
|
16
|
+
baseQuery,
|
|
17
|
+
limit = 10,
|
|
18
|
+
filter = {},
|
|
19
|
+
snakeCase = true,
|
|
20
|
+
}) {
|
|
21
|
+
const listQuery = baseQuery.clone()
|
|
22
|
+
const countQuery = baseQuery.clone()
|
|
23
|
+
|
|
24
|
+
const applyFilterFn = snakeCase ? applyFilterSnakeCase : applyFilter
|
|
25
|
+
|
|
26
|
+
applyFilterFn({ query: listQuery, filter })
|
|
27
|
+
applyFilterFn({ query: countQuery, filter })
|
|
28
|
+
|
|
29
|
+
applyOrderBy({ query: listQuery, orderBy })
|
|
30
|
+
applyPagination({ query: listQuery, page, limit })
|
|
31
|
+
|
|
32
|
+
const [rows, countResult] = await Promise.all([
|
|
33
|
+
listQuery.select('*'),
|
|
34
|
+
countQuery.count('* as count').first(),
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
const totalCount = normalizeNumberOrDefault(countResult?.count || 0)
|
|
38
|
+
const pages = Math.ceil(totalCount / limit)
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
page,
|
|
42
|
+
pages,
|
|
43
|
+
totalCount,
|
|
44
|
+
hasPrevious: page > 1,
|
|
45
|
+
hasNext: page < pages,
|
|
46
|
+
list: mapRow ? rows.map(mapRow) : rows,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -81,7 +81,7 @@ describe('phone normalization', () => {
|
|
|
81
81
|
})
|
|
82
82
|
|
|
83
83
|
it('returns identical normalized data when defaultRegion is provided', () => {
|
|
84
|
-
const out = normalizePhoneOrThrow('
|
|
84
|
+
const out = normalizePhoneOrThrow('0523443413', {
|
|
85
85
|
defaultRegion: 'IL',
|
|
86
86
|
})
|
|
87
87
|
|