@autofleet/sadot 0.6.7 → 0.6.8-beta.1
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/dist/scopes/filter.d.ts +3 -7
- package/dist/scopes/filter.js +50 -23
- package/dist/tests/helpers/database-config.d.ts +4 -1
- package/dist/tests/helpers/database-config.js +1 -1
- package/dist/utils/helpers/index.d.ts +1 -0
- package/dist/utils/helpers/index.js +6 -1
- package/package.json +4 -3
- package/src/scopes/filter.ts +50 -26
- package/src/tests/helpers/database-config.ts +1 -1
- package/src/utils/helpers/index.ts +5 -0
package/dist/scopes/filter.d.ts
CHANGED
|
@@ -15,23 +15,19 @@ export type CustomFieldSort = {
|
|
|
15
15
|
};
|
|
16
16
|
export type CustomFieldFilterOptions = {
|
|
17
17
|
where?: WhereOptions;
|
|
18
|
+
replacements?: Record<string, string>;
|
|
18
19
|
};
|
|
19
|
-
/**
|
|
20
|
-
* A Sequelize scope for filtering models by custom fields.
|
|
21
|
-
* This scope builds a WHERE clause to be applied on the main query.
|
|
22
|
-
*
|
|
23
|
-
* @param {string} name - The model type name used to join custom_field_definitions.
|
|
24
|
-
* @returns {Function} - A function that takes conditions and returns the Sequelize options object.
|
|
25
|
-
*/
|
|
26
20
|
export declare const customFieldsFilterScope: (name: string) => (conditions: Record<string, ConditionValue>) => CustomFieldFilterOptions;
|
|
27
21
|
export declare const scopeName = "filterByCustomFields";
|
|
28
22
|
export declare const customFieldsSortScope: (name: string) => (sort: CustomFieldSort[]) => {
|
|
29
23
|
attributes?: undefined;
|
|
30
24
|
order?: undefined;
|
|
25
|
+
replacements?: undefined;
|
|
31
26
|
} | {
|
|
32
27
|
attributes: {
|
|
33
28
|
include: (string | import("sequelize/types/utils").Literal)[][];
|
|
34
29
|
};
|
|
35
30
|
order: import("sequelize/types/utils").Literal[];
|
|
31
|
+
replacements: Record<string, string>;
|
|
36
32
|
};
|
|
37
33
|
export {};
|
package/dist/scopes/filter.js
CHANGED
|
@@ -1,49 +1,68 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.customFieldsSortScope = exports.scopeName = exports.customFieldsFilterScope = void 0;
|
|
4
7
|
/* eslint-disable import/prefer-default-export */
|
|
5
8
|
const sequelize_1 = require("sequelize");
|
|
6
9
|
const sequelize_typescript_1 = require("sequelize-typescript");
|
|
7
10
|
const common_types_1 = require("@autofleet/common-types");
|
|
11
|
+
const moment_1 = __importDefault(require("moment"));
|
|
12
|
+
const helpers_1 = require("../utils/helpers");
|
|
8
13
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
|
|
9
14
|
const castIfNeeded = (conditionValue) => {
|
|
10
|
-
if (
|
|
15
|
+
if (moment_1.default.isDate(conditionValue)) {
|
|
11
16
|
return '::timestamp';
|
|
12
17
|
}
|
|
18
|
+
if (!Number.isNaN(Number(conditionValue))) {
|
|
19
|
+
return '::numeric';
|
|
20
|
+
}
|
|
13
21
|
return '';
|
|
14
22
|
};
|
|
15
23
|
const AND_DELIMETER = ' AND ';
|
|
16
|
-
/**
|
|
17
|
-
* A Sequelize scope for filtering models by custom fields.
|
|
18
|
-
* This scope builds a WHERE clause to be applied on the main query.
|
|
19
|
-
*
|
|
20
|
-
* @param {string} name - The model type name used to join custom_field_definitions.
|
|
21
|
-
* @returns {Function} - A function that takes conditions and returns the Sequelize options object.
|
|
22
|
-
*/
|
|
23
24
|
const customFieldsFilterScope = (name) => (conditions) => {
|
|
24
25
|
if (!conditions || Object.keys(conditions).length === 0) {
|
|
25
26
|
return {};
|
|
26
27
|
}
|
|
28
|
+
const randomStr = (0, helpers_1.generateRandomString)();
|
|
29
|
+
const replacementMapPrefix = `customFieldsFilterScopeConditionMap_${randomStr}`;
|
|
30
|
+
const replacements = {};
|
|
31
|
+
replacements[`customFieldsFilterScopeConditionName_${randomStr}`] = `${name}`;
|
|
27
32
|
// Build the WHERE clause for custom field filtering
|
|
28
33
|
const conditionsStrings = Object.entries(conditions)
|
|
29
|
-
.map(([key, condition]) => {
|
|
34
|
+
.map(([key, condition], index) => {
|
|
35
|
+
const replacemetKey = `${replacementMapPrefix}Key${index}`;
|
|
36
|
+
replacements[replacemetKey] = `${key}`;
|
|
30
37
|
if (Array.isArray(condition)) {
|
|
31
38
|
if (condition.length === 0) {
|
|
32
39
|
// if empty array, the condition is ignored
|
|
33
40
|
return false;
|
|
34
41
|
}
|
|
35
42
|
if (typeof condition[0] === 'string') {
|
|
36
|
-
const values = condition.map((v) =>
|
|
37
|
-
|
|
43
|
+
const values = condition.map((v) => {
|
|
44
|
+
const valRandom = (0, helpers_1.generateRandomString)();
|
|
45
|
+
replacements[`${valRandom}`] = `${v}`;
|
|
46
|
+
return ` :${valRandom} `;
|
|
47
|
+
}).join(',');
|
|
48
|
+
return `(custom_fields->> :${replacemetKey} ) IN ( ${values} )`;
|
|
38
49
|
}
|
|
39
50
|
return condition
|
|
40
|
-
.map((c) =>
|
|
51
|
+
.map((c, cIndex) => {
|
|
52
|
+
const valRep = `${replacementMapPrefix}Values${index}${cIndex}`;
|
|
53
|
+
replacements[valRep] = `${c.value}`;
|
|
54
|
+
return `(custom_fields->> :${replacemetKey} )${castIfNeeded(c.value)} ${c.operator} :${valRep}`;
|
|
55
|
+
}).join(AND_DELIMETER);
|
|
41
56
|
}
|
|
42
57
|
if (typeof condition === 'string') {
|
|
43
|
-
|
|
58
|
+
const conditionRep = `${replacementMapPrefix}Condition${index}`;
|
|
59
|
+
replacements[conditionRep] = `${condition}`;
|
|
60
|
+
return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition)} = :${conditionRep}`;
|
|
44
61
|
}
|
|
45
62
|
if (condition?.operator) {
|
|
46
|
-
|
|
63
|
+
const valueRep = `${replacementMapPrefix}Values${index}`;
|
|
64
|
+
replacements[valueRep] = `${condition.value}`;
|
|
65
|
+
return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition.value)} ${condition.operator} :${valueRep}`;
|
|
47
66
|
}
|
|
48
67
|
return false;
|
|
49
68
|
})
|
|
@@ -56,15 +75,16 @@ const customFieldsFilterScope = (name) => (conditions) => {
|
|
|
56
75
|
+ 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
|
|
57
76
|
+ 'FROM custom_field_values AS cv '
|
|
58
77
|
+ 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
|
|
59
|
-
+ `AND cd.model_type =
|
|
78
|
+
+ `AND cd.model_type = :${`customFieldsFilterScopeConditionName_${randomStr}`} `
|
|
60
79
|
+ 'GROUP BY cv.model_id'
|
|
61
|
-
+ ') AS CustomFieldAggregation WHERE '}${customFieldConditions}`;
|
|
80
|
+
+ ') AS CustomFieldAggregation WHERE '} ${customFieldConditions}`;
|
|
62
81
|
return {
|
|
63
82
|
where: {
|
|
64
83
|
id: {
|
|
65
84
|
[sequelize_1.Op.in]: sequelize_typescript_1.Sequelize.literal(`(${subQuery})`),
|
|
66
85
|
},
|
|
67
86
|
},
|
|
87
|
+
replacements,
|
|
68
88
|
};
|
|
69
89
|
};
|
|
70
90
|
exports.customFieldsFilterScope = customFieldsFilterScope;
|
|
@@ -73,26 +93,33 @@ const customFieldsSortScope = (name) => (sort) => {
|
|
|
73
93
|
if (!sort || sort.length === 0) {
|
|
74
94
|
return {};
|
|
75
95
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
96
|
+
const randomStr = (0, helpers_1.generateRandomString)();
|
|
97
|
+
const replacements = {};
|
|
98
|
+
const includes = Object.entries(sort).map(([key], index) => {
|
|
99
|
+
const keyReplacement = `key_${randomStr}_${index}`;
|
|
100
|
+
replacements[`keyCustomFields_${randomStr}_${index}`] = `customFields_${key}`;
|
|
101
|
+
replacements[keyReplacement] = `${key}`;
|
|
102
|
+
return ([
|
|
103
|
+
sequelize_typescript_1.Sequelize.literal(`(
|
|
78
104
|
SELECT value
|
|
79
105
|
FROM (SELECT cv.model_id, cv.value
|
|
80
106
|
FROM custom_field_values AS cv INNER JOIN custom_field_definitions AS cd
|
|
81
107
|
ON cv.custom_field_definition_id = cd.id
|
|
82
108
|
AND cd.model_type = '${name}'
|
|
83
109
|
WHERE cv.model_id = "${name}"."id"
|
|
84
|
-
AND cd.name =
|
|
110
|
+
AND cd.name = :${keyReplacement}
|
|
85
111
|
) AS CustomFieldAggregation
|
|
86
112
|
)
|
|
87
|
-
`),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const orders = Object.entries(sort).map(([
|
|
113
|
+
`), randomStr,
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
const orders = Object.entries(sort).map(([, value]) => sequelize_typescript_1.Sequelize.literal(`"${randomStr}" ${value}`));
|
|
91
117
|
return {
|
|
92
118
|
attributes: {
|
|
93
119
|
include: includes,
|
|
94
120
|
},
|
|
95
121
|
order: orders,
|
|
122
|
+
replacements,
|
|
96
123
|
};
|
|
97
124
|
};
|
|
98
125
|
exports.customFieldsSortScope = customFieldsSortScope;
|
|
@@ -21,5 +21,6 @@ interface CustomFieldsSearchPayload {
|
|
|
21
21
|
where: WhereOptions;
|
|
22
22
|
replacements: BindOrReplacements;
|
|
23
23
|
}
|
|
24
|
+
export declare const generateRandomString: (length?: number) => string;
|
|
24
25
|
export declare const generateCustomFieldSearchQueryPayload: (searchTerm: string, model: ModelStatic, entityId: string, customFieldsTypesToExclude?: CustomFieldDefinitionType[]) => CustomFieldsSearchPayload;
|
|
25
26
|
export {};
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateCustomFieldSearchQueryPayload = void 0;
|
|
3
|
+
exports.generateCustomFieldSearchQueryPayload = exports.generateRandomString = void 0;
|
|
4
4
|
/* eslint-disable import/prefer-default-export */
|
|
5
5
|
const sequelize_1 = require("sequelize");
|
|
6
6
|
const sequelize_typescript_1 = require("sequelize-typescript");
|
|
7
7
|
const constants_1 = require("../constants");
|
|
8
|
+
const generateRandomString = (length = 5) => {
|
|
9
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
10
|
+
return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
|
|
11
|
+
};
|
|
12
|
+
exports.generateRandomString = generateRandomString;
|
|
8
13
|
const generateCustomFieldSearchQueryPayload = (searchTerm, model, entityId, customFieldsTypesToExclude = [
|
|
9
14
|
constants_1.CustomFieldDefinitionType.DATETIME,
|
|
10
15
|
constants_1.CustomFieldDefinitionType.DATE,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8-beta.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"@autofleet/logger": "^2.0.5",
|
|
37
37
|
"express": "^4.18.2",
|
|
38
38
|
"joi": "^17.7.0",
|
|
39
|
+
"moment": "^2.30.1",
|
|
39
40
|
"pg": "^8.10.0",
|
|
40
41
|
"reflect-metadata": "^0.1.13",
|
|
41
42
|
"sequelize": "^6.31.1",
|
|
@@ -52,12 +53,12 @@
|
|
|
52
53
|
"eslint-config-airbnb-typescript": "^12.0.0",
|
|
53
54
|
"eslint-plugin-import": "^2.22.1",
|
|
54
55
|
"jest": "^29.7.0",
|
|
55
|
-
"ts-jest": "^29.1.2",
|
|
56
56
|
"npm-watch": "^0.11.0",
|
|
57
|
+
"ts-jest": "^29.1.2",
|
|
57
58
|
"ts-node": "^8.6.2",
|
|
58
59
|
"typescript": "^5.3.3",
|
|
59
60
|
"typescript-eslint": "^0.0.1-alpha.0"
|
|
60
61
|
},
|
|
61
62
|
"author": "Autofleet",
|
|
62
63
|
"license": "ISC"
|
|
63
|
-
}
|
|
64
|
+
}
|
package/src/scopes/filter.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { Op, type WhereOptions } from 'sequelize';
|
|
3
3
|
import { Sequelize } from 'sequelize-typescript';
|
|
4
4
|
import { customFields } from '@autofleet/common-types';
|
|
5
|
+
import moment from 'moment';
|
|
6
|
+
import { generateRandomString } from '../utils/helpers';
|
|
5
7
|
|
|
6
8
|
const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
|
|
7
9
|
|
|
@@ -22,51 +24,68 @@ export type CustomFieldSort = {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
export type CustomFieldFilterOptions = {
|
|
25
|
-
|
|
27
|
+
where?: WhereOptions;
|
|
28
|
+
replacements?: Record<string, string>;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
const castIfNeeded = (conditionValue: string): string => {
|
|
29
|
-
if (
|
|
32
|
+
if (moment.isDate(conditionValue)) {
|
|
30
33
|
return '::timestamp';
|
|
34
|
+
} if (!Number.isNaN(Number(conditionValue))) {
|
|
35
|
+
return '::numeric';
|
|
31
36
|
}
|
|
32
37
|
return '';
|
|
33
38
|
};
|
|
34
39
|
const AND_DELIMETER = ' AND ';
|
|
35
|
-
|
|
36
|
-
* A Sequelize scope for filtering models by custom fields.
|
|
37
|
-
* This scope builds a WHERE clause to be applied on the main query.
|
|
38
|
-
*
|
|
39
|
-
* @param {string} name - The model type name used to join custom_field_definitions.
|
|
40
|
-
* @returns {Function} - A function that takes conditions and returns the Sequelize options object.
|
|
41
|
-
*/
|
|
40
|
+
|
|
42
41
|
export const customFieldsFilterScope = (
|
|
43
42
|
name: string,
|
|
44
43
|
) => (conditions: Record<string, ConditionValue>): CustomFieldFilterOptions => {
|
|
45
44
|
if (!conditions || Object.keys(conditions).length === 0) {
|
|
46
45
|
return {};
|
|
47
46
|
}
|
|
47
|
+
const randomStr = generateRandomString();
|
|
48
|
+
const replacementMapPrefix = `customFieldsFilterScopeConditionMap_${randomStr}`;
|
|
49
|
+
const replacements: Record<string, string> = {};
|
|
50
|
+
replacements[`customFieldsFilterScopeConditionName_${randomStr}`] = `${name}`;
|
|
51
|
+
|
|
48
52
|
// Build the WHERE clause for custom field filtering
|
|
49
53
|
const conditionsStrings = Object.entries(conditions)
|
|
50
54
|
.map(
|
|
51
|
-
([key, condition]) => {
|
|
55
|
+
([key, condition], index) => {
|
|
56
|
+
const replacemetKey = `${replacementMapPrefix}Key${index}`;
|
|
57
|
+
replacements[replacemetKey] = `${key}`;
|
|
52
58
|
if (Array.isArray(condition)) {
|
|
53
59
|
if (condition.length === 0) {
|
|
54
60
|
// if empty array, the condition is ignored
|
|
55
61
|
return false;
|
|
56
62
|
}
|
|
57
63
|
if (typeof condition[0] === 'string') {
|
|
58
|
-
const values = condition.map((v) =>
|
|
59
|
-
|
|
64
|
+
const values = condition.map((v) => {
|
|
65
|
+
const valRandom = generateRandomString();
|
|
66
|
+
replacements[`${valRandom}`] = `${v}`;
|
|
67
|
+
return ` :${valRandom} `;
|
|
68
|
+
}).join(',');
|
|
69
|
+
return `(custom_fields->> :${replacemetKey} ) IN ( ${values} )`;
|
|
60
70
|
}
|
|
61
71
|
return condition
|
|
62
|
-
.map((c) =>
|
|
72
|
+
.map((c, cIndex) => {
|
|
73
|
+
const valRep = `${replacementMapPrefix}Values${index}${cIndex}`;
|
|
74
|
+
replacements[valRep] = `${c.value}`;
|
|
75
|
+
return `(custom_fields->> :${replacemetKey} )${castIfNeeded(c.value)} ${c.operator} :${valRep}`;
|
|
76
|
+
}).join(AND_DELIMETER);
|
|
63
77
|
}
|
|
64
78
|
if (typeof condition === 'string') {
|
|
65
|
-
|
|
79
|
+
const conditionRep = `${replacementMapPrefix}Condition${index}`;
|
|
80
|
+
replacements[conditionRep] = `${condition}`;
|
|
81
|
+
return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition)} = :${conditionRep}`;
|
|
66
82
|
}
|
|
67
83
|
if (condition?.operator) {
|
|
68
|
-
|
|
84
|
+
const valueRep = `${replacementMapPrefix}Values${index}`;
|
|
85
|
+
replacements[valueRep] = `${condition.value}`;
|
|
86
|
+
return `(custom_fields->> :${replacemetKey} ) ${castIfNeeded(condition.value)} ${condition.operator} :${valueRep}`;
|
|
69
87
|
}
|
|
88
|
+
|
|
70
89
|
return false;
|
|
71
90
|
},
|
|
72
91
|
)
|
|
@@ -75,21 +94,20 @@ export const customFieldsFilterScope = (
|
|
|
75
94
|
return {};
|
|
76
95
|
}
|
|
77
96
|
const customFieldConditions = conditionsStrings.join(AND_DELIMETER);
|
|
78
|
-
|
|
79
97
|
const subQuery = `${'SELECT model_id FROM ('
|
|
80
98
|
+ 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
|
|
81
99
|
+ 'FROM custom_field_values AS cv '
|
|
82
100
|
+ 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
|
|
83
|
-
+ `AND cd.model_type =
|
|
101
|
+
+ `AND cd.model_type = :${`customFieldsFilterScopeConditionName_${randomStr}`} `
|
|
84
102
|
+ 'GROUP BY cv.model_id'
|
|
85
|
-
+ ') AS CustomFieldAggregation WHERE '}${customFieldConditions}`;
|
|
86
|
-
|
|
103
|
+
+ ') AS CustomFieldAggregation WHERE '} ${customFieldConditions}`;
|
|
87
104
|
return {
|
|
88
105
|
where: {
|
|
89
106
|
id: {
|
|
90
107
|
[Op.in]: Sequelize.literal(`(${subQuery})`),
|
|
91
108
|
},
|
|
92
109
|
},
|
|
110
|
+
replacements,
|
|
93
111
|
};
|
|
94
112
|
};
|
|
95
113
|
|
|
@@ -101,8 +119,13 @@ export const customFieldsSortScope = (
|
|
|
101
119
|
if (!sort || sort.length === 0) {
|
|
102
120
|
return {};
|
|
103
121
|
}
|
|
104
|
-
const
|
|
105
|
-
|
|
122
|
+
const randomStr = generateRandomString();
|
|
123
|
+
const replacements: Record<string, string> = {};
|
|
124
|
+
const includes = Object.entries(sort).map(([key], index) => {
|
|
125
|
+
const keyReplacement = `key_${randomStr}_${index}`;
|
|
126
|
+
replacements[`keyCustomFields_${randomStr}_${index}`] = `customFields_${key}`;
|
|
127
|
+
replacements[keyReplacement] = `${key}`;
|
|
128
|
+
return ([
|
|
106
129
|
Sequelize.literal(`(
|
|
107
130
|
SELECT value
|
|
108
131
|
FROM (SELECT cv.model_id, cv.value
|
|
@@ -110,18 +133,19 @@ export const customFieldsSortScope = (
|
|
|
110
133
|
ON cv.custom_field_definition_id = cd.id
|
|
111
134
|
AND cd.model_type = '${name}'
|
|
112
135
|
WHERE cv.model_id = "${name}"."id"
|
|
113
|
-
AND cd.name =
|
|
136
|
+
AND cd.name = :${keyReplacement}
|
|
114
137
|
) AS CustomFieldAggregation
|
|
115
138
|
)
|
|
116
|
-
`),
|
|
117
|
-
|
|
118
|
-
|
|
139
|
+
`), randomStr,
|
|
140
|
+
]);
|
|
141
|
+
});
|
|
119
142
|
|
|
120
|
-
const orders = Object.entries(sort).map(([
|
|
143
|
+
const orders = Object.entries(sort).map(([, value]) => Sequelize.literal(`"${randomStr}" ${value}`));
|
|
121
144
|
return {
|
|
122
145
|
attributes: {
|
|
123
146
|
include: includes,
|
|
124
147
|
},
|
|
125
148
|
order: orders,
|
|
149
|
+
replacements,
|
|
126
150
|
};
|
|
127
151
|
};
|
|
@@ -25,6 +25,11 @@ interface CustomFieldsSearchPayload {
|
|
|
25
25
|
replacements: BindOrReplacements;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export const generateRandomString = (length = 5): string => {
|
|
29
|
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
30
|
+
return Array.from({ length }, () => characters.charAt(Math.floor(Math.random() * characters.length))).join('');
|
|
31
|
+
};
|
|
32
|
+
|
|
28
33
|
export const generateCustomFieldSearchQueryPayload = (
|
|
29
34
|
searchTerm: string,
|
|
30
35
|
model: ModelStatic,
|