@autofleet/sadot 0.6.2-beta.0 → 0.6.2-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.
@@ -1,14 +1,11 @@
1
1
  import { WhereOptions } from 'sequelize';
2
+ import { Sequelize } from 'sequelize-typescript';
2
3
  /**
3
4
  * Type representing possible condition values.
4
5
  * Currently supporting strings and arrays of strings.
5
6
  * More types to be added (TBA).
6
7
  */
7
- type ConditionWithOperator = {
8
- operator: string;
9
- value: string;
10
- };
11
- export type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];
8
+ export type ConditionValue = string | string[];
12
9
  export type CustomFieldSort = {
13
10
  field: string;
14
11
  direction: 'ASC' | 'DESC';
@@ -16,6 +13,9 @@ export type CustomFieldSort = {
16
13
  export type CustomFieldFilterOptions = {
17
14
  where?: WhereOptions;
18
15
  };
16
+ export type CustomFieldSortOptions = {
17
+ order?: [Sequelize['literal']];
18
+ };
19
19
  /**
20
20
  * A Sequelize scope for filtering models by custom fields.
21
21
  * This scope builds a WHERE clause to be applied on the main query.
@@ -24,5 +24,12 @@ export type CustomFieldFilterOptions = {
24
24
  * @returns {Function} - A function that takes conditions and returns the Sequelize options object.
25
25
  */
26
26
  export declare const customFieldsFilterScope: (name: string) => (conditions: Record<string, ConditionValue>) => CustomFieldFilterOptions;
27
- export declare const scopeName = "filterByCustomFields";
28
- export {};
27
+ /**
28
+ * A Sequelize scope for sorting models by custom fields.
29
+ * This scope builds a order clause to be applied on the main query.
30
+ */
31
+ export declare const customFieldsSortScope: (name: string) => (sorts: CustomFieldSort[]) => {
32
+ order?: undefined;
33
+ } | {
34
+ order: import("sequelize/types/utils").Literal[];
35
+ };
@@ -1,18 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.scopeName = exports.customFieldsFilterScope = void 0;
3
+ exports.customFieldsSortScope = exports.customFieldsFilterScope = 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
- const common_types_1 = require("@autofleet/common-types");
8
- const { CUSTOM_FIELDS_FILTER_SCOPE } = common_types_1.customFields;
9
- const castIfNeeded = (conditionValue) => {
10
- if (!Number.isNaN(Date.parse(conditionValue))) {
11
- return '::timestamp';
12
- }
13
- return '';
14
- };
15
- const AND_DELIMETER = ' AND ';
16
7
  /**
17
8
  * A Sequelize scope for filtering models by custom fields.
18
9
  * This scope builds a WHERE clause to be applied on the main query.
@@ -26,39 +17,30 @@ const customFieldsFilterScope = (name) => (conditions) => {
26
17
  }
27
18
  // Build the WHERE clause for custom field filtering
28
19
  const conditionsStrings = Object.entries(conditions)
29
- .map(([key, condition]) => {
30
- if (Array.isArray(condition)) {
31
- if (condition.length === 0) {
20
+ .map(([key, value]) => {
21
+ if (Array.isArray(value)) {
22
+ if (value.length === 0) {
32
23
  // if empty array, the condition is ignored
33
24
  return false;
34
25
  }
35
- if (typeof condition[0] === 'string') {
36
- const values = condition.map((v) => `'${v}'`).join(',');
37
- return `(custom_fields->>'${key}') IN (${values})`;
38
- }
39
- return condition
40
- .map((c) => `(custom_fields->>'${key}')${castIfNeeded(c.value)} ${c.operator} '${c.value}'`).join(AND_DELIMETER);
41
- }
42
- if (typeof condition === 'string') {
43
- return `(custom_fields->>'${key}')${castIfNeeded(condition)} = '${condition}'`;
44
- }
45
- if (condition?.operator) {
46
- return `(custom_fields->>'${key}')${castIfNeeded(condition.value)} ${condition.operator} '${condition.value}'`;
26
+ const values = value.map((v) => `'${v}'`).join(',');
27
+ return `custom_fields->>'${key}' IN (${values})`;
47
28
  }
48
- return false;
29
+ return `custom_fields->>'${key}' = '${value}'`;
49
30
  })
50
31
  .filter(Boolean);
51
32
  if (conditionsStrings.length === 0) {
52
33
  return {};
53
34
  }
54
- const customFieldConditions = conditionsStrings.join(AND_DELIMETER);
35
+ const customFieldConditions = conditionsStrings.join(' AND ');
55
36
  const subQuery = `${'SELECT model_id FROM ('
56
37
  + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
57
38
  + 'FROM custom_field_values AS cv '
58
39
  + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
59
40
  + `AND cd.model_type = '${name}' `
60
41
  + 'GROUP BY cv.model_id'
61
- + ') AS CustomFieldAggregation WHERE '}${customFieldConditions}`;
42
+ + ') AS CustomFieldAggregation '
43
+ + 'WHERE '}${customFieldConditions}`;
62
44
  return {
63
45
  where: {
64
46
  id: {
@@ -68,4 +50,25 @@ const customFieldsFilterScope = (name) => (conditions) => {
68
50
  };
69
51
  };
70
52
  exports.customFieldsFilterScope = customFieldsFilterScope;
71
- exports.scopeName = CUSTOM_FIELDS_FILTER_SCOPE;
53
+ /**
54
+ * A Sequelize scope for sorting models by custom fields.
55
+ * This scope builds a order clause to be applied on the main query.
56
+ */
57
+ const customFieldsSortScope = (name) => (sorts) => {
58
+ if (!sorts || sorts.length === 0) {
59
+ return {};
60
+ }
61
+ const sortStrings = sorts.map(({ field, direction }) => `custom_fields->>'${field}' ${direction}`);
62
+ const subQuery = `${'SELECT model_id FROM ('
63
+ + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
64
+ + 'FROM custom_field_values AS cv '
65
+ + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
66
+ + `AND cd.model_type = '${name}' `
67
+ + 'GROUP BY cv.model_id'
68
+ + ') AS CustomFieldAggregation '
69
+ + 'ORDER BY '}${sortStrings.join(', ')}`;
70
+ return {
71
+ order: [sequelize_typescript_1.Sequelize.literal(`(${subQuery})`)],
72
+ };
73
+ };
74
+ exports.customFieldsSortScope = customFieldsSortScope;
@@ -1,2 +1,2 @@
1
- import { customFieldsFilterScope } from './filter';
2
- export { customFieldsFilterScope, };
1
+ import { customFieldsFilterScope, customFieldsSortScope } from './filter';
2
+ export { customFieldsFilterScope, customFieldsSortScope, };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.customFieldsFilterScope = void 0;
3
+ exports.customFieldsSortScope = exports.customFieldsFilterScope = void 0;
4
4
  /* eslint-disable import/prefer-default-export */
5
5
  const filter_1 = require("./filter");
6
6
  Object.defineProperty(exports, "customFieldsFilterScope", { enumerable: true, get: function () { return filter_1.customFieldsFilterScope; } });
7
+ Object.defineProperty(exports, "customFieldsSortScope", { enumerable: true, get: function () { return filter_1.customFieldsSortScope; } });
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.applyCustomAssociation = exports.addScopes = exports.removeHooks = exports.addHooks = void 0;
7
7
  const sequelize_1 = require("sequelize");
8
8
  const common_types_1 = require("@autofleet/common-types");
9
+ const custom_fields_1 = require("@autofleet/common-types/lib/custom-fields");
9
10
  const models_1 = require("../models");
10
11
  const hooks_1 = require("../hooks");
11
12
  const scopes_1 = require("../scopes");
@@ -91,6 +92,7 @@ const addScopes = (models, getModel) => {
91
92
  addAssociations(model, name);
92
93
  // Add filter scope
93
94
  model.addScope(CUSTOM_FIELDS_FILTER_SCOPE, (0, scopes_1.customFieldsFilterScope)(name));
95
+ model.addScope(custom_fields_1.CUSTOM_FIELDS_SORT_SCOPE, (0, scopes_1.customFieldsSortScope)(name));
94
96
  }
95
97
  catch (e) {
96
98
  logger_1.default.error(`Could not add custom fields scopes to model ${name}. `, e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.6.2-beta.0",
3
+ "version": "0.6.2-beta.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -29,7 +29,7 @@
29
29
  }
30
30
  },
31
31
  "dependencies": {
32
- "@autofleet/common-types": "^1.7.29",
32
+ "@autofleet/common-types": "^1.7.41-beta.1",
33
33
  "@autofleet/errors": "^1.0.10",
34
34
  "@autofleet/events": "^2.0.0",
35
35
  "@autofleet/logger": "^2.0.5",
@@ -1,20 +1,13 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
2
  import { Op, WhereOptions } from 'sequelize';
3
3
  import { Sequelize } from 'sequelize-typescript';
4
- import { customFields } from '@autofleet/common-types';
5
-
6
- const { CUSTOM_FIELDS_FILTER_SCOPE } = customFields;
7
4
 
8
5
  /**
9
6
  * Type representing possible condition values.
10
7
  * Currently supporting strings and arrays of strings.
11
8
  * More types to be added (TBA).
12
9
  */
13
- type ConditionWithOperator = {
14
- operator: string;
15
- value: string;
16
- };
17
- export type ConditionValue = ConditionWithOperator | ConditionWithOperator[] | string | string[];
10
+ export type ConditionValue = string | string[];
18
11
 
19
12
  export type CustomFieldSort = {
20
13
  field: string;
@@ -25,13 +18,10 @@ export type CustomFieldFilterOptions = {
25
18
  where?: WhereOptions;
26
19
  }
27
20
 
28
- const castIfNeeded = (conditionValue: string): string => {
29
- if (!Number.isNaN(Date.parse(conditionValue))) {
30
- return '::timestamp';
31
- }
32
- return '';
33
- };
34
- const AND_DELIMETER = ' AND ';
21
+ export type CustomFieldSortOptions = {
22
+ order?: [Sequelize['literal']];
23
+ }
24
+
35
25
  /**
36
26
  * A Sequelize scope for filtering models by custom fields.
37
27
  * This scope builds a WHERE clause to be applied on the main query.
@@ -48,33 +38,24 @@ export const customFieldsFilterScope = (
48
38
  // Build the WHERE clause for custom field filtering
49
39
  const conditionsStrings = Object.entries(conditions)
50
40
  .map(
51
- ([key, condition]) => {
52
- if (Array.isArray(condition)) {
53
- if (condition.length === 0) {
41
+ ([key, value]) => {
42
+ if (Array.isArray(value)) {
43
+ if (value.length === 0) {
54
44
  // if empty array, the condition is ignored
55
45
  return false;
56
46
  }
57
- if (typeof condition[0] === 'string') {
58
- const values = condition.map((v) => `'${v}'`).join(',');
59
- return `(custom_fields->>'${key}') IN (${values})`;
60
- }
61
- return condition
62
- .map((c) => `(custom_fields->>'${key}')${castIfNeeded(c.value)} ${c.operator} '${c.value}'`).join(AND_DELIMETER);
63
- }
64
- if (typeof condition === 'string') {
65
- return `(custom_fields->>'${key}')${castIfNeeded(condition)} = '${condition}'`;
66
- }
67
- if (condition?.operator) {
68
- return `(custom_fields->>'${key}')${castIfNeeded(condition.value)} ${condition.operator} '${condition.value}'`;
47
+ const values = value.map((v) => `'${v}'`).join(',');
48
+ return `custom_fields->>'${key}' IN (${values})`;
69
49
  }
70
- return false;
50
+ return `custom_fields->>'${key}' = '${value}'`;
71
51
  },
72
52
  )
73
53
  .filter(Boolean);
54
+
74
55
  if (conditionsStrings.length === 0) {
75
56
  return {};
76
57
  }
77
- const customFieldConditions = conditionsStrings.join(AND_DELIMETER);
58
+ const customFieldConditions = conditionsStrings.join(' AND ');
78
59
 
79
60
  const subQuery = `${'SELECT model_id FROM ('
80
61
  + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
@@ -82,7 +63,8 @@ export const customFieldsFilterScope = (
82
63
  + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
83
64
  + `AND cd.model_type = '${name}' `
84
65
  + 'GROUP BY cv.model_id'
85
- + ') AS CustomFieldAggregation WHERE '}${customFieldConditions}`;
66
+ + ') AS CustomFieldAggregation '
67
+ + 'WHERE '}${customFieldConditions}`;
86
68
 
87
69
  return {
88
70
  where: {
@@ -93,4 +75,29 @@ export const customFieldsFilterScope = (
93
75
  };
94
76
  };
95
77
 
96
- export const scopeName = CUSTOM_FIELDS_FILTER_SCOPE;
78
+ /**
79
+ * A Sequelize scope for sorting models by custom fields.
80
+ * This scope builds a order clause to be applied on the main query.
81
+ */
82
+ export const customFieldsSortScope = (
83
+ name: string,
84
+ ) => (sorts: CustomFieldSort[]) => {
85
+ if (!sorts || sorts.length === 0) {
86
+ return {};
87
+ }
88
+
89
+ const sortStrings = sorts.map(({ field, direction }) => `custom_fields->>'${field}' ${direction}`);
90
+
91
+ const subQuery = `${'SELECT model_id FROM ('
92
+ + 'SELECT cv.model_id, jsonb_object_agg(cd.name, cv.value) AS custom_fields '
93
+ + 'FROM custom_field_values AS cv '
94
+ + 'INNER JOIN custom_field_definitions AS cd ON cv.custom_field_definition_id = cd.id '
95
+ + `AND cd.model_type = '${name}' `
96
+ + 'GROUP BY cv.model_id'
97
+ + ') AS CustomFieldAggregation '
98
+ + 'ORDER BY '}${sortStrings.join(', ')}`;
99
+
100
+ return {
101
+ order: [Sequelize.literal(`(${subQuery})`)],
102
+ };
103
+ };
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- import { customFieldsFilterScope } from './filter';
2
+ import { customFieldsFilterScope, customFieldsSortScope } from './filter';
3
3
 
4
4
  export {
5
5
  customFieldsFilterScope,
6
+ customFieldsSortScope,
6
7
  };
package/src/utils/init.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DataTypes } from 'sequelize';
2
2
  import { ModelCtor } from 'sequelize-typescript';
3
3
  import { customFields } from '@autofleet/common-types';
4
+ import { CUSTOM_FIELDS_SORT_SCOPE } from '@autofleet/common-types/lib/custom-fields';
4
5
  import {
5
6
  CustomFieldDefinition,
6
7
  CustomFieldValue,
@@ -13,7 +14,7 @@ import {
13
14
  beforeCreate,
14
15
  beforeBulkCreate,
15
16
  } from '../hooks';
16
- import { customFieldsFilterScope } from '../scopes';
17
+ import { customFieldsFilterScope, customFieldsSortScope } from '../scopes';
17
18
  import logger from './logger';
18
19
  import type { ModelFetcher, Models } from '../types';
19
20
 
@@ -99,6 +100,7 @@ export const addScopes = (models: Models[], getModel: ModelFetcher): void => {
99
100
  addAssociations(model, name);
100
101
  // Add filter scope
101
102
  model.addScope(CUSTOM_FIELDS_FILTER_SCOPE, customFieldsFilterScope(name));
103
+ model.addScope(CUSTOM_FIELDS_SORT_SCOPE, customFieldsSortScope(name));
102
104
  } catch (e) {
103
105
  logger.error(`Could not add custom fields scopes to model ${name}. `, e);
104
106
  }