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.
Files changed (32) hide show
  1. package/package.json +2 -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 +27 -356
  13. package/tests/postgresql/apply-filter.integration.test.js +31 -81
  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 +55 -36
  20. package/tests/postgresql/pagination/paginate.js +49 -0
  21. package/tests/postgresql/validate-schema.integration.test.js +9 -5
  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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core-services-sdk",
3
- "version": "1.3.64",
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
+ }
@@ -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('+972523443413', {
84
+ const out = normalizePhoneOrThrow('0523443413', {
85
85
  defaultRegion: 'IL',
86
86
  })
87
87