@verdant-web/common 2.7.3 → 2.9.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.
- package/dist/esm/authz.js.map +1 -1
- package/dist/esm/baseline.d.ts +2 -1
- package/dist/esm/diffing.d.ts +40 -0
- package/dist/esm/diffing.js +317 -0
- package/dist/esm/diffing.js.map +1 -0
- package/dist/esm/diffing.test.d.ts +1 -0
- package/dist/esm/diffing.test.js +1680 -0
- package/dist/esm/diffing.test.js.map +1 -0
- package/dist/esm/index.d.ts +20 -19
- package/dist/esm/index.js +17 -16
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/oids.d.ts +5 -0
- package/dist/esm/oids.js +23 -1
- package/dist/esm/oids.js.map +1 -1
- package/dist/esm/operation.d.ts +11 -24
- package/dist/esm/operation.js +32 -270
- package/dist/esm/operation.js.map +1 -1
- package/dist/esm/operation.test.js +122 -592
- package/dist/esm/operation.test.js.map +1 -1
- package/dist/esm/patch.d.ts +19 -15
- package/dist/esm/patch.js +79 -18
- package/dist/esm/patch.js.map +1 -1
- package/dist/esm/schema/validation.d.ts +8 -1
- package/dist/esm/schema/validation.js +133 -63
- package/dist/esm/schema/validation.js.map +1 -1
- package/dist/esm/schema/validation.test.d.ts +1 -0
- package/dist/esm/schema/validation.test.js +60 -0
- package/dist/esm/schema/validation.test.js.map +1 -0
- package/dist/esm/timestamp.d.ts +1 -0
- package/dist/esm/timestamp.js +3 -0
- package/dist/esm/timestamp.js.map +1 -1
- package/dist/esm/undo.d.ts +2 -1
- package/dist/esm/undo.js +53 -146
- package/dist/esm/undo.js.map +1 -1
- package/dist/esm/undo.test.d.ts +1 -0
- package/dist/esm/undo.test.js +81 -0
- package/dist/esm/undo.test.js.map +1 -0
- package/package.json +1 -1
- package/src/authz.ts +2 -0
- package/src/baseline.ts +3 -1
- package/src/diffing.test.ts +1803 -0
- package/src/diffing.ts +402 -0
- package/src/index.ts +21 -20
- package/src/oids.ts +23 -1
- package/src/operation.test.ts +148 -623
- package/src/operation.ts +45 -378
- package/src/patch.ts +146 -14
- package/src/schema/validation.test.ts +66 -0
- package/src/schema/validation.ts +156 -73
- package/src/timestamp.ts +4 -1
- package/src/undo.test.ts +100 -0
- package/src/undo.ts +83 -144
package/src/operation.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
+
import { AuthorizationKey } from './authz.js';
|
|
1
2
|
import { DocumentBaseline } from './baseline.js';
|
|
2
3
|
import { FileRef, isFileRef } from './files.js';
|
|
3
4
|
import {
|
|
4
|
-
areOidsRelated,
|
|
5
5
|
assignOid,
|
|
6
6
|
assignOidsToAllSubObjects,
|
|
7
|
-
createRef,
|
|
8
|
-
createSubOid,
|
|
9
|
-
ensureOid,
|
|
10
7
|
getOid,
|
|
11
8
|
getOidRoot,
|
|
12
9
|
maybeGetOid,
|
|
@@ -14,7 +11,6 @@ import {
|
|
|
14
11
|
ObjectIdentifier,
|
|
15
12
|
removeOid,
|
|
16
13
|
} from './oids.js';
|
|
17
|
-
import { isOidKey } from './oidsLegacy.js';
|
|
18
14
|
import { compareRefs, isRef } from './refs.js';
|
|
19
15
|
import { assert, cloneDeep, findLastIndex, isObject } from './utils.js';
|
|
20
16
|
|
|
@@ -156,374 +152,9 @@ export type Operation = {
|
|
|
156
152
|
oid: ObjectIdentifier;
|
|
157
153
|
timestamp: string;
|
|
158
154
|
data: OperationPatch;
|
|
159
|
-
authz?:
|
|
155
|
+
authz?: AuthorizationKey;
|
|
160
156
|
};
|
|
161
157
|
|
|
162
|
-
function isDiffableObject(val: any) {
|
|
163
|
-
return isObject(val) && !isRef(val);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function compareNonDiffable(a: any, b: any) {
|
|
167
|
-
if (a === b) return true;
|
|
168
|
-
if (isRef(a) && isRef(b)) return compareRefs(a, b);
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function diffToPatches<T extends { [key: string]: any } | any[]>(
|
|
173
|
-
from: T,
|
|
174
|
-
to: T,
|
|
175
|
-
getNow: () => string,
|
|
176
|
-
createSubId?: () => string,
|
|
177
|
-
patches: Operation[] = [],
|
|
178
|
-
options?: {
|
|
179
|
-
/**
|
|
180
|
-
* If an object is merged with another and the new one does not
|
|
181
|
-
* have an OID assigned, assume it is the same identity as previous
|
|
182
|
-
*/
|
|
183
|
-
mergeUnknownObjects?: boolean;
|
|
184
|
-
/**
|
|
185
|
-
* If an incoming value is not assigned on the new object, use the previous value.
|
|
186
|
-
* If false, undefined properties will erase the previous value.
|
|
187
|
-
*/
|
|
188
|
-
defaultUndefined?: boolean;
|
|
189
|
-
/**
|
|
190
|
-
* Authorization to apply to all created operations
|
|
191
|
-
*/
|
|
192
|
-
authz?: string;
|
|
193
|
-
},
|
|
194
|
-
): Operation[] {
|
|
195
|
-
const authz = options?.authz;
|
|
196
|
-
const oid = getOid(from);
|
|
197
|
-
|
|
198
|
-
// FIXME: allocating this function inline seems wasteful.
|
|
199
|
-
function diffItems(
|
|
200
|
-
key: string | number,
|
|
201
|
-
value: any,
|
|
202
|
-
oldValue: any,
|
|
203
|
-
isList: boolean,
|
|
204
|
-
) {
|
|
205
|
-
if (!isDiffableObject(value)) {
|
|
206
|
-
// for primitive fields, we can use plain sets and
|
|
207
|
-
// do not need to recurse, of course
|
|
208
|
-
if (!compareNonDiffable(value, oldValue)) {
|
|
209
|
-
if (isList) {
|
|
210
|
-
patches.push(
|
|
211
|
-
addAuthzToOp(
|
|
212
|
-
{
|
|
213
|
-
oid,
|
|
214
|
-
timestamp: getNow(),
|
|
215
|
-
data: {
|
|
216
|
-
op: 'list-set',
|
|
217
|
-
index: key as number,
|
|
218
|
-
value,
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
authz,
|
|
222
|
-
),
|
|
223
|
-
);
|
|
224
|
-
} else {
|
|
225
|
-
patches.push(
|
|
226
|
-
addAuthzToOp(
|
|
227
|
-
{
|
|
228
|
-
oid,
|
|
229
|
-
timestamp: getNow(),
|
|
230
|
-
data: {
|
|
231
|
-
op: 'set',
|
|
232
|
-
name: key,
|
|
233
|
-
value,
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
authz,
|
|
237
|
-
),
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
} else {
|
|
242
|
-
const oldValueOid = maybeGetOid(oldValue);
|
|
243
|
-
let valueOid = maybeGetOid(value);
|
|
244
|
-
// the user supplied an object which was already assigned an OID,
|
|
245
|
-
// but that OID is from a separate entity: this object must
|
|
246
|
-
// be cloned to a new identity.
|
|
247
|
-
if (valueOid && !areOidsRelated(oid, valueOid)) {
|
|
248
|
-
value = cloneDeep(value, false);
|
|
249
|
-
valueOid = createSubOid(oid, createSubId);
|
|
250
|
-
assignOid(value, valueOid);
|
|
251
|
-
} else if (!options?.mergeUnknownObjects) {
|
|
252
|
-
valueOid = ensureOid(value, oid, createSubId);
|
|
253
|
-
} else {
|
|
254
|
-
// if merge unknown objects is requested, we copy the previous value's oid
|
|
255
|
-
// to any mirrored new value if it doesn't have one assigned already.
|
|
256
|
-
if (!valueOid && oldValueOid) {
|
|
257
|
-
assignOid(value, oldValueOid);
|
|
258
|
-
valueOid = oldValueOid;
|
|
259
|
-
} else {
|
|
260
|
-
valueOid = ensureOid(value, oid, createSubId);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
assert(
|
|
264
|
-
!!valueOid,
|
|
265
|
-
'Error: no value OID was resolved during diff. This is a bug in Verdant.',
|
|
266
|
-
);
|
|
267
|
-
assert(
|
|
268
|
-
areOidsRelated(oid, valueOid),
|
|
269
|
-
`Error: value OID ${valueOid} is not related to parent OID ${oid}. This is a bug in Verdant.`,
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
if (oldValue === undefined || valueOid !== oldValueOid) {
|
|
273
|
-
// first case: previous value exists but the OIDs are different,
|
|
274
|
-
// meaning the object identity has changed
|
|
275
|
-
// we add the whole new object and also update the reference on this
|
|
276
|
-
// key of the parent to point to the new object
|
|
277
|
-
initialToPatches(value, valueOid, getNow, createSubId, patches);
|
|
278
|
-
patches.push(
|
|
279
|
-
addAuthzToOp(
|
|
280
|
-
{
|
|
281
|
-
oid,
|
|
282
|
-
timestamp: getNow(),
|
|
283
|
-
data: {
|
|
284
|
-
op: 'set',
|
|
285
|
-
name: key,
|
|
286
|
-
value: createRef(valueOid),
|
|
287
|
-
},
|
|
288
|
-
},
|
|
289
|
-
authz,
|
|
290
|
-
),
|
|
291
|
-
);
|
|
292
|
-
// if there was an old value, we need to delete it altogether.
|
|
293
|
-
if (oldValueOid !== undefined) {
|
|
294
|
-
patches.push(
|
|
295
|
-
addAuthzToOp(
|
|
296
|
-
{
|
|
297
|
-
oid: oldValueOid,
|
|
298
|
-
timestamp: getNow(),
|
|
299
|
-
data: {
|
|
300
|
-
op: 'delete',
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
authz,
|
|
304
|
-
),
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
} else {
|
|
308
|
-
// third case: OIDs are the same, meaning the identity is the same,
|
|
309
|
-
// and we must diff the objects
|
|
310
|
-
diffToPatches(oldValue, value, getNow, createSubId, patches, options);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (Array.isArray(from) && Array.isArray(to)) {
|
|
316
|
-
// diffing is more naive than native array operations.
|
|
317
|
-
// we can only look at each element and decide if it should
|
|
318
|
-
// be replaced or removed - no moves or pushes, etc.
|
|
319
|
-
for (let i = 0; i < to.length; i++) {
|
|
320
|
-
const value = to[i];
|
|
321
|
-
const oldValue = from[i];
|
|
322
|
-
diffItems(i, value, oldValue, true);
|
|
323
|
-
}
|
|
324
|
-
// remove any remaining items at the end of the array
|
|
325
|
-
const deletedItemsAtEnd = from.length - to.length;
|
|
326
|
-
if (deletedItemsAtEnd > 0) {
|
|
327
|
-
// if sub-items were objects, we need to delete them all
|
|
328
|
-
for (let i = to.length; i < from.length; i++) {
|
|
329
|
-
const value = from[i];
|
|
330
|
-
const valueOid = maybeGetOid(value);
|
|
331
|
-
if (valueOid) {
|
|
332
|
-
patches.push(
|
|
333
|
-
addAuthzToOp(
|
|
334
|
-
{
|
|
335
|
-
oid: valueOid,
|
|
336
|
-
timestamp: getNow(),
|
|
337
|
-
data: {
|
|
338
|
-
op: 'delete',
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
authz,
|
|
342
|
-
),
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
// push the list-delete for the deleted items
|
|
347
|
-
patches.push(
|
|
348
|
-
addAuthzToOp(
|
|
349
|
-
{
|
|
350
|
-
oid,
|
|
351
|
-
timestamp: getNow(),
|
|
352
|
-
data: {
|
|
353
|
-
op: 'list-delete',
|
|
354
|
-
index: to.length,
|
|
355
|
-
count: deletedItemsAtEnd,
|
|
356
|
-
},
|
|
357
|
-
},
|
|
358
|
-
authz,
|
|
359
|
-
),
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
} else if (Array.isArray(from) || Array.isArray(to)) {
|
|
363
|
-
throw new Error('Cannot diff an array with an object');
|
|
364
|
-
} else if (isDiffableObject(from) && isDiffableObject(to)) {
|
|
365
|
-
const oldKeys = new Set(Object.keys(from));
|
|
366
|
-
for (const [key, value] of Object.entries(to)) {
|
|
367
|
-
if (value === undefined && options?.defaultUndefined) continue;
|
|
368
|
-
|
|
369
|
-
oldKeys.delete(key);
|
|
370
|
-
|
|
371
|
-
if (isOidKey(key)) continue;
|
|
372
|
-
|
|
373
|
-
const oldValue = from[key];
|
|
374
|
-
|
|
375
|
-
diffItems(key, value, oldValue, false);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// this set now only contains keys which were not in the new object
|
|
379
|
-
if (!options?.defaultUndefined) {
|
|
380
|
-
for (const key of oldKeys) {
|
|
381
|
-
if (isOidKey(key)) continue;
|
|
382
|
-
// push the delete for the property
|
|
383
|
-
patches.push(
|
|
384
|
-
addAuthzToOp(
|
|
385
|
-
{
|
|
386
|
-
oid,
|
|
387
|
-
timestamp: getNow(),
|
|
388
|
-
data: {
|
|
389
|
-
op: 'remove',
|
|
390
|
-
name: key,
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
authz,
|
|
394
|
-
),
|
|
395
|
-
);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return patches;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
export function shallowDiffToPatches(
|
|
404
|
-
from: any,
|
|
405
|
-
to: any,
|
|
406
|
-
getNow: () => string,
|
|
407
|
-
patches: Operation[] = [],
|
|
408
|
-
options?: { authz?: string },
|
|
409
|
-
) {
|
|
410
|
-
const oid = getOid(from);
|
|
411
|
-
const authz = options?.authz;
|
|
412
|
-
|
|
413
|
-
function diffItems(
|
|
414
|
-
key: string | number,
|
|
415
|
-
value: any,
|
|
416
|
-
oldValue: any,
|
|
417
|
-
isList: boolean,
|
|
418
|
-
) {
|
|
419
|
-
if (!isDiffableObject(value)) {
|
|
420
|
-
// for primitive fields, we can use plain sets and
|
|
421
|
-
// do not need to recurse, of course
|
|
422
|
-
if (!compareNonDiffable(value, oldValue)) {
|
|
423
|
-
if (isList) {
|
|
424
|
-
patches.push(
|
|
425
|
-
addAuthzToOp(
|
|
426
|
-
{
|
|
427
|
-
oid,
|
|
428
|
-
timestamp: getNow(),
|
|
429
|
-
data: {
|
|
430
|
-
op: 'list-set',
|
|
431
|
-
index: key as number,
|
|
432
|
-
value,
|
|
433
|
-
},
|
|
434
|
-
},
|
|
435
|
-
authz,
|
|
436
|
-
),
|
|
437
|
-
);
|
|
438
|
-
} else {
|
|
439
|
-
patches.push(
|
|
440
|
-
addAuthzToOp(
|
|
441
|
-
{
|
|
442
|
-
oid,
|
|
443
|
-
timestamp: getNow(),
|
|
444
|
-
data: {
|
|
445
|
-
op: 'set',
|
|
446
|
-
name: key,
|
|
447
|
-
value,
|
|
448
|
-
},
|
|
449
|
-
},
|
|
450
|
-
authz,
|
|
451
|
-
),
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
} else {
|
|
456
|
-
throw new Error(
|
|
457
|
-
'Shallow diff was given a nested object. This is an error in verdant!',
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
if (Array.isArray(from) && Array.isArray(to)) {
|
|
463
|
-
// diffing is more naive than native array operations.
|
|
464
|
-
// we can only look at each element and decide if it should
|
|
465
|
-
// be replaced or removed - no moves or pushes, etc.
|
|
466
|
-
for (let i = 0; i < to.length; i++) {
|
|
467
|
-
const value = to[i];
|
|
468
|
-
const oldValue = from[i];
|
|
469
|
-
diffItems(i, value, oldValue, true);
|
|
470
|
-
}
|
|
471
|
-
// remove any remaining items at the end of the array
|
|
472
|
-
const deletedItemsAtEnd = from.length - to.length;
|
|
473
|
-
if (deletedItemsAtEnd > 0) {
|
|
474
|
-
// push the list-delete for the deleted items
|
|
475
|
-
patches.push(
|
|
476
|
-
addAuthzToOp(
|
|
477
|
-
{
|
|
478
|
-
oid,
|
|
479
|
-
timestamp: getNow(),
|
|
480
|
-
data: {
|
|
481
|
-
op: 'list-delete',
|
|
482
|
-
index: to.length,
|
|
483
|
-
count: deletedItemsAtEnd,
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
authz,
|
|
487
|
-
),
|
|
488
|
-
);
|
|
489
|
-
}
|
|
490
|
-
} else if (Array.isArray(from) || Array.isArray(to)) {
|
|
491
|
-
throw new Error('Cannot diff an array with an object');
|
|
492
|
-
} else if (isDiffableObject(from) && isDiffableObject(to)) {
|
|
493
|
-
const oldKeys = new Set(Object.keys(from));
|
|
494
|
-
for (const [key, value] of Object.entries(to)) {
|
|
495
|
-
oldKeys.delete(key);
|
|
496
|
-
|
|
497
|
-
if (isOidKey(key)) continue;
|
|
498
|
-
|
|
499
|
-
const oldValue = from[key];
|
|
500
|
-
|
|
501
|
-
diffItems(key, value, oldValue, false);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// this set now only contains keys which were not in the new object
|
|
505
|
-
for (const key of oldKeys) {
|
|
506
|
-
if (isOidKey(key)) continue;
|
|
507
|
-
// push the delete for the property
|
|
508
|
-
patches.push(
|
|
509
|
-
addAuthzToOp(
|
|
510
|
-
{
|
|
511
|
-
oid,
|
|
512
|
-
timestamp: getNow(),
|
|
513
|
-
data: {
|
|
514
|
-
op: 'remove',
|
|
515
|
-
name: key,
|
|
516
|
-
},
|
|
517
|
-
},
|
|
518
|
-
authz,
|
|
519
|
-
),
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
return patches;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
158
|
/**
|
|
528
159
|
* Takes a basic object and constructs a patch list to create it and
|
|
529
160
|
* all of its nested objects.
|
|
@@ -534,7 +165,7 @@ export function initialToPatches(
|
|
|
534
165
|
getNow: () => string,
|
|
535
166
|
createSubId?: () => string,
|
|
536
167
|
patches: Operation[] = [],
|
|
537
|
-
options?: { authz?:
|
|
168
|
+
options?: { authz?: AuthorizationKey },
|
|
538
169
|
) {
|
|
539
170
|
assignOid(initial, rootOid);
|
|
540
171
|
assignOidsToAllSubObjects(initial, createSubId);
|
|
@@ -559,7 +190,7 @@ export function shallowInitialToPatches(
|
|
|
559
190
|
rootOid: ObjectIdentifier,
|
|
560
191
|
getNow: () => string,
|
|
561
192
|
patches: Operation[] = [],
|
|
562
|
-
options?: { authz?:
|
|
193
|
+
options?: { authz?: AuthorizationKey },
|
|
563
194
|
) {
|
|
564
195
|
const op: Operation = {
|
|
565
196
|
oid: rootOid,
|
|
@@ -575,7 +206,7 @@ export function shallowInitialToPatches(
|
|
|
575
206
|
|
|
576
207
|
// saves a bit of network traffic by not attaching authz
|
|
577
208
|
// key at all if not present
|
|
578
|
-
function addAuthzToOp(op: Operation, authz?:
|
|
209
|
+
export function addAuthzToOp(op: Operation, authz?: AuthorizationKey) {
|
|
579
210
|
if (authz) {
|
|
580
211
|
op.authz = authz;
|
|
581
212
|
}
|
|
@@ -712,18 +343,18 @@ export function applyPatch<T extends NormalizedObject>(
|
|
|
712
343
|
do {
|
|
713
344
|
const valueToRemove = patch.value;
|
|
714
345
|
if (patch.only === 'last') {
|
|
715
|
-
if (
|
|
346
|
+
if (isRef(valueToRemove)) {
|
|
716
347
|
index = findLastIndex(
|
|
717
348
|
base,
|
|
718
|
-
(item: any) => item
|
|
349
|
+
(item: any) => isRef(item) && compareRefs(item, valueToRemove),
|
|
719
350
|
);
|
|
720
351
|
} else {
|
|
721
352
|
index = base.lastIndexOf(valueToRemove);
|
|
722
353
|
}
|
|
723
354
|
} else {
|
|
724
|
-
if (
|
|
355
|
+
if (isRef(valueToRemove)) {
|
|
725
356
|
index = base.findIndex(
|
|
726
|
-
(item: any) => item
|
|
357
|
+
(item: any) => isRef(item) && compareRefs(item, valueToRemove),
|
|
727
358
|
);
|
|
728
359
|
} else {
|
|
729
360
|
index = base.indexOf(valueToRemove);
|
|
@@ -875,6 +506,14 @@ export function substituteFirstLevelObjectsWithRefs<
|
|
|
875
506
|
if (Array.isArray(base)) {
|
|
876
507
|
for (let i = 0; i < base.length; i++) {
|
|
877
508
|
const item = base[i];
|
|
509
|
+
// special handling of file refs, which are treated like primitives
|
|
510
|
+
if (isFileRef(item)) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
assert(
|
|
514
|
+
!isRef(item),
|
|
515
|
+
'An object with refs was passed to substituteFirstLevelObjectsWithRefs, this is not allowed',
|
|
516
|
+
);
|
|
878
517
|
if (isObject(item)) {
|
|
879
518
|
const oid = getOid(item);
|
|
880
519
|
base[i] = {
|
|
@@ -886,6 +525,14 @@ export function substituteFirstLevelObjectsWithRefs<
|
|
|
886
525
|
}
|
|
887
526
|
} else {
|
|
888
527
|
for (const [key, value] of Object.entries(base)) {
|
|
528
|
+
// special handling of file refs, which are treated like primitives
|
|
529
|
+
if (isFileRef(value)) {
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
assert(
|
|
533
|
+
!isRef(value),
|
|
534
|
+
'An object with refs was passed to substituteFirstLevelObjectsWithRefs, this is not allowed',
|
|
535
|
+
);
|
|
889
536
|
if (isObject(value)) {
|
|
890
537
|
base[key] = {
|
|
891
538
|
'@@type': 'ref',
|
|
@@ -900,6 +547,26 @@ export function substituteFirstLevelObjectsWithRefs<
|
|
|
900
547
|
return refObjects;
|
|
901
548
|
}
|
|
902
549
|
|
|
550
|
+
/**
|
|
551
|
+
* Takes a snapshot with applied OIDs and deconstructs it entirely
|
|
552
|
+
* into individual objects with ref fields. Populates the supplied
|
|
553
|
+
* map with the final objects.
|
|
554
|
+
*/
|
|
555
|
+
export function deconstructSnapshotToRefs(
|
|
556
|
+
snapshot: any,
|
|
557
|
+
refTargets: Map<ObjectIdentifier, any>,
|
|
558
|
+
) {
|
|
559
|
+
if (typeof snapshot !== 'object') {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
refTargets.set(getOid(snapshot), snapshot);
|
|
563
|
+
const thisLevelOfTargets = substituteFirstLevelObjectsWithRefs(snapshot);
|
|
564
|
+
for (const [oid, obj] of thisLevelOfTargets.entries()) {
|
|
565
|
+
refTargets.set(oid, obj);
|
|
566
|
+
deconstructSnapshotToRefs(obj, refTargets);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
903
570
|
export function operationSupersedes(op: Operation): PropertyName | boolean {
|
|
904
571
|
// todo: add 'remove'
|
|
905
572
|
if (op.data.op === 'set') {
|