@verdant-web/common 2.8.0 → 2.9.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/esm/authz.js.map +1 -1
- package/dist/esm/baseline.d.ts +2 -1
- package/dist/esm/diffing.d.ts +3 -2
- package/dist/esm/diffing.js +79 -19
- package/dist/esm/diffing.js.map +1 -1
- package/dist/esm/diffing.test.js +967 -183
- package/dist/esm/diffing.test.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 +5 -4
- package/dist/esm/operation.js +4 -4
- package/dist/esm/operation.js.map +1 -1
- package/dist/esm/patch.d.ts +17 -13
- package/dist/esm/patch.js +42 -6
- package/dist/esm/patch.js.map +1 -1
- package/dist/esm/schema/fields.d.ts +1 -0
- package/dist/esm/schema/fields.js +5 -0
- package/dist/esm/schema/fields.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 +8 -0
- package/dist/esm/timestamp.js +15 -0
- package/dist/esm/timestamp.js.map +1 -1
- package/dist/esm/undo.d.ts +2 -1
- package/dist/esm/undo.js +52 -158
- package/dist/esm/undo.js.map +1 -1
- package/dist/esm/undo.test.js +68 -7
- package/dist/esm/undo.test.js.map +1 -1
- package/package.json +1 -1
- package/src/authz.ts +2 -0
- package/src/baseline.ts +3 -1
- package/src/diffing.test.ts +1089 -225
- package/src/diffing.ts +99 -22
- package/src/oids.ts +23 -1
- package/src/operation.ts +9 -8
- package/src/patch.ts +92 -17
- package/src/schema/fields.ts +8 -0
- package/src/schema/validation.test.ts +66 -0
- package/src/schema/validation.ts +156 -73
- package/src/timestamp.ts +14 -0
- package/src/undo.test.ts +87 -8
- package/src/undo.ts +83 -157
package/src/undo.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { AuthorizationKey } from './authz.js';
|
|
1
2
|
import { ObjectIdentifier } from './oids.js';
|
|
2
3
|
import { applyPatch, Operation } from './operation.js';
|
|
4
|
+
import { PatchCreator } from './patch.js';
|
|
5
|
+
import { isRef } from './refs.js';
|
|
3
6
|
import { cloneDeep } from './utils.js';
|
|
4
7
|
|
|
5
8
|
export function getUndoOperations(
|
|
@@ -7,7 +10,10 @@ export function getUndoOperations(
|
|
|
7
10
|
initial: any,
|
|
8
11
|
operations: Operation[],
|
|
9
12
|
getNow: () => string,
|
|
13
|
+
getSubId?: () => string,
|
|
14
|
+
authz?: AuthorizationKey,
|
|
10
15
|
): Operation[] {
|
|
16
|
+
const patchCreator = new PatchCreator(getNow, getSubId);
|
|
11
17
|
if (initial === undefined || initial === null) {
|
|
12
18
|
// if the initial state is nothing, then the undo is to delete everything.
|
|
13
19
|
// there's nothing else to worry about!
|
|
@@ -18,6 +24,7 @@ export function getUndoOperations(
|
|
|
18
24
|
data: {
|
|
19
25
|
op: 'delete',
|
|
20
26
|
},
|
|
27
|
+
authz,
|
|
21
28
|
},
|
|
22
29
|
];
|
|
23
30
|
}
|
|
@@ -27,7 +34,7 @@ export function getUndoOperations(
|
|
|
27
34
|
let state = cloneDeep(initial);
|
|
28
35
|
const undoOperations: Operation[] = [];
|
|
29
36
|
for (const operation of operations) {
|
|
30
|
-
const undo = getUndoOperation(oid, state, operation,
|
|
37
|
+
const undo = getUndoOperation(oid, state, operation, patchCreator, authz);
|
|
31
38
|
undoOperations.unshift(...undo);
|
|
32
39
|
applyPatch(state, operation.data);
|
|
33
40
|
}
|
|
@@ -38,195 +45,114 @@ function getUndoOperation(
|
|
|
38
45
|
oid: ObjectIdentifier,
|
|
39
46
|
initial: any,
|
|
40
47
|
operation: Operation,
|
|
41
|
-
|
|
48
|
+
patchCreator: PatchCreator,
|
|
49
|
+
authz?: AuthorizationKey,
|
|
42
50
|
): Operation[] {
|
|
43
51
|
const data = operation.data;
|
|
44
52
|
switch (data.op) {
|
|
45
53
|
case 'set':
|
|
54
|
+
if (initial[data.name] === undefined) {
|
|
55
|
+
return patchCreator.createRemove(oid, data.name, authz);
|
|
56
|
+
}
|
|
57
|
+
return patchCreator.createSet(oid, data.name, initial[data.name], authz);
|
|
46
58
|
case 'remove':
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
data: {
|
|
52
|
-
op: 'set',
|
|
53
|
-
name: data.name,
|
|
54
|
-
value: initial[data.name],
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
];
|
|
59
|
+
if (initial[data.name] === undefined) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
return patchCreator.createSet(oid, data.name, initial[data.name], authz);
|
|
58
63
|
case 'list-insert':
|
|
59
|
-
return [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
count: 1,
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
];
|
|
64
|
+
if (data.value === undefined && data.values?.length === 0) return [];
|
|
65
|
+
return patchCreator.createListDelete(
|
|
66
|
+
oid,
|
|
67
|
+
data.index,
|
|
68
|
+
data.value ? 1 : data.values?.length || 0,
|
|
69
|
+
authz,
|
|
70
|
+
);
|
|
70
71
|
case 'list-delete':
|
|
71
|
-
return
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
index: data.index,
|
|
78
|
-
values: initial.slice(data.index, data.count),
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
];
|
|
72
|
+
return patchCreator.createListInsertMany(
|
|
73
|
+
oid,
|
|
74
|
+
data.index,
|
|
75
|
+
initial.slice(data.index, data.index + data.count),
|
|
76
|
+
authz,
|
|
77
|
+
);
|
|
82
78
|
case 'list-move-by-ref':
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
value: data.value,
|
|
90
|
-
index: initial.indexOf(data.value),
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
];
|
|
79
|
+
return patchCreator.createListMoveByRef(
|
|
80
|
+
oid,
|
|
81
|
+
data.value,
|
|
82
|
+
findItemRefIndex(initial, data.value),
|
|
83
|
+
authz,
|
|
84
|
+
);
|
|
94
85
|
case 'list-move-by-index':
|
|
95
|
-
return
|
|
96
|
-
{
|
|
97
|
-
oid,
|
|
98
|
-
timestamp: getNow(),
|
|
99
|
-
data: {
|
|
100
|
-
op: 'list-move-by-index',
|
|
101
|
-
from: data.to,
|
|
102
|
-
to: data.from,
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
];
|
|
86
|
+
return patchCreator.createListMoveByIndex(oid, data.to, data.from, authz);
|
|
106
87
|
case 'delete':
|
|
107
|
-
return
|
|
108
|
-
{
|
|
109
|
-
oid,
|
|
110
|
-
timestamp: getNow(),
|
|
111
|
-
data: {
|
|
112
|
-
op: 'initialize',
|
|
113
|
-
value: initial,
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
];
|
|
88
|
+
return patchCreator.createInitialize(initial, oid, authz, true);
|
|
117
89
|
case 'list-push':
|
|
118
|
-
return
|
|
119
|
-
{
|
|
120
|
-
oid,
|
|
121
|
-
timestamp: getNow(),
|
|
122
|
-
data: {
|
|
123
|
-
op: 'list-remove',
|
|
124
|
-
value: data.value,
|
|
125
|
-
// best heuristic here - remove the last instance of the item.
|
|
126
|
-
only: 'last',
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
];
|
|
90
|
+
return patchCreator.createListRemove(oid, data.value, 'last', authz);
|
|
130
91
|
case 'list-remove':
|
|
131
92
|
if (data.only === 'last') {
|
|
132
|
-
const index = initial
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
op: 'list-insert',
|
|
139
|
-
index,
|
|
140
|
-
values: [data.value],
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
];
|
|
93
|
+
const index = findItemRefIndex(initial, data.value, 0, 'backward');
|
|
94
|
+
if (index === -1) {
|
|
95
|
+
// if the value isn't in the list, then there's nothing to undo.
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
return patchCreator.createListInsert(oid, index, data.value, authz);
|
|
144
99
|
} else if (data.only === 'first') {
|
|
145
|
-
const index = initial
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
op: 'list-insert',
|
|
152
|
-
index,
|
|
153
|
-
values: [data.value],
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
];
|
|
100
|
+
const index = findItemRefIndex(initial, data.value);
|
|
101
|
+
if (index === -1) {
|
|
102
|
+
// if the value isn't in the list, then there's nothing to undo.
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
return patchCreator.createListInsert(oid, index, data.value, authz);
|
|
157
106
|
} else {
|
|
158
107
|
// find all instances of value and restore them at their
|
|
159
108
|
// original index
|
|
160
109
|
const indexesOfValue = [];
|
|
161
|
-
let index = initial
|
|
110
|
+
let index = findItemRefIndex(initial, data.value);
|
|
162
111
|
while (index !== -1) {
|
|
163
112
|
indexesOfValue.push(index);
|
|
164
|
-
index = initial
|
|
113
|
+
index = findItemRefIndex(initial, data.value, index + 1);
|
|
165
114
|
}
|
|
166
|
-
return indexesOfValue.
|
|
167
|
-
oid,
|
|
168
|
-
|
|
169
|
-
data: {
|
|
170
|
-
op: 'list-insert',
|
|
171
|
-
index,
|
|
172
|
-
value: data.value,
|
|
173
|
-
},
|
|
174
|
-
}));
|
|
115
|
+
return indexesOfValue.flatMap((index) =>
|
|
116
|
+
patchCreator.createListInsert(oid, index, data.value, authz),
|
|
117
|
+
);
|
|
175
118
|
}
|
|
176
119
|
case 'list-add':
|
|
177
120
|
// this one is kind of ambiguous. in theory the set may have
|
|
178
121
|
// already included the value and so no change happened. but
|
|
179
122
|
// basically we infer the intent is to remove what was meant
|
|
180
123
|
// to be added by this change.
|
|
181
|
-
return
|
|
182
|
-
{
|
|
183
|
-
oid,
|
|
184
|
-
timestamp: getNow(),
|
|
185
|
-
data: {
|
|
186
|
-
op: 'list-remove',
|
|
187
|
-
value: data.value,
|
|
188
|
-
only: 'last',
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
];
|
|
124
|
+
return patchCreator.createListRemove(oid, data.value, 'last', authz);
|
|
192
125
|
case 'list-set':
|
|
193
|
-
if (initial[data.index]) {
|
|
194
|
-
return
|
|
195
|
-
{
|
|
196
|
-
oid,
|
|
197
|
-
timestamp: getNow(),
|
|
198
|
-
data: {
|
|
199
|
-
op: 'list-set',
|
|
200
|
-
index: data.index,
|
|
201
|
-
value: initial[data.index],
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
];
|
|
205
|
-
}
|
|
206
|
-
return [
|
|
207
|
-
{
|
|
126
|
+
if (initial[data.index] !== undefined) {
|
|
127
|
+
return patchCreator.createListSet(
|
|
208
128
|
oid,
|
|
209
|
-
|
|
210
|
-
data
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
},
|
|
216
|
-
];
|
|
129
|
+
data.index,
|
|
130
|
+
initial[data.index],
|
|
131
|
+
authz,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return patchCreator.createListDelete(oid, data.index, 1, authz);
|
|
217
135
|
case 'initialize':
|
|
218
|
-
return
|
|
219
|
-
{
|
|
220
|
-
oid,
|
|
221
|
-
timestamp: getNow(),
|
|
222
|
-
data: {
|
|
223
|
-
op: 'delete',
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
];
|
|
136
|
+
return patchCreator.createDelete(oid, authz);
|
|
227
137
|
case 'touch':
|
|
228
138
|
return [];
|
|
229
139
|
default:
|
|
230
140
|
throw new Error(`Cannot undo operation type: ${(data as any).op}`);
|
|
231
141
|
}
|
|
232
142
|
}
|
|
143
|
+
|
|
144
|
+
function findItemRefIndex(
|
|
145
|
+
list: any[],
|
|
146
|
+
item: any,
|
|
147
|
+
from: number = 0,
|
|
148
|
+
dir: 'forward' | 'backward' = 'forward',
|
|
149
|
+
) {
|
|
150
|
+
const method = dir === 'forward' ? 'findIndex' : 'findLastIndex';
|
|
151
|
+
// for object items, attempt to find the matching item by OID.
|
|
152
|
+
if (isRef(item)) {
|
|
153
|
+
return list[method](
|
|
154
|
+
(i, idx) => isRef(i) && i.id === item.id && idx >= from,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return list[method]((i, idx) => i === item && idx >= from);
|
|
158
|
+
}
|