core-services-sdk 1.3.66 → 1.3.68
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 +1 -1
- package/src/postgresql/filters/apply-filter-snake-case.js +1 -0
- package/src/postgresql/filters/apply-filter.js +6 -1
- package/src/postgresql/filters/apply-or-filter.js +39 -0
- package/src/postgresql/pagination/paginate.js +60 -2
- package/tests/postgresql/filters/apply-or-filter.integration.test.js +104 -0
- package/tests/postgresql/filters/apply-or-filter.unit.test.js +112 -0
- package/tests/postgresql/paginate.integration.test.js +1 -2
- package/types/postgresql/pagination/paginate.d.ts +65 -14
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getTableNameFromQuery } from '../core/get-table-name.js'
|
|
2
2
|
import { OPERATORS } from './operators.js'
|
|
3
3
|
import { applyFilterObject } from './apply-filter-object.js'
|
|
4
|
+
import { applyOrFilter } from './apply-or-filter.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Applies MongoDB-style filters to a Knex QueryBuilder.
|
|
@@ -10,7 +11,7 @@ import { applyFilterObject } from './apply-filter-object.js'
|
|
|
10
11
|
* @param {Object} [params.filter]
|
|
11
12
|
* @returns {import('knex').Knex.QueryBuilder}
|
|
12
13
|
*/
|
|
13
|
-
export function applyFilter({ query, filter = {} }) {
|
|
14
|
+
export function applyFilter({ query, filter = {}, snakeCase = false }) {
|
|
14
15
|
const tableName = getTableNameFromQuery(query)
|
|
15
16
|
|
|
16
17
|
if (!filter || Object.keys(filter).length === 0) {
|
|
@@ -18,6 +19,10 @@ export function applyFilter({ query, filter = {} }) {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
return Object.entries(filter).reduce((q, [key, value]) => {
|
|
22
|
+
if (key === 'or' && Array.isArray(value)) {
|
|
23
|
+
return applyOrFilter(q, value, tableName, snakeCase)
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
const qualifiedKey = tableName ? `${tableName}.${key}` : key
|
|
22
27
|
|
|
23
28
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { snakeCase } from 'lodash-es'
|
|
2
|
+
|
|
3
|
+
import { OPERATORS } from './operators.js'
|
|
4
|
+
import { applyFilterObject } from './apply-filter-object.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Applies OR filters.
|
|
8
|
+
*
|
|
9
|
+
* @param {import('knex').Knex.QueryBuilder} query
|
|
10
|
+
* @param {Array<Object>} orFilters
|
|
11
|
+
* @param {string | null} tableName
|
|
12
|
+
* @returns {import('knex').Knex.QueryBuilder}
|
|
13
|
+
*/
|
|
14
|
+
export function applyOrFilter(
|
|
15
|
+
query,
|
|
16
|
+
orFilters,
|
|
17
|
+
tableName,
|
|
18
|
+
snakeCaseFields = false,
|
|
19
|
+
) {
|
|
20
|
+
return query.where(function () {
|
|
21
|
+
orFilters.forEach((filterObj, index) => {
|
|
22
|
+
this[index === 0 ? 'where' : 'orWhere'](function () {
|
|
23
|
+
Object.entries(filterObj).forEach(([key, value]) => {
|
|
24
|
+
const qualifiedKey = tableName
|
|
25
|
+
? `${tableName}.${snakeCaseFields ? snakeCase(key) : key}`
|
|
26
|
+
: key
|
|
27
|
+
|
|
28
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
29
|
+
applyFilterObject(this, qualifiedKey, value)
|
|
30
|
+
} else if (Array.isArray(value)) {
|
|
31
|
+
OPERATORS.in(this, qualifiedKey, value)
|
|
32
|
+
} else {
|
|
33
|
+
OPERATORS.eq(this, qualifiedKey, value)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
}
|
|
@@ -1,13 +1,71 @@
|
|
|
1
1
|
import { applyFilter } from '../filters/apply-filter.js'
|
|
2
|
-
import { applyFilterSnakeCase } from '../filters/apply-filter-snake-case.js'
|
|
3
2
|
import { applyOrderBy } from '../modifiers/apply-order-by.js'
|
|
4
3
|
import { applyPagination } from '../modifiers/apply-pagination.js'
|
|
4
|
+
import { applyFilterSnakeCase } from '../filters/apply-filter-snake-case.js'
|
|
5
5
|
import { normalizeNumberOrDefault } from '../../core/normalize-premitives-types-or-default.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Executes paginated query.
|
|
8
|
+
* Executes a paginated SQL query using a Knex query builder.
|
|
9
|
+
*
|
|
10
|
+
* This helper clones the provided base query twice:
|
|
11
|
+
* one query is used to fetch the paginated list of rows,
|
|
12
|
+
* and the second query is used to calculate the total count
|
|
13
|
+
* of records matching the same filters.
|
|
14
|
+
*
|
|
15
|
+
* Filters are applied consistently to both queries.
|
|
16
|
+
* Ordering and pagination are applied only to the list query.
|
|
17
|
+
*
|
|
18
|
+
* The function supports both camelCase and snake_case filters,
|
|
19
|
+
* controlled by the `snakeCase` flag.
|
|
9
20
|
*
|
|
10
21
|
* @async
|
|
22
|
+
* @param {Object} params
|
|
23
|
+
* @param {import('knex').Knex.QueryBuilder} params.baseQuery
|
|
24
|
+
* A prepared Knex query builder representing the base query.
|
|
25
|
+
* It should not include pagination or ordering.
|
|
26
|
+
*
|
|
27
|
+
* @param {Object} [params.filter={}]
|
|
28
|
+
* An object describing filter conditions to apply.
|
|
29
|
+
* The structure is expected to be compatible with `applyFilter`
|
|
30
|
+
* or `applyFilterSnakeCase`.
|
|
31
|
+
*
|
|
32
|
+
* @param {boolean} [params.snakeCase=true]
|
|
33
|
+
* When true, applies filters assuming database columns
|
|
34
|
+
* are in snake_case. When false, camelCase is assumed.
|
|
35
|
+
*
|
|
36
|
+
* @param {Object|Array} [params.orderBy]
|
|
37
|
+
* Ordering definition passed directly to `applyOrderBy`.
|
|
38
|
+
* Can be a single order definition or an array of them.
|
|
39
|
+
*
|
|
40
|
+
* @param {number} [params.page=1]
|
|
41
|
+
* The current page number. Pages are 1-based.
|
|
42
|
+
*
|
|
43
|
+
* @param {number} [params.limit=10]
|
|
44
|
+
* The maximum number of rows per page.
|
|
45
|
+
*
|
|
46
|
+
* @param {Function} [params.mapRow]
|
|
47
|
+
* Optional mapping function applied to each returned row.
|
|
48
|
+
* Useful for transforming DB rows into domain entities.
|
|
49
|
+
*
|
|
50
|
+
* @returns {Promise<Object>} Pagination result object
|
|
51
|
+
* @returns {number} return.page
|
|
52
|
+
* The current page number.
|
|
53
|
+
*
|
|
54
|
+
* @returns {number} return.pages
|
|
55
|
+
* Total number of available pages.
|
|
56
|
+
*
|
|
57
|
+
* @returns {number} return.totalCount
|
|
58
|
+
* Total number of records matching the filters.
|
|
59
|
+
*
|
|
60
|
+
* @returns {boolean} return.hasPrevious
|
|
61
|
+
* Indicates whether a previous page exists.
|
|
62
|
+
*
|
|
63
|
+
* @returns {boolean} return.hasNext
|
|
64
|
+
* Indicates whether a next page exists.
|
|
65
|
+
*
|
|
66
|
+
* @returns {Array<Object>} return.list
|
|
67
|
+
* The list of records for the current page.
|
|
68
|
+
* Rows are mapped using `mapRow` if provided.
|
|
11
69
|
*/
|
|
12
70
|
export async function sqlPaginate({
|
|
13
71
|
mapRow,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { describe, it, beforeAll, afterAll, beforeEach, expect } from 'vitest'
|
|
3
|
+
import knex from 'knex'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
startPostgres,
|
|
7
|
+
stopPostgres,
|
|
8
|
+
buildPostgresUri,
|
|
9
|
+
} from '../../../src/postgresql/start-stop-postgres-docker.js'
|
|
10
|
+
|
|
11
|
+
import { applyOrFilter } from '../../../src/postgresql/filters/apply-or-filter.js'
|
|
12
|
+
|
|
13
|
+
const PG_OPTIONS = {
|
|
14
|
+
port: 5445,
|
|
15
|
+
db: 'testdb',
|
|
16
|
+
user: 'testuser',
|
|
17
|
+
pass: 'testpass',
|
|
18
|
+
containerName: 'postgres-or-filter-test-5445',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DATABASE_URI = buildPostgresUri(PG_OPTIONS)
|
|
22
|
+
|
|
23
|
+
let db
|
|
24
|
+
|
|
25
|
+
beforeAll(async () => {
|
|
26
|
+
startPostgres(PG_OPTIONS)
|
|
27
|
+
|
|
28
|
+
db = knex({
|
|
29
|
+
client: 'pg',
|
|
30
|
+
connection: DATABASE_URI,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
await db.schema.createTable('tenants', (table) => {
|
|
34
|
+
table.uuid('id').primary()
|
|
35
|
+
table.string('name').notNullable()
|
|
36
|
+
table.string('type').notNullable()
|
|
37
|
+
table.integer('age').notNullable()
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
afterAll(async () => {
|
|
42
|
+
if (db) {
|
|
43
|
+
await db.destroy()
|
|
44
|
+
}
|
|
45
|
+
stopPostgres(PG_OPTIONS.containerName)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
beforeEach(async () => {
|
|
49
|
+
await db('tenants').truncate()
|
|
50
|
+
|
|
51
|
+
await db('tenants').insert([
|
|
52
|
+
{
|
|
53
|
+
id: '00000000-0000-0000-0000-000000000001',
|
|
54
|
+
name: 'Tenant A',
|
|
55
|
+
type: 'business',
|
|
56
|
+
age: 2,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: '00000000-0000-0000-0000-000000000002',
|
|
60
|
+
name: 'Tenant B',
|
|
61
|
+
type: 'business',
|
|
62
|
+
age: 5,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: '00000000-0000-0000-0000-000000000003',
|
|
66
|
+
name: 'Tenant C',
|
|
67
|
+
type: 'cpa',
|
|
68
|
+
age: 7,
|
|
69
|
+
},
|
|
70
|
+
])
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe('applyOrFilter (integration)', () => {
|
|
74
|
+
it('returns records matching OR conditions', async () => {
|
|
75
|
+
const query = db('tenants')
|
|
76
|
+
|
|
77
|
+
applyOrFilter(query, [{ type: 'cpa' }, { age: { gte: 5 } }], 'tenants')
|
|
78
|
+
|
|
79
|
+
const rows = await query.select('*')
|
|
80
|
+
|
|
81
|
+
const names = rows.map((r) => r.name).sort()
|
|
82
|
+
expect(names).toEqual(['Tenant B', 'Tenant C'])
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('returns empty result when no OR conditions match', async () => {
|
|
86
|
+
const query = db('tenants')
|
|
87
|
+
|
|
88
|
+
applyOrFilter(query, [{ age: { gt: 100 } }], 'tenants')
|
|
89
|
+
|
|
90
|
+
const rows = await query.select('*')
|
|
91
|
+
expect(rows).toEqual([])
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('supports array IN operator inside OR', async () => {
|
|
95
|
+
const query = db('tenants')
|
|
96
|
+
|
|
97
|
+
applyOrFilter(query, [{ name: ['Tenant A', 'Tenant C'] }], 'tenants')
|
|
98
|
+
|
|
99
|
+
const rows = await query.select('*')
|
|
100
|
+
const names = rows.map((r) => r.name).sort()
|
|
101
|
+
|
|
102
|
+
expect(names).toEqual(['Tenant A', 'Tenant C'])
|
|
103
|
+
})
|
|
104
|
+
})
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { OPERATORS } from '../../../src/postgresql/filters/operators.js'
|
|
5
|
+
import { applyOrFilter } from '../../../src/postgresql/filters/apply-or-filter.js'
|
|
6
|
+
import * as applyFilterObjectModule from '../../../src/postgresql/filters/apply-filter-object.js'
|
|
7
|
+
|
|
8
|
+
describe('applyOrFilter (unit)', () => {
|
|
9
|
+
it('applies eq operator for primitive values', () => {
|
|
10
|
+
const query = {
|
|
11
|
+
where: vi.fn(function (cb) {
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
cb.call(this)
|
|
14
|
+
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
return this
|
|
17
|
+
}),
|
|
18
|
+
orWhere: vi.fn(function (cb) {
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
cb.call(this)
|
|
21
|
+
|
|
22
|
+
// @ts-ignore
|
|
23
|
+
return this
|
|
24
|
+
}),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// @ts-ignore
|
|
28
|
+
const eqSpy = vi.spyOn(OPERATORS, 'eq').mockImplementation(() => query)
|
|
29
|
+
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
applyOrFilter(query, [{ name: 'A' }, { name: 'B' }], null)
|
|
32
|
+
|
|
33
|
+
expect(eqSpy).toHaveBeenCalledTimes(2)
|
|
34
|
+
expect(eqSpy).toHaveBeenCalledWith(expect.anything(), 'name', 'A')
|
|
35
|
+
expect(eqSpy).toHaveBeenCalledWith(expect.anything(), 'name', 'B')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('applies IN operator for array values', () => {
|
|
39
|
+
const query = {
|
|
40
|
+
where: vi.fn(function (cb) {
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
cb.call(this)
|
|
43
|
+
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
return this
|
|
46
|
+
}),
|
|
47
|
+
orWhere: vi.fn(function (cb) {
|
|
48
|
+
// @ts-ignore
|
|
49
|
+
cb.call(this)
|
|
50
|
+
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
return this
|
|
53
|
+
}),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
const inSpy = vi.spyOn(OPERATORS, 'in').mockImplementation(() => query)
|
|
58
|
+
|
|
59
|
+
// @ts-ignore
|
|
60
|
+
applyOrFilter(query, [{ id: [1, 2, 3] }], null)
|
|
61
|
+
|
|
62
|
+
expect(inSpy).toHaveBeenCalledWith(expect.anything(), 'id', [1, 2, 3])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('delegates object values to applyFilterObject', () => {
|
|
66
|
+
const query = {
|
|
67
|
+
where: vi.fn(function (cb) {
|
|
68
|
+
cb.call(this)
|
|
69
|
+
return this
|
|
70
|
+
}),
|
|
71
|
+
orWhere: vi.fn(function (cb) {
|
|
72
|
+
cb.call(this)
|
|
73
|
+
return this
|
|
74
|
+
}),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const spy = vi
|
|
78
|
+
.spyOn(applyFilterObjectModule, 'applyFilterObject')
|
|
79
|
+
.mockImplementation(() => query)
|
|
80
|
+
|
|
81
|
+
applyOrFilter(query, [{ age: { gte: 18 } }], null)
|
|
82
|
+
|
|
83
|
+
expect(spy).toHaveBeenCalledWith(expect.anything(), 'age', { gte: 18 })
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('applies snake_case when enabled', () => {
|
|
87
|
+
const query = {
|
|
88
|
+
where: vi.fn(function (cb) {
|
|
89
|
+
// @ts-ignore
|
|
90
|
+
cb.call(this)
|
|
91
|
+
|
|
92
|
+
// @ts-ignore
|
|
93
|
+
return this
|
|
94
|
+
}),
|
|
95
|
+
orWhere: vi.fn(function (cb) {
|
|
96
|
+
// @ts-ignore
|
|
97
|
+
cb.call(this)
|
|
98
|
+
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
return this
|
|
101
|
+
}),
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// @ts-ignore
|
|
105
|
+
const eqSpy = vi.spyOn(OPERATORS, 'eq').mockImplementation(() => query)
|
|
106
|
+
|
|
107
|
+
// @ts-ignore
|
|
108
|
+
applyOrFilter(query, [{ createdAt: 1 }], 't', true)
|
|
109
|
+
|
|
110
|
+
expect(eqSpy).toHaveBeenCalledWith(expect.anything(), 't.created_at', 1)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
} from '../../src/postgresql/start-stop-postgres-docker.js'
|
|
10
10
|
|
|
11
11
|
import { sqlPaginate } from '../../src/postgresql/pagination/paginate.js'
|
|
12
|
-
import { sub } from 'date-fns'
|
|
13
12
|
|
|
14
13
|
const PG_OPTIONS = {
|
|
15
14
|
port: 5442,
|
|
@@ -123,7 +122,7 @@ describe('paginate integration', () => {
|
|
|
123
122
|
})
|
|
124
123
|
|
|
125
124
|
it('applies filters correctly', async () => {
|
|
126
|
-
const minDate =
|
|
125
|
+
const minDate = new Date('2024-01-01')
|
|
127
126
|
const result = await sqlPaginate({
|
|
128
127
|
baseQuery: db('tenants'),
|
|
129
128
|
filter: {
|
|
@@ -1,7 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Executes paginated query.
|
|
2
|
+
* Executes a paginated SQL query using a Knex query builder.
|
|
3
|
+
*
|
|
4
|
+
* This helper clones the provided base query twice:
|
|
5
|
+
* one query is used to fetch the paginated list of rows,
|
|
6
|
+
* and the second query is used to calculate the total count
|
|
7
|
+
* of records matching the same filters.
|
|
8
|
+
*
|
|
9
|
+
* Filters are applied consistently to both queries.
|
|
10
|
+
* Ordering and pagination are applied only to the list query.
|
|
11
|
+
*
|
|
12
|
+
* The function supports both camelCase and snake_case filters,
|
|
13
|
+
* controlled by the `snakeCase` flag.
|
|
3
14
|
*
|
|
4
15
|
* @async
|
|
16
|
+
* @param {Object} params
|
|
17
|
+
* @param {import('knex').Knex.QueryBuilder} params.baseQuery
|
|
18
|
+
* A prepared Knex query builder representing the base query.
|
|
19
|
+
* It should not include pagination or ordering.
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} [params.filter={}]
|
|
22
|
+
* An object describing filter conditions to apply.
|
|
23
|
+
* The structure is expected to be compatible with `applyFilter`
|
|
24
|
+
* or `applyFilterSnakeCase`.
|
|
25
|
+
*
|
|
26
|
+
* @param {boolean} [params.snakeCase=true]
|
|
27
|
+
* When true, applies filters assuming database columns
|
|
28
|
+
* are in snake_case. When false, camelCase is assumed.
|
|
29
|
+
*
|
|
30
|
+
* @param {Object|Array} [params.orderBy]
|
|
31
|
+
* Ordering definition passed directly to `applyOrderBy`.
|
|
32
|
+
* Can be a single order definition or an array of them.
|
|
33
|
+
*
|
|
34
|
+
* @param {number} [params.page=1]
|
|
35
|
+
* The current page number. Pages are 1-based.
|
|
36
|
+
*
|
|
37
|
+
* @param {number} [params.limit=10]
|
|
38
|
+
* The maximum number of rows per page.
|
|
39
|
+
*
|
|
40
|
+
* @param {Function} [params.mapRow]
|
|
41
|
+
* Optional mapping function applied to each returned row.
|
|
42
|
+
* Useful for transforming DB rows into domain entities.
|
|
43
|
+
*
|
|
44
|
+
* @returns {Promise<Object>} Pagination result object
|
|
45
|
+
* @returns {number} return.page
|
|
46
|
+
* The current page number.
|
|
47
|
+
*
|
|
48
|
+
* @returns {number} return.pages
|
|
49
|
+
* Total number of available pages.
|
|
50
|
+
*
|
|
51
|
+
* @returns {number} return.totalCount
|
|
52
|
+
* Total number of records matching the filters.
|
|
53
|
+
*
|
|
54
|
+
* @returns {boolean} return.hasPrevious
|
|
55
|
+
* Indicates whether a previous page exists.
|
|
56
|
+
*
|
|
57
|
+
* @returns {boolean} return.hasNext
|
|
58
|
+
* Indicates whether a next page exists.
|
|
59
|
+
*
|
|
60
|
+
* @returns {Array<Object>} return.list
|
|
61
|
+
* The list of records for the current page.
|
|
62
|
+
* Rows are mapped using `mapRow` if provided.
|
|
5
63
|
*/
|
|
6
64
|
export function sqlPaginate({
|
|
7
65
|
mapRow,
|
|
@@ -12,18 +70,11 @@ export function sqlPaginate({
|
|
|
12
70
|
filter,
|
|
13
71
|
snakeCase,
|
|
14
72
|
}: {
|
|
15
|
-
|
|
16
|
-
|
|
73
|
+
baseQuery: import('knex').Knex.QueryBuilder
|
|
74
|
+
filter?: any
|
|
75
|
+
snakeCase?: boolean
|
|
76
|
+
orderBy?: any | any[]
|
|
17
77
|
page?: number
|
|
18
|
-
baseQuery: any
|
|
19
78
|
limit?: number
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}): Promise<{
|
|
23
|
-
totalCount: number
|
|
24
|
-
totalPages: number
|
|
25
|
-
currentPage: number
|
|
26
|
-
hasPrevious: boolean
|
|
27
|
-
hasNext: boolean
|
|
28
|
-
list: any
|
|
29
|
-
}>
|
|
79
|
+
mapRow?: Function
|
|
80
|
+
}): Promise<any>
|