@autofleet/sadot 0.13.3-beta-1bef9935.8 → 0.13.3

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 {};
@@ -78,6 +80,7 @@ const serializeCustomFields = (customFieldValues, customFieldDefinitionsHash) =>
78
80
  * A hook to attach the custom fields when fetching a model instances.
79
81
  */
80
82
  const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {}, sadotOptions = { useCustomFieldsEntries: false }) => async (instancesOrInstance, options) => {
83
+ var _a;
81
84
  if (options.originalAttributes?.length > 0
82
85
  && !options.originalAttributes?.includes?.('customFields')) {
83
86
  return;
@@ -93,7 +96,27 @@ const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {},
93
96
  ...map,
94
97
  [identifier]: [],
95
98
  }), {});
96
- const customFieldDefinitions = await DefinitionRepo.findByEntityIds(modelType, uniqueIdentifiers, { transaction: options.transaction, modelOptions });
99
+ // Cache for definitions by model type and transaction to avoid redundant DB queries
100
+ let customFieldDefinitionsPromise;
101
+ let cacheKey;
102
+ if (options.transaction) {
103
+ // Initialize definition cache Map if not already present directly on the transaction object
104
+ (_a = options.transaction).definitionCache || (_a.definitionCache = new Map());
105
+ cacheKey = `${modelType}:${uniqueIdentifiers.slice().sort().join(',')}`;
106
+ customFieldDefinitionsPromise = options.transaction.definitionCache.get(cacheKey);
107
+ }
108
+ if (!customFieldDefinitionsPromise) {
109
+ // Fetch from database (either first time in this transaction or no transaction)
110
+ customFieldDefinitionsPromise = DefinitionRepo.findByEntityIds(modelType, uniqueIdentifiers, { transaction: options.transaction, modelOptions, attributes: CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL });
111
+ options.transaction?.definitionCache?.set(cacheKey, customFieldDefinitionsPromise);
112
+ }
113
+ const customFieldDefinitions = await customFieldDefinitionsPromise;
114
+ if (customFieldDefinitions.length === 0) {
115
+ // if no custom fields, we can return
116
+ instances.forEach((instance) => {
117
+ instance.customFields = {};
118
+ });
119
+ }
97
120
  if (modelOptions?.include && modelOptions.useEntityIdFromInclude) {
98
121
  // if we pass useEntityIdFromInclude,
99
122
  // map the entity from the options to the identifierCustomFieldDefinitionsMapping
@@ -114,16 +137,18 @@ const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {},
114
137
  // Get the values per instates ids:
115
138
  const instancesIds = instances.map((i) => i[primaryKey]);
116
139
  // 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
- });
140
+ const [valuesGroupByInstance, customFieldEntriesByInstanceId] = await Promise.all([
141
+ (0, exports.getValuesGroupByInstance)({
142
+ instancesIds,
143
+ options,
144
+ sadotOptions,
145
+ }),
146
+ (0, exports.getCustomFieldEntriesByInstanceId)({
147
+ instancesIds,
148
+ options,
149
+ sadotOptions,
150
+ }),
151
+ ]);
127
152
  // Attach custom fields to the instances
128
153
  instances.forEach((instance) => {
129
154
  const { id } = instance;
@@ -38,6 +38,7 @@ const errors_2 = require("../errors");
38
38
  const scopeAttributes_1 = __importDefault(require("../utils/scopeAttributes"));
39
39
  const updateInstanceValues_1 = __importDefault(require("./utils/updateInstanceValues"));
40
40
  const constants_1 = require("../utils/constants");
41
+ const CUSTOM_VALIDATOR_ATTRIBUTES_TO_PULL = ['schema'];
41
42
  // Initialize Ajv with relaxed settings to avoid warnings
42
43
  const ajv = new ajv_1.default({
43
44
  allErrors: true,
@@ -90,6 +91,7 @@ const getCompleteCustomFields = async (instance, options) => {
90
91
  * Validates the model using custom validators
91
92
  */
92
93
  const validateModel = async (instance, options, scopeAttributes, isCreate = false) => {
94
+ var _a;
93
95
  const modelType = instance.constructor.name;
94
96
  logger_1.default.debug('sadot - validating model', { isCreate, modelType });
95
97
  const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
@@ -106,7 +108,24 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
106
108
  logger_1.default.debug('sadot - skipping validation: no entityId');
107
109
  return;
108
110
  }
109
- const validators = await ValidatorRepo.findAllByModelType(modelType, entityId, { transaction: options.transaction });
111
+ let validatorsPromise;
112
+ let cacheKey;
113
+ if (options.transaction) {
114
+ // eslint-disable-next-line no-param-reassign
115
+ (_a = options.transaction).validationsCache || (_a.validationsCache = new Map());
116
+ cacheKey = `${modelType}-${entityId}`;
117
+ validatorsPromise = options.transaction.validationsCache.get(cacheKey);
118
+ }
119
+ if (!validatorsPromise) {
120
+ validatorsPromise = ValidatorRepo.findAllByModelType(modelType, entityId, {
121
+ transaction: options.transaction,
122
+ attributes: CUSTOM_VALIDATOR_ATTRIBUTES_TO_PULL,
123
+ });
124
+ if (options.transaction) {
125
+ options?.transaction?.validationsCache.set(cacheKey, validatorsPromise);
126
+ }
127
+ }
128
+ const validators = await validatorsPromise;
110
129
  logger_1.default.debug('sadot - validators found', { count: validators.length });
111
130
  if (!validators.length) {
112
131
  logger_1.default.debug('sadot - skipping validation: no validators found');
@@ -125,16 +144,6 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
125
144
  const completeCustomFields = !isCreate
126
145
  ? await getCompleteCustomFields(instance, options)
127
146
  : instance.customFields || {};
128
- // For debugging in case of update
129
- if (!isCreate && process.env.NODE_ENV !== 'production') {
130
- // Create after object for logging
131
- const logAfterObj = manualObjectCopy(instance.dataValues, { customFields: completeCustomFields });
132
- logger_1.default.debug('sadot - validate with values', {
133
- before: originalValues,
134
- after: logAfterObj,
135
- schema: validators[0].schema,
136
- });
137
- }
138
147
  // eslint-disable-next-line no-restricted-syntax
139
148
  for (const validator of validators) {
140
149
  const { schema } = validator;
@@ -180,7 +189,7 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
180
189
  };
181
190
  // Validate
182
191
  const isValid = validateSchema(JSON.parse(JSON.stringify(payload)));
183
- logger_1.default.info('sadot - validation result', {
192
+ logger_1.default.debug('sadot - validation result', {
184
193
  isValid,
185
194
  test: {
186
195
  before: originalValues,
@@ -188,10 +197,9 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
188
197
  },
189
198
  });
190
199
  if (!isValid) {
191
- const errorDetails = validateSchema.errors?.map((err) => {
192
- logger_1.default.info('dor', err);
193
- return `${err.instancePath || ''} ${err.message || 'Invalid value'}`;
194
- }).join(', ');
200
+ const errorDetails = validateSchema
201
+ .errors
202
+ ?.map((err) => `${err.instancePath || ''} ${err.message || 'Invalid value'}`).join(', ');
195
203
  throw new errors_1.BadRequest([new Error(`Validation failed for ${modelType}: ${errorDetails}`)]);
196
204
  }
197
205
  }
package/dist/index.js CHANGED
@@ -43,36 +43,21 @@ __exportStar(require("./utils/helpers"), exports);
43
43
  * @see {@link 'custom-fields/config'} for configurations
44
44
  */
45
45
  const useCustomFields = async (app, getModel, options) => {
46
- console.log('useCustomFields: start');
47
- (0, logger_1.tryAddingTraceIdMiddleware)()
48
- .then(() => console.log('useCustomFields: tryAddingTraceIdMiddleware done'))
49
- .catch(() => console.log('useCustomFields: tryAddingTraceIdMiddleware failed'));
46
+ (0, logger_1.tryAddingTraceIdMiddleware)().catch(() => null);
50
47
  const { models, useCustomFieldsEntries } = options;
51
- console.log('useCustomFields: options extracted', { models, useCustomFieldsEntries });
52
48
  if (app) {
53
- console.log('useCustomFields: app exists, setting up /api route');
54
49
  app.use('/api', api_1.default);
55
50
  }
56
- else {
57
- console.log('useCustomFields: no app provided');
58
- }
59
51
  const sequelize = options.sequelize ?? (0, db_1.default)(options.databaseConfig);
60
- if (!options.sequelize) {
61
- console.log('useCustomFields: initialized new Sequelize instance');
62
- }
63
- else {
64
- console.log('useCustomFields: using provided Sequelize instance');
52
+ if (process.env.NODE_ENV === 'test') {
53
+ await (0, models_1.initTestModels)(sequelize);
65
54
  }
66
- console.log('useCustomFields: adding hooks');
55
+ // The order is important
67
56
  (0, init_1.addHooks)(models, getModel, { useCustomFieldsEntries });
68
- console.log('useCustomFields: initializing tables');
69
57
  await (0, models_1.initTables)(sequelize, options.getUser, { useCustomFieldsEntries });
70
- console.log('useCustomFields: adding scopes');
71
58
  (0, init_1.addScopes)(models, getModel, { useCustomFieldsEntries });
72
- console.log('useCustomFields: applying custom associations');
73
59
  (0, init_1.applyCustomAssociation)(models);
74
60
  logger_1.default.debug('sadot - custom fields finished initializing with models', models);
75
- console.log('useCustomFields: finished and returning sequelize');
76
61
  return sequelize;
77
62
  };
78
63
  exports.default = useCustomFields;
@@ -112,44 +112,20 @@ const initTables = async (sequelize, getUser, { schemaPrefix = SADOT_MIGRATION_P
112
112
  };
113
113
  exports.initTables = initTables;
114
114
  const initTestModels = async (sequelize) => {
115
- // console.log('initTestModels: start');
116
- // logger.info('custom-fields: initialize custom-fields test models');
117
- //
118
- // if (!sequelize.addModels) {
119
- // console.error('initTestModels: sequelize missing addModels function');
120
- // throw new Error('sequelize instance must have addModels function');
121
- // }
122
- // console.log('initTestModels: addModels function exists');
123
- //
124
- // sequelize.addModels(testModels);
125
- // console.log('initTestModels: test models added');
126
- //
127
- // try {
128
- // await sequelize.dropSchema('custom-fields', { logging: true });
129
- // console.log('initTestModels: schema dropped');
130
- //
131
- // await sequelize.createSchema('custom-fields', { logging: true });
132
- // console.log('initTestModels: schema created');
133
- // } catch (error) {
134
- // console.log('initTestModels: error dropping or creating schema', error);
135
- // throw new Error('Failed to drop or create schema');
136
- // }
137
- //
138
- // logger.info('custom-fields: test models added');
139
- //
140
- // console.log('initTestModels: syncing TestModel');
141
- // await TestModel.sync({ alter: true });
142
- //
143
- // console.log('initTestModels: syncing AssociatedTestModel');
144
- // await AssociatedTestModel.sync({ alter: true });
145
- //
146
- // console.log('initTestModels: syncing ContextTestModel');
147
- // await ContextTestModel.sync({ alter: true });
148
- //
149
- // console.log('initTestModels: syncing ContextAwareTestModel');
150
- // await ContextAwareTestModel.sync({ alter: true });
151
- //
152
- // logger.info('custom-fields: test models synced');
153
- // console.log('initTestModels: finished');
115
+ logger_1.default.info('custom-fields: initialize custom-fields test models');
116
+ // Detect models and import them to the orm
117
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
118
+ if (!sequelize.addModels) {
119
+ throw new Error('sequelize instance must have addModels function');
120
+ }
121
+ sequelize.addModels(testModels);
122
+ await sequelize.dropSchema('custom-fields', { logging: false });
123
+ await sequelize.createSchema('custom-fields', { logging: false });
124
+ logger_1.default.info('custom-fields: test models added');
125
+ await TestModel_1.default.sync({ alter: true });
126
+ await AssociatedTestModel_1.default.sync({ alter: true });
127
+ await ContextTestModel_1.default.sync({ alter: true });
128
+ await ContextAwareTestModel_1.default.sync({ alter: true });
129
+ logger_1.default.info('custom-fields: test models synced');
154
130
  };
155
131
  exports.initTestModels = initTestModels;
@@ -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,15 @@
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';
5
+ import type CustomValidator from '../models/CustomValidator';
4
6
  export type ModelFetcher = (name: string) => any;
7
+ export interface TransactionOptions extends Record<string, any> {
8
+ transaction?: Transaction & {
9
+ definitionCache?: Map<string, CustomFieldDefinition[]>;
10
+ validationsCache?: Map<string, CustomValidator[]>;
11
+ };
12
+ }
5
13
  export type ModelOptions = {
6
14
  /**
7
15
  * Include options for the model
@@ -16,6 +24,10 @@ export type ModelOptions = {
16
24
  * Whether to use the entity id from the instance per scope attribute
17
25
  */
18
26
  useEntityIdFromInclude?: boolean;
27
+ /**
28
+ * Which attributes to include in the model
29
+ */
30
+ attributes?: string[];
19
31
  };
20
32
  export type Models = {
21
33
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autofleet/sadot",
3
- "version": "0.13.3-beta-1bef9935.8",
3
+ "version": "0.13.3",
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,37 @@ 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
+ if (options.transaction) {
142
+ // Initialize definition cache Map if not already present directly on the transaction object
143
+ options.transaction.definitionCache ||= new Map();
144
+ cacheKey = `${modelType}:${uniqueIdentifiers.slice().sort().join(',')}`;
145
+ customFieldDefinitionsPromise = options.transaction.definitionCache.get(cacheKey);
146
+ }
147
+
148
+ if (!customFieldDefinitionsPromise) {
149
+ // Fetch from database (either first time in this transaction or no transaction)
150
+ customFieldDefinitionsPromise = DefinitionRepo.findByEntityIds(
151
+ modelType,
152
+ uniqueIdentifiers,
153
+ { transaction: options.transaction, modelOptions, attributes: CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL },
154
+ );
155
+
156
+ options.transaction?.definitionCache?.set(cacheKey, customFieldDefinitionsPromise);
157
+ }
158
+ const customFieldDefinitions = await customFieldDefinitionsPromise;
159
+
160
+ if (customFieldDefinitions.length === 0) {
161
+ // if no custom fields, we can return
162
+ instances.forEach((instance) => {
163
+ instance.customFields = {};
164
+ });
165
+ }
142
166
 
143
167
  if (modelOptions?.include && modelOptions.useEntityIdFromInclude) {
144
168
  // if we pass useEntityIdFromInclude,
@@ -164,17 +188,18 @@ const enrichResults = (
164
188
  const instancesIds = instances.map((i) => i[primaryKey]);
165
189
 
166
190
  // 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
- });
191
+ const [valuesGroupByInstance, customFieldEntriesByInstanceId] = await Promise.all([
192
+ getValuesGroupByInstance({
193
+ instancesIds,
194
+ options,
195
+ sadotOptions,
196
+ }),
197
+ getCustomFieldEntriesByInstanceId({
198
+ instancesIds,
199
+ options,
200
+ sadotOptions,
201
+ }),
202
+ ]);
178
203
 
179
204
  // Attach custom fields to the instances
180
205
  instances.forEach((instance) => {
@@ -13,6 +13,8 @@ import updateInstanceValues from './utils/updateInstanceValues';
13
13
  import { CustomFieldDefinitionType } from '../utils/constants';
14
14
  import type { CustomFieldDefinition } from '../models';
15
15
 
16
+ const CUSTOM_VALIDATOR_ATTRIBUTES_TO_PULL = ['schema'];
17
+
16
18
  // Initialize Ajv with relaxed settings to avoid warnings
17
19
  const ajv = new Ajv({
18
20
  allErrors: true,
@@ -105,11 +107,29 @@ const validateModel = async (
105
107
  return;
106
108
  }
107
109
 
108
- const validators = await ValidatorRepo.findAllByModelType(
109
- modelType,
110
- entityId,
111
- { transaction: options.transaction },
112
- );
110
+ let validatorsPromise;
111
+ let cacheKey;
112
+ if (options.transaction) {
113
+ // eslint-disable-next-line no-param-reassign
114
+ options.transaction.validationsCache ||= new Map();
115
+ cacheKey = `${modelType}-${entityId}`;
116
+ validatorsPromise = options.transaction.validationsCache.get(cacheKey);
117
+ }
118
+
119
+ if (!validatorsPromise) {
120
+ validatorsPromise = ValidatorRepo.findAllByModelType(
121
+ modelType,
122
+ entityId,
123
+ {
124
+ transaction: options.transaction,
125
+ attributes: CUSTOM_VALIDATOR_ATTRIBUTES_TO_PULL,
126
+ },
127
+ );
128
+ if (options.transaction) {
129
+ options?.transaction?.validationsCache.set(cacheKey, validatorsPromise);
130
+ }
131
+ }
132
+ const validators = await validatorsPromise;
113
133
 
114
134
  logger.debug('sadot - validators found', { count: validators.length });
115
135
 
@@ -134,18 +154,6 @@ const validateModel = async (
134
154
  ? await getCompleteCustomFields(instance, options)
135
155
  : instance.customFields || {};
136
156
 
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
-
149
157
  // eslint-disable-next-line no-restricted-syntax
150
158
  for (const validator of validators) {
151
159
  const { schema } = validator;
@@ -201,7 +209,7 @@ const validateModel = async (
201
209
  // Validate
202
210
  const isValid = validateSchema(JSON.parse(JSON.stringify(payload)));
203
211
 
204
- logger.info('sadot - validation result', {
212
+ logger.debug('sadot - validation result', {
205
213
  isValid,
206
214
  test: {
207
215
  before: originalValues,
@@ -210,10 +218,9 @@ const validateModel = async (
210
218
  });
211
219
 
212
220
  if (!isValid) {
213
- const errorDetails = validateSchema.errors?.map((err) => {
214
- logger.info('dor', err);
215
- return `${(err as any).instancePath || ''} ${(err as any).message || 'Invalid value'}`;
216
- }).join(', ');
221
+ const errorDetails = validateSchema
222
+ .errors
223
+ ?.map((err) => `${(err as any).instancePath || ''} ${(err as any).message || 'Invalid value'}`).join(', ');
217
224
 
218
225
  throw new BadRequest([new Error(`Validation failed for ${modelType}: ${errorDetails}`)]);
219
226
  }
package/src/index.ts CHANGED
@@ -26,44 +26,22 @@ const useCustomFields = async (
26
26
  getModel: ModelFetcher,
27
27
  options: CustomFieldOptions,
28
28
  ): Promise<Sequelize> => {
29
- console.log('useCustomFields: start');
30
-
31
- tryAddingTraceIdMiddleware()
32
- .then(() => console.log('useCustomFields: tryAddingTraceIdMiddleware done'))
33
- .catch(() => console.log('useCustomFields: tryAddingTraceIdMiddleware failed'));
34
-
29
+ tryAddingTraceIdMiddleware().catch(() => null);
35
30
  const { models, useCustomFieldsEntries } = options;
36
- console.log('useCustomFields: options extracted', { models, useCustomFieldsEntries });
37
-
38
31
  if (app) {
39
- console.log('useCustomFields: app exists, setting up /api route');
40
32
  app.use('/api', api);
41
- } else {
42
- console.log('useCustomFields: no app provided');
43
33
  }
44
-
45
34
  const sequelize = options.sequelize ?? initDB(options.databaseConfig);
46
- if (!options.sequelize) {
47
- console.log('useCustomFields: initialized new Sequelize instance');
48
- } else {
49
- console.log('useCustomFields: using provided Sequelize instance');
35
+ if (process.env.NODE_ENV === 'test') {
36
+ await initTestModels(sequelize);
50
37
  }
51
-
52
- console.log('useCustomFields: adding hooks');
38
+ // The order is important
53
39
  addHooks(models, getModel, { useCustomFieldsEntries });
54
-
55
- console.log('useCustomFields: initializing tables');
56
40
  await initTables(sequelize, options.getUser, { useCustomFieldsEntries });
57
-
58
- console.log('useCustomFields: adding scopes');
59
41
  addScopes(models, getModel, { useCustomFieldsEntries });
60
-
61
- console.log('useCustomFields: applying custom associations');
62
42
  applyCustomAssociation(models);
63
43
 
64
44
  logger.debug('sadot - custom fields finished initializing with models', models);
65
- console.log('useCustomFields: finished and returning sequelize');
66
-
67
45
  return sequelize;
68
46
  };
69
47
 
@@ -132,45 +132,23 @@ const initTables = async (
132
132
  };
133
133
 
134
134
  const initTestModels = async (sequelize: Sequelize): Promise<void> => {
135
- // console.log('initTestModels: start');
136
- // logger.info('custom-fields: initialize custom-fields test models');
137
- //
138
- // if (!sequelize.addModels) {
139
- // console.error('initTestModels: sequelize missing addModels function');
140
- // throw new Error('sequelize instance must have addModels function');
141
- // }
142
- // console.log('initTestModels: addModels function exists');
143
- //
144
- // sequelize.addModels(testModels);
145
- // console.log('initTestModels: test models added');
146
- //
147
- // try {
148
- // await sequelize.dropSchema('custom-fields', { logging: true });
149
- // console.log('initTestModels: schema dropped');
150
- //
151
- // await sequelize.createSchema('custom-fields', { logging: true });
152
- // console.log('initTestModels: schema created');
153
- // } catch (error) {
154
- // console.log('initTestModels: error dropping or creating schema', error);
155
- // throw new Error('Failed to drop or create schema');
156
- // }
157
- //
158
- // logger.info('custom-fields: test models added');
159
- //
160
- // console.log('initTestModels: syncing TestModel');
161
- // await TestModel.sync({ alter: true });
162
- //
163
- // console.log('initTestModels: syncing AssociatedTestModel');
164
- // await AssociatedTestModel.sync({ alter: true });
165
- //
166
- // console.log('initTestModels: syncing ContextTestModel');
167
- // await ContextTestModel.sync({ alter: true });
168
- //
169
- // console.log('initTestModels: syncing ContextAwareTestModel');
170
- // await ContextAwareTestModel.sync({ alter: true });
171
- //
172
- // logger.info('custom-fields: test models synced');
173
- // console.log('initTestModels: finished');
135
+ logger.info('custom-fields: initialize custom-fields test models');
136
+ // Detect models and import them to the orm
137
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
138
+ if (!sequelize.addModels) {
139
+ throw new Error('sequelize instance must have addModels function');
140
+ }
141
+
142
+ sequelize.addModels(testModels);
143
+ await sequelize.dropSchema('custom-fields', { logging: false });
144
+ await sequelize.createSchema('custom-fields', { logging: false });
145
+
146
+ logger.info('custom-fields: test models added');
147
+ await TestModel.sync({ alter: true });
148
+ await AssociatedTestModel.sync({ alter: true });
149
+ await ContextTestModel.sync({ alter: true });
150
+ await ContextAwareTestModel.sync({ alter: true });
151
+ logger.info('custom-fields: test models synced');
174
152
  };
175
153
 
176
154
  export {
@@ -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,18 @@
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';
5
+ import type CustomValidator from '../models/CustomValidator';
4
6
 
5
7
  export type ModelFetcher = (name: string) => any;
6
8
 
9
+ export interface TransactionOptions extends Record<string, any> {
10
+ transaction?: Transaction & {
11
+ definitionCache?: Map<string, CustomFieldDefinition[]>;
12
+ validationsCache?: Map<string, CustomValidator[]>;
13
+ };
14
+ }
15
+
7
16
  export type ModelOptions = {
8
17
  /**
9
18
  * Include options for the model
@@ -19,6 +28,10 @@ export type ModelOptions = {
19
28
  * Whether to use the entity id from the instance per scope attribute
20
29
  */
21
30
  useEntityIdFromInclude?: boolean
31
+ /**
32
+ * Which attributes to include in the model
33
+ */
34
+ attributes?: string[];
22
35
  }
23
36
  export type Models = {
24
37
  name: string;
package/.env DELETED
@@ -1,4 +0,0 @@
1
- AF_SERVICE_NAME=sadot_package_test
2
- NODE_ENV=test
3
- DB_USERNAME=postgres
4
- DB_PASSWORD=postgres