@sera4/essentia 3.0.11 → 3.0.13-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache/index.d.ts +22 -0
- package/dist/cache/index.js +166 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/constants/index.d.ts +6 -0
- package/dist/constants/index.js +9 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/formatter/index.d.ts +37 -0
- package/dist/formatter/index.js +75 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/hal/index.d.ts +5 -0
- package/dist/hal/index.js +69 -0
- package/dist/hal/index.js.map +1 -0
- package/dist/health/health-logger.d.ts +9 -0
- package/dist/health/health-logger.js +34 -0
- package/dist/health/health-logger.js.map +1 -0
- package/dist/health/index.d.ts +19 -0
- package/dist/health/index.js +175 -0
- package/dist/health/index.js.map +1 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +2 -0
- package/dist/helpers/index.js.map +1 -0
- package/dist/helpers/test-server-wrapper.d.ts +120 -0
- package/dist/helpers/test-server-wrapper.js +165 -0
- package/dist/helpers/test-server-wrapper.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/last-commit.d.ts +1 -0
- package/dist/last-commit.js.map +1 -0
- package/dist/last_commit/index.d.ts +7 -0
- package/dist/last_commit/index.js +83 -0
- package/dist/last_commit/index.js.map +1 -0
- package/dist/paginator/s4-pagination.d.ts +9 -0
- package/dist/paginator/s4-pagination.js +49 -0
- package/dist/paginator/s4-pagination.js.map +1 -0
- package/dist/paginator/sql-pagination.d.ts +27 -0
- package/dist/paginator/sql-pagination.js +166 -0
- package/dist/paginator/sql-pagination.js.map +1 -0
- package/dist/paper-trail/helpers.d.ts +11 -0
- package/dist/paper-trail/helpers.js +74 -0
- package/dist/paper-trail/helpers.js.map +1 -0
- package/dist/paper-trail/index.d.ts +13 -0
- package/dist/paper-trail/index.js +662 -0
- package/dist/paper-trail/index.js.map +1 -0
- package/dist/prompts/index.d.ts +14 -0
- package/dist/prompts/index.js +63 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/queue/index.d.ts +166 -0
- package/dist/queue/index.js +371 -0
- package/dist/queue/index.js.map +1 -0
- package/dist/queue/publisher.d.ts +31 -0
- package/dist/queue/publisher.js +84 -0
- package/dist/queue/publisher.js.map +1 -0
- package/dist/queue/queue-logger.d.ts +9 -0
- package/dist/queue/queue-logger.js +35 -0
- package/dist/queue/queue-logger.js.map +1 -0
- package/dist/queue/subscriber.d.ts +39 -0
- package/dist/queue/subscriber.js +97 -0
- package/dist/queue/subscriber.js.map +1 -0
- package/dist/safe_proxy/index.d.ts +13 -0
- package/dist/safe_proxy/index.js +54 -0
- package/dist/safe_proxy/index.js.map +1 -0
- package/dist/serializer/index.d.ts +5 -0
- package/dist/serializer/index.js +83 -0
- package/dist/serializer/index.js.map +1 -0
- package/dist/ts/logger/s4-logger.js +2 -0
- package/dist/ts/logger/s4-logger.js.map +1 -1
- package/dist/ts/queue/index.js +54 -65
- package/dist/ts/queue/index.js.map +1 -1
- package/dist/ts/task-scheduler/index.d.ts +130 -0
- package/dist/ts/task-scheduler/index.js +259 -0
- package/dist/ts/task-scheduler/index.js.map +1 -0
- package/package.json +9 -3
- package/scripts/publish.sh +46 -0
- package/package.tar.gz +0 -0
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
import * as jsdiff from 'diff';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
import helpers from './helpers.js';
|
|
4
|
+
import paginator from "../paginator/sql-pagination.js";
|
|
5
|
+
import { utils } from "../ts/utils/index.js";
|
|
6
|
+
const censorText = "**********";
|
|
7
|
+
let failHard = false;
|
|
8
|
+
let Sequelize = null;
|
|
9
|
+
const paperTrail = {};
|
|
10
|
+
paperTrail.init = (sequelize, sequelizePackage, optionsArg) => {
|
|
11
|
+
Sequelize = sequelizePackage;
|
|
12
|
+
// In case that options is being parsed as a readonly attribute.
|
|
13
|
+
// Or it is not passed at all
|
|
14
|
+
const optsArg = _.cloneDeep(optionsArg || {});
|
|
15
|
+
const defaultOptions = {
|
|
16
|
+
debug: false,
|
|
17
|
+
log: null,
|
|
18
|
+
exclude: [
|
|
19
|
+
'id',
|
|
20
|
+
'createdAt',
|
|
21
|
+
'updatedAt',
|
|
22
|
+
'deletedAt',
|
|
23
|
+
'created_at',
|
|
24
|
+
'updated_at',
|
|
25
|
+
'deleted_at',
|
|
26
|
+
'audit_source',
|
|
27
|
+
'revision',
|
|
28
|
+
],
|
|
29
|
+
// additional fields to exclude on a global basis
|
|
30
|
+
excludeExtras: [],
|
|
31
|
+
excludeVirtualAttributes: true, // by default do not record these
|
|
32
|
+
censor: ["password", "password_salt", "temp_secret", "mfa_totp_secret"],
|
|
33
|
+
revisionAttribute: 'revision',
|
|
34
|
+
revisionModel: 'Revision',
|
|
35
|
+
revisionChangeModel: 'RevisionChange',
|
|
36
|
+
enableRevisionChangeModel: false,
|
|
37
|
+
UUID: false,
|
|
38
|
+
// does this project use int's and UUIDs for some references (postgres)
|
|
39
|
+
mixedDocumentReference: false,
|
|
40
|
+
underscored: false,
|
|
41
|
+
underscoredAttributes: false,
|
|
42
|
+
// always store these keys in a reference value if seen
|
|
43
|
+
includeReferences: [],
|
|
44
|
+
defaultAttributes: {
|
|
45
|
+
documentId: 'documentId',
|
|
46
|
+
revisionId: 'revisionId',
|
|
47
|
+
autoGenerated: 'autoGenerated'
|
|
48
|
+
},
|
|
49
|
+
als: null,
|
|
50
|
+
userModel: false,
|
|
51
|
+
userModelAttribute: 'userId',
|
|
52
|
+
tenantModel: false,
|
|
53
|
+
tenantModelAttribute: 'tenantId',
|
|
54
|
+
enableCompression: false,
|
|
55
|
+
enableMigration: false,
|
|
56
|
+
enableStrictDiff: true,
|
|
57
|
+
continuationNamespace: null,
|
|
58
|
+
continuationKey: 'userId',
|
|
59
|
+
continuationTenantKey: 'tenantId',
|
|
60
|
+
metaDataFields: null,
|
|
61
|
+
metaDataContinuationKey: 'metaData',
|
|
62
|
+
mysql: false,
|
|
63
|
+
};
|
|
64
|
+
if (optsArg.mixedDocumentReference)
|
|
65
|
+
defaultOptions.defaultAttributes.documentUuid = "documentUuid";
|
|
66
|
+
if (optsArg.underscoredAttributes) {
|
|
67
|
+
helpers.toUnderscored(defaultOptions.defaultAttributes);
|
|
68
|
+
}
|
|
69
|
+
// deals with not wanting to overwrite the initial ones, just add some
|
|
70
|
+
if (optsArg.excludeExtras) {
|
|
71
|
+
if (optsArg.exclude) {
|
|
72
|
+
optsArg.exclude = [...optsArg.exclude, ...optsArg.excludeExtras];
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
optsArg.exclude = [...defaultOptions.exclude, ...optsArg.excludeExtras];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const options = _.defaults(optsArg, defaultOptions);
|
|
79
|
+
const log = options.log || console.log;
|
|
80
|
+
function createBeforeHook(operation) {
|
|
81
|
+
const beforeHook = function beforeHook(instance, opt) {
|
|
82
|
+
if (options.debug) {
|
|
83
|
+
log('beforeHook called');
|
|
84
|
+
log('instance:', instance);
|
|
85
|
+
log('opt:', opt);
|
|
86
|
+
}
|
|
87
|
+
if (opt.noPaperTrail) {
|
|
88
|
+
if (options.debug) {
|
|
89
|
+
log('noPaperTrail opt: is true, not logging');
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const destroyOperation = operation === 'destroy';
|
|
94
|
+
const restoreOperation = operation === 'restore';
|
|
95
|
+
let previousVersion = _.cloneDeep(instance._previousDataValues);
|
|
96
|
+
let currentVersion = _.cloneDeep(instance.dataValues);
|
|
97
|
+
if (!destroyOperation && options.enableCompression) {
|
|
98
|
+
_.forEach(opt.fields, a => {
|
|
99
|
+
previousVersion[a] = instance._previousDataValues[a];
|
|
100
|
+
currentVersion[a] = instance.dataValues[a];
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
previousVersion = instance._previousDataValues;
|
|
105
|
+
currentVersion = instance.dataValues;
|
|
106
|
+
}
|
|
107
|
+
const excludes = options.exclude.concat([...this.extraRevisionAttributesExclude]);
|
|
108
|
+
// Supported nested models, allowing JSONB objects.
|
|
109
|
+
previousVersion = _.omitBy(previousVersion, i => i != null && typeof i === 'object' && !(i instanceof Date || _.isPlainObject(i)));
|
|
110
|
+
previousVersion = _.omit(previousVersion, excludes);
|
|
111
|
+
currentVersion = _.omitBy(currentVersion, i => i != null && typeof i === 'object' && !(i instanceof Date || _.isPlainObject(i)));
|
|
112
|
+
currentVersion = _.omit(currentVersion, excludes);
|
|
113
|
+
// Disallow change of revision
|
|
114
|
+
instance.set(options.revisionAttribute, instance._previousDataValues[options.revisionAttribute]);
|
|
115
|
+
// Get diffs
|
|
116
|
+
const delta = helpers.calcDelta(previousVersion, currentVersion, options.exclude, options.enableStrictDiff);
|
|
117
|
+
const currentRevisionId = instance.get(options.revisionAttribute);
|
|
118
|
+
if (failHard && !currentRevisionId && opt.type === 'UPDATE') {
|
|
119
|
+
throw new Error('Revision Id was undefined');
|
|
120
|
+
}
|
|
121
|
+
if (options.debug) {
|
|
122
|
+
log('delta:', delta);
|
|
123
|
+
log('revisionId', currentRevisionId);
|
|
124
|
+
}
|
|
125
|
+
// Check if all required fields have been provided to the opts / ALS
|
|
126
|
+
if (options.metaDataFields) {
|
|
127
|
+
// get all required field keys as an array
|
|
128
|
+
const requiredFields = _.keys(_.pickBy(options.metaDataFields, function isMetaDataFieldRequired(required) {
|
|
129
|
+
return required;
|
|
130
|
+
}));
|
|
131
|
+
if (requiredFields && requiredFields.length) {
|
|
132
|
+
const store = options.als && options.als.getStore();
|
|
133
|
+
const metaData = (store && store.get(metaDataContinuationKey)) || opt.metaData;
|
|
134
|
+
const requiredFieldsProvided = _.filter(requiredFields, function isMetaDataFieldNonUndefined(field) {
|
|
135
|
+
return metaData[field] !== undefined;
|
|
136
|
+
});
|
|
137
|
+
if (requiredFieldsProvided.length !== requiredFields.length) {
|
|
138
|
+
log('Required fields: ', options.metaDataFields, requiredFields);
|
|
139
|
+
log('Required fields provided: ', metaData, requiredFieldsProvided);
|
|
140
|
+
throw new Error('Not all required fields are provided to paper trail!');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (destroyOperation || restoreOperation || (delta && delta.length > 0)) {
|
|
145
|
+
const revisionId = (currentRevisionId || 0) + 1;
|
|
146
|
+
instance.set(options.revisionAttribute, revisionId);
|
|
147
|
+
if (!instance.context) {
|
|
148
|
+
instance.context = {};
|
|
149
|
+
}
|
|
150
|
+
instance.context.delta = delta;
|
|
151
|
+
}
|
|
152
|
+
if (options.debug) {
|
|
153
|
+
log('end of beforeHook');
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return beforeHook;
|
|
157
|
+
}
|
|
158
|
+
function createAfterHook(operation) {
|
|
159
|
+
const afterHook = function afterHook(instance, opt) {
|
|
160
|
+
const store = options.als && options.als.getStore();
|
|
161
|
+
if (options.debug) {
|
|
162
|
+
log('afterHook called');
|
|
163
|
+
log('instance:', instance);
|
|
164
|
+
log('opt:', opt);
|
|
165
|
+
if (store) {
|
|
166
|
+
log(`ALS ${options.continuationKey}:`, store.get(options.continuationKey));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const destroyOperation = operation === 'destroy';
|
|
170
|
+
const createOperation = operation === 'create';
|
|
171
|
+
const restoreOperation = operation === 'restore';
|
|
172
|
+
if (instance.context &&
|
|
173
|
+
((instance.context.delta &&
|
|
174
|
+
instance.context.delta.length > 0) ||
|
|
175
|
+
destroyOperation || restoreOperation)) {
|
|
176
|
+
const Revision = sequelize.model(options.revisionModel);
|
|
177
|
+
let RevisionChange;
|
|
178
|
+
if (options.enableRevisionChangeModel) {
|
|
179
|
+
RevisionChange = sequelize.model(options.revisionChangeModel);
|
|
180
|
+
}
|
|
181
|
+
const { delta } = instance.context;
|
|
182
|
+
let previousVersion = {};
|
|
183
|
+
let currentVersion = {};
|
|
184
|
+
if (!destroyOperation && options.enableCompression) {
|
|
185
|
+
opt.fields = opt.fields || instance._options.attributes;
|
|
186
|
+
_.forEach(opt.fields, a => {
|
|
187
|
+
previousVersion[a] = instance._previousDataValues[a];
|
|
188
|
+
currentVersion[a] = instance.dataValues[a];
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
previousVersion = instance._previousDataValues;
|
|
193
|
+
currentVersion = instance.dataValues;
|
|
194
|
+
}
|
|
195
|
+
// we need to support nested Objects in a different way
|
|
196
|
+
// if the type of change is a nested object, we need to lookat the deltas and join the path with periods
|
|
197
|
+
// to get and show the differences.
|
|
198
|
+
// Supported nested models.
|
|
199
|
+
previousVersion = _.omitBy(previousVersion, value => {
|
|
200
|
+
if (value == null)
|
|
201
|
+
return false; // keep null/undefined
|
|
202
|
+
if (value instanceof Date)
|
|
203
|
+
return false; // keep Dates
|
|
204
|
+
if (_.isPlainObject(value))
|
|
205
|
+
return false; // keep plain objects (including nested objects)
|
|
206
|
+
return typeof value === 'object'; // omit if it's an object that's not plain or a Date
|
|
207
|
+
});
|
|
208
|
+
previousVersion = _.omit(previousVersion, options.exclude);
|
|
209
|
+
currentVersion = _.omitBy(currentVersion, value => {
|
|
210
|
+
if (value == null)
|
|
211
|
+
return false;
|
|
212
|
+
if (value instanceof Date)
|
|
213
|
+
return false;
|
|
214
|
+
if (_.isPlainObject(value))
|
|
215
|
+
return false;
|
|
216
|
+
return typeof value === 'object';
|
|
217
|
+
});
|
|
218
|
+
currentVersion = _.omit(currentVersion, options.exclude);
|
|
219
|
+
const store = options.als && options.als.getStore();
|
|
220
|
+
if (store && !store.has(options.continuationKey)) {
|
|
221
|
+
if (failHard)
|
|
222
|
+
throw new Error(`The ALS continuationKey ${options.continuationKey} was not defined.`);
|
|
223
|
+
}
|
|
224
|
+
let objectsChanged = new Set();
|
|
225
|
+
// if the previous version or new version contain objects; push them onto this list
|
|
226
|
+
_.forEach(previousVersion, (value, key) => {
|
|
227
|
+
if (typeof (value) === 'object') {
|
|
228
|
+
objectsChanged.add(key);
|
|
229
|
+
// blank it so we can set it equal to ONLY the values changed in the next segment
|
|
230
|
+
previousVersion[key] = {};
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
_.forEach(currentVersion, (value, key) => {
|
|
234
|
+
if (typeof (value) === 'object') {
|
|
235
|
+
objectsChanged.add(key);
|
|
236
|
+
currentVersion[key] = {};
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
// we don't want to see a full object; we want to see only
|
|
240
|
+
// x.y.z -> value
|
|
241
|
+
if (delta && objectsChanged.size > 0) {
|
|
242
|
+
// the format of these are A,E,D,N (added, edited, deleted, new) for the kind
|
|
243
|
+
// the array is either the key or key + subkeys ['options','subkey','subkey2'] etc.
|
|
244
|
+
// {kind: 'E', path: Array(2), lhs: false, rhs: true}
|
|
245
|
+
for (const difference of delta) {
|
|
246
|
+
// blank the currentVersion[difference.path[0]]
|
|
247
|
+
// and previousVersion[difference.path[0]]
|
|
248
|
+
_.set(currentVersion, difference.path, difference.rhs);
|
|
249
|
+
_.set(previousVersion, difference.path, difference.lhs);
|
|
250
|
+
}
|
|
251
|
+
;
|
|
252
|
+
}
|
|
253
|
+
let document;
|
|
254
|
+
if (destroyOperation) {
|
|
255
|
+
document = { oldValues: currentVersion };
|
|
256
|
+
}
|
|
257
|
+
else if (createOperation || restoreOperation) {
|
|
258
|
+
// we want to print out the entire object as values MAY have changed when restoring
|
|
259
|
+
// and for NEW objects, we want the initial state
|
|
260
|
+
document = { newValues: currentVersion };
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
document = { oldValues: previousVersion, newValues: currentVersion };
|
|
264
|
+
}
|
|
265
|
+
const references = _.pick(instance.dataValues, options.includeReferences);
|
|
266
|
+
if (Object.keys(references).length) {
|
|
267
|
+
document['references'] = references;
|
|
268
|
+
}
|
|
269
|
+
if (options.mysql) {
|
|
270
|
+
document = JSON.stringify(document);
|
|
271
|
+
}
|
|
272
|
+
// Build revision
|
|
273
|
+
const query = {
|
|
274
|
+
model: this.name,
|
|
275
|
+
document,
|
|
276
|
+
operation,
|
|
277
|
+
};
|
|
278
|
+
// Add all extra data fields to the query object
|
|
279
|
+
if (options.metaDataFields) {
|
|
280
|
+
const metaData = (store && store.get(options.metaDataContinuationKey)) ||
|
|
281
|
+
opt.metaData;
|
|
282
|
+
if (metaData) {
|
|
283
|
+
_.forEach(options.metaDataFields, function getMetaDataValues(required, field) {
|
|
284
|
+
const value = metaData[field];
|
|
285
|
+
if (options.debug) {
|
|
286
|
+
log(`Adding metaData field to Revision - ${field} => ${value}`);
|
|
287
|
+
}
|
|
288
|
+
if (!(field in query)) {
|
|
289
|
+
query[field] = value;
|
|
290
|
+
}
|
|
291
|
+
else if (options.debug) {
|
|
292
|
+
log(`Revision object already has a value at ${field} => ${query[field]}`);
|
|
293
|
+
log('Not overwriting the original value');
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
query[options.defaultAttributes.autoGenerated] = opt.autoGenerated;
|
|
299
|
+
// in case of custom user models that are not 'userId'
|
|
300
|
+
query[options.userModelAttribute] =
|
|
301
|
+
(store && store.get(options.continuationKey)) || opt.userId;
|
|
302
|
+
query[options.tenantModelAttribute] =
|
|
303
|
+
(store && store.get(options.continuationTenantKey)) || opt.tenantId;
|
|
304
|
+
if (options.mixedDocumentReference && utils.isValidUuidV4(instance.id)) {
|
|
305
|
+
query[options.defaultAttributes.documentUuid] = instance.id;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
query[options.defaultAttributes.documentId] = instance.id;
|
|
309
|
+
}
|
|
310
|
+
const revision = Revision.build(query);
|
|
311
|
+
revision[options.revisionAttribute] = instance.get(options.revisionAttribute);
|
|
312
|
+
return revision
|
|
313
|
+
.save({ transaction: opt.transaction })
|
|
314
|
+
.then(objectRevision => {
|
|
315
|
+
// Loop diffs and create a revision-diff for each
|
|
316
|
+
if (options.enableRevisionChangeModel) {
|
|
317
|
+
_.forEach(delta, difference => {
|
|
318
|
+
const o = helpers.diffToString(difference.item
|
|
319
|
+
? difference.item.lhs
|
|
320
|
+
: difference.lhs);
|
|
321
|
+
const n = helpers.diffToString(difference.item
|
|
322
|
+
? difference.item.rhs
|
|
323
|
+
: difference.rhs);
|
|
324
|
+
// let document = difference;
|
|
325
|
+
document = difference;
|
|
326
|
+
let diff = o || n ? jsdiff.diffChars(o, n) : [];
|
|
327
|
+
if (options.mysql) {
|
|
328
|
+
document = JSON.stringify(document);
|
|
329
|
+
diff = JSON.stringify(diff);
|
|
330
|
+
}
|
|
331
|
+
const d = RevisionChange.build({
|
|
332
|
+
path: difference.path[0],
|
|
333
|
+
document,
|
|
334
|
+
diff,
|
|
335
|
+
revisionId: objectRevision.id,
|
|
336
|
+
});
|
|
337
|
+
d.save({ transaction: opt.transaction })
|
|
338
|
+
.then(savedD => {
|
|
339
|
+
// Add diff to revision
|
|
340
|
+
objectRevision[`add${helpers.capitalizeFirstLetter(options.revisionChangeModel)}`](savedD);
|
|
341
|
+
return null;
|
|
342
|
+
})
|
|
343
|
+
.catch(err => {
|
|
344
|
+
log('RevisionChange save error', err);
|
|
345
|
+
throw err;
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
})
|
|
351
|
+
.catch(err => {
|
|
352
|
+
log('Revision save error', err);
|
|
353
|
+
throw err;
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
if (options.debug) {
|
|
357
|
+
log('end of afterHook');
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
};
|
|
361
|
+
return afterHook;
|
|
362
|
+
}
|
|
363
|
+
// order in which sequelize processes the hooks
|
|
364
|
+
// (1)
|
|
365
|
+
// beforeBulkCreate(instances, options, fn)
|
|
366
|
+
// beforeBulkDestroy(instances, options, fn)
|
|
367
|
+
// beforeBulkUpdate(instances, options, fn)
|
|
368
|
+
// (2)
|
|
369
|
+
// beforeValidate(instance, options, fn)
|
|
370
|
+
// (-)
|
|
371
|
+
// validate
|
|
372
|
+
// (3)
|
|
373
|
+
// afterValidate(instance, options, fn)
|
|
374
|
+
// - or -
|
|
375
|
+
// validationFailed(instance, options, error, fn)
|
|
376
|
+
// (4)
|
|
377
|
+
// beforeCreate(instance, options, fn)
|
|
378
|
+
// beforeDestroy(instance, options, fn)
|
|
379
|
+
// beforeUpdate(instance, options, fn)
|
|
380
|
+
// beforeRestore(instance, options, fn)
|
|
381
|
+
// (-)
|
|
382
|
+
// create
|
|
383
|
+
// destroy
|
|
384
|
+
// update
|
|
385
|
+
// (5)
|
|
386
|
+
// afterCreate(instance, options, fn)
|
|
387
|
+
// afterDestroy(instance, options, fn)
|
|
388
|
+
// afterUpdate(instance, options, fn)
|
|
389
|
+
// afterRestore(instance, options, fn)
|
|
390
|
+
// (6)
|
|
391
|
+
// afterBulkCreate(instances, options, fn)
|
|
392
|
+
// afterBulkDestroy(instances, options, fn)
|
|
393
|
+
// afterBulkUpdate(instances, options, fn)
|
|
394
|
+
// Extend model prototype with "hasPaperTrail" function
|
|
395
|
+
// Call model.hasPaperTrail() to enable revisions for model
|
|
396
|
+
_.assignIn(Sequelize.Model, {
|
|
397
|
+
hasPaperTrail: function hasPaperTrail() {
|
|
398
|
+
if (options.debug) {
|
|
399
|
+
log('Enabling paper trail on', this.name);
|
|
400
|
+
}
|
|
401
|
+
this.rawAttributes[options.revisionAttribute] = {
|
|
402
|
+
type: Sequelize.INTEGER,
|
|
403
|
+
defaultValue: 0,
|
|
404
|
+
};
|
|
405
|
+
this.revisionable = true;
|
|
406
|
+
// not sure if we need this
|
|
407
|
+
this.refreshAttributes();
|
|
408
|
+
this.extraRevisionAttributesExclude = new Set();
|
|
409
|
+
if (options.excludeVirtualAttributes) {
|
|
410
|
+
for (const attribute in this.rawAttributes) {
|
|
411
|
+
if (this.rawAttributes[attribute].type == "VIRTUAL") {
|
|
412
|
+
this.extraRevisionAttributesExclude.add(attribute);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (options.enableMigration) {
|
|
417
|
+
const tableName = this.getTableName();
|
|
418
|
+
const queryInterface = sequelize.getQueryInterface();
|
|
419
|
+
queryInterface.describeTable(tableName).then(attributes => {
|
|
420
|
+
if (!attributes[options.revisionAttribute]) {
|
|
421
|
+
if (options.debug) {
|
|
422
|
+
log('adding revision attribute to the database');
|
|
423
|
+
}
|
|
424
|
+
queryInterface
|
|
425
|
+
.addColumn(tableName, options.revisionAttribute, {
|
|
426
|
+
type: Sequelize.INTEGER,
|
|
427
|
+
defaultValue: 0,
|
|
428
|
+
})
|
|
429
|
+
.then(() => null)
|
|
430
|
+
.catch(err => {
|
|
431
|
+
log('something went really wrong..', err);
|
|
432
|
+
return null;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
return null;
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
this.addHook('beforeCreate', createBeforeHook('create'));
|
|
439
|
+
this.addHook('beforeDestroy', createBeforeHook('destroy'));
|
|
440
|
+
this.addHook('beforeUpdate', createBeforeHook('update'));
|
|
441
|
+
this.addHook('beforeRestore', createBeforeHook('restore'));
|
|
442
|
+
this.addHook('afterCreate', createAfterHook('create'));
|
|
443
|
+
this.addHook('afterDestroy', createAfterHook('destroy'));
|
|
444
|
+
this.addHook('afterUpdate', createAfterHook('update'));
|
|
445
|
+
this.addHook('afterRestore', createAfterHook('restore'));
|
|
446
|
+
// create association
|
|
447
|
+
if (options.mixedDocumentReference) {
|
|
448
|
+
this.hasMany(sequelize.models[options.revisionModel], {
|
|
449
|
+
constraints: false,
|
|
450
|
+
foreignKey: options.defaultAttributes.documentUuid,
|
|
451
|
+
});
|
|
452
|
+
this.hasMany(sequelize.models[options.revisionModel], {
|
|
453
|
+
foreignKey: options.defaultAttributes.documentId,
|
|
454
|
+
constraints: false,
|
|
455
|
+
scope: {
|
|
456
|
+
model: this.name,
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
else {
|
|
461
|
+
return this.hasMany(sequelize.models[options.revisionModel], {
|
|
462
|
+
foreignKey: options.defaultAttributes.documentId,
|
|
463
|
+
constraints: false,
|
|
464
|
+
scope: {
|
|
465
|
+
model: this.name,
|
|
466
|
+
},
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
},
|
|
470
|
+
});
|
|
471
|
+
return {
|
|
472
|
+
// TWS-1032 since ALS must be set after a .run()
|
|
473
|
+
// invocation this CANNOT be set on init(). If you would
|
|
474
|
+
// like to use ALS, set it after the first invocation of run()
|
|
475
|
+
setupALS: function (asyncLocalStorage) {
|
|
476
|
+
options.als = asyncLocalStorage;
|
|
477
|
+
},
|
|
478
|
+
alsEnabled: function () {
|
|
479
|
+
return (options.als === true);
|
|
480
|
+
},
|
|
481
|
+
defineModels: function defineModels(db) {
|
|
482
|
+
// Attributes for RevisionModel
|
|
483
|
+
let attributes = {
|
|
484
|
+
model: {
|
|
485
|
+
type: Sequelize.TEXT,
|
|
486
|
+
allowNull: false,
|
|
487
|
+
},
|
|
488
|
+
document: {
|
|
489
|
+
type: Sequelize.JSONB,
|
|
490
|
+
allowNull: false,
|
|
491
|
+
},
|
|
492
|
+
operation: Sequelize.STRING(7),
|
|
493
|
+
};
|
|
494
|
+
if (options.mysql) {
|
|
495
|
+
attributes.document.type = Sequelize.TEXT('MEDIUMTEXT');
|
|
496
|
+
}
|
|
497
|
+
attributes[options.defaultAttributes.documentId] = {
|
|
498
|
+
type: Sequelize.INTEGER,
|
|
499
|
+
allowNull: false,
|
|
500
|
+
};
|
|
501
|
+
attributes[options.defaultAttributes.autoGenerated] = {
|
|
502
|
+
type: Sequelize.BOOLEAN,
|
|
503
|
+
defaultValue: false,
|
|
504
|
+
allowNull: false,
|
|
505
|
+
};
|
|
506
|
+
attributes[options.revisionAttribute] = {
|
|
507
|
+
type: Sequelize.INTEGER,
|
|
508
|
+
allowNull: false,
|
|
509
|
+
};
|
|
510
|
+
attributes[options.userModelAttribute] = {
|
|
511
|
+
type: options.UUID ? Sequelize.UUID : Sequelize.INTEGER,
|
|
512
|
+
allowNull: true
|
|
513
|
+
};
|
|
514
|
+
attributes[options.tenantModelAttribute] = {
|
|
515
|
+
type: options.UUID ? Sequelize.UUID : Sequelize.INTEGER,
|
|
516
|
+
allowNull: true
|
|
517
|
+
};
|
|
518
|
+
if (options.UUID) {
|
|
519
|
+
attributes.id = {
|
|
520
|
+
primaryKey: true,
|
|
521
|
+
type: Sequelize.UUID,
|
|
522
|
+
defaultValue: Sequelize.UUIDV4,
|
|
523
|
+
};
|
|
524
|
+
if (!options.mixedDocumentReference)
|
|
525
|
+
attributes[options.defaultAttributes.documentId].type = Sequelize.UUID;
|
|
526
|
+
attributes[options.userModelAttribute].type = Sequelize.UUID;
|
|
527
|
+
}
|
|
528
|
+
if (options.mixedDocumentReference) {
|
|
529
|
+
attributes[options.defaultAttributes.documentId].allowNull = true;
|
|
530
|
+
attributes[options.defaultAttributes.documentUuid] = {
|
|
531
|
+
type: Sequelize.UUID,
|
|
532
|
+
allowNull: true
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
if (options.debug) {
|
|
536
|
+
log('attributes', attributes);
|
|
537
|
+
}
|
|
538
|
+
let indexes = [{
|
|
539
|
+
name: "document_reference_id",
|
|
540
|
+
fields: [options.defaultAttributes.documentId]
|
|
541
|
+
}];
|
|
542
|
+
if (options.userModel)
|
|
543
|
+
indexes.push({ fields: [options.userModelAttribute] });
|
|
544
|
+
if (options.tenantModel)
|
|
545
|
+
indexes.push({ fields: [options.tenantModelAttribute] });
|
|
546
|
+
if (options.mixedDocumentReference) {
|
|
547
|
+
indexes.push({
|
|
548
|
+
name: "document_reference_uuid",
|
|
549
|
+
fields: [options.defaultAttributes.documentUuid]
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
// Revision model
|
|
553
|
+
const Revision = sequelize.define(options.revisionModel, attributes, {
|
|
554
|
+
underscored: options.underscored,
|
|
555
|
+
tableName: options.tableName,
|
|
556
|
+
timestamps: true,
|
|
557
|
+
createdAt: options.underscored ? "created_at" : "createdAt",
|
|
558
|
+
updatedAt: options.underscored ? "updated_at" : "updatedAt",
|
|
559
|
+
indexes
|
|
560
|
+
});
|
|
561
|
+
// safely prune down values
|
|
562
|
+
Revision.prototype.toJSON = function () {
|
|
563
|
+
let values = this.get();
|
|
564
|
+
const excludedFields = ["updated_at"];
|
|
565
|
+
// ensure that we always reference the id as document_id
|
|
566
|
+
values.document_id = values.document_id || values.document_uuid;
|
|
567
|
+
delete values.document_uuid;
|
|
568
|
+
_.forEach(defaultOptions.censor, a => {
|
|
569
|
+
if (!utils.isBlank(values.document.newValues) && !utils.isBlank(values.document.newValues[a])) {
|
|
570
|
+
values.document.newValues[a] = censorText;
|
|
571
|
+
}
|
|
572
|
+
if (!utils.isBlank(values.document.oldValues) && !utils.isBlank(values.document.oldValues[a])) {
|
|
573
|
+
values.document.oldValues[a] = censorText;
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
return _.omitBy(values, function (value, key) {
|
|
577
|
+
return (excludedFields.includes(key) || (value === null));
|
|
578
|
+
});
|
|
579
|
+
};
|
|
580
|
+
Revision.associate = function associate(models) {
|
|
581
|
+
if (options.debug) {
|
|
582
|
+
log('models', models);
|
|
583
|
+
}
|
|
584
|
+
if (options.userModel) {
|
|
585
|
+
Revision.belongsTo(sequelize.model(options.userModel), options.belongsToUserOptions);
|
|
586
|
+
}
|
|
587
|
+
if (options.tenantModel) {
|
|
588
|
+
Revision.belongsTo(sequelize.model(options.tenantModel), options.belongsToTenantOptions);
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
if (options.enableRevisionChangeModel) {
|
|
592
|
+
// Attributes for RevisionChangeModel
|
|
593
|
+
attributes = {
|
|
594
|
+
path: {
|
|
595
|
+
type: Sequelize.TEXT,
|
|
596
|
+
allowNull: false,
|
|
597
|
+
},
|
|
598
|
+
document: {
|
|
599
|
+
type: Sequelize.JSONB,
|
|
600
|
+
allowNull: false,
|
|
601
|
+
},
|
|
602
|
+
diff: {
|
|
603
|
+
type: Sequelize.JSONB,
|
|
604
|
+
allowNull: false,
|
|
605
|
+
},
|
|
606
|
+
};
|
|
607
|
+
if (options.mysql) {
|
|
608
|
+
attributes.document.type = Sequelize.TEXT('MEDIUMTEXT');
|
|
609
|
+
attributes.diff.type = Sequelize.TEXT('MEDIUMTEXT');
|
|
610
|
+
}
|
|
611
|
+
if (options.UUID) {
|
|
612
|
+
attributes.id = {
|
|
613
|
+
primaryKey: true,
|
|
614
|
+
type: Sequelize.UUID,
|
|
615
|
+
defaultValue: Sequelize.UUIDV4,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
// RevisionChange model
|
|
619
|
+
const RevisionChange = sequelize.define(options.revisionChangeModel, attributes, {
|
|
620
|
+
underscored: options.underscored,
|
|
621
|
+
});
|
|
622
|
+
// Set associations
|
|
623
|
+
Revision.hasMany(RevisionChange, {
|
|
624
|
+
foreignKey: options.defaultAttributes.revisionId,
|
|
625
|
+
constraints: false,
|
|
626
|
+
});
|
|
627
|
+
RevisionChange.belongsTo(Revision);
|
|
628
|
+
if (db)
|
|
629
|
+
db[RevisionChange.name] = RevisionChange;
|
|
630
|
+
}
|
|
631
|
+
paginator.paginate(Revision);
|
|
632
|
+
if (db)
|
|
633
|
+
db[Revision.name] = Revision;
|
|
634
|
+
/*
|
|
635
|
+
* We could extract this to a separate function so that having a
|
|
636
|
+
* user model doesn't require different loading
|
|
637
|
+
*
|
|
638
|
+
* or perhaps we could omit this because we are creating the
|
|
639
|
+
* association through the associate call above.
|
|
640
|
+
*/
|
|
641
|
+
if (options.userModel) {
|
|
642
|
+
Revision.belongsTo(sequelize.model(options.userModel), {
|
|
643
|
+
as: "account",
|
|
644
|
+
foreignKey: {
|
|
645
|
+
fieldName: options.userModelAttribute
|
|
646
|
+
},
|
|
647
|
+
onDelete: "CASCADE"
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
return Revision;
|
|
651
|
+
},
|
|
652
|
+
};
|
|
653
|
+
};
|
|
654
|
+
/**
|
|
655
|
+
* Throw exceptions when the user identifier from ALS is not set or if the
|
|
656
|
+
* revisionAttribute was not loaded on the model.
|
|
657
|
+
*/
|
|
658
|
+
paperTrail.enableFailHard = () => {
|
|
659
|
+
failHard = true;
|
|
660
|
+
};
|
|
661
|
+
export default paperTrail;
|
|
662
|
+
//# sourceMappingURL=index.js.map
|