@autofleet/sadot 0.13.2-beta.0 → 0.13.2-beta.100

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,6 +1,5 @@
1
- import type { Transaction } from 'sequelize';
2
1
  import type CustomFieldValue from '../models/CustomFieldValue';
3
- import type { CustomFieldOptions, ModelOptions } from '../types';
2
+ import type { CustomFieldOptions, ModelOptions, TransactionOptions } from '../types';
4
3
  type SupportedHookTypes = 'afterFind' | 'afterCreate' | 'afterUpdate';
5
4
  type CustomFieldEntries = Record<string, any>;
6
5
  interface GetValuesGroupByInstanceResponse {
@@ -11,20 +10,16 @@ interface GetCustomFieldEntriesByInstanceIdResponse {
11
10
  }
12
11
  export declare const getCustomFieldEntriesByInstanceId: ({ instancesIds, options, sadotOptions, }: {
13
12
  instancesIds: string[];
14
- options?: {
15
- transaction: Transaction;
16
- };
13
+ options?: TransactionOptions;
17
14
  sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>;
18
15
  }) => Promise<GetCustomFieldEntriesByInstanceIdResponse>;
19
16
  export declare const getValuesGroupByInstance: ({ instancesIds, options, sadotOptions, }: {
20
17
  instancesIds: string[];
21
- options?: {
22
- transaction: Transaction;
23
- };
18
+ options?: TransactionOptions;
24
19
  sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>;
25
20
  }) => Promise<GetValuesGroupByInstanceResponse>;
26
21
  /**
27
22
  * A hook to attach the custom fields when fetching a model instances.
28
23
  */
29
- declare const enrichResults: (modelType: string, scopeAttributes: string[], hookType?: SupportedHookTypes, modelOptions?: ModelOptions, sadotOptions?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>) => (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>;
30
25
  export default enrichResults;
@@ -27,10 +27,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.getValuesGroupByInstance = exports.getCustomFieldEntriesByInstanceId = void 0;
30
+ /* eslint-disable no-param-reassign */
30
31
  const ValueRepo = __importStar(require("../repository/value"));
31
32
  const DefinitionRepo = __importStar(require("../repository/definition"));
32
33
  const EntriesRepo = __importStar(require("../repository/entries"));
33
34
  const scopeAttributes_1 = __importDefault(require("../utils/scopeAttributes"));
35
+ const CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL = ['id', 'name', 'entityId'];
34
36
  const getCustomFieldEntriesByInstanceId = async ({ instancesIds, options, sadotOptions, }) => {
35
37
  if (!sadotOptions.useCustomFieldsEntries) {
36
38
  return {};
@@ -93,7 +95,37 @@ const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {},
93
95
  ...map,
94
96
  [identifier]: [],
95
97
  }), {});
96
- const customFieldDefinitions = await DefinitionRepo.findByEntityIds(modelType, uniqueIdentifiers, { transaction: options.transaction, modelOptions });
98
+ // Cache for definitions by model type and transaction to avoid redundant DB queries
99
+ let customFieldDefinitionsPromise;
100
+ let cacheKey;
101
+ // Check if caching is disabled via environment variable
102
+ const cachingDisabled = process.env.SADOT_DISABLE_DEFINITION_CACHE === 'true';
103
+ if (options.transaction && !cachingDisabled) {
104
+ // Initialize definition cache Map if not already present directly on the transaction object
105
+ options.transaction.definitionCache = options.transaction.definitionCache || new Map();
106
+ // Create a unique cache key combining model type and sorted identifiers
107
+ cacheKey = `${modelType}:${uniqueIdentifiers.sort().join(',')}`;
108
+ // Check if these definitions are already cached for this transaction
109
+ if (options.transaction.definitionCache.has(cacheKey)) {
110
+ // Use cached result to avoid duplicate database queries
111
+ customFieldDefinitionsPromise = options.transaction.definitionCache.get(cacheKey);
112
+ }
113
+ }
114
+ if (!customFieldDefinitionsPromise) {
115
+ // Fetch from database (either first time in this transaction or no transaction)
116
+ customFieldDefinitionsPromise = DefinitionRepo.findByEntityIds(modelType, uniqueIdentifiers, { transaction: options.transaction, modelOptions, attributes: CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL });
117
+ // Store in cache if within a transaction and caching isn't disabled
118
+ if (options.transaction && options.transaction.definitionCache) {
119
+ options.transaction.definitionCache.set(cacheKey, customFieldDefinitionsPromise);
120
+ }
121
+ }
122
+ const customFieldDefinitions = await customFieldDefinitionsPromise;
123
+ if (customFieldDefinitions.length === 0) {
124
+ // if no custom fields, we can return
125
+ instances.forEach((instance) => {
126
+ instance.customFields = {};
127
+ });
128
+ }
97
129
  if (modelOptions?.include && modelOptions.useEntityIdFromInclude) {
98
130
  // if we pass useEntityIdFromInclude,
99
131
  // map the entity from the options to the identifierCustomFieldDefinitionsMapping
@@ -114,16 +146,18 @@ const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {},
114
146
  // Get the values per instates ids:
115
147
  const instancesIds = instances.map((i) => i[primaryKey]);
116
148
  // Group fields by modelId
117
- const valuesGroupByInstance = await (0, exports.getValuesGroupByInstance)({
118
- instancesIds,
119
- options,
120
- sadotOptions,
121
- });
122
- const customFieldEntriesByInstanceId = await (0, exports.getCustomFieldEntriesByInstanceId)({
123
- instancesIds,
124
- options,
125
- sadotOptions,
126
- });
149
+ const [valuesGroupByInstance, customFieldEntriesByInstanceId] = await Promise.all([
150
+ (0, exports.getValuesGroupByInstance)({
151
+ instancesIds,
152
+ options,
153
+ sadotOptions,
154
+ }),
155
+ (0, exports.getCustomFieldEntriesByInstanceId)({
156
+ instancesIds,
157
+ options,
158
+ sadotOptions,
159
+ }),
160
+ ]);
127
161
  // Attach custom fields to the instances
128
162
  instances.forEach((instance) => {
129
163
  const { id } = instance;
@@ -28,6 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.beforeBulkUpdate = exports.beforeBulkCreate = exports.beforeUpdate = exports.beforeCreate = void 0;
30
30
  const ajv_1 = __importDefault(require("ajv"));
31
+ const joi_1 = __importDefault(require("joi"));
31
32
  const ajv_formats_1 = __importDefault(require("ajv-formats"));
32
33
  const errors_1 = require("@autofleet/errors");
33
34
  const logger_1 = __importDefault(require("../utils/logger"));
@@ -105,7 +106,24 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
105
106
  logger_1.default.debug('sadot - skipping validation: no entityId');
106
107
  return;
107
108
  }
108
- const validators = await ValidatorRepo.findAllByModelType(modelType, entityId, { transaction: options.transaction });
109
+ let validatorsPromise;
110
+ let cacheKey;
111
+ if (options.transaction) {
112
+ // eslint-disable-next-line no-param-reassign
113
+ options.transaction.validationsCache = options.transaction.validationsCache || new Map();
114
+ cacheKey = `${modelType}-${entityId}`;
115
+ validatorsPromise = options.transaction.validationsCache.get(cacheKey);
116
+ }
117
+ if (!validatorsPromise) {
118
+ validatorsPromise = ValidatorRepo.findAllByModelType(modelType, entityId, {
119
+ transaction: options.transaction,
120
+ attributes: ['schema'],
121
+ });
122
+ if (options.transaction) {
123
+ options.transaction.validationsCache.set(cacheKey, validatorsPromise);
124
+ }
125
+ }
126
+ const validators = await validatorsPromise;
109
127
  logger_1.default.debug('sadot - validators found', { count: validators.length });
110
128
  if (!validators.length) {
111
129
  logger_1.default.debug('sadot - skipping validation: no validators found');
@@ -124,16 +142,6 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
124
142
  const completeCustomFields = !isCreate
125
143
  ? await getCompleteCustomFields(instance, options)
126
144
  : instance.customFields || {};
127
- // For debugging in case of update
128
- if (!isCreate && process.env.NODE_ENV !== 'production') {
129
- // Create after object for logging
130
- const logAfterObj = manualObjectCopy(instance.dataValues, { customFields: completeCustomFields });
131
- logger_1.default.debug('sadot - validate with values', {
132
- before: originalValues,
133
- after: logAfterObj,
134
- schema: validators[0].schema,
135
- });
136
- }
137
145
  // eslint-disable-next-line no-restricted-syntax
138
146
  for (const validator of validators) {
139
147
  const { schema } = validator;
@@ -196,7 +204,7 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
196
204
  }
197
205
  }
198
206
  };
199
- const getFieldDefinitions = async ({ modelType, modelOptions, identifiers, options }) => {
207
+ const getFieldDefinitions = async ({ modelType, modelOptions, identifiers, options, }) => {
200
208
  const { include, useEntityIdFromInclude } = modelOptions;
201
209
  const where = {
202
210
  modelType,
@@ -210,14 +218,18 @@ const getFieldDefinitions = async ({ modelType, modelOptions, identifiers, optio
210
218
  });
211
219
  return fieldDefinitions;
212
220
  };
213
- const formatDates = (fieldDefinitions = [], instance) => {
214
- fieldDefinitions.forEach((fieldDefinition) => {
221
+ const formatDates = (fieldDefinitions, instance) => {
222
+ (fieldDefinitions || []).forEach((fieldDefinition) => {
215
223
  const { fieldType, name } = fieldDefinition;
216
224
  if ([constants_1.CustomFieldDefinitionType.DATE, constants_1.CustomFieldDefinitionType.DATETIME].includes(fieldType)) {
217
225
  const value = instance.customFields?.[name];
218
226
  if (value) {
227
+ const { value: joiValue, error: validationError } = joi_1.default.date().validate(value);
228
+ if (validationError) {
229
+ throw new errors_2.InvalidValueError(value, name, validationError);
230
+ }
219
231
  // eslint-disable-next-line no-param-reassign
220
- instance.customFields[name] = new Date(value).toISOString();
232
+ instance.customFields[name] = joiValue.toISOString();
221
233
  }
222
234
  }
223
235
  });
@@ -232,7 +244,7 @@ const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
232
244
  const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
233
245
  // Step 1: Handle custom fields default values and required fields
234
246
  const fieldDefinitions = await getFieldDefinitions({
235
- modelType, modelOptions, identifiers, options
247
+ modelType, modelOptions, identifiers, options,
236
248
  });
237
249
  // Apply default values
238
250
  const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
@@ -290,7 +302,7 @@ const beforeUpdate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
290
302
  const modelType = instance.constructor.name;
291
303
  const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
292
304
  const fieldDefinitions = await getFieldDefinitions({
293
- modelType, modelOptions, identifiers, options
305
+ modelType, modelOptions, identifiers, options,
294
306
  });
295
307
  // Step 1: Validate the model data (including custom fields)
296
308
  await validateModel(instance, options, scopeAttributes, false);
@@ -26,12 +26,8 @@ exports.CustomValidator = CustomValidator_1.default;
26
26
  const productionModels = [CustomFieldDefinition_1.default, CustomFieldValue_1.default, CustomValidator_1.default];
27
27
  const testModels = [TestModel_1.default, AssociatedTestModel_1.default, ContextAwareTestModel_1.default, ContextTestModel_1.default];
28
28
  const SADOT_MIGRATION_PREFIX = 'sadot-migration';
29
- const SCHEMA_VERSION = 'fb0fa867-1241-4816-b08d-5ed9060c7ae5';
30
- const initTables = async (sequelize, getUser, { schemaPrefix, schemaVersion, useCustomFieldsEntries, } = {
31
- schemaPrefix: SADOT_MIGRATION_PREFIX,
32
- schemaVersion: SCHEMA_VERSION,
33
- useCustomFieldsEntries: false,
34
- }) => {
29
+ const SCHEMA_VERSION = '49c9dd1d-b1cc-445b-a911-fd349d783110';
30
+ const initTables = async (sequelize, getUser, { schemaPrefix = SADOT_MIGRATION_PREFIX, schemaVersion = SCHEMA_VERSION, useCustomFieldsEntries = false, } = {}) => {
35
31
  const CUSTOM_FIELDS_SCHEMA_VERSION = `${schemaPrefix}_${schemaVersion}${useCustomFieldsEntries ? '_withEntries' : ''}`;
36
32
  logger_1.default.info('custom-fields: initialize custom-fields tables');
37
33
  // Detect models and import them to the orm
@@ -87,10 +83,13 @@ const initTables = async (sequelize, getUser, { schemaPrefix, schemaVersion, use
87
83
  timestamps: false,
88
84
  schema: 'public',
89
85
  });
86
+ logger_1.default.info('custom-fields: starting migrations');
90
87
  const migrations = await SequelizeMeta.findAll({ where: { name: { [sequelize_1.Op.like]: `${schemaPrefix}%` } }, raw: true });
91
88
  const currentSadotSchemaVersion = migrations.at(-1);
92
89
  const expectedSchemaVersionIndex = migrations.findIndex((m) => m.name === CUSTOM_FIELDS_SCHEMA_VERSION);
90
+ logger_1.default.info('custom-fields: migrations', { migrations, currentSadotSchemaVersion, expectedSchemaVersionIndex });
93
91
  if (!currentSadotSchemaVersion || currentSadotSchemaVersion.name !== CUSTOM_FIELDS_SCHEMA_VERSION) {
92
+ logger_1.default.info('custom-fields: syncing models');
94
93
  await CustomFieldDefinition_1.default.sync({ alter: true });
95
94
  await CustomFieldValue_1.default.sync({ alter: true });
96
95
  // T.Y TODO: Remove the if statement once we're ready to add the new entries table for all MS
@@ -2,6 +2,7 @@ import type { Transactionable } from 'sequelize';
2
2
  import { CustomValidator } from '../models';
3
3
  export interface FindValidatorOptions extends Transactionable {
4
4
  withDisabled?: boolean;
5
+ attributes?: string[];
5
6
  }
6
7
  export interface ValidatorAttributes {
7
8
  entityId: string;
@@ -1,7 +1,13 @@
1
- import type { IncludeOptions } from 'sequelize';
1
+ import type { IncludeOptions, Transaction } from 'sequelize';
2
2
  import type { ModelCtor, Sequelize } from 'sequelize-typescript';
3
3
  import type { getUser as GetUserType } from '@autofleet/zehut';
4
+ import type CustomFieldDefinition from '../models/CustomFieldDefinition';
4
5
  export type ModelFetcher = (name: string) => any;
6
+ export interface TransactionOptions extends Record<string, any> {
7
+ transaction?: Transaction & {
8
+ definitionCache?: Map<string, CustomFieldDefinition[]>;
9
+ };
10
+ }
5
11
  export type ModelOptions = {
6
12
  /**
7
13
  * Include options for the model
@@ -16,6 +22,10 @@ export type ModelOptions = {
16
22
  * Whether to use the entity id from the instance per scope attribute
17
23
  */
18
24
  useEntityIdFromInclude?: boolean;
25
+ /**
26
+ * Which attributes to include in the model
27
+ */
28
+ attributes?: string[];
19
29
  };
20
30
  export type Models = {
21
31
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.13.2-beta.0",
3
+ "version": "0.13.2-beta.100",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -1,14 +1,15 @@
1
1
  /* eslint-disable no-param-reassign */
2
- import type { Transaction } from 'sequelize';
3
2
  import * as ValueRepo from '../repository/value';
4
3
  import * as DefinitionRepo from '../repository/definition';
5
4
  import * as EntriesRepo from '../repository/entries';
6
5
  import type CustomFieldValue from '../models/CustomFieldValue';
7
6
  import type CustomFieldDefinition from '../models/CustomFieldDefinition';
8
7
  import type { SerializedCustomFields } from '../types/definition';
9
- import type { CustomFieldOptions, ModelOptions } from '../types';
8
+ import type { CustomFieldOptions, ModelOptions, TransactionOptions } from '../types';
10
9
  import applyScopeToInstance from '../utils/scopeAttributes';
11
10
 
11
+ const CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL = ['id', 'name', 'entityId'];
12
+
12
13
  type SupportedHookTypes = 'afterFind' | 'afterCreate' | 'afterUpdate';
13
14
 
14
15
  type CustomFieldEntries = Record<string, any>;
@@ -27,7 +28,7 @@ export const getCustomFieldEntriesByInstanceId = async ({
27
28
  sadotOptions,
28
29
  }: {
29
30
  instancesIds: string[],
30
- options?: { transaction: Transaction },
31
+ options?: TransactionOptions,
31
32
  sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>,
32
33
  }): Promise<GetCustomFieldEntriesByInstanceIdResponse> => {
33
34
  if (!sadotOptions.useCustomFieldsEntries) {
@@ -60,7 +61,7 @@ export const getValuesGroupByInstance = async ({
60
61
  sadotOptions,
61
62
  }: {
62
63
  instancesIds: string[],
63
- options?: { transaction: Transaction },
64
+ options?: TransactionOptions,
64
65
  sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>,
65
66
  }): Promise<GetValuesGroupByInstanceResponse> => {
66
67
  if (sadotOptions.useCustomFieldsEntries) {
@@ -108,7 +109,7 @@ const enrichResults = (
108
109
  sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'> = { useCustomFieldsEntries: false },
109
110
  ) => async (
110
111
  instancesOrInstance: any | any[],
111
- options,
112
+ options: TransactionOptions,
112
113
  ): Promise<void> => {
113
114
  if (
114
115
  options.originalAttributes?.length > 0
@@ -131,14 +132,50 @@ const enrichResults = (
131
132
  const identifierCustomFieldDefinitionsMapping = uniqueIdentifiers.reduce((map, identifier) => ({
132
133
  ...map,
133
134
  [identifier]: [],
134
-
135
135
  }), {});
136
136
 
137
- const customFieldDefinitions = await DefinitionRepo.findByEntityIds(
138
- modelType,
139
- uniqueIdentifiers,
140
- { transaction: options.transaction, modelOptions },
141
- );
137
+ // Cache for definitions by model type and transaction to avoid redundant DB queries
138
+ let customFieldDefinitionsPromise;
139
+ let cacheKey;
140
+
141
+ // Check if caching is disabled via environment variable
142
+ const cachingDisabled = process.env.SADOT_DISABLE_DEFINITION_CACHE === 'true';
143
+
144
+ if (options.transaction && !cachingDisabled) {
145
+ // Initialize definition cache Map if not already present directly on the transaction object
146
+ options.transaction.definitionCache = options.transaction.definitionCache || new Map();
147
+
148
+ // Create a unique cache key combining model type and sorted identifiers
149
+ cacheKey = `${modelType}:${uniqueIdentifiers.sort().join(',')}`;
150
+
151
+ // Check if these definitions are already cached for this transaction
152
+ if (options.transaction.definitionCache.has(cacheKey)) {
153
+ // Use cached result to avoid duplicate database queries
154
+ customFieldDefinitionsPromise = options.transaction.definitionCache.get(cacheKey);
155
+ }
156
+ }
157
+
158
+ if (!customFieldDefinitionsPromise) {
159
+ // Fetch from database (either first time in this transaction or no transaction)
160
+ customFieldDefinitionsPromise = DefinitionRepo.findByEntityIds(
161
+ modelType,
162
+ uniqueIdentifiers,
163
+ { transaction: options.transaction, modelOptions, attributes: CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL },
164
+ );
165
+
166
+ // Store in cache if within a transaction and caching isn't disabled
167
+ if (options.transaction && options.transaction.definitionCache) {
168
+ options.transaction.definitionCache.set(cacheKey, customFieldDefinitionsPromise);
169
+ }
170
+ }
171
+ const customFieldDefinitions = await customFieldDefinitionsPromise;
172
+
173
+ if (customFieldDefinitions.length === 0) {
174
+ // if no custom fields, we can return
175
+ instances.forEach((instance) => {
176
+ instance.customFields = {};
177
+ });
178
+ }
142
179
 
143
180
  if (modelOptions?.include && modelOptions.useEntityIdFromInclude) {
144
181
  // if we pass useEntityIdFromInclude,
@@ -164,17 +201,18 @@ const enrichResults = (
164
201
  const instancesIds = instances.map((i) => i[primaryKey]);
165
202
 
166
203
  // Group fields by modelId
167
- const valuesGroupByInstance = await getValuesGroupByInstance({
168
- instancesIds,
169
- options,
170
- sadotOptions,
171
- });
172
-
173
- const customFieldEntriesByInstanceId = await getCustomFieldEntriesByInstanceId({
174
- instancesIds,
175
- options,
176
- sadotOptions,
177
- });
204
+ const [valuesGroupByInstance, customFieldEntriesByInstanceId] = await Promise.all([
205
+ getValuesGroupByInstance({
206
+ instancesIds,
207
+ options,
208
+ sadotOptions,
209
+ }),
210
+ getCustomFieldEntriesByInstanceId({
211
+ instancesIds,
212
+ options,
213
+ sadotOptions,
214
+ }),
215
+ ]);
178
216
 
179
217
  // Attach custom fields to the instances
180
218
  instances.forEach((instance) => {
@@ -105,11 +105,29 @@ const validateModel = async (
105
105
  return;
106
106
  }
107
107
 
108
- const validators = await ValidatorRepo.findAllByModelType(
109
- modelType,
110
- entityId,
111
- { transaction: options.transaction },
112
- );
108
+ let validatorsPromise;
109
+ let cacheKey;
110
+ if (options.transaction) {
111
+ // eslint-disable-next-line no-param-reassign
112
+ options.transaction.validationsCache = options.transaction.validationsCache || new Map();
113
+ cacheKey = `${modelType}-${entityId}`;
114
+ validatorsPromise = options.transaction.validationsCache.get(cacheKey);
115
+ }
116
+
117
+ if (!validatorsPromise) {
118
+ validatorsPromise = ValidatorRepo.findAllByModelType(
119
+ modelType,
120
+ entityId,
121
+ {
122
+ transaction: options.transaction,
123
+ attributes: ['schema'],
124
+ },
125
+ );
126
+ if (options.transaction) {
127
+ options.transaction.validationsCache.set(cacheKey, validatorsPromise);
128
+ }
129
+ }
130
+ const validators = await validatorsPromise;
113
131
 
114
132
  logger.debug('sadot - validators found', { count: validators.length });
115
133
 
@@ -134,17 +152,6 @@ const validateModel = async (
134
152
  ? await getCompleteCustomFields(instance, options)
135
153
  : instance.customFields || {};
136
154
 
137
- // For debugging in case of update
138
- if (!isCreate && process.env.NODE_ENV !== 'production') {
139
- // Create after object for logging
140
- const logAfterObj = manualObjectCopy(instance.dataValues, { customFields: completeCustomFields });
141
-
142
- logger.debug('sadot - validate with values', {
143
- before: originalValues,
144
- after: logAfterObj,
145
- schema: validators[0].schema,
146
- });
147
- }
148
155
 
149
156
  // eslint-disable-next-line no-restricted-syntax
150
157
  for (const validator of validators) {
@@ -253,12 +260,12 @@ const formatDates = (fieldDefinitions: CustomFieldDefinition[], instance: any) =
253
260
  if ([CustomFieldDefinitionType.DATE, CustomFieldDefinitionType.DATETIME].includes(fieldType)) {
254
261
  const value = instance.customFields?.[name];
255
262
  if (value) {
256
- const validationError = Joi.date().validate(value).error;
263
+ const { value: joiValue, error: validationError } = Joi.date().validate(value);
257
264
  if (validationError) {
258
265
  throw new InvalidValueError(value, name, validationError);
259
266
  }
260
267
  // eslint-disable-next-line no-param-reassign
261
- instance.customFields[name] = new Date(value).toISOString();
268
+ instance.customFields[name] = joiValue.toISOString();
262
269
  }
263
270
  }
264
271
  });
@@ -23,20 +23,16 @@ const productionModels: ProductionModel[] = [CustomFieldDefinition, CustomFieldV
23
23
  const testModels = [TestModel, AssociatedTestModel, ContextAwareTestModel, ContextTestModel];
24
24
 
25
25
  const SADOT_MIGRATION_PREFIX = 'sadot-migration';
26
- const SCHEMA_VERSION = 'fb0fa867-1241-4816-b08d-5ed9060c7ae5';
26
+ const SCHEMA_VERSION = '49c9dd1d-b1cc-445b-a911-fd349d783110';
27
27
 
28
28
  const initTables = async (
29
29
  sequelize: Sequelize,
30
30
  getUser: CustomFieldOptions['getUser'],
31
31
  {
32
- schemaPrefix,
33
- schemaVersion,
34
- useCustomFieldsEntries,
35
- }: InitTablesOptions = {
36
- schemaPrefix: SADOT_MIGRATION_PREFIX,
37
- schemaVersion: SCHEMA_VERSION,
38
- useCustomFieldsEntries: false,
39
- },
32
+ schemaPrefix = SADOT_MIGRATION_PREFIX,
33
+ schemaVersion = SCHEMA_VERSION,
34
+ useCustomFieldsEntries = false,
35
+ }: InitTablesOptions = {},
40
36
  ): Promise<void> => {
41
37
  const CUSTOM_FIELDS_SCHEMA_VERSION = `${schemaPrefix}_${schemaVersion}${useCustomFieldsEntries ? '_withEntries' : ''}`;
42
38
  logger.info('custom-fields: initialize custom-fields tables');
@@ -104,11 +100,14 @@ const initTables = async (
104
100
  },
105
101
  );
106
102
 
103
+ logger.info('custom-fields: starting migrations');
107
104
  const migrations = await SequelizeMeta.findAll({ where: { name: { [Op.like]: `${schemaPrefix}%` } }, raw: true });
108
105
  const currentSadotSchemaVersion = migrations.at(-1);
109
106
  const expectedSchemaVersionIndex = migrations.findIndex((m) => (m as any).name === CUSTOM_FIELDS_SCHEMA_VERSION);
110
107
 
108
+ logger.info('custom-fields: migrations', { migrations, currentSadotSchemaVersion, expectedSchemaVersionIndex });
111
109
  if (!currentSadotSchemaVersion || (currentSadotSchemaVersion as any).name !== CUSTOM_FIELDS_SCHEMA_VERSION) {
110
+ logger.info('custom-fields: syncing models');
112
111
  await CustomFieldDefinition.sync({ alter: true });
113
112
  await CustomFieldValue.sync({ alter: true });
114
113
  // T.Y TODO: Remove the if statement once we're ready to add the new entries table for all MS
@@ -4,6 +4,7 @@ import { CustomValidator } from '../models';
4
4
 
5
5
  export interface FindValidatorOptions extends Transactionable {
6
6
  withDisabled?: boolean;
7
+ attributes?: string[];
7
8
  }
8
9
 
9
10
  // Make sure this interface is compatible with the Sequelize model
@@ -1,9 +1,16 @@
1
- import type { IncludeOptions } from 'sequelize';
1
+ import type { IncludeOptions, Transaction } from 'sequelize';
2
2
  import type { ModelCtor, Sequelize } from 'sequelize-typescript';
3
3
  import type { getUser as GetUserType } from '@autofleet/zehut';
4
+ import type CustomFieldDefinition from '../models/CustomFieldDefinition';
4
5
 
5
6
  export type ModelFetcher = (name: string) => any;
6
7
 
8
+ export interface TransactionOptions extends Record<string, any> {
9
+ transaction?: Transaction & {
10
+ definitionCache?: Map<string, CustomFieldDefinition[]>;
11
+ };
12
+ }
13
+
7
14
  export type ModelOptions = {
8
15
  /**
9
16
  * Include options for the model
@@ -19,6 +26,10 @@ export type ModelOptions = {
19
26
  * Whether to use the entity id from the instance per scope attribute
20
27
  */
21
28
  useEntityIdFromInclude?: boolean
29
+ /**
30
+ * Which attributes to include in the model
31
+ */
32
+ attributes?: string[];
22
33
  }
23
34
  export type Models = {
24
35
  name: string;