core-services-sdk 1.3.63 → 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 (35) hide show
  1. package/package.json +1 -1
  2. package/src/core/normalize-phone-number.js +144 -25
  3. package/src/postgresql/core/get-table-name.js +10 -0
  4. package/src/postgresql/filters/apply-filter-object.js +19 -0
  5. package/src/postgresql/filters/apply-filter-snake-case.js +16 -0
  6. package/src/postgresql/filters/apply-filter.js +33 -0
  7. package/src/postgresql/filters/operators.js +32 -0
  8. package/src/postgresql/index.js +5 -2
  9. package/src/postgresql/modifiers/apply-order-by.js +19 -0
  10. package/src/postgresql/modifiers/apply-pagination.js +12 -0
  11. package/src/postgresql/pagination/paginate.js +48 -0
  12. package/tests/core/normalize-phone-number.unit.test.js +45 -0
  13. package/tests/postgresql/apply-filter-snake-case.integration.test.js +220 -0
  14. package/tests/postgresql/apply-filter.integration.test.js +34 -353
  15. package/tests/postgresql/core/get-table-name.unit.test.js +20 -0
  16. package/tests/postgresql/filters/apply-filter-object.test.js +23 -0
  17. package/tests/postgresql/filters/operators.unit.test.js +23 -0
  18. package/tests/postgresql/modifiers/apply-order-by.test.js +80 -0
  19. package/tests/postgresql/modifiers/apply-pagination.unit.test.js +18 -0
  20. package/tests/postgresql/paginate.integration.test.js +10 -18
  21. package/tests/postgresql/pagination/paginate.js +48 -0
  22. package/tests/postgresql/validate-schema.integration.test.js +9 -5
  23. package/types/core/normalize-phone-number.d.ts +97 -27
  24. package/types/postgresql/apply-filter.d.ts +131 -20
  25. package/types/postgresql/core/get-table-name.d.ts +10 -0
  26. package/types/postgresql/filters/apply-filter-object.d.ts +15 -0
  27. package/types/postgresql/filters/apply-filter-snake-case.d.ts +14 -0
  28. package/types/postgresql/filters/apply-filter.d.ts +15 -0
  29. package/types/postgresql/filters/operators.d.ts +14 -0
  30. package/types/postgresql/index.d.ts +5 -2
  31. package/types/postgresql/modifiers/apply-order-by.d.ts +17 -0
  32. package/types/postgresql/modifiers/apply-pagination.d.ts +17 -0
  33. package/types/postgresql/pagination/paginate.d.ts +29 -0
  34. package/src/postgresql/apply-filter.js +0 -275
  35. package/src/postgresql/paginate.js +0 -61
@@ -8,11 +8,11 @@ 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
12
 
13
13
  const PG_OPTIONS = {
14
- port: 5443,
15
- containerName: 'postgres-paginate-test',
14
+ port: 5442,
15
+ containerName: 'postgres-paginate-test-5442',
16
16
  user: 'testuser',
17
17
  pass: 'testpass',
18
18
  db: 'testdb',
@@ -73,8 +73,7 @@ beforeEach(async () => {
73
73
  describe('paginate integration', () => {
74
74
  it('returns first page without ordering guarantees', async () => {
75
75
  const result = await sqlPaginate({
76
- db,
77
- tableName: 'tenants',
76
+ baseQuery: db('tenants'),
78
77
  page: 1,
79
78
  limit: 2,
80
79
  })
@@ -84,14 +83,12 @@ describe('paginate integration', () => {
84
83
  expect(result.currentPage).toBe(1)
85
84
  expect(result.hasPrevious).toBe(false)
86
85
  expect(result.hasNext).toBe(true)
87
-
88
86
  expect(result.list).toHaveLength(2)
89
87
  })
90
88
 
91
89
  it('returns second page without ordering guarantees', async () => {
92
90
  const result = await sqlPaginate({
93
- db,
94
- tableName: 'tenants',
91
+ baseQuery: db('tenants'),
95
92
  page: 2,
96
93
  limit: 2,
97
94
  })
@@ -101,14 +98,12 @@ describe('paginate integration', () => {
101
98
  expect(result.currentPage).toBe(2)
102
99
  expect(result.hasPrevious).toBe(true)
103
100
  expect(result.hasNext).toBe(false)
104
-
105
101
  expect(result.list).toHaveLength(1)
106
102
  })
107
103
 
108
- it('applies filters correctly without ordering guarantees', async () => {
104
+ it('applies filters correctly', async () => {
109
105
  const result = await sqlPaginate({
110
- db,
111
- tableName: 'tenants',
106
+ baseQuery: db('tenants'),
112
107
  filter: { type: 'business' },
113
108
  limit: 10,
114
109
  })
@@ -121,8 +116,7 @@ describe('paginate integration', () => {
121
116
 
122
117
  it('supports custom ordering', async () => {
123
118
  const result = await sqlPaginate({
124
- db,
125
- tableName: 'tenants',
119
+ baseQuery: db('tenants'),
126
120
  orderBy: {
127
121
  column: 'created_at',
128
122
  direction: 'asc',
@@ -138,8 +132,7 @@ describe('paginate integration', () => {
138
132
 
139
133
  it('supports row mapping', async () => {
140
134
  const result = await sqlPaginate({
141
- db,
142
- tableName: 'tenants',
135
+ baseQuery: db('tenants'),
143
136
  mapRow: (row) => ({
144
137
  ...row,
145
138
  mapped: true,
@@ -152,8 +145,7 @@ describe('paginate integration', () => {
152
145
 
153
146
  it('returns empty list when no records match filter', async () => {
154
147
  const result = await sqlPaginate({
155
- db,
156
- tableName: 'tenants',
148
+ baseQuery: db('tenants'),
157
149
  filter: { type: 'non-existing' },
158
150
  })
159
151
 
@@ -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 totalPages = Math.ceil(totalCount / limit)
39
+
40
+ return {
41
+ totalCount,
42
+ totalPages,
43
+ currentPage: page,
44
+ hasPrevious: page > 1,
45
+ hasNext: page < totalPages,
46
+ list: mapRow ? rows.map(mapRow) : rows,
47
+ }
48
+ }
@@ -5,12 +5,13 @@ 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
- port: 5433,
13
- containerName: 'postgres-validate-schema-test',
13
+ port: 5431,
14
+ containerName: 'postgres-validate-schema-test-5431',
14
15
  user: 'testuser',
15
16
  pass: 'testpass',
16
17
  db: 'testdb',
@@ -18,7 +19,7 @@ const PG_OPTIONS = {
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({ connection: DATABASE_URI, tables: ['files'] }),
42
+ validateSchema({
43
+ connection: DATABASE_URI,
44
+ tables: ['files'],
45
+ }),
42
46
  ).resolves.not.toThrow()
43
47
  })
44
48
 
@@ -1,25 +1,82 @@
1
- /** Resolve libphonenumber regardless of interop shape */
2
- export function getLib(): any
3
- export function phoneUtil(): any
4
1
  /**
5
- * Parse & validate an international number (must start with '+').
6
- * @param {string} input
7
- * @returns {{e164:string,national:string,international:string,regionCode:string|undefined,type:number}}
8
- * @throws {Error} If the number is invalid
2
+ * @typedef {Object} NormalizedPhone
3
+ * @property {string} e164
4
+ * @property {number} type
5
+ * @property {string} national
6
+ * @property {number} countryCode
7
+ * @property {string} nationalClean
8
+ * @property {string} international
9
+ * @property {string} countryCodeE164
10
+ * @property {string} internationalClean
11
+ * @property {string | undefined} regionCode
9
12
  */
10
- export function normalizePhoneOrThrowIntl(input: string): {
11
- e164: string
12
- national: string
13
- international: string
14
- regionCode: string | undefined
15
- type: number
13
+ /**
14
+ * normalize-phone-number.js
15
+ *
16
+ * Utilities for parsing, validating, and normalizing phone numbers
17
+ * using google-libphonenumber.
18
+ *
19
+ * Supports both ESM and CJS interop builds.
20
+ * All normalization outputs are canonical and safe for persistence,
21
+ * comparison, indexing, and login identifiers.
22
+ */
23
+ /**
24
+ * Resolve google-libphonenumber exports regardless of ESM / CJS shape.
25
+ *
26
+ * Some builds expose:
27
+ * - raw.PhoneNumberUtil
28
+ * Others expose:
29
+ * - raw.default.PhoneNumberUtil
30
+ *
31
+ * This helper guarantees a consistent API.
32
+ *
33
+ * @returns {{
34
+ * PhoneNumberUtil: any,
35
+ * PhoneNumberFormat: any
36
+ * }}
37
+ * @throws {Error} If required exports are missing
38
+ */
39
+ export function getLib(): {
40
+ PhoneNumberUtil: any
41
+ PhoneNumberFormat: any
16
42
  }
17
43
  /**
18
- * Parse & validate a national number using a region hint.
19
- * @param {string} input
20
- * @param {string} defaultRegion
21
- * @returns {{e164:string,national:string,international:string,regionCode:string|undefined,type:number}}
22
- * @throws {Error} If the number is invalid
44
+ * Lazy singleton accessor for PhoneNumberUtil.
45
+ *
46
+ * Ensures:
47
+ * - Single instance per process
48
+ * - No eager initialization cost
49
+ *
50
+ * @returns {any} PhoneNumberUtil instance
51
+ */
52
+ export function phoneUtil(): any
53
+ /**
54
+ * Normalize and validate an international phone number.
55
+ *
56
+ * Input MUST start with '+'.
57
+ *
58
+ * @param {string} input International phone number (E.164-like)
59
+ * @returns {NormalizedPhone}
60
+ * @throws {Error} If the phone number is invalid
61
+ */
62
+ export function normalizePhoneOrThrowIntl(input: string): NormalizedPhone
63
+ /**
64
+ * Normalize and validate a national phone number using a region hint.
65
+ *
66
+ * Example:
67
+ * input: "0523444444"
68
+ * defaultRegion: "IL"
69
+ *
70
+ * @param {string} input National phone number
71
+ * @param {string} defaultRegion ISO 3166-1 alpha-2 country code
72
+ * @returns {{
73
+ * e164: string,
74
+ * national: string,
75
+ * international: string,
76
+ * regionCode: string | undefined,
77
+ * type: number
78
+ * }}
79
+ * @throws {Error} If the phone number is invalid
23
80
  */
24
81
  export function normalizePhoneOrThrowWithRegion(
25
82
  input: string,
@@ -32,23 +89,36 @@ export function normalizePhoneOrThrowWithRegion(
32
89
  type: number
33
90
  }
34
91
  /**
35
- * Smart normalization:
36
- * - If input starts with '+', parse as international.
37
- * - Otherwise require a defaultRegion and parse as national.
38
- * @param {string} input
39
- * @param {{ defaultRegion?: string }} [opts]
40
- * @returns {{e164:string,national:string,international:string,regionCode:string|undefined,type:number}}
41
- * @throws {Error} If invalid or defaultRegion is missing for non-international input
92
+ * Smart normalization entry point.
93
+ *
94
+ * Behavior:
95
+ * - If input starts with '+', parses as international
96
+ * - Otherwise requires defaultRegion and parses as national
97
+ *
98
+ * This is the recommended function for login, signup, and verification flows.
99
+ *
100
+ * @param {string} input Phone number (international or national)
101
+ * @param {{
102
+ * defaultRegion?: string
103
+ * }} [opts]
104
+ *
105
+ * @returns {NormalizedPhone}
106
+ * @throws {Error} If invalid or defaultRegion is missing
42
107
  */
43
108
  export function normalizePhoneOrThrow(
44
109
  input: string,
45
110
  opts?: {
46
111
  defaultRegion?: string
47
112
  },
48
- ): {
113
+ ): NormalizedPhone
114
+ export type NormalizedPhone = {
49
115
  e164: string
116
+ type: number
50
117
  national: string
118
+ countryCode: number
119
+ nationalClean: string
51
120
  international: string
121
+ countryCodeE164: string
122
+ internationalClean: string
52
123
  regionCode: string | undefined
53
- type: number
54
124
  }
@@ -2,9 +2,10 @@
2
2
  * Builds a Knex query with MongoDB-style filter operators.
3
3
  * Pure utility function that can be used across repositories.
4
4
  *
5
- * This function converts camelCase filter keys to snake_case and applies
6
- * various filter operators to build SQL WHERE clauses. All column names
7
- * are automatically qualified with the provided table name.
5
+ * This function applies various filter operators to build SQL WHERE clauses. All column names
6
+ * are automatically qualified with the provided table name. Filter keys are used as-is without
7
+ * any case conversion. If you need automatic camelCase to snake_case conversion, use
8
+ * {@link applyFilterSnakeCase} instead.
8
9
  *
9
10
  * **Supported Operators:**
10
11
  * - `eq` - Equality (default for simple values)
@@ -21,15 +22,19 @@
21
22
  * - `isNotNull` - Check if field is NOT NULL
22
23
  *
23
24
  * **Key Features:**
24
- * - Automatic camelCase to snake_case conversion for filter keys
25
+ * - Filter keys are used as-is (no automatic case conversion)
25
26
  * - Qualified column names (table.column format)
26
27
  * - Multiple operators can be applied to the same field
27
28
  * - Unknown operators are silently ignored
28
29
  * - Arrays are automatically converted to IN clauses
29
30
  *
31
+ * **Note:** This function does NOT convert filter keys. If your database uses snake_case columns
32
+ * but your filter keys are in camelCase, use {@link applyFilterSnakeCase} instead, or ensure
33
+ * your filter keys already match your database column names.
34
+ *
30
35
  * @param {Object} params - Function parameters
31
36
  * @param {import('knex').Knex.QueryBuilder} params.query - Knex query builder instance to apply filters to
32
- * @param {Object<string, *>} params.filter - Filter object with camelCase keys (will be converted to snake_case)
37
+ * @param {Object<string, *>} params.filter - Filter object with keys matching database column names (used as-is)
33
38
  * Filter values can be:
34
39
  * - Simple values (string, number, boolean) → treated as equality
35
40
  * - Arrays → treated as IN operator
@@ -89,9 +94,10 @@
89
94
  * })
90
95
  *
91
96
  * @example
92
- * // camelCase conversion - deletedAt converts to deleted_at
93
- * applyFilter({ query, filter: { deletedAt: { isNull: true } }, tableName: 'assets' })
97
+ * // Filter keys are used as-is - ensure they match your database column names
98
+ * applyFilter({ query, filter: { deleted_at: { isNull: true } }, tableName: 'assets' })
94
99
  * // SQL: WHERE assets.deleted_at IS NULL
100
+ * // Note: If you need camelCase conversion, use applyFilterSnakeCase instead
95
101
  */
96
102
  export function applyFilter({
97
103
  query,
@@ -104,17 +110,122 @@ export function applyFilter({
104
110
  }
105
111
  tableName: string
106
112
  }): import('knex').Knex.QueryBuilder
107
- export type OperatorFunction = {
108
- /**
109
- * - Knex query builder instance
110
- */
113
+ /**
114
+ * Applies MongoDB-style filter operators to a Knex query with automatic camelCase to snake_case conversion.
115
+ *
116
+ * This function is a convenience wrapper around {@link applyFilter} that automatically converts
117
+ * all filter keys from camelCase to snake_case before applying the filters. This is useful when
118
+ * your application code uses camelCase naming conventions but your database columns use snake_case.
119
+ *
120
+ * The function first converts all filter object keys using `toSnakeCase`, then applies the filters
121
+ * using the standard `applyFilter` function. This ensures that filter keys like `userId` are
122
+ * converted to `user_id` before being used in SQL queries.
123
+ *
124
+ * **Key Features:**
125
+ * - Automatic camelCase to snake_case key conversion
126
+ * - Same operator support as `applyFilter`
127
+ * - Qualified column names (table.column format)
128
+ * - Multiple operators can be applied to the same field
129
+ * - Arrays are automatically converted to IN clauses
130
+ *
131
+ * **Supported Operators:**
132
+ * Same as {@link applyFilter}:
133
+ * - `eq` - Equality (default for simple values)
134
+ * - `ne` / `neq` - Not equal
135
+ * - `in` - Array membership (or pass array directly)
136
+ * - `nin` - Not in array
137
+ * - `gt` - Greater than
138
+ * - `gte` - Greater than or equal
139
+ * - `lt` - Less than
140
+ * - `lte` - Less than or equal
141
+ * - `like` - Case-sensitive pattern matching (SQL LIKE)
142
+ * - `ilike` - Case-insensitive pattern matching (PostgreSQL ILIKE)
143
+ * - `isNull` - Check if field is NULL
144
+ * - `isNotNull` - Check if field is NOT NULL
145
+ *
146
+ * @param {Object} params - Function parameters
147
+ * @param {import('knex').Knex.QueryBuilder} params.query - Knex query builder instance to apply filters to
148
+ * @param {Object<string, *>} params.filter - Filter object with camelCase keys (will be converted to snake_case)
149
+ * Filter values can be:
150
+ * - Simple values (string, number, boolean) → treated as equality
151
+ * - Arrays → treated as IN operator
152
+ * - Objects with operator keys → apply specific operators
153
+ * @param {string} params.tableName - Table name used to qualify column names (e.g., "assets" → "assets.column_name")
154
+ * @returns {import('knex').Knex.QueryBuilder} Modified query builder with applied WHERE clauses (using snake_case column names)
155
+ *
156
+ * @throws {TypeError} If query is not a valid Knex QueryBuilder instance
157
+ *
158
+ * @example
159
+ * // Simple equality with camelCase key - converts to WHERE assets.user_id = 1
160
+ * const query = db('assets').select('*')
161
+ * applyFilterSnakeCase({ query, filter: { userId: 1 }, tableName: 'assets' })
162
+ * // SQL: WHERE assets.user_id = 1
163
+ *
164
+ * @example
165
+ * // Not equal with camelCase key - converts to WHERE assets.status != 'deleted'
166
+ * applyFilterSnakeCase({
167
+ * query,
168
+ * filter: { status: { ne: 'deleted' } },
169
+ * tableName: 'assets'
170
+ * })
171
+ * // SQL: WHERE assets.status != 'deleted'
172
+ *
173
+ * @example
174
+ * // Array/IN operator with camelCase key - converts to WHERE assets.status IN ('active', 'pending')
175
+ * applyFilterSnakeCase({
176
+ * query,
177
+ * filter: { status: ['active', 'pending'] },
178
+ * tableName: 'assets'
179
+ * })
180
+ * // SQL: WHERE assets.status IN ('active', 'pending')
181
+ *
182
+ * @example
183
+ * // Range operators with camelCase keys - converts to WHERE assets.price >= 100 AND assets.price <= 200
184
+ * applyFilterSnakeCase({
185
+ * query,
186
+ * filter: { price: { gte: 100, lte: 200 } },
187
+ * tableName: 'assets'
188
+ * })
189
+ * // SQL: WHERE assets.price >= 100 AND assets.price <= 200
190
+ *
191
+ * @example
192
+ * // Null checks with camelCase key - converts to WHERE assets.deleted_at IS NULL
193
+ * applyFilterSnakeCase({
194
+ * query,
195
+ * filter: { deletedAt: { isNull: true } },
196
+ * tableName: 'assets'
197
+ * })
198
+ * // SQL: WHERE assets.deleted_at IS NULL
199
+ *
200
+ * @example
201
+ * // Multiple filters with camelCase keys
202
+ * applyFilterSnakeCase({
203
+ * query,
204
+ * filter: {
205
+ * userId: 123, // Converts to user_id
206
+ * createdAt: { gte: '2024-01-01' }, // Converts to created_at
207
+ * status: 'active'
208
+ * },
209
+ * tableName: 'assets'
210
+ * })
211
+ * // SQL: WHERE assets.user_id = 123 AND assets.created_at >= '2024-01-01' AND assets.status = 'active'
212
+ *
213
+ * @see {@link applyFilter} For the base function without key conversion
214
+ * @see {@link toSnakeCase} For details on the key conversion process
215
+ */
216
+ export function applyFilterSnakeCase({
217
+ query,
218
+ filter,
219
+ tableName,
220
+ }: {
111
221
  query: import('knex').Knex.QueryBuilder
112
- /**
113
- * - Column name (qualified with table name)
114
- */
115
- key: string
116
- /**
117
- * - Value to compare against
118
- */
119
- value: any
120
- }
222
+ filter: {
223
+ [x: string]: any
224
+ }
225
+ tableName: string
226
+ }): import('knex').Knex.QueryBuilder
227
+ /**
228
+ * Type definition for filter operator functions.
229
+ * Each operator function applies a WHERE condition to a Knex query builder.
230
+ */
231
+ export type OperatorFunction = Function
@@ -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
+ }>