@autofleet/sadot 1.0.0-beta.1 → 1.0.0

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 (135) hide show
  1. package/README.md +5 -0
  2. package/dist/api/index.d.ts +2 -1
  3. package/dist/api/index.js +3 -2
  4. package/dist/api/v1/definition/index.d.ts +2 -1
  5. package/dist/api/v1/definition/index.js +12 -14
  6. package/dist/api/v1/definition/validations.js +14 -12
  7. package/dist/api/v1/errors.d.ts +3 -1
  8. package/dist/api/v1/errors.js +1 -4
  9. package/dist/api/v1/index.d.ts +2 -1
  10. package/dist/api/v1/index.js +5 -2
  11. package/dist/api/v1/validator/index.d.ts +3 -0
  12. package/dist/api/v1/validator/index.js +143 -0
  13. package/dist/api/v1/validator/validations.d.ts +6 -0
  14. package/dist/api/v1/validator/validations.js +40 -0
  15. package/dist/errors/index.d.ts +9 -1
  16. package/dist/errors/index.js +25 -4
  17. package/dist/events/index.d.ts +2 -1
  18. package/dist/events/index.js +17 -11
  19. package/dist/hooks/create.d.ts +2 -2
  20. package/dist/hooks/create.js +40 -19
  21. package/dist/hooks/enrich.d.ts +20 -2
  22. package/dist/hooks/enrich.js +88 -16
  23. package/dist/hooks/hooks.d.ts +17 -0
  24. package/dist/hooks/hooks.js +379 -0
  25. package/dist/hooks/index.d.ts +2 -3
  26. package/dist/hooks/index.js +6 -7
  27. package/dist/hooks/update.d.ts +2 -2
  28. package/dist/hooks/update.js +16 -26
  29. package/dist/hooks/utils/updateInstanceValues.d.ts +15 -0
  30. package/dist/hooks/utils/updateInstanceValues.js +50 -0
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.js +19 -6
  33. package/dist/models/CustomFieldDefinition.d.ts +1 -0
  34. package/dist/models/CustomFieldDefinition.js +10 -2
  35. package/dist/models/CustomFieldEntries.d.ts +15 -0
  36. package/dist/models/CustomFieldEntries.js +123 -0
  37. package/dist/models/CustomFieldValue.d.ts +3 -2
  38. package/dist/models/CustomFieldValue.js +22 -14
  39. package/dist/models/CustomValidator.d.ts +17 -0
  40. package/dist/models/CustomValidator.js +98 -0
  41. package/dist/models/index.d.ts +10 -2
  42. package/dist/models/index.js +60 -22
  43. package/dist/repository/definition.d.ts +23 -7
  44. package/dist/repository/definition.js +36 -7
  45. package/dist/repository/entries.d.ts +13 -0
  46. package/dist/repository/entries.js +92 -0
  47. package/dist/repository/utils/formatValues.d.ts +3 -0
  48. package/dist/repository/utils/formatValues.js +16 -0
  49. package/dist/repository/validator.d.ts +21 -0
  50. package/dist/repository/validator.js +62 -0
  51. package/dist/repository/value.d.ts +1 -1
  52. package/dist/repository/value.js +7 -32
  53. package/dist/scopes/filter.d.ts +5 -22
  54. package/dist/scopes/filter.js +18 -65
  55. package/dist/scopes/helpers/filter.helpers.d.ts +42 -0
  56. package/dist/scopes/helpers/filter.helpers.js +204 -0
  57. package/dist/tests/api/test-api.js +6 -24
  58. package/dist/tests/helpers/commonHooks.d.ts +6 -0
  59. package/dist/tests/helpers/commonHooks.js +62 -0
  60. package/dist/tests/helpers/database-config.js +1 -1
  61. package/dist/tests/helpers/index.d.ts +6 -1
  62. package/dist/tests/helpers/index.js +15 -2
  63. package/dist/tests/mocks/definition.mock.d.ts +5 -2
  64. package/dist/tests/mocks/definition.mock.js +10 -1
  65. package/dist/tests/mocks/events.mock.d.ts +1 -0
  66. package/dist/tests/mocks/events.mock.js +4 -2
  67. package/dist/types/definition/index.d.ts +1 -0
  68. package/dist/types/entries/index.d.ts +25 -0
  69. package/dist/types/entries/index.js +2 -0
  70. package/dist/types/index.d.ts +19 -3
  71. package/dist/utils/constants/index.d.ts +1 -1
  72. package/dist/utils/helpers/index.d.ts +4 -3
  73. package/dist/utils/helpers/index.js +22 -30
  74. package/dist/utils/init.d.ts +5 -3
  75. package/dist/utils/init.js +13 -11
  76. package/dist/utils/logger/index.d.ts +1 -0
  77. package/dist/utils/logger/index.js +34 -0
  78. package/dist/utils/validations/index.d.ts +7 -1
  79. package/dist/utils/validations/index.js +28 -7
  80. package/dist/utils/validations/schema/validator-schema.d.ts +9 -0
  81. package/dist/utils/validations/schema/validator-schema.js +95 -0
  82. package/dist/utils/validations/type.d.ts +2 -1
  83. package/dist/utils/validations/validators/index.js +9 -9
  84. package/dist/utils/validations/validators/select.validator.js +5 -2
  85. package/dist/utils/validations/validators/status.validator.js +8 -2
  86. package/package.json +28 -12
  87. package/src/api/index.ts +3 -2
  88. package/src/api/v1/definition/index.ts +20 -23
  89. package/src/api/v1/definition/validations.ts +16 -16
  90. package/src/api/v1/errors.ts +4 -7
  91. package/src/api/v1/index.ts +5 -3
  92. package/src/api/v1/validator/index.ts +141 -0
  93. package/src/api/v1/validator/validations.ts +39 -0
  94. package/src/errors/index.ts +31 -3
  95. package/src/events/index.ts +25 -13
  96. package/src/hooks/create.ts +50 -28
  97. package/src/hooks/enrich.ts +137 -28
  98. package/src/hooks/hooks.ts +467 -0
  99. package/src/hooks/index.ts +10 -5
  100. package/src/hooks/update.ts +20 -7
  101. package/src/hooks/utils/updateInstanceValues.ts +63 -0
  102. package/src/index.ts +10 -8
  103. package/src/models/CustomFieldDefinition.ts +9 -2
  104. package/src/models/CustomFieldEntries.ts +81 -0
  105. package/src/models/CustomFieldValue.ts +25 -17
  106. package/src/models/CustomValidator.ts +78 -0
  107. package/src/models/index.ts +83 -25
  108. package/src/repository/definition.ts +62 -14
  109. package/src/repository/entries.ts +88 -0
  110. package/src/repository/utils/formatValues.ts +14 -0
  111. package/src/repository/validator.ts +104 -0
  112. package/src/repository/value.ts +5 -35
  113. package/src/scopes/filter.ts +33 -106
  114. package/src/scopes/helpers/filter.helpers.ts +227 -0
  115. package/src/tests/api/test-api.ts +4 -2
  116. package/src/tests/helpers/commonHooks.ts +43 -0
  117. package/src/tests/helpers/database-config.ts +1 -1
  118. package/src/tests/helpers/index.ts +18 -2
  119. package/src/tests/mocks/definition.mock.ts +18 -9
  120. package/src/tests/mocks/events.mock.ts +4 -1
  121. package/src/types/definition/index.ts +1 -0
  122. package/src/types/entries/index.ts +27 -0
  123. package/src/types/index.ts +20 -3
  124. package/src/utils/helpers/index.ts +28 -35
  125. package/src/utils/init.ts +17 -12
  126. package/src/utils/logger/index.ts +9 -0
  127. package/src/utils/validations/index.ts +30 -6
  128. package/src/utils/validations/schema/README.md +93 -0
  129. package/src/utils/validations/schema/validator-schema.ts +106 -0
  130. package/src/utils/validations/type.ts +2 -1
  131. package/src/utils/validations/validators/index.ts +9 -9
  132. package/src/utils/validations/validators/select.validator.ts +3 -2
  133. package/src/utils/validations/validators/status.validator.ts +6 -2
  134. package/tsconfig.build.json +7 -0
  135. package/tsconfig.json +1 -1
@@ -1,7 +1,25 @@
1
- import type { ModelOptions } from '../types';
1
+ import type CustomFieldValue from '../models/CustomFieldValue';
2
+ import type { CustomFieldOptions, ModelOptions, TransactionOptions } from '../types';
2
3
  type SupportedHookTypes = 'afterFind' | 'afterCreate' | 'afterUpdate';
4
+ type CustomFieldEntries = Record<string, any>;
5
+ interface GetValuesGroupByInstanceResponse {
6
+ [modelId: string]: CustomFieldValue[];
7
+ }
8
+ interface GetCustomFieldEntriesByInstanceIdResponse {
9
+ [modelId: string]: CustomFieldEntries;
10
+ }
11
+ export declare const getCustomFieldEntriesByInstanceId: ({ instancesIds, options, sadotOptions, }: {
12
+ instancesIds: string[];
13
+ options?: TransactionOptions;
14
+ sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>;
15
+ }) => Promise<GetCustomFieldEntriesByInstanceIdResponse>;
16
+ export declare const getValuesGroupByInstance: ({ instancesIds, options, sadotOptions, }: {
17
+ instancesIds: string[];
18
+ options?: TransactionOptions;
19
+ sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>;
20
+ }) => Promise<GetValuesGroupByInstanceResponse>;
3
21
  /**
4
22
  * A hook to attach the custom fields when fetching a model instances.
5
23
  */
6
- declare const enrichResults: (modelType: string, scopeAttributes: string[], hookType?: SupportedHookTypes, modelOptions?: ModelOptions) => (instancesOrInstance: any | any[], options: any) => Promise<void>;
24
+ declare const enrichResults: (modelType: string, scopeAttributes: string[], hookType?: SupportedHookTypes, modelOptions?: ModelOptions, sadotOptions?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>) => (instancesOrInstance: any | any[], options: TransactionOptions) => Promise<void>;
7
25
  export default enrichResults;
@@ -26,10 +26,58 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.getValuesGroupByInstance = exports.getCustomFieldEntriesByInstanceId = void 0;
29
30
  /* eslint-disable no-param-reassign */
30
31
  const ValueRepo = __importStar(require("../repository/value"));
31
32
  const DefinitionRepo = __importStar(require("../repository/definition"));
33
+ const EntriesRepo = __importStar(require("../repository/entries"));
32
34
  const scopeAttributes_1 = __importDefault(require("../utils/scopeAttributes"));
35
+ // Include all required fields for proper functioning
36
+ const CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL = [
37
+ 'id',
38
+ 'name',
39
+ 'entityId',
40
+ 'fieldType',
41
+ 'displayName',
42
+ 'validation',
43
+ 'entityType',
44
+ 'modelType',
45
+ 'required',
46
+ 'disabled',
47
+ 'defaultValue',
48
+ ];
49
+ const getCustomFieldEntriesByInstanceId = async ({ instancesIds, options, sadotOptions, }) => {
50
+ if (!sadotOptions.useCustomFieldsEntries) {
51
+ return {};
52
+ }
53
+ const customFieldEntries = await EntriesRepo.findEntriesByModelIds(instancesIds, options ?? {});
54
+ const customFieldEntriesByInstanceId = Object.fromEntries(customFieldEntries.map((instanceEntries) => {
55
+ const { modelId, customFields } = instanceEntries?.dataValues ?? {};
56
+ if (!modelId) {
57
+ return undefined;
58
+ }
59
+ return [modelId, customFields];
60
+ }).filter(Boolean));
61
+ instancesIds.forEach((instanceId) => {
62
+ customFieldEntriesByInstanceId[instanceId] ?? (customFieldEntriesByInstanceId[instanceId] = {});
63
+ });
64
+ return customFieldEntriesByInstanceId;
65
+ };
66
+ exports.getCustomFieldEntriesByInstanceId = getCustomFieldEntriesByInstanceId;
67
+ const getValuesGroupByInstance = async ({ instancesIds, options, sadotOptions, }) => {
68
+ if (sadotOptions.useCustomFieldsEntries) {
69
+ return {};
70
+ }
71
+ const customFieldValues = await ValueRepo.findValuesByModelIds(instancesIds, options ?? {});
72
+ // Group fields by modelId
73
+ return customFieldValues.reduce((acc, v) => {
74
+ const { modelId } = v;
75
+ acc[modelId] ?? (acc[modelId] = []);
76
+ acc[modelId].push(v);
77
+ return acc;
78
+ }, {});
79
+ };
80
+ exports.getValuesGroupByInstance = getValuesGroupByInstance;
33
81
  /**
34
82
  * Serialize custom fields value into the format of {[name] -> [fieldData]}
35
83
  */
@@ -44,7 +92,8 @@ const serializeCustomFields = (customFieldValues, customFieldDefinitionsHash) =>
44
92
  /**
45
93
  * A hook to attach the custom fields when fetching a model instances.
46
94
  */
47
- const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {}) => async (instancesOrInstance, options) => {
95
+ const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {}, sadotOptions = { useCustomFieldsEntries: false }) => async (instancesOrInstance, options) => {
96
+ var _a;
48
97
  if (options.originalAttributes?.length > 0
49
98
  && !options.originalAttributes?.includes?.('customFields')) {
50
99
  return;
@@ -60,7 +109,28 @@ const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {})
60
109
  ...map,
61
110
  [identifier]: [],
62
111
  }), {});
63
- const customFieldDefinitions = await DefinitionRepo.findByEntityIds(modelType, uniqueIdentifiers, { transaction: options.transaction, modelOptions });
112
+ // Cache for definitions by model type and transaction to avoid redundant DB queries
113
+ let customFieldDefinitionsPromise;
114
+ let cacheKey;
115
+ if (options.transaction) {
116
+ // Initialize definition cache Map if not already present directly on the transaction object
117
+ (_a = options.transaction).definitionCache || (_a.definitionCache = new Map());
118
+ cacheKey = `${modelType}:${uniqueIdentifiers.slice().sort().join(',')}`;
119
+ customFieldDefinitionsPromise = options.transaction.definitionCache.get(cacheKey);
120
+ }
121
+ if (!customFieldDefinitionsPromise) {
122
+ // Fetch from database (either first time in this transaction or no transaction)
123
+ customFieldDefinitionsPromise = DefinitionRepo.findByEntityIds(modelType, uniqueIdentifiers, { transaction: options.transaction, modelOptions, attributes: CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL });
124
+ options.transaction?.definitionCache?.set(cacheKey, customFieldDefinitionsPromise);
125
+ }
126
+ const customFieldDefinitions = await customFieldDefinitionsPromise;
127
+ if (customFieldDefinitions.length === 0) {
128
+ // if no custom fields, we can return
129
+ instances.forEach((instance) => {
130
+ instance.customFields = {};
131
+ });
132
+ return;
133
+ }
64
134
  if (modelOptions?.include && modelOptions.useEntityIdFromInclude) {
65
135
  // if we pass useEntityIdFromInclude,
66
136
  // map the entity from the options to the identifierCustomFieldDefinitionsMapping
@@ -80,25 +150,27 @@ const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {})
80
150
  });
81
151
  // Get the values per instates ids:
82
152
  const instancesIds = instances.map((i) => i[primaryKey]);
83
- const customFieldValues = await ValueRepo.findValuesByModelIds(instancesIds, { transaction: options.transaction });
84
153
  // Group fields by modelId
85
- const valuesGroupByInstance = customFieldValues.reduce((acc, v) => {
86
- const { modelId } = v;
87
- if (!acc[modelId]) {
88
- acc[modelId] = [];
89
- }
90
- acc[modelId].push(v);
91
- return acc;
92
- }, {});
154
+ const [valuesGroupByInstance, customFieldEntriesByInstanceId] = await Promise.all([
155
+ (0, exports.getValuesGroupByInstance)({
156
+ instancesIds,
157
+ options,
158
+ sadotOptions,
159
+ }),
160
+ (0, exports.getCustomFieldEntriesByInstanceId)({
161
+ instancesIds,
162
+ options,
163
+ sadotOptions,
164
+ }),
165
+ ]);
93
166
  // Attach custom fields to the instances
94
167
  instances.forEach((instance) => {
95
- const customFields = {};
96
168
  const { id } = instance;
97
169
  const instanceValues = valuesGroupByInstance[id];
98
- if (instanceValues) {
99
- const serializedCustomFields = serializeCustomFields(instanceValues, definitionsMap);
100
- Object.assign(customFields, serializedCustomFields);
101
- }
170
+ const serializedCustomFieldsValues = instanceValues ? serializeCustomFields(instanceValues, definitionsMap) : {};
171
+ const customFields = sadotOptions.useCustomFieldsEntries
172
+ ? customFieldEntriesByInstanceId[id]
173
+ : serializedCustomFieldsValues;
102
174
  scopeAttributes.forEach((attribute) => {
103
175
  const identifier = instance[attribute];
104
176
  const entityCustomFieldDefinitions = identifierCustomFieldDefinitionsMapping[identifier];
@@ -0,0 +1,17 @@
1
+ import type { CustomFieldOptions, ModelOptions } from '../types';
2
+ /**
3
+ * Hook to handle validation and custom fields during creation
4
+ */
5
+ export declare const beforeCreate: (scopeAttributes: string[], modelOptions?: ModelOptions, sadotOptions?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>) => (instance: any, options: any) => Promise<void>;
6
+ /**
7
+ * Hook to handle validation and custom fields during update
8
+ */
9
+ export declare const beforeUpdate: (scopeAttributes: string[], modelOptions?: ModelOptions, sadotOptions?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>) => (instance: any, options: any) => Promise<void>;
10
+ /**
11
+ * Hook to enable individual hooks for bulk create operations
12
+ */
13
+ export declare const beforeBulkCreate: (options: any) => void;
14
+ /**
15
+ * Hook to enable individual hooks for bulk update operations
16
+ */
17
+ export declare const beforeBulkUpdate: (options: any) => void;
@@ -0,0 +1,379 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.beforeBulkUpdate = exports.beforeBulkCreate = exports.beforeUpdate = exports.beforeCreate = void 0;
30
+ const ajv_1 = __importDefault(require("ajv"));
31
+ const joi_1 = __importDefault(require("joi"));
32
+ const ajv_formats_1 = __importDefault(require("ajv-formats"));
33
+ const errors_1 = require("@autofleet/errors");
34
+ const ajv_errors_1 = __importDefault(require("ajv-errors"));
35
+ const logger_1 = __importDefault(require("../utils/logger"));
36
+ const ValidatorRepo = __importStar(require("../repository/validator"));
37
+ const DefinitionRepo = __importStar(require("../repository/definition"));
38
+ const errors_2 = require("../errors");
39
+ const scopeAttributes_1 = __importDefault(require("../utils/scopeAttributes"));
40
+ const updateInstanceValues_1 = __importDefault(require("./utils/updateInstanceValues"));
41
+ const constants_1 = require("../utils/constants");
42
+ // Include all required fields for proper validation
43
+ const CUSTOM_VALIDATOR_ATTRIBUTES_TO_PULL = ['id', 'schema', 'modelType', 'entityId', 'disabled'];
44
+ // Initialize Ajv with relaxed settings to avoid warnings
45
+ const ajv = new ajv_1.default({
46
+ allErrors: true,
47
+ strict: false, // Disable strict mode to avoid warnings
48
+ strictTypes: false, // Disable strict type checking
49
+ $data: true, // Enable $data references
50
+ });
51
+ (0, ajv_formats_1.default)(ajv);
52
+ (0, ajv_errors_1.default)(ajv);
53
+ /**
54
+ * Helper function to manually copy object properties
55
+ * This is more efficient for large objects and avoids excessive object creation
56
+ */
57
+ // eslint-disable-next-line prefer-object-spread
58
+ const manualObjectCopy = (sourceObj, additionalProps) => ({ __proto__: null, ...sourceObj, ...additionalProps });
59
+ /**
60
+ * Fetches complete custom fields for an instance by merging DB values with update values
61
+ * This is needed for partial updates to ensure all related fields are available for validation
62
+ */
63
+ const getCompleteCustomFields = async (instance, options) => {
64
+ // If we don't have an instance id or no custom fields being updated, return original fields
65
+ if (!instance.id || !instance.customFields || Object.keys(instance.customFields).length === 0) {
66
+ return instance.customFields || {};
67
+ }
68
+ try {
69
+ const ModelClass = instance.constructor;
70
+ // Only select the customFields column to minimize data transfer
71
+ const currentCustomFields = await ModelClass.findOne({
72
+ where: { id: instance.id },
73
+ attributes: ['customFields'],
74
+ transaction: options.transaction,
75
+ raw: true, // Get plain object instead of model instance for better performance
76
+ });
77
+ if (currentCustomFields?.customFields) {
78
+ // Merge existing fields with update fields using our helper function
79
+ const completeFields = manualObjectCopy(currentCustomFields.customFields, instance.customFields);
80
+ logger_1.default.debug('sadot - fetched complete custom fields for validation', {
81
+ fieldsCount: Object.keys(completeFields).length,
82
+ updateFieldsCount: Object.keys(instance.customFields).length,
83
+ });
84
+ return completeFields;
85
+ }
86
+ }
87
+ catch (error) {
88
+ logger_1.default.error('sadot - error fetching complete model for validation', { error });
89
+ // Continue with partial data if we can't fetch the complete model
90
+ }
91
+ return instance.customFields || {};
92
+ };
93
+ const formatAjvErrors = (errors) => errors.reduce((acc, err) => {
94
+ const basePath = (err.instancePath || '')
95
+ .split('/')
96
+ .filter(Boolean)
97
+ .join('.')
98
+ .replace(/^after\./, '');
99
+ const missingProp = err.keyword === 'required' ? `.${err.params?.missingProperty}` : '';
100
+ const key = (basePath + missingProp).replace(/^\./, '') || 'root';
101
+ const message = err.message || 'Invalid value';
102
+ acc[key] = message;
103
+ return acc;
104
+ }, {});
105
+ /**
106
+ * Validates the model using custom validators
107
+ */
108
+ const validateModel = async (instance, options, scopeAttributes, modelOptions = {}, isCreate = false) => {
109
+ var _a;
110
+ const modelType = instance.constructor.name;
111
+ logger_1.default.debug('sadot - validating model', { isCreate, modelType });
112
+ const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
113
+ logger_1.default.debug('sadot - identifiers', { identifiers });
114
+ // Skip if no identifiers
115
+ if (!identifiers || Object.keys(identifiers).length === 0) {
116
+ logger_1.default.debug('sadot - skipping validation: no identifiers');
117
+ return;
118
+ }
119
+ // Find the entityId from identifiers (fleetId, businessModelId, etc.)
120
+ const entityId = Object.values(identifiers)[0]; // Get the first value as entityId
121
+ logger_1.default.debug('sadot - entityId', { entityId });
122
+ if (!entityId) {
123
+ logger_1.default.debug('sadot - skipping validation: no entityId');
124
+ return;
125
+ }
126
+ let validatorsPromise;
127
+ let cacheKey;
128
+ if (options.transaction) {
129
+ // eslint-disable-next-line no-param-reassign
130
+ (_a = options.transaction).validationsCache || (_a.validationsCache = new Map());
131
+ cacheKey = `${modelType}-${entityId}`;
132
+ validatorsPromise = options.transaction.validationsCache.get(cacheKey);
133
+ }
134
+ if (!validatorsPromise) {
135
+ validatorsPromise = ValidatorRepo.findAllByModelType(modelType, entityId, {
136
+ transaction: options.transaction,
137
+ attributes: CUSTOM_VALIDATOR_ATTRIBUTES_TO_PULL,
138
+ ...(modelOptions.include && {
139
+ include: modelOptions.include?.(entityId),
140
+ }),
141
+ raw: true,
142
+ });
143
+ if (options.transaction) {
144
+ options?.transaction?.validationsCache.set(cacheKey, validatorsPromise);
145
+ }
146
+ }
147
+ const validators = await validatorsPromise;
148
+ logger_1.default.debug('sadot - validators found', { count: validators.length });
149
+ if (!validators.length) {
150
+ logger_1.default.debug('sadot - skipping validation: no validators found');
151
+ return;
152
+ }
153
+ // For updates, get the previous values
154
+ let originalValues = null;
155
+ if (!isCreate) {
156
+ // Create originalValues with our helper function
157
+ originalValues = manualObjectCopy(instance.previous());
158
+ // Add customFields separately
159
+ originalValues.customFields = instance.previous('customFields') || {};
160
+ }
161
+ // Get complete custom fields by merging DB values with update values
162
+ // This is especially important for partial updates to ensure all related fields are available
163
+ const completeCustomFields = !isCreate
164
+ ? await getCompleteCustomFields(instance, options)
165
+ : instance.customFields || {};
166
+ // eslint-disable-next-line no-restricted-syntax
167
+ for (const validator of validators) {
168
+ const { schema } = validator;
169
+ const typedSchema = schema;
170
+ logger_1.default.debug('sadot - validating with schema', {
171
+ schema,
172
+ hasAfterProps: !!typedSchema.properties?.after,
173
+ hasBeforeProps: !!typedSchema.properties?.before,
174
+ });
175
+ if (isCreate) {
176
+ // For create operations, we only need the 'after' state
177
+ if (typedSchema.properties?.after) {
178
+ const validateSchema = ajv.compile({
179
+ ...schema,
180
+ // Focus only on the 'after' validation part for create
181
+ properties: {
182
+ after: typedSchema.properties.after,
183
+ },
184
+ });
185
+ const isValid = validateSchema(JSON.parse(JSON.stringify({
186
+ after: {
187
+ ...instance.dataValues,
188
+ customFields: completeCustomFields,
189
+ },
190
+ })));
191
+ if (!isValid) {
192
+ const errorDetails = validateSchema.errors?.map((err) => `${err.instancePath || ''} ${err.message || 'Invalid value'}`).join(', ');
193
+ const formattedErrors = formatAjvErrors(validateSchema.errors);
194
+ throw new errors_1.BadRequest([new Error(`Validation failed for ${modelType}: ${errorDetails}`)], undefined, {
195
+ customError: formattedErrors,
196
+ });
197
+ }
198
+ }
199
+ }
200
+ else {
201
+ // For update operations, we need both before and after
202
+ const validateSchema = ajv.compile(typedSchema);
203
+ // Create after object with our helper function
204
+ const afterObj = manualObjectCopy(instance.dataValues);
205
+ // Add complete custom fields
206
+ afterObj.customFields = completeCustomFields;
207
+ // Create validation payload
208
+ const payload = {
209
+ before: originalValues,
210
+ after: afterObj,
211
+ };
212
+ // Validate
213
+ const isValid = validateSchema(JSON.parse(JSON.stringify(payload)));
214
+ logger_1.default.debug('sadot - validation result', {
215
+ isValid,
216
+ test: {
217
+ before: originalValues,
218
+ after: afterObj,
219
+ },
220
+ });
221
+ if (!isValid) {
222
+ const errorDetails = validateSchema
223
+ .errors
224
+ ?.map((err) => `${err.instancePath || ''} ${err.message || 'Invalid value'}`).join(', ');
225
+ const formattedErrors = formatAjvErrors(validateSchema.errors);
226
+ throw new errors_1.BadRequest([new Error(`Validation failed for ${modelType}: ${errorDetails}`)], undefined, {
227
+ customError: formattedErrors,
228
+ });
229
+ }
230
+ }
231
+ }
232
+ };
233
+ const getFieldDefinitions = async ({ modelType, modelOptions, identifiers, options, }) => {
234
+ const { include, useEntityIdFromInclude } = modelOptions;
235
+ const where = {
236
+ modelType,
237
+ disabled: false,
238
+ ...(!useEntityIdFromInclude && { entityId: identifiers }),
239
+ };
240
+ const fieldDefinitions = await DefinitionRepo.findAll(where, {
241
+ withDisabled: false,
242
+ transaction: options.transaction,
243
+ include: include?.(identifiers),
244
+ });
245
+ return fieldDefinitions;
246
+ };
247
+ const formatDates = (fieldDefinitions, instance) => {
248
+ (fieldDefinitions || []).forEach((fieldDefinition) => {
249
+ const { fieldType, name } = fieldDefinition;
250
+ if ([constants_1.CustomFieldDefinitionType.DATE, constants_1.CustomFieldDefinitionType.DATETIME].includes(fieldType)) {
251
+ const value = instance.customFields?.[name];
252
+ if (value) {
253
+ const { value: joiValue, error: validationError } = joi_1.default.date().validate(value);
254
+ if (validationError) {
255
+ throw new errors_2.InvalidValueError(value, name, validationError);
256
+ }
257
+ // eslint-disable-next-line no-param-reassign
258
+ instance.customFields[name] = joiValue.toISOString();
259
+ }
260
+ }
261
+ });
262
+ };
263
+ /**
264
+ * Hook to handle validation and custom fields during creation
265
+ */
266
+ const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCustomFieldsEntries: false }) => async (instance, options) => {
267
+ logger_1.default.debug('sadot - before create hook');
268
+ const { fields } = options;
269
+ const modelType = instance.constructor.name;
270
+ const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
271
+ // Step 1: Handle custom fields default values and required fields
272
+ const fieldDefinitions = await getFieldDefinitions({
273
+ modelType, modelOptions, identifiers, options,
274
+ });
275
+ // Apply default values
276
+ const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
277
+ if (fieldsWithDefaultValue.length) {
278
+ // eslint-disable-next-line no-param-reassign
279
+ instance.customFields || (instance.customFields = {});
280
+ fieldsWithDefaultValue
281
+ .filter((def) => (instance.customFields?.[def.name] === undefined))
282
+ .forEach(({ name, defaultValue }) => {
283
+ // eslint-disable-next-line no-param-reassign
284
+ instance.customFields[name] = defaultValue;
285
+ });
286
+ }
287
+ // Check for required fields
288
+ const requiredFieldsNames = Array.from(new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)));
289
+ const { customFields } = instance;
290
+ const fieldsNames = Object.keys(customFields ?? {});
291
+ const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
292
+ if (missingFields?.length) {
293
+ throw new errors_2.MissingRequiredCustomFieldError(missingFields);
294
+ }
295
+ // Step 2: Validate the model data (including custom fields)
296
+ await validateModel(instance, options, scopeAttributes, modelOptions, true);
297
+ // format date and datetime fields
298
+ formatDates(fieldDefinitions, instance);
299
+ // Step 3: Save custom field values if they exist
300
+ const customFieldsIdx = fields.indexOf('customFields');
301
+ if (customFieldsIdx === -1 || !customFields || !Object.keys(customFields).length) {
302
+ // No custom fields to update
303
+ return;
304
+ }
305
+ // Save custom field values
306
+ await (0, updateInstanceValues_1.default)({
307
+ modelId: instance.id,
308
+ modelType,
309
+ identifiers,
310
+ customFields,
311
+ options: {
312
+ useCustomFieldsEntries: sadotOptions.useCustomFieldsEntries,
313
+ transaction: options.transaction,
314
+ modelOptions,
315
+ },
316
+ });
317
+ // Remove customFields from fields array after handling
318
+ // eslint-disable-next-line no-param-reassign
319
+ fields.splice(customFieldsIdx, 1);
320
+ };
321
+ exports.beforeCreate = beforeCreate;
322
+ /**
323
+ * Hook to handle validation and custom fields during update
324
+ */
325
+ const beforeUpdate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCustomFieldsEntries: false }) => async (instance, options) => {
326
+ logger_1.default.debug('sadot - before update hook');
327
+ const { fields } = options;
328
+ const modelType = instance.constructor.name;
329
+ const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
330
+ const fieldDefinitions = await getFieldDefinitions({
331
+ modelType, modelOptions, identifiers, options,
332
+ });
333
+ // Step 1: Validate the model data (including custom fields)
334
+ await validateModel(instance, options, scopeAttributes, modelOptions, false);
335
+ // format date and datetime fields
336
+ formatDates(fieldDefinitions, instance);
337
+ // Step 2: Update custom field values if they exist
338
+ const customFieldsIdx = fields.indexOf('customFields');
339
+ if (customFieldsIdx > -1) {
340
+ const { customFields } = instance;
341
+ if (!Object.keys(customFields).length) {
342
+ return;
343
+ }
344
+ // Save custom field values
345
+ await (0, updateInstanceValues_1.default)({
346
+ modelId: instance.id,
347
+ modelType,
348
+ identifiers,
349
+ customFields,
350
+ options: {
351
+ useCustomFieldsEntries: sadotOptions.useCustomFieldsEntries,
352
+ transaction: options.transaction,
353
+ modelOptions,
354
+ },
355
+ });
356
+ // Remove customFields from fields array after handling
357
+ // eslint-disable-next-line no-param-reassign
358
+ fields.splice(customFieldsIdx, 1);
359
+ }
360
+ };
361
+ exports.beforeUpdate = beforeUpdate;
362
+ /**
363
+ * Hook to enable individual hooks for bulk create operations
364
+ */
365
+ const beforeBulkCreate = (options) => {
366
+ // This will activate the beforeCreate hook on each instance
367
+ // eslint-disable-next-line no-param-reassign
368
+ options.individualHooks = true;
369
+ };
370
+ exports.beforeBulkCreate = beforeBulkCreate;
371
+ /**
372
+ * Hook to enable individual hooks for bulk update operations
373
+ */
374
+ const beforeBulkUpdate = (options) => {
375
+ // This will activate the beforeUpdate hook on each instance
376
+ // eslint-disable-next-line no-param-reassign
377
+ options.individualHooks = true;
378
+ };
379
+ exports.beforeBulkUpdate = beforeBulkUpdate;
@@ -1,6 +1,5 @@
1
1
  import enrichResults from './enrich';
2
2
  import { beforeFind } from './find';
3
- import { beforeBulkUpdate, beforeUpdate } from './update';
4
- import { beforeBulkCreate, beforeCreate } from './create';
5
3
  import workaround from './workaround';
6
- export { enrichResults, beforeFind, beforeBulkUpdate, beforeUpdate, beforeBulkCreate, beforeCreate, workaround, };
4
+ import { beforeCreate, beforeUpdate, beforeBulkCreate, beforeBulkUpdate } from './hooks';
5
+ export { enrichResults, beforeFind, workaround, beforeCreate, beforeUpdate, beforeBulkCreate, beforeBulkUpdate, };
@@ -3,16 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.workaround = exports.beforeCreate = exports.beforeBulkCreate = exports.beforeUpdate = exports.beforeBulkUpdate = exports.beforeFind = exports.enrichResults = void 0;
6
+ exports.beforeBulkUpdate = exports.beforeBulkCreate = exports.beforeUpdate = exports.beforeCreate = exports.workaround = exports.beforeFind = exports.enrichResults = void 0;
7
7
  const enrich_1 = __importDefault(require("./enrich"));
8
8
  exports.enrichResults = enrich_1.default;
9
9
  const find_1 = require("./find");
10
10
  Object.defineProperty(exports, "beforeFind", { enumerable: true, get: function () { return find_1.beforeFind; } });
11
- const update_1 = require("./update");
12
- Object.defineProperty(exports, "beforeBulkUpdate", { enumerable: true, get: function () { return update_1.beforeBulkUpdate; } });
13
- Object.defineProperty(exports, "beforeUpdate", { enumerable: true, get: function () { return update_1.beforeUpdate; } });
14
- const create_1 = require("./create");
15
- Object.defineProperty(exports, "beforeBulkCreate", { enumerable: true, get: function () { return create_1.beforeBulkCreate; } });
16
- Object.defineProperty(exports, "beforeCreate", { enumerable: true, get: function () { return create_1.beforeCreate; } });
17
11
  const workaround_1 = __importDefault(require("./workaround"));
18
12
  exports.workaround = workaround_1.default;
13
+ const hooks_1 = require("./hooks");
14
+ Object.defineProperty(exports, "beforeCreate", { enumerable: true, get: function () { return hooks_1.beforeCreate; } });
15
+ Object.defineProperty(exports, "beforeUpdate", { enumerable: true, get: function () { return hooks_1.beforeUpdate; } });
16
+ Object.defineProperty(exports, "beforeBulkCreate", { enumerable: true, get: function () { return hooks_1.beforeBulkCreate; } });
17
+ Object.defineProperty(exports, "beforeBulkUpdate", { enumerable: true, get: function () { return hooks_1.beforeBulkUpdate; } });
@@ -1,4 +1,4 @@
1
- import type { ModelOptions } from '../types';
1
+ import type { CustomFieldOptions, ModelOptions } from '../types';
2
2
  /**
3
3
  * A hook to update the custom fields when updating a model (more then one instance).
4
4
  */
@@ -7,4 +7,4 @@ export declare const beforeBulkUpdate: (options: any) => void;
7
7
  * A hook to update the custom fields when updating a model instance.
8
8
  * TODO - cleanup if update fail
9
9
  */
10
- export declare const beforeUpdate: (scopeAttributes: string[], modelOptions?: ModelOptions) => (instance: any, options: any) => Promise<void>;
10
+ export declare const beforeUpdate: (scopeAttributes: string[], modelOptions?: ModelOptions, sadotOptions?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>) => (instance: any, options: any) => Promise<void>;