@autofleet/sadot 0.13.2-beta.100 → 0.13.2-beta.2
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.
- package/dist/hooks/enrich.d.ts +9 -4
- package/dist/hooks/enrich.js +11 -45
- package/dist/hooks/hooks.js +17 -29
- package/dist/models/index.js +6 -5
- package/dist/repository/validator.d.ts +0 -1
- package/dist/types/index.d.ts +1 -11
- package/package.json +1 -1
- package/src/hooks/enrich.ts +22 -60
- package/src/hooks/hooks.ts +110 -114
- package/src/models/index.ts +8 -5
- package/src/repository/validator.ts +0 -1
- package/src/types/index.ts +1 -12
package/dist/hooks/enrich.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { Transaction } from 'sequelize';
|
|
1
2
|
import type CustomFieldValue from '../models/CustomFieldValue';
|
|
2
|
-
import type { CustomFieldOptions, ModelOptions
|
|
3
|
+
import type { CustomFieldOptions, ModelOptions } from '../types';
|
|
3
4
|
type SupportedHookTypes = 'afterFind' | 'afterCreate' | 'afterUpdate';
|
|
4
5
|
type CustomFieldEntries = Record<string, any>;
|
|
5
6
|
interface GetValuesGroupByInstanceResponse {
|
|
@@ -10,16 +11,20 @@ interface GetCustomFieldEntriesByInstanceIdResponse {
|
|
|
10
11
|
}
|
|
11
12
|
export declare const getCustomFieldEntriesByInstanceId: ({ instancesIds, options, sadotOptions, }: {
|
|
12
13
|
instancesIds: string[];
|
|
13
|
-
options?:
|
|
14
|
+
options?: {
|
|
15
|
+
transaction: Transaction;
|
|
16
|
+
};
|
|
14
17
|
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>;
|
|
15
18
|
}) => Promise<GetCustomFieldEntriesByInstanceIdResponse>;
|
|
16
19
|
export declare const getValuesGroupByInstance: ({ instancesIds, options, sadotOptions, }: {
|
|
17
20
|
instancesIds: string[];
|
|
18
|
-
options?:
|
|
21
|
+
options?: {
|
|
22
|
+
transaction: Transaction;
|
|
23
|
+
};
|
|
19
24
|
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>;
|
|
20
25
|
}) => Promise<GetValuesGroupByInstanceResponse>;
|
|
21
26
|
/**
|
|
22
27
|
* A hook to attach the custom fields when fetching a model instances.
|
|
23
28
|
*/
|
|
24
|
-
declare const enrichResults: (modelType: string, scopeAttributes: string[], hookType?: SupportedHookTypes, modelOptions?: ModelOptions, sadotOptions?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>) => (instancesOrInstance: any | any[], options:
|
|
29
|
+
declare const enrichResults: (modelType: string, scopeAttributes: string[], hookType?: SupportedHookTypes, modelOptions?: ModelOptions, sadotOptions?: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>) => (instancesOrInstance: any | any[], options: any) => Promise<void>;
|
|
25
30
|
export default enrichResults;
|
package/dist/hooks/enrich.js
CHANGED
|
@@ -27,12 +27,10 @@ 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 */
|
|
31
30
|
const ValueRepo = __importStar(require("../repository/value"));
|
|
32
31
|
const DefinitionRepo = __importStar(require("../repository/definition"));
|
|
33
32
|
const EntriesRepo = __importStar(require("../repository/entries"));
|
|
34
33
|
const scopeAttributes_1 = __importDefault(require("../utils/scopeAttributes"));
|
|
35
|
-
const CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL = ['id', 'name', 'entityId'];
|
|
36
34
|
const getCustomFieldEntriesByInstanceId = async ({ instancesIds, options, sadotOptions, }) => {
|
|
37
35
|
if (!sadotOptions.useCustomFieldsEntries) {
|
|
38
36
|
return {};
|
|
@@ -95,37 +93,7 @@ const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {},
|
|
|
95
93
|
...map,
|
|
96
94
|
[identifier]: [],
|
|
97
95
|
}), {});
|
|
98
|
-
|
|
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
|
-
}
|
|
96
|
+
const customFieldDefinitions = await DefinitionRepo.findByEntityIds(modelType, uniqueIdentifiers, { transaction: options.transaction, modelOptions });
|
|
129
97
|
if (modelOptions?.include && modelOptions.useEntityIdFromInclude) {
|
|
130
98
|
// if we pass useEntityIdFromInclude,
|
|
131
99
|
// map the entity from the options to the identifierCustomFieldDefinitionsMapping
|
|
@@ -146,18 +114,16 @@ const enrichResults = (modelType, scopeAttributes, hookType, modelOptions = {},
|
|
|
146
114
|
// Get the values per instates ids:
|
|
147
115
|
const instancesIds = instances.map((i) => i[primaryKey]);
|
|
148
116
|
// Group fields by modelId
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}),
|
|
160
|
-
]);
|
|
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
|
+
});
|
|
161
127
|
// Attach custom fields to the instances
|
|
162
128
|
instances.forEach((instance) => {
|
|
163
129
|
const { id } = instance;
|
package/dist/hooks/hooks.js
CHANGED
|
@@ -28,7 +28,6 @@ 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"));
|
|
32
31
|
const ajv_formats_1 = __importDefault(require("ajv-formats"));
|
|
33
32
|
const errors_1 = require("@autofleet/errors");
|
|
34
33
|
const logger_1 = __importDefault(require("../utils/logger"));
|
|
@@ -106,24 +105,7 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
106
105
|
logger_1.default.debug('sadot - skipping validation: no entityId');
|
|
107
106
|
return;
|
|
108
107
|
}
|
|
109
|
-
|
|
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;
|
|
108
|
+
const validators = await ValidatorRepo.findAllByModelType(modelType, entityId, { transaction: options.transaction });
|
|
127
109
|
logger_1.default.debug('sadot - validators found', { count: validators.length });
|
|
128
110
|
if (!validators.length) {
|
|
129
111
|
logger_1.default.debug('sadot - skipping validation: no validators found');
|
|
@@ -142,6 +124,16 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
142
124
|
const completeCustomFields = !isCreate
|
|
143
125
|
? await getCompleteCustomFields(instance, options)
|
|
144
126
|
: 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
|
+
}
|
|
145
137
|
// eslint-disable-next-line no-restricted-syntax
|
|
146
138
|
for (const validator of validators) {
|
|
147
139
|
const { schema } = validator;
|
|
@@ -204,7 +196,7 @@ const validateModel = async (instance, options, scopeAttributes, isCreate = fals
|
|
|
204
196
|
}
|
|
205
197
|
}
|
|
206
198
|
};
|
|
207
|
-
const getFieldDefinitions = async ({ modelType, modelOptions, identifiers, options
|
|
199
|
+
const getFieldDefinitions = async ({ modelType, modelOptions, identifiers, options }) => {
|
|
208
200
|
const { include, useEntityIdFromInclude } = modelOptions;
|
|
209
201
|
const where = {
|
|
210
202
|
modelType,
|
|
@@ -218,18 +210,14 @@ const getFieldDefinitions = async ({ modelType, modelOptions, identifiers, optio
|
|
|
218
210
|
});
|
|
219
211
|
return fieldDefinitions;
|
|
220
212
|
};
|
|
221
|
-
const formatDates = (fieldDefinitions, instance) => {
|
|
222
|
-
|
|
213
|
+
const formatDates = (fieldDefinitions = [], instance) => {
|
|
214
|
+
fieldDefinitions.forEach((fieldDefinition) => {
|
|
223
215
|
const { fieldType, name } = fieldDefinition;
|
|
224
216
|
if ([constants_1.CustomFieldDefinitionType.DATE, constants_1.CustomFieldDefinitionType.DATETIME].includes(fieldType)) {
|
|
225
217
|
const value = instance.customFields?.[name];
|
|
226
218
|
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
|
-
}
|
|
231
219
|
// eslint-disable-next-line no-param-reassign
|
|
232
|
-
instance.customFields[name] =
|
|
220
|
+
instance.customFields[name] = new Date(value).toISOString();
|
|
233
221
|
}
|
|
234
222
|
}
|
|
235
223
|
});
|
|
@@ -244,7 +232,7 @@ const beforeCreate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
244
232
|
const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
|
|
245
233
|
// Step 1: Handle custom fields default values and required fields
|
|
246
234
|
const fieldDefinitions = await getFieldDefinitions({
|
|
247
|
-
modelType, modelOptions, identifiers, options
|
|
235
|
+
modelType, modelOptions, identifiers, options
|
|
248
236
|
});
|
|
249
237
|
// Apply default values
|
|
250
238
|
const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
|
|
@@ -302,7 +290,7 @@ const beforeUpdate = (scopeAttributes, modelOptions = {}, sadotOptions = { useCu
|
|
|
302
290
|
const modelType = instance.constructor.name;
|
|
303
291
|
const identifiers = (0, scopeAttributes_1.default)(instance, scopeAttributes);
|
|
304
292
|
const fieldDefinitions = await getFieldDefinitions({
|
|
305
|
-
modelType, modelOptions, identifiers, options
|
|
293
|
+
modelType, modelOptions, identifiers, options
|
|
306
294
|
});
|
|
307
295
|
// Step 1: Validate the model data (including custom fields)
|
|
308
296
|
await validateModel(instance, options, scopeAttributes, false);
|
package/dist/models/index.js
CHANGED
|
@@ -26,8 +26,12 @@ 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 = '
|
|
30
|
-
const initTables = async (sequelize, getUser, { schemaPrefix
|
|
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
|
+
}) => {
|
|
31
35
|
const CUSTOM_FIELDS_SCHEMA_VERSION = `${schemaPrefix}_${schemaVersion}${useCustomFieldsEntries ? '_withEntries' : ''}`;
|
|
32
36
|
logger_1.default.info('custom-fields: initialize custom-fields tables');
|
|
33
37
|
// Detect models and import them to the orm
|
|
@@ -83,13 +87,10 @@ const initTables = async (sequelize, getUser, { schemaPrefix = SADOT_MIGRATION_P
|
|
|
83
87
|
timestamps: false,
|
|
84
88
|
schema: 'public',
|
|
85
89
|
});
|
|
86
|
-
logger_1.default.info('custom-fields: starting migrations');
|
|
87
90
|
const migrations = await SequelizeMeta.findAll({ where: { name: { [sequelize_1.Op.like]: `${schemaPrefix}%` } }, raw: true });
|
|
88
91
|
const currentSadotSchemaVersion = migrations.at(-1);
|
|
89
92
|
const expectedSchemaVersionIndex = migrations.findIndex((m) => m.name === CUSTOM_FIELDS_SCHEMA_VERSION);
|
|
90
|
-
logger_1.default.info('custom-fields: migrations', { migrations, currentSadotSchemaVersion, expectedSchemaVersionIndex });
|
|
91
93
|
if (!currentSadotSchemaVersion || currentSadotSchemaVersion.name !== CUSTOM_FIELDS_SCHEMA_VERSION) {
|
|
92
|
-
logger_1.default.info('custom-fields: syncing models');
|
|
93
94
|
await CustomFieldDefinition_1.default.sync({ alter: true });
|
|
94
95
|
await CustomFieldValue_1.default.sync({ alter: true });
|
|
95
96
|
// T.Y TODO: Remove the if statement once we're ready to add the new entries table for all MS
|
|
@@ -2,7 +2,6 @@ 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[];
|
|
6
5
|
}
|
|
7
6
|
export interface ValidatorAttributes {
|
|
8
7
|
entityId: string;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import type { IncludeOptions
|
|
1
|
+
import type { IncludeOptions } 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
4
|
export type ModelFetcher = (name: string) => any;
|
|
6
|
-
export interface TransactionOptions extends Record<string, any> {
|
|
7
|
-
transaction?: Transaction & {
|
|
8
|
-
definitionCache?: Map<string, CustomFieldDefinition[]>;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
5
|
export type ModelOptions = {
|
|
12
6
|
/**
|
|
13
7
|
* Include options for the model
|
|
@@ -22,10 +16,6 @@ export type ModelOptions = {
|
|
|
22
16
|
* Whether to use the entity id from the instance per scope attribute
|
|
23
17
|
*/
|
|
24
18
|
useEntityIdFromInclude?: boolean;
|
|
25
|
-
/**
|
|
26
|
-
* Which attributes to include in the model
|
|
27
|
-
*/
|
|
28
|
-
attributes?: string[];
|
|
29
19
|
};
|
|
30
20
|
export type Models = {
|
|
31
21
|
name: string;
|
package/package.json
CHANGED
package/src/hooks/enrich.ts
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
/* eslint-disable no-param-reassign */
|
|
2
|
+
import type { Transaction } from 'sequelize';
|
|
2
3
|
import * as ValueRepo from '../repository/value';
|
|
3
4
|
import * as DefinitionRepo from '../repository/definition';
|
|
4
5
|
import * as EntriesRepo from '../repository/entries';
|
|
5
6
|
import type CustomFieldValue from '../models/CustomFieldValue';
|
|
6
7
|
import type CustomFieldDefinition from '../models/CustomFieldDefinition';
|
|
7
8
|
import type { SerializedCustomFields } from '../types/definition';
|
|
8
|
-
import type { CustomFieldOptions, ModelOptions
|
|
9
|
+
import type { CustomFieldOptions, ModelOptions } from '../types';
|
|
9
10
|
import applyScopeToInstance from '../utils/scopeAttributes';
|
|
10
11
|
|
|
11
|
-
const CUSTOM_FIELD_DEFINITION_ATTRIBUTES_TO_PULL = ['id', 'name', 'entityId'];
|
|
12
|
-
|
|
13
12
|
type SupportedHookTypes = 'afterFind' | 'afterCreate' | 'afterUpdate';
|
|
14
13
|
|
|
15
14
|
type CustomFieldEntries = Record<string, any>;
|
|
@@ -28,7 +27,7 @@ export const getCustomFieldEntriesByInstanceId = async ({
|
|
|
28
27
|
sadotOptions,
|
|
29
28
|
}: {
|
|
30
29
|
instancesIds: string[],
|
|
31
|
-
options?:
|
|
30
|
+
options?: { transaction: Transaction },
|
|
32
31
|
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>,
|
|
33
32
|
}): Promise<GetCustomFieldEntriesByInstanceIdResponse> => {
|
|
34
33
|
if (!sadotOptions.useCustomFieldsEntries) {
|
|
@@ -61,7 +60,7 @@ export const getValuesGroupByInstance = async ({
|
|
|
61
60
|
sadotOptions,
|
|
62
61
|
}: {
|
|
63
62
|
instancesIds: string[],
|
|
64
|
-
options?:
|
|
63
|
+
options?: { transaction: Transaction },
|
|
65
64
|
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'>,
|
|
66
65
|
}): Promise<GetValuesGroupByInstanceResponse> => {
|
|
67
66
|
if (sadotOptions.useCustomFieldsEntries) {
|
|
@@ -109,7 +108,7 @@ const enrichResults = (
|
|
|
109
108
|
sadotOptions: Pick<CustomFieldOptions, 'useCustomFieldsEntries'> = { useCustomFieldsEntries: false },
|
|
110
109
|
) => async (
|
|
111
110
|
instancesOrInstance: any | any[],
|
|
112
|
-
options
|
|
111
|
+
options,
|
|
113
112
|
): Promise<void> => {
|
|
114
113
|
if (
|
|
115
114
|
options.originalAttributes?.length > 0
|
|
@@ -132,50 +131,14 @@ const enrichResults = (
|
|
|
132
131
|
const identifierCustomFieldDefinitionsMapping = uniqueIdentifiers.reduce((map, identifier) => ({
|
|
133
132
|
...map,
|
|
134
133
|
[identifier]: [],
|
|
135
|
-
}), {});
|
|
136
|
-
|
|
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
134
|
|
|
148
|
-
|
|
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;
|
|
135
|
+
}), {});
|
|
172
136
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
137
|
+
const customFieldDefinitions = await DefinitionRepo.findByEntityIds(
|
|
138
|
+
modelType,
|
|
139
|
+
uniqueIdentifiers,
|
|
140
|
+
{ transaction: options.transaction, modelOptions },
|
|
141
|
+
);
|
|
179
142
|
|
|
180
143
|
if (modelOptions?.include && modelOptions.useEntityIdFromInclude) {
|
|
181
144
|
// if we pass useEntityIdFromInclude,
|
|
@@ -201,18 +164,17 @@ const enrichResults = (
|
|
|
201
164
|
const instancesIds = instances.map((i) => i[primaryKey]);
|
|
202
165
|
|
|
203
166
|
// Group fields by modelId
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
]);
|
|
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
|
+
});
|
|
216
178
|
|
|
217
179
|
// Attach custom fields to the instances
|
|
218
180
|
instances.forEach((instance) => {
|
package/src/hooks/hooks.ts
CHANGED
|
@@ -105,29 +105,11 @@ const validateModel = async (
|
|
|
105
105
|
return;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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;
|
|
108
|
+
const validators = await ValidatorRepo.findAllByModelType(
|
|
109
|
+
modelType,
|
|
110
|
+
entityId,
|
|
111
|
+
{ transaction: options.transaction },
|
|
112
|
+
);
|
|
131
113
|
|
|
132
114
|
logger.debug('sadot - validators found', { count: validators.length });
|
|
133
115
|
|
|
@@ -152,6 +134,17 @@ const validateModel = async (
|
|
|
152
134
|
? await getCompleteCustomFields(instance, options)
|
|
153
135
|
: instance.customFields || {};
|
|
154
136
|
|
|
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
|
+
}
|
|
155
148
|
|
|
156
149
|
// eslint-disable-next-line no-restricted-syntax
|
|
157
150
|
for (const validator of validators) {
|
|
@@ -265,7 +258,10 @@ const formatDates = (fieldDefinitions: CustomFieldDefinition[], instance: any) =
|
|
|
265
258
|
throw new InvalidValueError(value, name, validationError);
|
|
266
259
|
}
|
|
267
260
|
// eslint-disable-next-line no-param-reassign
|
|
261
|
+
const typeofjoi = typeof joiValue;
|
|
262
|
+
logger.info('sadot - formatting date', { name, value, joiValue, type: typeof joiValue });
|
|
268
263
|
instance.customFields[name] = joiValue.toISOString();
|
|
264
|
+
new Date().toString
|
|
269
265
|
}
|
|
270
266
|
}
|
|
271
267
|
});
|
|
@@ -282,72 +278,72 @@ export const beforeCreate = (
|
|
|
282
278
|
instance,
|
|
283
279
|
options,
|
|
284
280
|
): Promise<void> => {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
logger.debug('sadot - before create hook');
|
|
282
|
+
const { fields } = options;
|
|
283
|
+
const modelType = instance.constructor.name;
|
|
288
284
|
|
|
289
|
-
|
|
285
|
+
const identifiers = applyScopeToInstance(instance, scopeAttributes);
|
|
290
286
|
|
|
291
|
-
|
|
287
|
+
// Step 1: Handle custom fields default values and required fields
|
|
292
288
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
289
|
+
const fieldDefinitions = await getFieldDefinitions({
|
|
290
|
+
modelType, modelOptions, identifiers, options,
|
|
291
|
+
});
|
|
296
292
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
293
|
+
// Apply default values
|
|
294
|
+
const fieldsWithDefaultValue = fieldDefinitions.filter((def) => ![null, undefined].includes(def.defaultValue));
|
|
295
|
+
if (fieldsWithDefaultValue.length) {
|
|
296
|
+
// eslint-disable-next-line no-param-reassign
|
|
297
|
+
instance.customFields ||= {};
|
|
298
|
+
fieldsWithDefaultValue
|
|
299
|
+
.filter((def) => (instance.customFields?.[def.name] === undefined))
|
|
300
|
+
.forEach(({ name, defaultValue }) => {
|
|
301
|
+
// eslint-disable-next-line no-param-reassign
|
|
302
|
+
instance.customFields[name] = defaultValue;
|
|
303
|
+
});
|
|
304
|
+
}
|
|
309
305
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
306
|
+
// Check for required fields
|
|
307
|
+
const requiredFieldsNames = Array.from(
|
|
308
|
+
new Set(fieldDefinitions.filter(({ required }) => required).map(({ name }) => name)),
|
|
309
|
+
);
|
|
310
|
+
const { customFields } = instance;
|
|
311
|
+
const fieldsNames = Object.keys(customFields ?? {});
|
|
312
|
+
const missingFields = requiredFieldsNames.filter((name) => !fieldsNames.includes(name));
|
|
313
|
+
if (missingFields?.length) {
|
|
314
|
+
throw new MissingRequiredCustomFieldError(missingFields);
|
|
315
|
+
}
|
|
320
316
|
|
|
321
|
-
|
|
322
|
-
|
|
317
|
+
// Step 2: Validate the model data (including custom fields)
|
|
318
|
+
await validateModel(instance, options, scopeAttributes, true);
|
|
323
319
|
|
|
324
|
-
|
|
325
|
-
|
|
320
|
+
// format date and datetime fields
|
|
321
|
+
formatDates(fieldDefinitions, instance);
|
|
326
322
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
323
|
+
// Step 3: Save custom field values if they exist
|
|
324
|
+
const customFieldsIdx = fields.indexOf('customFields');
|
|
325
|
+
if (customFieldsIdx === -1 || !customFields || !Object.keys(customFields).length) {
|
|
326
|
+
// No custom fields to update
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
333
329
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
330
|
+
// Save custom field values
|
|
331
|
+
await updateInstanceValues({
|
|
332
|
+
modelId: instance.id,
|
|
333
|
+
modelType,
|
|
334
|
+
identifiers,
|
|
335
|
+
customFields,
|
|
336
|
+
options: {
|
|
337
|
+
useCustomFieldsEntries: sadotOptions.useCustomFieldsEntries,
|
|
338
|
+
transaction: options.transaction,
|
|
339
|
+
modelOptions,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
346
342
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
};
|
|
343
|
+
// Remove customFields from fields array after handling
|
|
344
|
+
// eslint-disable-next-line no-param-reassign
|
|
345
|
+
fields.splice(customFieldsIdx, 1);
|
|
346
|
+
};
|
|
351
347
|
|
|
352
348
|
/**
|
|
353
349
|
* Hook to handle validation and custom fields during update
|
|
@@ -360,48 +356,48 @@ export const beforeUpdate = (
|
|
|
360
356
|
instance,
|
|
361
357
|
options,
|
|
362
358
|
): Promise<void> => {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
359
|
+
logger.debug('sadot - before update hook');
|
|
360
|
+
const { fields } = options;
|
|
361
|
+
const modelType = instance.constructor.name;
|
|
362
|
+
const identifiers = applyScopeToInstance(instance, scopeAttributes);
|
|
367
363
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
364
|
+
const fieldDefinitions = await getFieldDefinitions({
|
|
365
|
+
modelType, modelOptions, identifiers, options,
|
|
366
|
+
});
|
|
371
367
|
|
|
372
|
-
|
|
373
|
-
|
|
368
|
+
// Step 1: Validate the model data (including custom fields)
|
|
369
|
+
await validateModel(instance, options, scopeAttributes, false);
|
|
374
370
|
|
|
375
|
-
|
|
376
|
-
|
|
371
|
+
// format date and datetime fields
|
|
372
|
+
formatDates(fieldDefinitions, instance);
|
|
377
373
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
374
|
+
// Step 2: Update custom field values if they exist
|
|
375
|
+
const customFieldsIdx = fields.indexOf('customFields');
|
|
376
|
+
if (customFieldsIdx > -1) {
|
|
377
|
+
const { customFields } = instance;
|
|
382
378
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
379
|
+
if (!Object.keys(customFields).length) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
386
382
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
383
|
+
// Save custom field values
|
|
384
|
+
await updateInstanceValues({
|
|
385
|
+
modelId: instance.id,
|
|
386
|
+
modelType,
|
|
387
|
+
identifiers,
|
|
388
|
+
customFields,
|
|
389
|
+
options: {
|
|
390
|
+
useCustomFieldsEntries: sadotOptions.useCustomFieldsEntries,
|
|
391
|
+
transaction: options.transaction,
|
|
392
|
+
modelOptions,
|
|
393
|
+
},
|
|
394
|
+
});
|
|
399
395
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
};
|
|
396
|
+
// Remove customFields from fields array after handling
|
|
397
|
+
// eslint-disable-next-line no-param-reassign
|
|
398
|
+
fields.splice(customFieldsIdx, 1);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
405
401
|
|
|
406
402
|
/**
|
|
407
403
|
* Hook to enable individual hooks for bulk create operations
|
package/src/models/index.ts
CHANGED
|
@@ -29,10 +29,14 @@ const initTables = async (
|
|
|
29
29
|
sequelize: Sequelize,
|
|
30
30
|
getUser: CustomFieldOptions['getUser'],
|
|
31
31
|
{
|
|
32
|
-
schemaPrefix
|
|
33
|
-
schemaVersion
|
|
34
|
-
useCustomFieldsEntries
|
|
35
|
-
}: InitTablesOptions = {
|
|
32
|
+
schemaPrefix,
|
|
33
|
+
schemaVersion,
|
|
34
|
+
useCustomFieldsEntries,
|
|
35
|
+
}: InitTablesOptions = {
|
|
36
|
+
schemaPrefix: SADOT_MIGRATION_PREFIX,
|
|
37
|
+
schemaVersion: SCHEMA_VERSION,
|
|
38
|
+
useCustomFieldsEntries: false,
|
|
39
|
+
},
|
|
36
40
|
): Promise<void> => {
|
|
37
41
|
const CUSTOM_FIELDS_SCHEMA_VERSION = `${schemaPrefix}_${schemaVersion}${useCustomFieldsEntries ? '_withEntries' : ''}`;
|
|
38
42
|
logger.info('custom-fields: initialize custom-fields tables');
|
|
@@ -100,7 +104,6 @@ const initTables = async (
|
|
|
100
104
|
},
|
|
101
105
|
);
|
|
102
106
|
|
|
103
|
-
logger.info('custom-fields: starting migrations');
|
|
104
107
|
const migrations = await SequelizeMeta.findAll({ where: { name: { [Op.like]: `${schemaPrefix}%` } }, raw: true });
|
|
105
108
|
const currentSadotSchemaVersion = migrations.at(-1);
|
|
106
109
|
const expectedSchemaVersionIndex = migrations.findIndex((m) => (m as any).name === CUSTOM_FIELDS_SCHEMA_VERSION);
|
package/src/types/index.ts
CHANGED
|
@@ -1,16 +1,9 @@
|
|
|
1
|
-
import type { IncludeOptions
|
|
1
|
+
import type { IncludeOptions } 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
4
|
|
|
6
5
|
export type ModelFetcher = (name: string) => any;
|
|
7
6
|
|
|
8
|
-
export interface TransactionOptions extends Record<string, any> {
|
|
9
|
-
transaction?: Transaction & {
|
|
10
|
-
definitionCache?: Map<string, CustomFieldDefinition[]>;
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
7
|
export type ModelOptions = {
|
|
15
8
|
/**
|
|
16
9
|
* Include options for the model
|
|
@@ -26,10 +19,6 @@ export type ModelOptions = {
|
|
|
26
19
|
* Whether to use the entity id from the instance per scope attribute
|
|
27
20
|
*/
|
|
28
21
|
useEntityIdFromInclude?: boolean
|
|
29
|
-
/**
|
|
30
|
-
* Which attributes to include in the model
|
|
31
|
-
*/
|
|
32
|
-
attributes?: string[];
|
|
33
22
|
}
|
|
34
23
|
export type Models = {
|
|
35
24
|
name: string;
|