@autofleet/sadot 0.8.1-beta-e7a88a64.0 → 0.8.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.
@@ -28,7 +28,7 @@ type customFieldsFilterScopeParams = {
28
28
  * @param name - The model type name used to join custom_field_definitions.
29
29
  * @returns A function that takes conditions and returns the Sequelize options object.
30
30
  */
31
- export declare const customFieldsFilterScope: (name: string) => ({ replacementsMap: replacements, scopeValue: conditions, }: customFieldsFilterScopeParams) => CustomFieldFilterOptions;
31
+ export declare const customFieldsFilterScope: (name: string) => ({ replacementsMap: replacements, scopeValue: conditions }: customFieldsFilterScopeParams) => CustomFieldFilterOptions;
32
32
  export declare const scopeName = "filterByCustomFields";
33
33
  export declare const customFieldsSortScope: (name: string) => ({ replacementsMap, scopeValue: sort }: {
34
34
  replacementsMap: any;
@@ -7,23 +7,28 @@ const sequelize_typescript_1 = require("sequelize-typescript");
7
7
  const common_types_1 = require("@autofleet/common-types");
8
8
  const helpers_1 = require("../utils/helpers");
9
9
  const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
10
+ const isConditionStringArray = (input) => Array.isArray(input) && typeof input[0] === 'string';
11
+ const isBooleanString = (input) => ['true', 'false'].includes(input.toString());
10
12
  const isDate = (input) => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
11
- const castIfNeeded = (conditionValue) => {
13
+ const castValueToJsonb = (value, type) => `to_jsonb(${value}::${type})`;
14
+ const castValueToJsonbText = (value) => castValueToJsonb(value, 'text');
15
+ const castValueToJsonbBoolean = (value) => castValueToJsonb(value, 'boolean');
16
+ const castValueToJsonbNumeric = (value) => castValueToJsonb(value, 'numeric');
17
+ const castIfNeeded = (columnName, conditionValue) => {
12
18
  if (isDate(conditionValue)) {
13
- return '::timestamp';
19
+ return castValueToJsonb(columnName, 'timestamp');
14
20
  }
15
21
  if (!Number.isNaN(Number(conditionValue))) {
16
- return '::numeric';
22
+ return castValueToJsonbNumeric(columnName);
17
23
  }
18
- return '';
24
+ return columnName;
19
25
  };
20
- const AND_DELIMETER = ' AND ';
21
- const OR_DELIMETER = ' OR ';
26
+ const AND_DELIMITER = ' AND ';
27
+ const OR_DELIMITER = ' OR ';
22
28
  const CD_TABLE_ALIAS = 'cd';
23
29
  const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
24
30
  const CV_TABLE_ALIAS = 'cv';
25
- const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value)`;
26
- const castValueToJsonb = (value) => `to_jsonb(${value}::text)`;
31
+ const CV_VALUE_COLUMN = `${CV_TABLE_ALIAS}.value`;
27
32
  /**
28
33
  * A Sequelize scope for filtering models by custom fields.
29
34
  * This scope builds a WHERE clause to be applied on the main query.
@@ -31,60 +36,65 @@ const castValueToJsonb = (value) => `to_jsonb(${value}::text)`;
31
36
  * @param name - The model type name used to join custom_field_definitions.
32
37
  * @returns A function that takes conditions and returns the Sequelize options object.
33
38
  */
34
- const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scopeValue: conditions, }) => {
39
+ const customFieldsFilterScope = (name) => ({ replacementsMap: replacements, scopeValue: conditions }) => {
35
40
  if (!conditions || Object.keys(conditions).length === 0) {
36
41
  return {};
37
42
  }
43
+ const reverseReplacementsMap = new Map(Object.entries(replacements).map(([key, value]) => [value, key]));
38
44
  // Build the WHERE clause for custom field filtering
39
- const conditionsStrings = Object.entries(conditions)
40
- .map(([key, condition]) => {
41
- const replacemetKey = Object.keys(replacements).find((randomString) => replacements[randomString] === key);
42
- const columnCondition = `(${CD_NAME_COLUMN} = :${replacemetKey})`;
43
- if (!replacemetKey)
45
+ const conditionsStrings = Object.entries(conditions).map(([key, condition]) => {
46
+ const replacementKey = reverseReplacementsMap.get(key);
47
+ if (!replacementKey)
44
48
  return false;
49
+ const columnCondition = `(${CD_NAME_COLUMN} = :${replacementKey})`;
45
50
  if (Array.isArray(condition)) {
46
51
  if (condition.length === 0) {
47
52
  // if empty array, the condition is ignored
48
53
  return false;
49
54
  }
50
- if (typeof condition[0] === 'string') {
51
- const values = condition.map((v) => {
52
- const valRandom = Object.keys(replacements).find((randomString) => replacements[randomString] === v);
53
- return castValueToJsonb(`:${valRandom}`);
55
+ if (isConditionStringArray(condition)) {
56
+ const values = condition.flatMap((v) => {
57
+ const valRandom = reverseReplacementsMap.get(v);
58
+ if (isBooleanString(v)) {
59
+ return [castValueToJsonbText(`:${valRandom}`), castValueToJsonbBoolean(`:${valRandom}`)];
60
+ }
61
+ if (!Number.isNaN(Number(v))) {
62
+ return castValueToJsonbNumeric(`:${valRandom}`);
63
+ }
64
+ return castValueToJsonbText(`:${valRandom}`);
54
65
  }).join(',');
55
- return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN (${values}))`;
66
+ return `(${columnCondition}${AND_DELIMITER}${CV_VALUE_COLUMN} IN (${values}))`;
56
67
  }
57
- return condition
58
- .map((c) => {
59
- const valRep = Object.keys(replacements).find((replacementKey) => replacements[replacementKey] === c.value);
60
- const valueAsJsonb = castValueToJsonb(`:${valRep}`);
61
- return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} ${valueAsJsonb})`;
62
- }).join(AND_DELIMETER);
68
+ return condition.map((c) => {
69
+ const valRep = reverseReplacementsMap.get(c.value);
70
+ const valueAsJsonb = castValueToJsonbText(`:${valRep}`);
71
+ return `(${columnCondition}${AND_DELIMITER}${castIfNeeded(CV_VALUE_COLUMN, c.value)} ${c.operator} ${valueAsJsonb})`;
72
+ }).join(AND_DELIMITER);
63
73
  }
64
74
  if (typeof condition === 'string' || typeof condition === 'number') {
65
- const conditionRep = Object.keys(replacements).find((replacementKey) => replacements[replacementKey] === condition);
66
- const valueAsJsonb = castValueToJsonb(`:${conditionRep}`);
67
- return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = ${valueAsJsonb})`;
75
+ const conditionRep = reverseReplacementsMap.get(condition);
76
+ const valueAsJsonb = !Number.isNaN(Number(condition)) ? castValueToJsonbNumeric(`:${conditionRep}`) : castValueToJsonbText(`:${conditionRep}`);
77
+ const valueAsJsonbBoolean = isBooleanString(condition) ? `${OR_DELIMITER}${CV_VALUE_COLUMN} = ${castValueToJsonbBoolean(`:${conditionRep}`)}` : '';
78
+ return `(${columnCondition}${AND_DELIMITER}(${castIfNeeded(CV_VALUE_COLUMN, condition)} = ${valueAsJsonb}${valueAsJsonbBoolean}))`;
68
79
  }
69
80
  if (condition?.operator) {
70
- const valueRep = Object.keys(replacements).find((replacementKey) => replacements[replacementKey] === condition.value);
71
- const valueAsJsonb = castValueToJsonb(`:${valueRep}`);
72
- return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} ${valueAsJsonb})`;
81
+ const valueRep = reverseReplacementsMap.get(condition.value);
82
+ const valueAsJsonb = castValueToJsonbText(`:${valueRep}`);
83
+ return `( ${columnCondition}${AND_DELIMITER}${castIfNeeded(CV_VALUE_COLUMN, condition.value)} ${condition.operator} ${valueAsJsonb})`;
73
84
  }
74
85
  return false;
75
- })
76
- .filter(Boolean);
86
+ }).filter(Boolean);
77
87
  if (conditionsStrings.length === 0) {
78
88
  return {};
79
89
  }
80
- const customFieldConditions = conditionsStrings.join(OR_DELIMETER);
90
+ const customFieldConditions = conditionsStrings.join(OR_DELIMITER);
81
91
  const subQuery = `
82
92
  SELECT cv.model_id
83
93
  FROM custom_field_values AS cv
84
94
  INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
85
- AND cd.model_type = '${name}'
95
+ ${AND_DELIMITER}cd.model_type = '${name}'
86
96
  WHERE ${customFieldConditions}
87
- AND cv.deleted_at IS NULL AND cd.deleted_at IS NULL
97
+ ${AND_DELIMITER}cv.deleted_at IS NULL${AND_DELIMITER}cd.deleted_at IS NULL
88
98
  GROUP BY cv.model_id
89
99
  HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
90
100
  `.replace(/\n/g, '');
@@ -112,9 +122,9 @@ const customFieldsSortScope = (name) => ({ replacementsMap, scopeValue: sort })
112
122
  FROM (SELECT cv.model_id, cv.value
113
123
  FROM custom_field_values AS cv INNER JOIN custom_field_definitions AS cd
114
124
  ON cv.custom_field_definition_id = cd.id
115
- AND cd.model_type = '${name}'
125
+ ${AND_DELIMITER}cd.model_type = '${name}'
116
126
  WHERE cv.model_id = "${name}"."id"
117
- AND cd.name = :${replacemetKey}
127
+ ${AND_DELIMITER}cd.name = :${replacemetKey}
118
128
  ) AS CustomFieldAggregation
119
129
  )
120
130
  `), randomStr,
@@ -1,2 +1,6 @@
1
1
  export declare const cleanup: () => Promise<void>;
2
2
  export declare const getModel: (name: string) => any;
3
+ export declare const getReplacementMapWithScopeValue: (conditions: Record<string, any>) => {
4
+ replacementsMap: Record<string, string>;
5
+ scopeValue: Record<string, any>;
6
+ };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getModel = exports.cleanup = void 0;
3
+ exports.getReplacementMapWithScopeValue = exports.getModel = exports.cleanup = void 0;
4
+ const formatter_1 = require("@autofleet/sheilta/lib/formatter");
4
5
  const models_1 = require("../../models");
5
6
  // eslint-disable-next-line import/prefer-default-export
6
7
  const cleanup = async () => {
@@ -18,3 +19,8 @@ const getModel = (name) => {
18
19
  return models[name];
19
20
  };
20
21
  exports.getModel = getModel;
22
+ const getReplacementMapWithScopeValue = (conditions) => ({
23
+ replacementsMap: (0, formatter_1.generateFilterReplacements)(Object.fromEntries(Object.entries(conditions).map(([key, value]) => [`customFields.${key}`, value]))),
24
+ scopeValue: conditions,
25
+ });
26
+ exports.getReplacementMapWithScopeValue = getReplacementMapWithScopeValue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.8.1-beta-e7a88a64.0",
3
+ "version": "0.8.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -50,8 +50,7 @@
50
50
  "npm-watch": "^0.11.0",
51
51
  "ts-jest": "^29.1.2",
52
52
  "ts-node": "^8.6.2",
53
- "typescript": "^5.3.3",
54
- "typescript-eslint": "^0.0.1-alpha.0"
53
+ "typescript": "^5.3.3"
55
54
  },
56
55
  "peerDependencies": {
57
56
  "@autofleet/sheilta": ">=1.4.0"
@@ -32,24 +32,29 @@ type customFieldsFilterScopeParams = {
32
32
  scopeValue: Record<string, ConditionValue>;
33
33
  }
34
34
 
35
+ const isConditionStringArray = (input: any): input is string[] => Array.isArray(input) && typeof input[0] === 'string';
36
+ const isBooleanString = (input: string): boolean => ['true', 'false'].includes(input.toString());
35
37
  const isDate = (input: any): input is Date => input instanceof Date || Object.prototype.toString.call(input) === '[object Date]';
36
38
 
37
- const castIfNeeded = (conditionValue: string): string => {
39
+ const castValueToJsonb = (value: string, type: string) => `to_jsonb(${value}::${type})`;
40
+ const castValueToJsonbText = (value: string) => castValueToJsonb(value, 'text');
41
+ const castValueToJsonbBoolean = (value: string) => castValueToJsonb(value, 'boolean');
42
+ const castValueToJsonbNumeric = (value: string) => castValueToJsonb(value, 'numeric');
43
+ const castIfNeeded = (columnName: string, conditionValue: string): string => {
38
44
  if (isDate(conditionValue)) {
39
- return '::timestamp';
45
+ return castValueToJsonb(columnName, 'timestamp');
40
46
  }
41
47
  if (!Number.isNaN(Number(conditionValue))) {
42
- return '::numeric';
48
+ return castValueToJsonbNumeric(columnName);
43
49
  }
44
- return '';
50
+ return columnName;
45
51
  };
46
- const AND_DELIMETER = ' AND ';
47
- const OR_DELIMETER = ' OR ';
52
+ const AND_DELIMITER = ' AND ';
53
+ const OR_DELIMITER = ' OR ';
48
54
  const CD_TABLE_ALIAS = 'cd';
49
55
  const CD_NAME_COLUMN = `${CD_TABLE_ALIAS}.name`;
50
56
  const CV_TABLE_ALIAS = 'cv';
51
- const CV_VALUE_COLUMN = `(${CV_TABLE_ALIAS}.value)`;
52
- const castValueToJsonb = (value) => `to_jsonb(${value}::text)`;
57
+ const CV_VALUE_COLUMN = `${CV_TABLE_ALIAS}.value`;
53
58
 
54
59
  /**
55
60
  * A Sequelize scope for filtering models by custom fields.
@@ -60,77 +65,65 @@ const castValueToJsonb = (value) => `to_jsonb(${value}::text)`;
60
65
  */
61
66
  export const customFieldsFilterScope = (
62
67
  name: string,
63
- ) => (
64
- {
65
- replacementsMap: replacements,
66
- scopeValue: conditions,
67
- }: customFieldsFilterScopeParams,
68
- ): CustomFieldFilterOptions => {
68
+ ) => ({ replacementsMap: replacements, scopeValue: conditions }: customFieldsFilterScopeParams): CustomFieldFilterOptions => {
69
69
  if (!conditions || Object.keys(conditions).length === 0) {
70
70
  return {};
71
71
  }
72
+ const reverseReplacementsMap = new Map(Object.entries(replacements).map(([key, value]) => [value, key]));
72
73
  // Build the WHERE clause for custom field filtering
73
- const conditionsStrings = Object.entries(conditions)
74
- .map(
75
- ([key, condition]) => {
76
- const replacemetKey = Object.keys(replacements).find(
77
- (randomString) => replacements[randomString] === key,
78
- );
79
- const columnCondition = `(${CD_NAME_COLUMN} = :${replacemetKey})`;
80
- if (!replacemetKey) return false;
74
+ const conditionsStrings = Object.entries(conditions).map(([key, condition]) => {
75
+ const replacementKey = reverseReplacementsMap.get(key);
76
+ if (!replacementKey) return false;
77
+ const columnCondition = `(${CD_NAME_COLUMN} = :${replacementKey})`;
81
78
 
82
- if (Array.isArray(condition)) {
83
- if (condition.length === 0) {
84
- // if empty array, the condition is ignored
85
- return false;
79
+ if (Array.isArray(condition)) {
80
+ if (condition.length === 0) {
81
+ // if empty array, the condition is ignored
82
+ return false;
83
+ }
84
+ if (isConditionStringArray(condition)) {
85
+ const values = condition.flatMap((v) => {
86
+ const valRandom = reverseReplacementsMap.get(v);
87
+ if (isBooleanString(v)) {
88
+ return [castValueToJsonbText(`:${valRandom}`), castValueToJsonbBoolean(`:${valRandom}`)];
86
89
  }
87
- if (typeof condition[0] === 'string') {
88
- const values = condition.map((v) => {
89
- const valRandom = Object.keys(replacements).find(
90
- (randomString) => replacements[randomString] === v,
91
- );
92
- return castValueToJsonb(`:${valRandom}`);
93
- }).join(',');
94
- return `(${columnCondition} AND ${CV_VALUE_COLUMN} IN (${values}))`;
90
+ if (!Number.isNaN(Number(v))) {
91
+ return castValueToJsonbNumeric(`:${valRandom}`);
95
92
  }
96
- return condition
97
- .map((c) => {
98
- const valRep = Object.keys(replacements).find(
99
- (replacementKey) => replacements[replacementKey] === c.value,
100
- );
101
- const valueAsJsonb = castValueToJsonb(`:${valRep}`);
102
- return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(c.value)} ${c.operator} ${valueAsJsonb})`;
103
- }).join(AND_DELIMETER);
104
- }
105
- if (typeof condition === 'string' || typeof condition === 'number') {
106
- const conditionRep = Object.keys(replacements).find(
107
- (replacementKey) => replacements[replacementKey] === condition,
108
- );
109
- const valueAsJsonb = castValueToJsonb(`:${conditionRep}`);
110
- return `(${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition)} = ${valueAsJsonb})`;
111
- }
112
- if (condition?.operator) {
113
- const valueRep = Object.keys(replacements).find(
114
- (replacementKey) => replacements[replacementKey] === condition.value,
115
- );
116
- const valueAsJsonb = castValueToJsonb(`:${valueRep}`);
117
- return `( ${columnCondition} AND ${CV_VALUE_COLUMN}${castIfNeeded(condition.value)} ${condition.operator} ${valueAsJsonb})`;
118
- }
119
- return false;
120
- },
121
- )
122
- .filter(Boolean);
93
+ return castValueToJsonbText(`:${valRandom}`);
94
+ }).join(',');
95
+ return `(${columnCondition}${AND_DELIMITER}${CV_VALUE_COLUMN} IN (${values}))`;
96
+ }
97
+ return condition.map((c) => {
98
+ const valRep = reverseReplacementsMap.get(c.value);
99
+ const valueAsJsonb = castValueToJsonbText(`:${valRep}`);
100
+ return `(${columnCondition}${AND_DELIMITER}${castIfNeeded(CV_VALUE_COLUMN, c.value)} ${c.operator} ${valueAsJsonb})`;
101
+ }).join(AND_DELIMITER);
102
+ }
103
+ if (typeof condition === 'string' || typeof condition === 'number') {
104
+ const conditionRep = reverseReplacementsMap.get(condition);
105
+ const valueAsJsonb = !Number.isNaN(Number(condition)) ? castValueToJsonbNumeric(`:${conditionRep}`) : castValueToJsonbText(`:${conditionRep}`);
106
+ const valueAsJsonbBoolean = isBooleanString(condition) ? `${OR_DELIMITER}${CV_VALUE_COLUMN} = ${castValueToJsonbBoolean(`:${conditionRep}`)}` : '';
107
+ return `(${columnCondition}${AND_DELIMITER}(${castIfNeeded(CV_VALUE_COLUMN, condition)} = ${valueAsJsonb}${valueAsJsonbBoolean}))`;
108
+ }
109
+ if (condition?.operator) {
110
+ const valueRep = reverseReplacementsMap.get(condition.value);
111
+ const valueAsJsonb = castValueToJsonbText(`:${valueRep}`);
112
+ return `( ${columnCondition}${AND_DELIMITER}${castIfNeeded(CV_VALUE_COLUMN, condition.value)} ${condition.operator} ${valueAsJsonb})`;
113
+ }
114
+ return false;
115
+ }).filter(Boolean);
123
116
  if (conditionsStrings.length === 0) {
124
117
  return {};
125
118
  }
126
- const customFieldConditions = conditionsStrings.join(OR_DELIMETER);
119
+ const customFieldConditions = conditionsStrings.join(OR_DELIMITER);
127
120
  const subQuery = `
128
121
  SELECT cv.model_id
129
122
  FROM custom_field_values AS cv
130
123
  INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id
131
- AND cd.model_type = '${name}'
124
+ ${AND_DELIMITER}cd.model_type = '${name}'
132
125
  WHERE ${customFieldConditions}
133
- AND cv.deleted_at IS NULL AND cd.deleted_at IS NULL
126
+ ${AND_DELIMITER}cv.deleted_at IS NULL${AND_DELIMITER}cd.deleted_at IS NULL
134
127
  GROUP BY cv.model_id
135
128
  HAVING COUNT(DISTINCT cv.custom_field_definition_id) = ${conditionsStrings.length}
136
129
  `.replace(/\n/g, '');
@@ -164,9 +157,9 @@ export const customFieldsSortScope = (
164
157
  FROM (SELECT cv.model_id, cv.value
165
158
  FROM custom_field_values AS cv INNER JOIN custom_field_definitions AS cd
166
159
  ON cv.custom_field_definition_id = cd.id
167
- AND cd.model_type = '${name}'
160
+ ${AND_DELIMITER}cd.model_type = '${name}'
168
161
  WHERE cv.model_id = "${name}"."id"
169
- AND cd.name = :${replacemetKey}
162
+ ${AND_DELIMITER}cd.name = :${replacemetKey}
170
163
  ) AS CustomFieldAggregation
171
164
  )
172
165
  `), randomStr,
@@ -1,3 +1,4 @@
1
+ import { generateFilterReplacements } from '@autofleet/sheilta/lib/formatter';
1
2
  import {
2
3
  ContextAwareTestModel, ContextTestModel, CustomFieldDefinition, TestModel,
3
4
  } from '../../models';
@@ -17,3 +18,8 @@ export const getModel = (name: string) => {
17
18
  const models = require('../../models');
18
19
  return models[name];
19
20
  };
21
+
22
+ export const getReplacementMapWithScopeValue = (conditions: Record<string, any>) => ({
23
+ replacementsMap: generateFilterReplacements(Object.fromEntries(Object.entries(conditions).map(([key, value]) => [`customFields.${key}`, value]))),
24
+ scopeValue: conditions,
25
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": [
4
+ "src/**/*",
5
+ ],
6
+ "exclude": ["node_modules", "src/tests/**", "**/*.test.ts"]
7
+ }
package/tsconfig.json CHANGED
@@ -12,5 +12,5 @@
12
12
  "include": [
13
13
  "src/**/*",
14
14
  ],
15
- "exclude": ["node_modules", "**/*.test.ts"]
15
+ "exclude": ["node_modules"]
16
16
  }