@stamhoofd/backend 2.57.1 → 2.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
- import { ArrayDecoder, AutoEncoder, AutoEncoderPatchType, BooleanDecoder, DateDecoder, EnumDecoder, Field, IntegerDecoder, isPatchable, isPatchableArray, isPatchMap, MapDecoder, StringDecoder, SymbolDecoder } from '@simonbackx/simple-encoding';
2
- import { Address, AuditLogPatchItem, AuditLogPatchItemType, AuditLogReplacement, AuditLogReplacementType, BooleanStatus, Image, Parent, ParentTypeHelper, RichText } from '@stamhoofd/structures';
1
+ import { ArrayDecoder, AutoEncoder, BooleanDecoder, DateDecoder, EnumDecoder, Field, getOptionalId, IntegerDecoder, isPatchableArray, isPatchMap, MapDecoder, StringDecoder, SymbolDecoder } from '@simonbackx/simple-encoding';
2
+ import { AuditLogPatchItem, AuditLogPatchItemType, AuditLogReplacement, AuditLogReplacementType, BooleanStatus, Image, isEmptyFilter, isUuid, PropertyFilter, RichText, Version } from '@stamhoofd/structures';
3
3
  import { Formatter } from '@stamhoofd/utility';
4
+ import { get } from 'http';
4
5
 
5
6
  export type PatchExplainer = {
6
7
  key: string;
@@ -20,14 +21,34 @@ function createStringChangeHandler(key: string) {
20
21
 
21
22
  return [
22
23
  AuditLogPatchItem.create({
23
- key: AuditLogReplacement.key(key),
24
- oldValue: typeof oldValue === 'string' ? AuditLogReplacement.string(oldValue) : undefined,
25
- value: typeof value === 'string' ? AuditLogReplacement.string(value) : undefined,
24
+ key: getAutoEncoderKey(key),
25
+ oldValue: getAutoEncoderValue(oldValue) || getAutoEncoderName(oldValue) || undefined,
26
+ value: getAutoEncoderValue(value) || getAutoEncoderName(value) || undefined,
26
27
  }).autoType(),
27
28
  ];
28
29
  };
29
30
  }
30
31
 
32
+ function createEnumChangeHandler(key: string) {
33
+ return (oldValue: unknown, value: unknown) => {
34
+ if (oldValue === value) {
35
+ return [];
36
+ }
37
+
38
+ if (value === undefined) {
39
+ // Not altered
40
+ return [];
41
+ }
42
+
43
+ return [
44
+ AuditLogPatchItem.create({
45
+ key: getAutoEncoderKey(key),
46
+ oldValue: typeof oldValue === 'string' ? AuditLogReplacement.key(oldValue) : undefined,
47
+ value: typeof value === 'string' ? AuditLogReplacement.key(value) : undefined,
48
+ }).autoType(),
49
+ ];
50
+ };
51
+ }
31
52
  function createIntegerChangeHandler(key: string) {
32
53
  return (oldValue: unknown, value: unknown) => {
33
54
  if ((typeof oldValue !== 'number' && oldValue !== null) || (typeof value !== 'number' && value !== null)) {
@@ -40,7 +61,7 @@ function createIntegerChangeHandler(key: string) {
40
61
  const formatter: (typeof Formatter.price | typeof Formatter.integer) = key.toLowerCase().includes('price') ? Formatter.price.bind(Formatter) : Formatter.integer.bind(Formatter);
41
62
  return [
42
63
  AuditLogPatchItem.create({
43
- key: AuditLogReplacement.key(key),
64
+ key: getAutoEncoderKey(key),
44
65
  oldValue: oldValue !== null ? AuditLogReplacement.string(formatter(oldValue)) : undefined,
45
66
  value: value !== null ? AuditLogReplacement.string(formatter(value)) : undefined,
46
67
  }).autoType(),
@@ -50,7 +71,11 @@ function createIntegerChangeHandler(key: string) {
50
71
 
51
72
  function createDateChangeHandler(key: string) {
52
73
  return (oldValue: unknown, value: unknown) => {
53
- if ((!(oldValue instanceof Date) && oldValue !== null) || (!(value instanceof Date)) && value !== null) {
74
+ if (!(oldValue instanceof Date) && oldValue !== null) {
75
+ return [];
76
+ }
77
+
78
+ if ((!(value instanceof Date)) && value !== null) {
54
79
  return [];
55
80
  }
56
81
 
@@ -68,7 +93,7 @@ function createDateChangeHandler(key: string) {
68
93
 
69
94
  return [
70
95
  AuditLogPatchItem.create({
71
- key: AuditLogReplacement.key(key),
96
+ key: getAutoEncoderKey(key),
72
97
  oldValue: dno ? AuditLogReplacement.string(dno) : undefined,
73
98
  value: dn ? AuditLogReplacement.string(dn) : undefined,
74
99
  }).autoType(),
@@ -92,48 +117,91 @@ function createBooleanChangeHandler(key: string) {
92
117
 
93
118
  return [
94
119
  AuditLogPatchItem.create({
95
- key: AuditLogReplacement.key(key),
96
- oldValue: oldValue === true ? AuditLogReplacement.string('Aan') : (oldValue === false ? AuditLogReplacement.string('Uit') : undefined),
97
- value: value === true ? AuditLogReplacement.string('Aan') : (value === false ? AuditLogReplacement.string('Uit') : undefined),
120
+ key: getAutoEncoderKey(key),
121
+ oldValue: oldValue === true ? AuditLogReplacement.key('on') : (oldValue === false ? AuditLogReplacement.key('off') : undefined),
122
+ value: value === true ? AuditLogReplacement.key('on') : (value === false ? AuditLogReplacement.key('off') : undefined),
98
123
  }).autoType(),
99
124
  ];
100
125
  };
101
126
  }
102
127
 
103
- function getAutoEncoderName(autoEncoder: unknown) {
128
+ function getAutoEncoderKey(autoEncoder: string): AuditLogReplacement;
129
+ function getAutoEncoderKey(autoEncoder: unknown): AuditLogReplacement | null;
130
+ function getAutoEncoderKey(autoEncoder: unknown): AuditLogReplacement | null {
104
131
  if (typeof autoEncoder === 'string') {
105
- return autoEncoder;
132
+ if (isUuid(autoEncoder)) {
133
+ return AuditLogReplacement.uuid(autoEncoder);
134
+ }
135
+ return AuditLogReplacement.key(autoEncoder);
106
136
  }
137
+ return null;
138
+ }
107
139
 
108
- if (autoEncoder instanceof Parent) {
109
- return autoEncoder.name + ` (${ParentTypeHelper.getName(autoEncoder.type)})`;
140
+ function getAutoEncoderName(autoEncoder: unknown): AuditLogReplacement | null {
141
+ if (typeof autoEncoder === 'string') {
142
+ if (isUuid(autoEncoder)) {
143
+ return AuditLogReplacement.uuid(autoEncoder);
144
+ }
145
+ return AuditLogReplacement.string(autoEncoder);
110
146
  }
111
147
 
112
- if (autoEncoder instanceof Address) {
113
- return autoEncoder.shortString();
148
+ if (typeof autoEncoder === 'object' && autoEncoder !== null && 'getPatchName' in autoEncoder && typeof autoEncoder.getPatchName === 'function') {
149
+ const name = autoEncoder.getPatchName();
150
+ if (typeof name === 'string') {
151
+ return name ? AuditLogReplacement.string(name) : AuditLogReplacement.key('untitled');
152
+ }
114
153
  }
115
154
 
116
155
  if (typeof autoEncoder === 'object' && autoEncoder !== null && 'name' in autoEncoder && typeof autoEncoder.name === 'string') {
117
- return autoEncoder.name;
156
+ return autoEncoder.name ? AuditLogReplacement.string(autoEncoder.name) : AuditLogReplacement.key('untitled');
118
157
  }
119
158
  return null;
120
159
  }
160
+ function getAutoEncoderPutValue(autoEncoder: unknown): AuditLogReplacement | null {
161
+ if (typeof autoEncoder === 'object' && autoEncoder !== null && 'getPutValue' in autoEncoder && typeof autoEncoder.getPutValue === 'function') {
162
+ const name = autoEncoder.getPutValue();
163
+ if (typeof name === 'string') {
164
+ return AuditLogReplacement.string(name);
165
+ }
166
+ if (name instanceof AuditLogReplacement) {
167
+ return name;
168
+ }
169
+ }
170
+ return getAutoEncoderValue(autoEncoder);
171
+ }
121
172
 
122
173
  function getAutoEncoderValue(autoEncoder: unknown): AuditLogReplacement | null {
123
174
  if (typeof autoEncoder === 'string') {
175
+ if (isUuid(autoEncoder)) {
176
+ return AuditLogReplacement.uuid(autoEncoder);
177
+ }
124
178
  return AuditLogReplacement.string(autoEncoder);
125
179
  }
126
180
 
127
- if (autoEncoder instanceof Parent) {
128
- return AuditLogReplacement.string(autoEncoder.name + ` (${ParentTypeHelper.getName(autoEncoder.type)})`);
181
+ if (typeof autoEncoder === 'symbol') {
182
+ const name = Symbol.keyFor(autoEncoder);
183
+ if (name) {
184
+ return AuditLogReplacement.key(name);
185
+ }
186
+ return AuditLogReplacement.key('unknown');
129
187
  }
130
188
 
131
- if (autoEncoder instanceof Address) {
132
- return AuditLogReplacement.string(autoEncoder.shortString());
189
+ if (typeof autoEncoder === 'number') {
190
+ return AuditLogReplacement.string(Formatter.integer(autoEncoder));
133
191
  }
134
192
 
135
- if (typeof autoEncoder === 'object' && autoEncoder !== null && 'name' in autoEncoder && typeof autoEncoder.name === 'string') {
136
- return AuditLogReplacement.string(autoEncoder.name);
193
+ if (autoEncoder instanceof Date) {
194
+ return AuditLogReplacement.string(Formatter.dateTime(autoEncoder, true, true));
195
+ }
196
+
197
+ if (typeof autoEncoder === 'object' && autoEncoder !== null && 'getPatchValue' in autoEncoder && typeof autoEncoder.getPatchValue === 'function') {
198
+ const name = autoEncoder.getPatchValue();
199
+ if (typeof name === 'string') {
200
+ return AuditLogReplacement.string(name);
201
+ }
202
+ if (name instanceof AuditLogReplacement) {
203
+ return name;
204
+ }
137
205
  }
138
206
 
139
207
  if (autoEncoder instanceof Image) {
@@ -147,144 +215,239 @@ function getAutoEncoderValue(autoEncoder: unknown): AuditLogReplacement | null {
147
215
  if (autoEncoder instanceof RichText) {
148
216
  return AuditLogReplacement.string(autoEncoder.text);
149
217
  }
218
+
219
+ if (autoEncoder instanceof PropertyFilter) {
220
+ if (autoEncoder.isAlwaysEnabledAndRequired) {
221
+ return AuditLogReplacement.key('alwaysEnabledAndRequired');
222
+ }
223
+ if (autoEncoder.enabledWhen === null && autoEncoder.requiredWhen === null) {
224
+ return AuditLogReplacement.key('alwaysEnabledAndOptional');
225
+ }
226
+ if (autoEncoder.enabledWhen !== null && autoEncoder.requiredWhen === null) {
227
+ return AuditLogReplacement.key('sometimesEnabledAndOptional');
228
+ }
229
+ if (autoEncoder.enabledWhen === null && autoEncoder.requiredWhen !== null) {
230
+ return AuditLogReplacement.key('alwaysEnabledAndSometimesRequired');
231
+ }
232
+ if (autoEncoder.enabledWhen !== null && isEmptyFilter(autoEncoder.requiredWhen)) {
233
+ return AuditLogReplacement.key('sometimesEnabledAndRequired');
234
+ }
235
+ return AuditLogReplacement.key('sometimesEnabledAndSometimesRequired');
236
+ }
237
+
150
238
  return null;
151
239
  }
152
240
 
153
- function createArrayChangeHandler(key: string) {
154
- return (oldValue: unknown, value: unknown) => {
155
- if (!isPatchableArray(value)) {
156
- // Not supported
157
- return [];
241
+ function getKeySingular(key: string) {
242
+ return key.replace(/ies$/, 'y').replace(/s$/, '');
243
+ }
244
+
245
+ function findOriginalById(id: unknown, oldArray: unknown[]): unknown | null {
246
+ return id ? oldArray.find(v => getId(v) === id) : null;
247
+ }
248
+
249
+ function getId(object: unknown): string | number | null {
250
+ const id = getOptionalId(object);
251
+ if (typeof id !== 'string' && typeof id !== 'number') {
252
+ if (object instanceof AutoEncoder) {
253
+ const encoded = object.encode({ version: Version });
254
+ return JSON.stringify(encoded);
158
255
  }
159
- if (!Array.isArray(oldValue)) {
160
- // Not supported
161
- return [];
256
+ return JSON.stringify(object);
257
+ }
258
+ return id;
259
+ }
260
+
261
+ function findOriginal(put: unknown, oldArray: unknown[]): unknown | null {
262
+ return findOriginalById(getId(put), oldArray);
263
+ }
264
+
265
+ function processPut(key: string, put: unknown, original: unknown | null, createdIdSet?: Set<string>): AuditLogPatchItem[] {
266
+ const items: AuditLogPatchItem[] = [];
267
+ const keySingular = getKeySingular(key);
268
+ const v = getAutoEncoderPutValue(put);
269
+
270
+ // Added a new parent
271
+ if (!original) {
272
+ items.push(
273
+ AuditLogPatchItem.create({
274
+ key: AuditLogReplacement.key(keySingular).append(getAutoEncoderName(put)),
275
+ value: v || undefined,
276
+ type: AuditLogPatchItemType.Added,
277
+ }),
278
+ );
279
+ }
280
+
281
+ // Little hack: detect PUT/DELETE behaviour:
282
+ if (createdIdSet) {
283
+ const id = getId(put);
284
+ if (id && typeof id === 'string') {
285
+ createdIdSet.add(id);
162
286
  }
287
+ }
163
288
 
164
- const items: AuditLogPatchItem[] = [];
165
- const createdIdSet = new Set<string>();
289
+ if (!original && (v || getAutoEncoderName(put))) {
290
+ // Simplify addition: don't show all added properties
291
+ return items;
292
+ }
166
293
 
167
- const keySingular = key.replace(/ies$/, 'y').replace(/s$/, '');
294
+ items.push(
295
+ ...explainPatch(
296
+ original ?? null,
297
+ put,
298
+ ).map((i) => {
299
+ i.key = i.key.prepend(getAutoEncoderName(original) || getAutoEncoderName(put) || AuditLogReplacement.key('item'));
300
+ i.key = i.key.prepend(getAutoEncoderKey(key));
301
+ return i;
302
+ }),
303
+ );
304
+ return items;
305
+ }
168
306
 
169
- for (const { put } of value.getPuts()) {
170
- if (!(put instanceof AutoEncoder)) {
171
- // Not supported
172
- continue;
173
- }
307
+ function processPatch(key: string, patch: unknown, original: unknown | null): AuditLogPatchItem[] {
308
+ if (!original) {
309
+ // Not supported
310
+ return [];
311
+ }
174
312
 
175
- // Little hack: detect PUT/DELETE behaviour:
176
- let original = 'id' in put ? oldValue.find(v => v.id === put.id) : null;
177
- if (original && !(original instanceof AutoEncoder)) {
178
- // Not supported
179
- original = null;
180
- }
313
+ if (patch === original) {
314
+ return [];
315
+ }
181
316
 
182
- // Added a new parent
183
- if (!original) {
184
- items.push(
185
- AuditLogPatchItem.create({
186
- key: AuditLogReplacement.key(keySingular),
187
- value: getAutoEncoderValue(put) || AuditLogReplacement.string(keySingular),
188
- type: AuditLogPatchItemType.Added,
189
- }),
190
- );
191
- }
317
+ const items: AuditLogPatchItem[] = [];
318
+ const keySingular = getKeySingular(key);
319
+
320
+ const l = explainPatch(
321
+ original,
322
+ patch,
323
+ ).map((i) => {
324
+ i.key = i.key.prepend(getAutoEncoderName(original) || getAutoEncoderName(patch) || AuditLogReplacement.key('item'));
325
+ i.key = i.key.prepend(getAutoEncoderKey(key));
326
+ return i;
327
+ });
328
+ let ov = getAutoEncoderValue(original);
329
+ let v = getAutoEncoderValue(patch);
330
+
331
+ if (l.length === 0 && patch instanceof AutoEncoder && patch.isPatch()) {
332
+ items.push(
333
+ AuditLogPatchItem.create({
334
+ key: getAutoEncoderKey(keySingular).append(getAutoEncoderName(original) || getAutoEncoderName(patch) || AuditLogReplacement.key('item')),
335
+ oldValue: ov || undefined,
336
+ value: v || undefined,
337
+ type: AuditLogPatchItemType.Changed,
338
+ }),
339
+ );
340
+ return items;
341
+ }
192
342
 
193
- if ('id' in put && typeof put.id === 'string') {
194
- createdIdSet.add(put.id);
343
+ if (ov && v) {
344
+ if (ov.toString() === v.toString()) {
345
+ ov = null;
346
+ v = null;
347
+
348
+ if (l.length === 0) {
349
+ // Probably no change
350
+ return [];
195
351
  }
352
+ }
196
353
 
197
- items.push(
198
- ...explainPatch(
199
- original ?? null,
200
- put,
201
- ).map((i) => {
202
- const name = getAutoEncoderName(put);
203
- if (name) {
204
- i.key = i.key.prepend(AuditLogReplacement.string(name));
205
- }
206
- i.key = i.key.prepend(AuditLogReplacement.key(key));
207
- return i;
208
- }),
209
- );
354
+ // Simplify changes by providing one change instead of for all keys
355
+ items.push(
356
+ AuditLogPatchItem.create({
357
+ key: getAutoEncoderKey(keySingular).append(getAutoEncoderName(original) || getAutoEncoderName(v) || AuditLogReplacement.key('item')),
358
+ oldValue: ov || undefined,
359
+ value: v || undefined,
360
+ type: AuditLogPatchItemType.Changed,
361
+ }),
362
+ );
363
+ return items;
364
+ }
365
+
366
+ items.push(
367
+ ...l,
368
+ );
369
+
370
+ return items;
371
+ }
372
+
373
+ function processDelete(key: string, deletedItem: unknown, createdIdSet?: Set<string>): AuditLogPatchItem[] {
374
+ if (createdIdSet) {
375
+ const id = getId(deletedItem);
376
+ if (id && typeof id === 'string' && createdIdSet.has(id)) {
377
+ // DELETE + PUT happened
378
+ return [];
210
379
  }
380
+ }
211
381
 
212
- for (const patch of value.getPatches()) {
213
- const original = oldValue.find(v => v.id === patch.id);
214
- if (!original) {
215
- // Not supported
216
- continue;
217
- }
218
- if (!(original instanceof AutoEncoder)) {
219
- // Not supported
220
- continue;
221
- }
382
+ const v = getAutoEncoderPutValue(deletedItem);
222
383
 
223
- const l = explainPatch(
224
- original,
225
- patch,
226
- ).map((i) => {
227
- const name = getAutoEncoderName(original);
228
- if (name) {
229
- i.key = i.key.prepend(AuditLogReplacement.string(name));
230
- }
231
- i.key = i.key.prepend(AuditLogReplacement.key(key));
232
- return i;
233
- });
384
+ const keySingular = getKeySingular(key);
385
+ const k = AuditLogReplacement.key(keySingular).append(getAutoEncoderName(deletedItem));
234
386
 
235
- if (l.length === 0) {
236
- items.push(
237
- AuditLogPatchItem.create({
238
- key: AuditLogReplacement.key(keySingular),
239
- value: getAutoEncoderValue(original) || undefined,
240
- type: AuditLogPatchItemType.Changed,
241
- }),
242
- );
243
- }
387
+ return [
388
+ AuditLogPatchItem.create({
389
+ key: k,
390
+ type: AuditLogPatchItemType.Removed,
391
+ oldValue: v ?? undefined,
392
+ }),
393
+ ];
394
+ }
244
395
 
245
- items.push(
246
- ...l,
247
- );
396
+ function createArrayChangeHandler(key: string) {
397
+ return (oldValue: unknown, value: unknown) => {
398
+ if (!Array.isArray(oldValue)) {
399
+ // Not supported
400
+ return [];
248
401
  }
402
+ const items: AuditLogPatchItem[] = [];
249
403
 
250
- for (const id of value.getDeletes()) {
251
- if (typeof id !== 'string') {
252
- continue;
253
- }
254
- const original = oldValue.find(v => v.id === id);
255
- if (!original) {
256
- // Not supported
257
- continue;
258
- }
259
- if (!(original instanceof AutoEncoder)) {
260
- // Not supported
261
- continue;
262
- }
404
+ if (!isPatchableArray(value)) {
405
+ if (Array.isArray(value)) {
406
+ // Search for puts
407
+ for (const newItem of value) {
408
+ const original = findOriginal(newItem, oldValue);
409
+
410
+ if (!original) {
411
+ // Has been added
412
+ items.push(...processPut(key, newItem, original));
413
+ }
414
+ else {
415
+ // Has been overwritten
416
+ items.push(...processPatch(key, newItem, original));
417
+ }
418
+ }
263
419
 
264
- if (createdIdSet.has(id)) {
265
- // DELETE + PUT happened
266
- continue;
420
+ // Search for deletes
421
+ for (const original of oldValue) {
422
+ const newItem = findOriginal(original, value);
423
+ if (!newItem) {
424
+ // Has been deleted
425
+ items.push(...processDelete(key, original));
426
+ }
427
+ }
267
428
  }
429
+ // Not supported
430
+ return items;
431
+ }
268
432
 
269
- let k = AuditLogReplacement.key(keySingular);
433
+ const createdIdSet = new Set<string>();
270
434
 
271
- const name = getAutoEncoderName(original);
272
- if (name) {
273
- k = k.prepend(AuditLogReplacement.string(name));
274
- }
435
+ for (const { put } of value.getPuts()) {
436
+ items.push(...processPut(key, put, findOriginal(put, oldValue), createdIdSet));
437
+ }
275
438
 
276
- items.push(
277
- AuditLogPatchItem.create({
278
- key: k,
279
- type: AuditLogPatchItemType.Removed,
280
- }),
281
- );
439
+ for (const patch of value.getPatches()) {
440
+ items.push(...processPatch(key, patch, findOriginal(patch, oldValue)));
441
+ }
442
+
443
+ for (const id of value.getDeletes()) {
444
+ items.push(...processDelete(key, findOriginalById(id, oldValue), createdIdSet));
282
445
  }
283
446
 
284
447
  if (value.getMoves().length > 0) {
285
448
  items.push(
286
449
  AuditLogPatchItem.create({
287
- key: AuditLogReplacement.key(key),
450
+ key: getAutoEncoderKey(key),
288
451
  type: AuditLogPatchItemType.Reordered,
289
452
  }),
290
453
  );
@@ -293,9 +456,9 @@ function createArrayChangeHandler(key: string) {
293
456
  };
294
457
  }
295
458
 
296
- function createMapChangeHandler(key: string) {
459
+ function createMapChangeHandler(key?: string) {
297
460
  return (oldValue: unknown, value: unknown) => {
298
- if (!isPatchMap(value)) {
461
+ if (!(value instanceof Map)) {
299
462
  // Not supported
300
463
  return [];
301
464
  }
@@ -305,22 +468,27 @@ function createMapChangeHandler(key: string) {
305
468
  }
306
469
 
307
470
  const items: AuditLogPatchItem[] = [];
308
- const keySingular = key.replace(/ies$/, 'y').replace(/s$/, '');
471
+ const keySingular = key ? key.replace(/ies$/, 'y').replace(/s$/, '') : key;
472
+ const isPatch = isPatchMap(value);
309
473
 
310
474
  for (const [k, v] of value.entries()) {
311
475
  if (typeof k !== 'string') {
312
476
  // Not supported
313
477
  continue;
314
478
  }
315
- const original = oldValue.get(k);
479
+ let original = oldValue.get(k);
480
+
481
+ if (v instanceof Map && !original) {
482
+ original = new Map();
483
+ }
316
484
 
317
- if (v === null) {
485
+ if (v === null && isPatch) {
318
486
  // Delete
319
487
  if (original) {
320
488
  items.push(
321
489
  AuditLogPatchItem.create({
322
- key: AuditLogReplacement.key(keySingular),
323
- oldValue: getAutoEncoderValue(original as AutoEncoder) || AuditLogReplacement.key(k),
490
+ key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(original)),
491
+ oldValue: getAutoEncoderPutValue(original) || undefined,
324
492
  type: AuditLogPatchItemType.Removed,
325
493
  }),
326
494
  );
@@ -332,30 +500,55 @@ function createMapChangeHandler(key: string) {
332
500
  // added
333
501
  items.push(
334
502
  AuditLogPatchItem.create({
335
- key: AuditLogReplacement.key(keySingular),
336
- value: getAutoEncoderValue(v as AutoEncoder) || AuditLogReplacement.key(k),
503
+ key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(v)),
504
+ value: getAutoEncoderPutValue(v) || undefined,
337
505
  type: AuditLogPatchItemType.Added,
338
506
  }),
339
507
  );
340
508
  }
341
509
  else {
510
+ let ov = getAutoEncoderValue(original);
511
+ let nv = getAutoEncoderValue(v);
512
+
342
513
  const c = explainPatch(
343
514
  original,
344
- v as AutoEncoder,
515
+ v,
345
516
  ).map((i) => {
346
- const name = getAutoEncoderValue(original as AutoEncoder);
347
- if (name) {
348
- i.key = i.key.prepend(name);
349
- }
517
+ i.key = i.key.prepend(getAutoEncoderName(original) || getAutoEncoderName(v) || getAutoEncoderKey(k));
350
518
  i.key = i.key.prepend(AuditLogReplacement.key(keySingular));
351
519
  return i;
352
520
  });
353
521
 
354
- if (c.length === 0) {
522
+ if (ov && nv) {
523
+ if (ov.toString() === nv.toString()) {
524
+ ov = null;
525
+ nv = null;
526
+
527
+ if (c.length === 0) {
528
+ // Probably no change
529
+ continue;
530
+ }
531
+ }
532
+
533
+ // Simplify change
534
+ items.push(
535
+ AuditLogPatchItem.create({
536
+ key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(original) || getAutoEncoderName(v)),
537
+ oldValue: ov || undefined,
538
+ value: nv || undefined,
539
+ type: AuditLogPatchItemType.Changed,
540
+ }),
541
+ );
542
+ continue;
543
+ }
544
+
545
+ if (c.length === 0 && v instanceof AutoEncoder && v.isPatch()) {
355
546
  // Manual log
356
547
  items.push(
357
548
  AuditLogPatchItem.create({
358
- key: AuditLogReplacement.key(keySingular).append(getAutoEncoderValue(original as AutoEncoder) || AuditLogReplacement.key(k)),
549
+ key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(original) || getAutoEncoderName(v)),
550
+ oldValue: getAutoEncoderValue(original) || undefined,
551
+ value: getAutoEncoderValue(v) || undefined,
359
552
  type: AuditLogPatchItemType.Changed,
360
553
  }),
361
554
  );
@@ -368,121 +561,124 @@ function createMapChangeHandler(key: string) {
368
561
  }
369
562
  }
370
563
 
564
+ if (!isPatch) {
565
+ // Loop old values
566
+ for (const [k, v] of oldValue.entries()) {
567
+ if (typeof k !== 'string') {
568
+ // Not supported
569
+ continue;
570
+ }
571
+
572
+ if (value.has(k)) {
573
+ continue;
574
+ }
575
+
576
+ items.push(
577
+ AuditLogPatchItem.create({
578
+ key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(v)),
579
+ oldValue: getAutoEncoderPutValue(v) || undefined,
580
+ type: AuditLogPatchItemType.Removed,
581
+ }),
582
+ );
583
+ }
584
+ }
585
+
371
586
  return items;
372
587
  };
373
588
  }
374
589
 
375
- function createSimpleArrayChangeHandler(key: string) {
590
+ function createUnknownChangeHandler(key: string) {
376
591
  return (oldValue: unknown, value: unknown) => {
377
- if (!Array.isArray(oldValue)) {
378
- // Not supported
592
+ if (typeof value !== 'object' && value !== null) {
379
593
  return [];
380
594
  }
381
- const keySingular = key.replace(/ies$/, 'y').replace(/s$/, '');
382
-
383
- if (Array.isArray(value)) {
384
- if (!value.every(v => typeof v === 'string')) {
385
- // Not supported
386
- return [];
387
- }
388
- if (!oldValue.every(v => typeof v === 'string')) {
389
- // Not supported
390
- return [];
391
- }
392
595
 
393
- // Simple change
394
- const valueStr = (value as string[]).join(', ');
395
- const oldValueStr = (oldValue as string[]).join(', ');
396
-
397
- if (valueStr === oldValueStr) {
398
- return [];
399
- }
596
+ if (oldValue === value) {
597
+ return [];
598
+ }
400
599
 
600
+ if (!oldValue && getAutoEncoderValue(value)) {
601
+ // Simplify addition
401
602
  return [
402
603
  AuditLogPatchItem.create({
403
- key: AuditLogReplacement.key(keySingular),
404
- oldValue: oldValue.length ? AuditLogReplacement.string(oldValueStr) : undefined,
405
- value: value.length ? AuditLogReplacement.string(valueStr) : undefined,
406
- }).autoType(),
604
+ key: getAutoEncoderKey(key),
605
+ value: getAutoEncoderPutValue(value) || undefined,
606
+ type: AuditLogPatchItemType.Added,
607
+ }),
407
608
  ];
408
609
  }
409
610
 
410
- if (!isPatchableArray(value)) {
411
- // Not supported
412
- return [];
611
+ if (oldValue && value === null) {
612
+ return [
613
+ AuditLogPatchItem.create({
614
+ key: getAutoEncoderKey(key),
615
+ oldValue: getAutoEncoderPutValue(oldValue) || undefined,
616
+ type: AuditLogPatchItemType.Removed,
617
+ }),
618
+ ];
413
619
  }
414
620
 
415
- const items: AuditLogPatchItem[] = [];
416
- const createdIdSet = new Set<string>();
417
-
418
- for (const { put } of value.getPuts()) {
419
- if (typeof put !== 'string') {
420
- // Not supported
421
- continue;
422
- }
621
+ const items = explainPatch(oldValue, value).map((i) => {
622
+ i.key = i.key.prepend(getAutoEncoderKey(key));
623
+ return i;
624
+ });
423
625
 
424
- // Little hack: detect PUT/DELETE behaviour:
425
- const original = oldValue.find(v => v === put);
626
+ let v = getAutoEncoderValue(value);
627
+ let ov = getAutoEncoderValue(oldValue);
426
628
 
427
- // Added a new parent
428
- if (!original) {
429
- items.push(
430
- AuditLogPatchItem.create({
431
- key: AuditLogReplacement.key(keySingular),
432
- value: AuditLogReplacement.string(put),
433
- type: AuditLogPatchItemType.Added,
434
- }),
435
- );
436
- }
437
- createdIdSet.add(put);
629
+ if (oldValue && value && getAutoEncoderValue(value) && items.length === 0 && value instanceof AutoEncoder && value.isPatch()) {
630
+ return [
631
+ AuditLogPatchItem.create({
632
+ key: getAutoEncoderKey(key),
633
+ value: v || undefined,
634
+ oldValue: ov || undefined,
635
+ type: AuditLogPatchItemType.Changed,
636
+ }),
637
+ ];
438
638
  }
439
639
 
440
- for (const id of value.getDeletes()) {
441
- if (typeof id !== 'string') {
442
- continue;
443
- }
444
-
445
- if (createdIdSet.has(id)) {
446
- // DELETE + PUT happened
447
- continue;
448
- }
640
+ if (v && ov) {
641
+ // Simplify change
642
+ if (v.toString() === ov.toString()) {
643
+ v = null;
644
+ ov = null;
449
645
 
450
- const original = oldValue.find(v => v === id);
451
- if (!original || typeof original !== 'string') {
452
- // Not supported
453
- continue;
646
+ if (items.length === 0) {
647
+ // Probably no change
648
+ return [];
649
+ }
454
650
  }
455
651
 
456
- items.push(
457
- AuditLogPatchItem.create({
458
- key: AuditLogReplacement.key(keySingular),
459
- oldValue: AuditLogReplacement.string(original),
460
- type: AuditLogPatchItemType.Removed,
461
- }),
462
- );
463
- }
464
-
465
- if (value.getMoves().length > 0) {
466
- items.push(
652
+ return [
467
653
  AuditLogPatchItem.create({
468
- key: AuditLogReplacement.key(key),
469
- type: AuditLogPatchItemType.Reordered,
654
+ key: getAutoEncoderKey(key),
655
+ value: v || undefined,
656
+ oldValue: ov || undefined,
657
+ type: AuditLogPatchItemType.Changed,
470
658
  }),
471
- );
659
+ ];
472
660
  }
473
661
  return items;
474
662
  };
475
663
  }
476
664
 
477
665
  function getExplainerForField(field: Field<any>) {
478
- if (field.decoder === StringDecoder || field.decoder instanceof EnumDecoder) {
666
+ if (field.decoder === StringDecoder) {
479
667
  return createStringChangeHandler(field.property);
480
668
  }
481
669
 
670
+ if (field.decoder instanceof EnumDecoder) {
671
+ return createEnumChangeHandler(field.property);
672
+ }
673
+
482
674
  if (field.decoder instanceof SymbolDecoder) {
483
- if (field.decoder.decoder === StringDecoder || field.decoder.decoder instanceof EnumDecoder) {
675
+ if (field.decoder.decoder === StringDecoder) {
484
676
  return createStringChangeHandler(field.property);
485
677
  }
678
+
679
+ if (field.decoder.decoder instanceof EnumDecoder) {
680
+ return createEnumChangeHandler(field.property);
681
+ }
486
682
  }
487
683
 
488
684
  if (field.decoder === DateDecoder) {
@@ -497,10 +693,6 @@ function getExplainerForField(field: Field<any>) {
497
693
  return createIntegerChangeHandler(field.property);
498
694
  }
499
695
 
500
- if (field.decoder instanceof ArrayDecoder && field.decoder.decoder === StringDecoder) {
501
- return createSimpleArrayChangeHandler(field.property);
502
- }
503
-
504
696
  if (field.decoder instanceof ArrayDecoder) {
505
697
  return createArrayChangeHandler(field.property);
506
698
  }
@@ -524,106 +716,57 @@ function getExplainerForField(field: Field<any>) {
524
716
 
525
717
  return [
526
718
  AuditLogPatchItem.create({
527
- key: AuditLogReplacement.key(field.property),
528
- oldValue: wasTrueOld === true ? AuditLogReplacement.string('Aangevinkt') : (wasTrueOld === false ? AuditLogReplacement.string('Uitgevinkt') : undefined),
529
- value: isTrue === true ? AuditLogReplacement.string('Aangevinkt') : (isTrue === false ? AuditLogReplacement.string('Uitgevinkt') : undefined),
530
- }),
719
+ key: getAutoEncoderKey(field.property),
720
+ oldValue: wasTrueOld === true ? AuditLogReplacement.key('checked') : (wasTrueOld === false ? AuditLogReplacement.key('unchecked') : undefined),
721
+ value: isTrue === true ? AuditLogReplacement.key('checked') : (isTrue === false ? AuditLogReplacement.key('unchecked') : undefined),
722
+ }).autoType(),
531
723
  ];
532
724
  };
533
725
  }
534
726
 
535
- if ((field.decoder as any).prototype instanceof AutoEncoder || field.decoder === AutoEncoder) {
536
- return (oldValue: unknown, value: unknown) => {
537
- if (!(value instanceof AutoEncoder) && value !== null) {
538
- return [];
539
- }
540
-
541
- if (oldValue === value) {
542
- return [];
543
- }
544
-
545
- if (oldValue && value && getAutoEncoderValue(value as AutoEncoder)) {
546
- // Simplify addition
547
- return [
548
- AuditLogPatchItem.create({
549
- key: AuditLogReplacement.key(field.property),
550
- value: getAutoEncoderValue(value as AutoEncoder) || AuditLogReplacement.key(field.property),
551
- oldValue: getAutoEncoderValue(oldValue as AutoEncoder) || AuditLogReplacement.key(field.property),
552
- type: AuditLogPatchItemType.Changed,
553
- }),
554
- ];
555
- }
556
-
557
- if (!oldValue && getAutoEncoderValue(value as AutoEncoder)) {
558
- // Simplify addition
559
- return [
560
- AuditLogPatchItem.create({
561
- key: AuditLogReplacement.key(field.property),
562
- value: getAutoEncoderValue(value as AutoEncoder) || AuditLogReplacement.key(field.property),
563
- type: AuditLogPatchItemType.Added,
564
- }),
565
- ];
566
- }
567
-
568
- if (value === null) {
569
- return [
570
- AuditLogPatchItem.create({
571
- key: AuditLogReplacement.key(field.property),
572
- oldValue: getAutoEncoderValue(oldValue as AutoEncoder) || AuditLogReplacement.key(field.property),
573
- type: AuditLogPatchItemType.Removed,
574
- }),
575
- ];
576
- }
577
-
578
- return explainPatch(oldValue as AutoEncoder | null, value).map((i) => {
579
- i.key = i.key.prepend(AuditLogReplacement.key(field.property));
580
- return i;
581
- });
582
- };
583
- }
584
-
585
- // Simple addition/delete/change detection
586
- return (oldValue: unknown, value: unknown) => {
587
- if (value === undefined) {
588
- return [];
589
- }
590
-
591
- if (oldValue === value) {
592
- return [];
593
- }
594
-
595
- return [
596
- AuditLogPatchItem.create({
597
- key: AuditLogReplacement.key(field.property),
598
- type: AuditLogPatchItemType.Changed,
599
- }),
600
- ];
601
- };
727
+ return createUnknownChangeHandler(field.property);
602
728
  }
603
729
 
604
- export function explainPatch<T extends AutoEncoder>(original: T | null, patch: AutoEncoderPatchType<T> | T): AuditLogPatchItem[] {
730
+ export function explainPatch(original: unknown | null, patch: unknown): AuditLogPatchItem[] {
605
731
  if (isPatchableArray(patch)) {
606
732
  const b = createArrayChangeHandler('items');
607
733
  return b(original, patch);
608
734
  }
609
- if (!(patch instanceof AutoEncoder)) {
735
+
736
+ if (original instanceof Map) {
737
+ const b = createMapChangeHandler();
738
+ return b(original, patch);
739
+ }
740
+
741
+ if (typeof patch !== 'object' || patch === null) {
742
+ if (patch === null) {
743
+ // todo
744
+ }
610
745
  return [];
611
746
  }
612
- if (original && !(original instanceof AutoEncoder)) {
747
+ if (original && typeof original !== 'object') {
613
748
  return [];
614
749
  }
615
750
 
616
751
  const items: AuditLogPatchItem[] = [];
617
752
 
618
753
  for (const key in patch) {
619
- const field = original ? original.static.fields.find(f => f.property === key) : patch.static.fields.find(f => f.property === key);
620
- if (!field) {
621
- continue;
622
- }
623
-
754
+ const field = original instanceof AutoEncoder
755
+ ? original.static.latestFields.find(f => f.property === key)
756
+ : (
757
+ patch instanceof AutoEncoder
758
+ ? patch.static.latestFields.find(f => f.property === key)
759
+ : null
760
+ );
624
761
  const oldValue = original?.[key] ?? null;
625
762
  const value = patch[key];
626
763
 
764
+ if (!(patch instanceof AutoEncoder) || !field) {
765
+ // try manual without type information
766
+ items.push(...createUnknownChangeHandler(key)(oldValue, value));
767
+ continue;
768
+ }
769
+
627
770
  if (patch.isPut() && key === 'id') {
628
771
  continue;
629
772
  }