@verdant-web/common 1.11.0 → 1.12.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/cjs/EventSubscriber.d.ts +17 -17
- package/dist/cjs/EventSubscriber.js +66 -66
- package/dist/cjs/baseline.d.ts +5 -5
- package/dist/cjs/baseline.js +2 -2
- package/dist/cjs/batching.d.ts +41 -41
- package/dist/cjs/batching.js +88 -88
- package/dist/cjs/files.d.ts +19 -19
- package/dist/cjs/files.js +18 -18
- package/dist/cjs/index.d.ts +18 -18
- package/dist/cjs/index.js +35 -35
- package/dist/cjs/indexes.d.ts +14 -14
- package/dist/cjs/indexes.js +115 -115
- package/dist/cjs/indexes.test.d.ts +1 -1
- package/dist/cjs/indexes.test.js +127 -127
- package/dist/cjs/migration.d.ts +98 -98
- package/dist/cjs/migration.js +127 -127
- package/dist/cjs/oids.d.ts +85 -87
- package/dist/cjs/oids.js +363 -374
- package/dist/cjs/oids.js.map +1 -1
- package/dist/cjs/oids.test.d.ts +1 -1
- package/dist/cjs/oids.test.js +269 -251
- package/dist/cjs/oids.test.js.map +1 -1
- package/dist/cjs/operation.d.ts +139 -139
- package/dist/cjs/operation.js +532 -532
- package/dist/cjs/operation.js.map +1 -1
- package/dist/cjs/operation.test.d.ts +1 -1
- package/dist/cjs/operation.test.js +505 -505
- package/dist/cjs/operation.test.js.map +1 -1
- package/dist/cjs/patch.d.ts +26 -26
- package/dist/cjs/patch.js +226 -226
- package/dist/cjs/patch.js.map +1 -1
- package/dist/cjs/presence.d.ts +27 -27
- package/dist/cjs/presence.js +2 -2
- package/dist/cjs/protocol.d.ts +145 -145
- package/dist/cjs/protocol.js +3 -3
- package/dist/cjs/refs.d.ts +5 -5
- package/dist/cjs/refs.js +21 -21
- package/dist/cjs/replica.d.ts +28 -28
- package/dist/cjs/replica.js +28 -28
- package/dist/cjs/schema/defaults.d.ts +4 -4
- package/dist/cjs/schema/defaults.js +71 -71
- package/dist/cjs/schema/defaults.test.d.ts +1 -1
- package/dist/cjs/schema/defaults.test.js +20 -20
- package/dist/cjs/schema/defaults.test.js.map +1 -1
- package/dist/cjs/schema/index.d.ts +8 -8
- package/dist/cjs/schema/index.js +28 -28
- package/dist/cjs/schema/indexFilters.d.ts +6 -6
- package/dist/cjs/schema/indexFilters.js +30 -30
- package/dist/cjs/schema/types/collection.d.ts +51 -51
- package/dist/cjs/schema/types/collection.js +2 -2
- package/dist/cjs/schema/types/compounds.d.ts +12 -12
- package/dist/cjs/schema/types/compounds.js +2 -2
- package/dist/cjs/schema/types/fields.d.ts +59 -59
- package/dist/cjs/schema/types/fields.js +2 -2
- package/dist/cjs/schema/types/filters.d.ts +29 -29
- package/dist/cjs/schema/types/filters.js +2 -2
- package/dist/cjs/schema/types/shapes.d.ts +21 -21
- package/dist/cjs/schema/types/shapes.js +2 -2
- package/dist/cjs/schema/types/synthetics.d.ts +28 -28
- package/dist/cjs/schema/types/synthetics.js +2 -2
- package/dist/cjs/schema/types.d.ts +18 -18
- package/dist/cjs/schema/types.js +22 -22
- package/dist/cjs/timestamp.d.ts +48 -48
- package/dist/cjs/timestamp.js +243 -243
- package/dist/cjs/timestamp.test.d.ts +1 -1
- package/dist/cjs/timestamp.test.js +93 -93
- package/dist/cjs/undo.d.ts +3 -3
- package/dist/cjs/undo.js +198 -198
- package/dist/cjs/utils.d.ts +15 -15
- package/dist/cjs/utils.js +127 -127
- package/dist/cjs/utils.test.d.ts +1 -1
- package/dist/cjs/utils.test.js +36 -36
- package/dist/esm/EventSubscriber.d.ts +17 -17
- package/dist/esm/EventSubscriber.js +62 -62
- package/dist/esm/baseline.d.ts +5 -5
- package/dist/esm/baseline.js +1 -1
- package/dist/esm/batching.d.ts +41 -41
- package/dist/esm/batching.js +83 -83
- package/dist/esm/files.d.ts +19 -19
- package/dist/esm/files.js +12 -12
- package/dist/esm/index.d.ts +18 -18
- package/dist/esm/index.js +15 -15
- package/dist/esm/indexes.d.ts +14 -14
- package/dist/esm/indexes.js +105 -105
- package/dist/esm/indexes.test.d.ts +1 -1
- package/dist/esm/indexes.test.js +125 -125
- package/dist/esm/migration.d.ts +98 -98
- package/dist/esm/migration.js +121 -121
- package/dist/esm/oids.d.ts +85 -87
- package/dist/esm/oids.js +336 -347
- package/dist/esm/oids.js.map +1 -1
- package/dist/esm/oids.test.d.ts +1 -1
- package/dist/esm/oids.test.js +267 -249
- package/dist/esm/oids.test.js.map +1 -1
- package/dist/esm/operation.d.ts +139 -139
- package/dist/esm/operation.js +517 -517
- package/dist/esm/operation.js.map +1 -1
- package/dist/esm/operation.test.d.ts +1 -1
- package/dist/esm/operation.test.js +503 -503
- package/dist/esm/operation.test.js.map +1 -1
- package/dist/esm/patch.d.ts +26 -26
- package/dist/esm/patch.js +222 -222
- package/dist/esm/patch.js.map +1 -1
- package/dist/esm/presence.d.ts +27 -27
- package/dist/esm/presence.js +1 -1
- package/dist/esm/protocol.d.ts +145 -145
- package/dist/esm/protocol.js +2 -2
- package/dist/esm/refs.d.ts +5 -5
- package/dist/esm/refs.js +16 -16
- package/dist/esm/replica.d.ts +28 -28
- package/dist/esm/replica.js +25 -25
- package/dist/esm/schema/defaults.d.ts +4 -4
- package/dist/esm/schema/defaults.js +65 -65
- package/dist/esm/schema/defaults.test.d.ts +1 -1
- package/dist/esm/schema/defaults.test.js +18 -18
- package/dist/esm/schema/defaults.test.js.map +1 -1
- package/dist/esm/schema/index.d.ts +8 -8
- package/dist/esm/schema/index.js +9 -9
- package/dist/esm/schema/indexFilters.d.ts +6 -6
- package/dist/esm/schema/indexFilters.js +22 -22
- package/dist/esm/schema/types/collection.d.ts +51 -51
- package/dist/esm/schema/types/collection.js +1 -1
- package/dist/esm/schema/types/compounds.d.ts +12 -12
- package/dist/esm/schema/types/compounds.js +1 -1
- package/dist/esm/schema/types/fields.d.ts +59 -59
- package/dist/esm/schema/types/fields.js +1 -1
- package/dist/esm/schema/types/filters.d.ts +29 -29
- package/dist/esm/schema/types/filters.js +1 -1
- package/dist/esm/schema/types/shapes.d.ts +21 -21
- package/dist/esm/schema/types/shapes.js +1 -1
- package/dist/esm/schema/types/synthetics.d.ts +28 -28
- package/dist/esm/schema/types/synthetics.js +1 -1
- package/dist/esm/schema/types.d.ts +18 -18
- package/dist/esm/schema/types.js +6 -6
- package/dist/esm/timestamp.d.ts +48 -48
- package/dist/esm/timestamp.js +228 -228
- package/dist/esm/timestamp.test.d.ts +1 -1
- package/dist/esm/timestamp.test.js +91 -91
- package/dist/esm/undo.d.ts +3 -3
- package/dist/esm/undo.js +194 -194
- package/dist/esm/utils.d.ts +15 -15
- package/dist/esm/utils.js +113 -113
- package/dist/esm/utils.test.d.ts +1 -1
- package/dist/esm/utils.test.js +34 -34
- package/dist/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/oids.test.ts +62 -51
- package/src/oids.ts +7 -26
- package/src/operation.test.ts +32 -40
- package/src/operation.ts +4 -16
- package/src/patch.ts +5 -15
- package/src/schema/defaults.test.ts +1 -1
- package/dist/cjs/files.test.d.ts +0 -1
- package/dist/cjs/files.test.js +0 -81
- package/dist/cjs/files.test.js.map +0 -1
- package/dist/esm/files.test.d.ts +0 -1
- package/dist/esm/files.test.js +0 -79
- package/dist/esm/files.test.js.map +0 -1
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
import { describe, expect, it, vitest } from 'vitest';
|
|
2
|
-
import { convertOldHlcTimestamp, HybridLogicalClockTimestampProvider, OLD_encodeVersion, OLD_serializeHlcTimestamp, } from './timestamp.js';
|
|
3
|
-
describe('the hybrid logical clock', () => {
|
|
4
|
-
it('always produces incrementing timestamps', async () => {
|
|
5
|
-
const clock = new HybridLogicalClockTimestampProvider();
|
|
6
|
-
let prev = clock.now(1);
|
|
7
|
-
clock.update(prev);
|
|
8
|
-
let now;
|
|
9
|
-
for (let i = 0; i < 1000; i++) {
|
|
10
|
-
now = clock.now(1);
|
|
11
|
-
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
12
|
-
clock.update(now);
|
|
13
|
-
prev = now;
|
|
14
|
-
}
|
|
15
|
-
let time = new Date().getTime();
|
|
16
|
-
for (let i = 0; i < 1000; i++) {
|
|
17
|
-
vitest.setSystemTime(time++);
|
|
18
|
-
now = clock.now(1);
|
|
19
|
-
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
20
|
-
clock.update(now);
|
|
21
|
-
prev = now;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
it('groups blocks of time by version', () => {
|
|
25
|
-
const clock = new HybridLogicalClockTimestampProvider();
|
|
26
|
-
let prev = clock.now(1);
|
|
27
|
-
clock.update(prev);
|
|
28
|
-
let now;
|
|
29
|
-
for (let i = 0; i < 10; i++) {
|
|
30
|
-
now = clock.now(1);
|
|
31
|
-
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
32
|
-
clock.update(now);
|
|
33
|
-
prev = now;
|
|
34
|
-
}
|
|
35
|
-
for (let i = 0; i < 10; i++) {
|
|
36
|
-
now = clock.now(2);
|
|
37
|
-
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
38
|
-
clock.update(now);
|
|
39
|
-
prev = now;
|
|
40
|
-
}
|
|
41
|
-
const startOfVersion2 = now;
|
|
42
|
-
// jump back to 1
|
|
43
|
-
prev = clock.now(1);
|
|
44
|
-
clock.update(prev);
|
|
45
|
-
for (let i = 0; i < 10; i++) {
|
|
46
|
-
now = clock.now(1);
|
|
47
|
-
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
48
|
-
expect(now < startOfVersion2, `${now} not earlier than ${startOfVersion2}`).toBe(true);
|
|
49
|
-
clock.update(now);
|
|
50
|
-
prev = now;
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
it('is shorter than the previous format', () => {
|
|
54
|
-
const clock = new HybridLogicalClockTimestampProvider();
|
|
55
|
-
expect(clock.now(1).length).toBeLessThan(clock.OLD_now(1).length);
|
|
56
|
-
});
|
|
57
|
-
it('correctly converts old to new format', () => {
|
|
58
|
-
const clock = new HybridLogicalClockTimestampProvider();
|
|
59
|
-
const oldFormat = clock.OLD_now(1);
|
|
60
|
-
const value = clock.timerState();
|
|
61
|
-
const converted = convertOldHlcTimestamp(oldFormat);
|
|
62
|
-
const newFormat = clock.get(1, value);
|
|
63
|
-
expect(converted).toEqual(newFormat);
|
|
64
|
-
});
|
|
65
|
-
// this is mostly to verify as I move to the new format.
|
|
66
|
-
// instead of attempting a distributed migration of timestamps, it
|
|
67
|
-
// will be easier that the new format just acts as always in the
|
|
68
|
-
// future of the old one. this will only mess with offline clients,
|
|
69
|
-
// but nobody is using verdant but me yet at time of this change (2022-12-19)
|
|
70
|
-
// which is kind of why I'm doing it now anyway.
|
|
71
|
-
it('old format always comes before new format', () => {
|
|
72
|
-
const clock = new HybridLogicalClockTimestampProvider();
|
|
73
|
-
const newFormat = clock.now(1);
|
|
74
|
-
const oldFormat = clock.OLD_now(1);
|
|
75
|
-
expect(oldFormat < newFormat).toBe(true);
|
|
76
|
-
// even far-future old timestamps are before new timestamps
|
|
77
|
-
const oldFormat2 = OLD_serializeHlcTimestamp({
|
|
78
|
-
counter: 999,
|
|
79
|
-
node: '9876543210',
|
|
80
|
-
time: new Date(3000, 1, 1).getTime(),
|
|
81
|
-
});
|
|
82
|
-
expect(OLD_encodeVersion(1) + oldFormat2 < newFormat).toBe(true);
|
|
83
|
-
});
|
|
84
|
-
it('decodes timestamps', () => {
|
|
85
|
-
const time = new Date();
|
|
86
|
-
vitest.setSystemTime(time);
|
|
87
|
-
const clock = new HybridLogicalClockTimestampProvider();
|
|
88
|
-
const now = clock.now(1);
|
|
89
|
-
expect(clock.getWallClockTime(now)).toEqual(time.getTime());
|
|
90
|
-
});
|
|
91
|
-
});
|
|
1
|
+
import { describe, expect, it, vitest } from 'vitest';
|
|
2
|
+
import { convertOldHlcTimestamp, HybridLogicalClockTimestampProvider, OLD_encodeVersion, OLD_serializeHlcTimestamp, } from './timestamp.js';
|
|
3
|
+
describe('the hybrid logical clock', () => {
|
|
4
|
+
it('always produces incrementing timestamps', async () => {
|
|
5
|
+
const clock = new HybridLogicalClockTimestampProvider();
|
|
6
|
+
let prev = clock.now(1);
|
|
7
|
+
clock.update(prev);
|
|
8
|
+
let now;
|
|
9
|
+
for (let i = 0; i < 1000; i++) {
|
|
10
|
+
now = clock.now(1);
|
|
11
|
+
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
12
|
+
clock.update(now);
|
|
13
|
+
prev = now;
|
|
14
|
+
}
|
|
15
|
+
let time = new Date().getTime();
|
|
16
|
+
for (let i = 0; i < 1000; i++) {
|
|
17
|
+
vitest.setSystemTime(time++);
|
|
18
|
+
now = clock.now(1);
|
|
19
|
+
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
20
|
+
clock.update(now);
|
|
21
|
+
prev = now;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
it('groups blocks of time by version', () => {
|
|
25
|
+
const clock = new HybridLogicalClockTimestampProvider();
|
|
26
|
+
let prev = clock.now(1);
|
|
27
|
+
clock.update(prev);
|
|
28
|
+
let now;
|
|
29
|
+
for (let i = 0; i < 10; i++) {
|
|
30
|
+
now = clock.now(1);
|
|
31
|
+
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
32
|
+
clock.update(now);
|
|
33
|
+
prev = now;
|
|
34
|
+
}
|
|
35
|
+
for (let i = 0; i < 10; i++) {
|
|
36
|
+
now = clock.now(2);
|
|
37
|
+
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
38
|
+
clock.update(now);
|
|
39
|
+
prev = now;
|
|
40
|
+
}
|
|
41
|
+
const startOfVersion2 = now;
|
|
42
|
+
// jump back to 1
|
|
43
|
+
prev = clock.now(1);
|
|
44
|
+
clock.update(prev);
|
|
45
|
+
for (let i = 0; i < 10; i++) {
|
|
46
|
+
now = clock.now(1);
|
|
47
|
+
expect(now > prev, `${now} not later than ${prev}`).toBe(true);
|
|
48
|
+
expect(now < startOfVersion2, `${now} not earlier than ${startOfVersion2}`).toBe(true);
|
|
49
|
+
clock.update(now);
|
|
50
|
+
prev = now;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
it('is shorter than the previous format', () => {
|
|
54
|
+
const clock = new HybridLogicalClockTimestampProvider();
|
|
55
|
+
expect(clock.now(1).length).toBeLessThan(clock.OLD_now(1).length);
|
|
56
|
+
});
|
|
57
|
+
it('correctly converts old to new format', () => {
|
|
58
|
+
const clock = new HybridLogicalClockTimestampProvider();
|
|
59
|
+
const oldFormat = clock.OLD_now(1);
|
|
60
|
+
const value = clock.timerState();
|
|
61
|
+
const converted = convertOldHlcTimestamp(oldFormat);
|
|
62
|
+
const newFormat = clock.get(1, value);
|
|
63
|
+
expect(converted).toEqual(newFormat);
|
|
64
|
+
});
|
|
65
|
+
// this is mostly to verify as I move to the new format.
|
|
66
|
+
// instead of attempting a distributed migration of timestamps, it
|
|
67
|
+
// will be easier that the new format just acts as always in the
|
|
68
|
+
// future of the old one. this will only mess with offline clients,
|
|
69
|
+
// but nobody is using verdant but me yet at time of this change (2022-12-19)
|
|
70
|
+
// which is kind of why I'm doing it now anyway.
|
|
71
|
+
it('old format always comes before new format', () => {
|
|
72
|
+
const clock = new HybridLogicalClockTimestampProvider();
|
|
73
|
+
const newFormat = clock.now(1);
|
|
74
|
+
const oldFormat = clock.OLD_now(1);
|
|
75
|
+
expect(oldFormat < newFormat).toBe(true);
|
|
76
|
+
// even far-future old timestamps are before new timestamps
|
|
77
|
+
const oldFormat2 = OLD_serializeHlcTimestamp({
|
|
78
|
+
counter: 999,
|
|
79
|
+
node: '9876543210',
|
|
80
|
+
time: new Date(3000, 1, 1).getTime(),
|
|
81
|
+
});
|
|
82
|
+
expect(OLD_encodeVersion(1) + oldFormat2 < newFormat).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
it('decodes timestamps', () => {
|
|
85
|
+
const time = new Date();
|
|
86
|
+
vitest.setSystemTime(time);
|
|
87
|
+
const clock = new HybridLogicalClockTimestampProvider();
|
|
88
|
+
const now = clock.now(1);
|
|
89
|
+
expect(clock.getWallClockTime(now)).toEqual(time.getTime());
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
92
|
//# sourceMappingURL=timestamp.test.js.map
|
package/dist/esm/undo.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { ObjectIdentifier } from './oids.js';
|
|
2
|
-
import { Operation } from './operation.js';
|
|
3
|
-
export declare function getUndoOperations(oid: ObjectIdentifier, initial: any, operations: Operation[], getNow: () => string): Operation[];
|
|
1
|
+
import { ObjectIdentifier } from './oids.js';
|
|
2
|
+
import { Operation } from './operation.js';
|
|
3
|
+
export declare function getUndoOperations(oid: ObjectIdentifier, initial: any, operations: Operation[], getNow: () => string): Operation[];
|
package/dist/esm/undo.js
CHANGED
|
@@ -1,195 +1,195 @@
|
|
|
1
|
-
import { applyPatch } from './operation.js';
|
|
2
|
-
import { cloneDeep } from './utils.js';
|
|
3
|
-
export function getUndoOperations(oid, initial, operations, getNow) {
|
|
4
|
-
if (initial === undefined) {
|
|
5
|
-
// if the initial state is nothing, then the undo is to delete everything.
|
|
6
|
-
// there's nothing else to worry about!
|
|
7
|
-
return [
|
|
8
|
-
{
|
|
9
|
-
oid,
|
|
10
|
-
timestamp: getNow(),
|
|
11
|
-
data: {
|
|
12
|
-
op: 'delete',
|
|
13
|
-
},
|
|
14
|
-
},
|
|
15
|
-
];
|
|
16
|
-
}
|
|
17
|
-
// otherwise, traverse the operations one by one,
|
|
18
|
-
// applying them, and using the prior state to determine
|
|
19
|
-
// what the undo operation is.
|
|
20
|
-
let state = cloneDeep(initial);
|
|
21
|
-
const undoOperations = [];
|
|
22
|
-
for (const operation of operations) {
|
|
23
|
-
const undo = getUndoOperation(oid, state, operation, getNow);
|
|
24
|
-
undoOperations.unshift(...undo);
|
|
25
|
-
applyPatch(state, operation.data);
|
|
26
|
-
}
|
|
27
|
-
return undoOperations;
|
|
28
|
-
}
|
|
29
|
-
function getUndoOperation(oid, initial, operation, getNow) {
|
|
30
|
-
const data = operation.data;
|
|
31
|
-
switch (data.op) {
|
|
32
|
-
case 'set':
|
|
33
|
-
case 'remove':
|
|
34
|
-
return [
|
|
35
|
-
{
|
|
36
|
-
oid,
|
|
37
|
-
timestamp: getNow(),
|
|
38
|
-
data: {
|
|
39
|
-
op: 'set',
|
|
40
|
-
name: data.name,
|
|
41
|
-
value: initial[data.name],
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
case 'list-insert':
|
|
46
|
-
return [
|
|
47
|
-
{
|
|
48
|
-
oid,
|
|
49
|
-
timestamp: getNow(),
|
|
50
|
-
data: {
|
|
51
|
-
op: 'list-delete',
|
|
52
|
-
index: data.index,
|
|
53
|
-
count: 1,
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
];
|
|
57
|
-
case 'list-delete':
|
|
58
|
-
return [
|
|
59
|
-
{
|
|
60
|
-
oid,
|
|
61
|
-
timestamp: getNow(),
|
|
62
|
-
data: {
|
|
63
|
-
op: 'list-insert',
|
|
64
|
-
index: data.index,
|
|
65
|
-
values: initial.slice(data.index, data.count),
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
];
|
|
69
|
-
case 'list-move-by-ref':
|
|
70
|
-
return [
|
|
71
|
-
{
|
|
72
|
-
oid,
|
|
73
|
-
timestamp: getNow(),
|
|
74
|
-
data: {
|
|
75
|
-
op: 'list-move-by-ref',
|
|
76
|
-
value: data.value,
|
|
77
|
-
index: initial.indexOf(data.value),
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
];
|
|
81
|
-
case 'list-move-by-index':
|
|
82
|
-
return [
|
|
83
|
-
{
|
|
84
|
-
oid,
|
|
85
|
-
timestamp: getNow(),
|
|
86
|
-
data: {
|
|
87
|
-
op: 'list-move-by-index',
|
|
88
|
-
from: data.to,
|
|
89
|
-
to: data.from,
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
];
|
|
93
|
-
case 'delete':
|
|
94
|
-
return [
|
|
95
|
-
{
|
|
96
|
-
oid,
|
|
97
|
-
timestamp: getNow(),
|
|
98
|
-
data: {
|
|
99
|
-
op: 'initialize',
|
|
100
|
-
value: initial,
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
];
|
|
104
|
-
case 'list-push':
|
|
105
|
-
return [
|
|
106
|
-
{
|
|
107
|
-
oid,
|
|
108
|
-
timestamp: getNow(),
|
|
109
|
-
data: {
|
|
110
|
-
op: 'list-remove',
|
|
111
|
-
value: data.value,
|
|
112
|
-
// best heuristic here - remove the last instance of the item.
|
|
113
|
-
only: 'last',
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
];
|
|
117
|
-
case 'list-remove':
|
|
118
|
-
if (data.only === 'last') {
|
|
119
|
-
const index = initial.lastIndexOf(data.value);
|
|
120
|
-
return [
|
|
121
|
-
{
|
|
122
|
-
oid,
|
|
123
|
-
timestamp: getNow(),
|
|
124
|
-
data: {
|
|
125
|
-
op: 'list-insert',
|
|
126
|
-
index,
|
|
127
|
-
values: [data.value],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
];
|
|
131
|
-
}
|
|
132
|
-
else if (data.only === 'first') {
|
|
133
|
-
const index = initial.indexOf(data.value);
|
|
134
|
-
return [
|
|
135
|
-
{
|
|
136
|
-
oid,
|
|
137
|
-
timestamp: getNow(),
|
|
138
|
-
data: {
|
|
139
|
-
op: 'list-insert',
|
|
140
|
-
index,
|
|
141
|
-
values: [data.value],
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
];
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
// find all instances of value and restore them at their
|
|
148
|
-
// original index
|
|
149
|
-
const indexesOfValue = [];
|
|
150
|
-
let index = initial.indexOf(data.value);
|
|
151
|
-
while (index !== -1) {
|
|
152
|
-
indexesOfValue.push(index);
|
|
153
|
-
index = initial.indexOf(data.value, index + 1);
|
|
154
|
-
}
|
|
155
|
-
return indexesOfValue.map((index) => ({
|
|
156
|
-
oid,
|
|
157
|
-
timestamp: getNow(),
|
|
158
|
-
data: {
|
|
159
|
-
op: 'list-insert',
|
|
160
|
-
index,
|
|
161
|
-
value: data.value,
|
|
162
|
-
},
|
|
163
|
-
}));
|
|
164
|
-
}
|
|
165
|
-
case 'list-add':
|
|
166
|
-
// this one is kind of ambiguous. in theory the set may have
|
|
167
|
-
// already included the value and so no change happened. but
|
|
168
|
-
// basically we infer the intent is to remove what was meant
|
|
169
|
-
// to be added by this change.
|
|
170
|
-
return [
|
|
171
|
-
{
|
|
172
|
-
oid,
|
|
173
|
-
timestamp: getNow(),
|
|
174
|
-
data: {
|
|
175
|
-
op: 'list-remove',
|
|
176
|
-
value: data.value,
|
|
177
|
-
only: 'last',
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
];
|
|
181
|
-
case 'initialize':
|
|
182
|
-
return [
|
|
183
|
-
{
|
|
184
|
-
oid,
|
|
185
|
-
timestamp: getNow(),
|
|
186
|
-
data: {
|
|
187
|
-
op: 'delete',
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
];
|
|
191
|
-
default:
|
|
192
|
-
throw new Error(`Cannot undo operation type: ${data.op}`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
1
|
+
import { applyPatch } from './operation.js';
|
|
2
|
+
import { cloneDeep } from './utils.js';
|
|
3
|
+
export function getUndoOperations(oid, initial, operations, getNow) {
|
|
4
|
+
if (initial === undefined) {
|
|
5
|
+
// if the initial state is nothing, then the undo is to delete everything.
|
|
6
|
+
// there's nothing else to worry about!
|
|
7
|
+
return [
|
|
8
|
+
{
|
|
9
|
+
oid,
|
|
10
|
+
timestamp: getNow(),
|
|
11
|
+
data: {
|
|
12
|
+
op: 'delete',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
// otherwise, traverse the operations one by one,
|
|
18
|
+
// applying them, and using the prior state to determine
|
|
19
|
+
// what the undo operation is.
|
|
20
|
+
let state = cloneDeep(initial);
|
|
21
|
+
const undoOperations = [];
|
|
22
|
+
for (const operation of operations) {
|
|
23
|
+
const undo = getUndoOperation(oid, state, operation, getNow);
|
|
24
|
+
undoOperations.unshift(...undo);
|
|
25
|
+
applyPatch(state, operation.data);
|
|
26
|
+
}
|
|
27
|
+
return undoOperations;
|
|
28
|
+
}
|
|
29
|
+
function getUndoOperation(oid, initial, operation, getNow) {
|
|
30
|
+
const data = operation.data;
|
|
31
|
+
switch (data.op) {
|
|
32
|
+
case 'set':
|
|
33
|
+
case 'remove':
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
oid,
|
|
37
|
+
timestamp: getNow(),
|
|
38
|
+
data: {
|
|
39
|
+
op: 'set',
|
|
40
|
+
name: data.name,
|
|
41
|
+
value: initial[data.name],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
case 'list-insert':
|
|
46
|
+
return [
|
|
47
|
+
{
|
|
48
|
+
oid,
|
|
49
|
+
timestamp: getNow(),
|
|
50
|
+
data: {
|
|
51
|
+
op: 'list-delete',
|
|
52
|
+
index: data.index,
|
|
53
|
+
count: 1,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
case 'list-delete':
|
|
58
|
+
return [
|
|
59
|
+
{
|
|
60
|
+
oid,
|
|
61
|
+
timestamp: getNow(),
|
|
62
|
+
data: {
|
|
63
|
+
op: 'list-insert',
|
|
64
|
+
index: data.index,
|
|
65
|
+
values: initial.slice(data.index, data.count),
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
case 'list-move-by-ref':
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
oid,
|
|
73
|
+
timestamp: getNow(),
|
|
74
|
+
data: {
|
|
75
|
+
op: 'list-move-by-ref',
|
|
76
|
+
value: data.value,
|
|
77
|
+
index: initial.indexOf(data.value),
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
case 'list-move-by-index':
|
|
82
|
+
return [
|
|
83
|
+
{
|
|
84
|
+
oid,
|
|
85
|
+
timestamp: getNow(),
|
|
86
|
+
data: {
|
|
87
|
+
op: 'list-move-by-index',
|
|
88
|
+
from: data.to,
|
|
89
|
+
to: data.from,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
case 'delete':
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
oid,
|
|
97
|
+
timestamp: getNow(),
|
|
98
|
+
data: {
|
|
99
|
+
op: 'initialize',
|
|
100
|
+
value: initial,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
case 'list-push':
|
|
105
|
+
return [
|
|
106
|
+
{
|
|
107
|
+
oid,
|
|
108
|
+
timestamp: getNow(),
|
|
109
|
+
data: {
|
|
110
|
+
op: 'list-remove',
|
|
111
|
+
value: data.value,
|
|
112
|
+
// best heuristic here - remove the last instance of the item.
|
|
113
|
+
only: 'last',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
case 'list-remove':
|
|
118
|
+
if (data.only === 'last') {
|
|
119
|
+
const index = initial.lastIndexOf(data.value);
|
|
120
|
+
return [
|
|
121
|
+
{
|
|
122
|
+
oid,
|
|
123
|
+
timestamp: getNow(),
|
|
124
|
+
data: {
|
|
125
|
+
op: 'list-insert',
|
|
126
|
+
index,
|
|
127
|
+
values: [data.value],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
else if (data.only === 'first') {
|
|
133
|
+
const index = initial.indexOf(data.value);
|
|
134
|
+
return [
|
|
135
|
+
{
|
|
136
|
+
oid,
|
|
137
|
+
timestamp: getNow(),
|
|
138
|
+
data: {
|
|
139
|
+
op: 'list-insert',
|
|
140
|
+
index,
|
|
141
|
+
values: [data.value],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// find all instances of value and restore them at their
|
|
148
|
+
// original index
|
|
149
|
+
const indexesOfValue = [];
|
|
150
|
+
let index = initial.indexOf(data.value);
|
|
151
|
+
while (index !== -1) {
|
|
152
|
+
indexesOfValue.push(index);
|
|
153
|
+
index = initial.indexOf(data.value, index + 1);
|
|
154
|
+
}
|
|
155
|
+
return indexesOfValue.map((index) => ({
|
|
156
|
+
oid,
|
|
157
|
+
timestamp: getNow(),
|
|
158
|
+
data: {
|
|
159
|
+
op: 'list-insert',
|
|
160
|
+
index,
|
|
161
|
+
value: data.value,
|
|
162
|
+
},
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
case 'list-add':
|
|
166
|
+
// this one is kind of ambiguous. in theory the set may have
|
|
167
|
+
// already included the value and so no change happened. but
|
|
168
|
+
// basically we infer the intent is to remove what was meant
|
|
169
|
+
// to be added by this change.
|
|
170
|
+
return [
|
|
171
|
+
{
|
|
172
|
+
oid,
|
|
173
|
+
timestamp: getNow(),
|
|
174
|
+
data: {
|
|
175
|
+
op: 'list-remove',
|
|
176
|
+
value: data.value,
|
|
177
|
+
only: 'last',
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
case 'initialize':
|
|
182
|
+
return [
|
|
183
|
+
{
|
|
184
|
+
oid,
|
|
185
|
+
timestamp: getNow(),
|
|
186
|
+
data: {
|
|
187
|
+
op: 'delete',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
default:
|
|
192
|
+
throw new Error(`Cannot undo operation type: ${data.op}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
195
|
//# sourceMappingURL=undo.js.map
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
export declare function take<T extends object, Keys extends keyof T>(obj: T, keys: Keys[]): Pick<T, Keys>;
|
|
2
|
-
export declare function omit<T extends object, Keys extends keyof T>(obj: T, keys: Keys[]): Omit<T, Keys>;
|
|
3
|
-
export declare function getSortedIndex<T>(array: T[], insert: T, compare: (a: T, b: T) => number): number;
|
|
4
|
-
/**
|
|
5
|
-
* Consistently stringifies an object regardless
|
|
6
|
-
* of key insertion order
|
|
7
|
-
*/
|
|
8
|
-
export declare function stableStringify(obj: any): string;
|
|
9
|
-
export declare function cloneDeep<T>(obj: T): T;
|
|
10
|
-
export declare function hashObject(obj: any): string;
|
|
11
|
-
export declare function isObject(obj: any): any;
|
|
12
|
-
export declare function roughSizeOfObject(object: any): number;
|
|
13
|
-
export declare function assert(condition: any, message?: string): asserts condition;
|
|
14
|
-
export declare function generateId(length?: number): string;
|
|
15
|
-
export declare function findLastIndex<T>(array: T[], predicate: (item: T) => boolean): number;
|
|
1
|
+
export declare function take<T extends object, Keys extends keyof T>(obj: T, keys: Keys[]): Pick<T, Keys>;
|
|
2
|
+
export declare function omit<T extends object, Keys extends keyof T>(obj: T, keys: Keys[]): Omit<T, Keys>;
|
|
3
|
+
export declare function getSortedIndex<T>(array: T[], insert: T, compare: (a: T, b: T) => number): number;
|
|
4
|
+
/**
|
|
5
|
+
* Consistently stringifies an object regardless
|
|
6
|
+
* of key insertion order
|
|
7
|
+
*/
|
|
8
|
+
export declare function stableStringify(obj: any): string;
|
|
9
|
+
export declare function cloneDeep<T>(obj: T): T;
|
|
10
|
+
export declare function hashObject(obj: any): string;
|
|
11
|
+
export declare function isObject(obj: any): any;
|
|
12
|
+
export declare function roughSizeOfObject(object: any): number;
|
|
13
|
+
export declare function assert(condition: any, message?: string): asserts condition;
|
|
14
|
+
export declare function generateId(length?: number): string;
|
|
15
|
+
export declare function findLastIndex<T>(array: T[], predicate: (item: T) => boolean): number;
|