@verdant-web/common 2.8.0 → 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 +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/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/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/validation.test.ts +66 -0
- package/src/schema/validation.ts +156 -73
- package/src/undo.test.ts +87 -8
- package/src/undo.ts +83 -157
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { isFile, isFileData } from '../files.js';
|
|
1
|
+
import { isFile, isFileData, isFileRef } from '../files.js';
|
|
2
2
|
import { OID_KEY } from '../oidsLegacy.js';
|
|
3
|
+
import { isObjectRef } from '../operation.js';
|
|
3
4
|
import { isObject } from '../utils.js';
|
|
4
5
|
import { hasDefault, isNullable } from './fields.js';
|
|
5
6
|
export function validateEntity(schema, entity) {
|
|
@@ -26,12 +27,15 @@ export function validateEntity(schema, entity) {
|
|
|
26
27
|
}
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
|
-
export function validateEntityField({ field, value, fieldPath = [], depth, requireDefaults, }) {
|
|
30
|
+
export function validateEntityField({ field, value, fieldPath = [], depth, requireDefaults, expectRefs, hasPassedFirstLevel, }) {
|
|
31
|
+
// we only _actually_ start expecting refs after the first validation level.
|
|
32
|
+
// TODO: something more... elegant?
|
|
33
|
+
const actuallyExpectRefs = expectRefs && hasPassedFirstLevel;
|
|
30
34
|
if (depth !== undefined && depth <= 0)
|
|
31
35
|
return;
|
|
32
|
-
if (isNullable(field) && value === null)
|
|
36
|
+
if (isNullable(field) && (value === null || value === undefined))
|
|
33
37
|
return;
|
|
34
|
-
if (value === null) {
|
|
38
|
+
if (value === null || value === undefined) {
|
|
35
39
|
if (requireDefaults || !hasDefault(field)) {
|
|
36
40
|
return {
|
|
37
41
|
type: 'no-default',
|
|
@@ -41,71 +45,117 @@ export function validateEntityField({ field, value, fieldPath = [], depth, requi
|
|
|
41
45
|
}
|
|
42
46
|
}
|
|
43
47
|
if (field.type === 'object') {
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
if (actuallyExpectRefs) {
|
|
49
|
+
if (!isObjectRef(value)) {
|
|
50
|
+
return {
|
|
51
|
+
type: 'invalid-type',
|
|
52
|
+
fieldPath,
|
|
53
|
+
message: `Expected ref for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
50
56
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
else {
|
|
58
|
+
if (!isObject(value)) {
|
|
59
|
+
return {
|
|
60
|
+
type: 'invalid-type',
|
|
61
|
+
fieldPath,
|
|
62
|
+
message: `Expected object ${field.nullable ? 'or null ' : ''}for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
for (const [key, subField] of Object.entries(field.properties)) {
|
|
66
|
+
// legacy -- old objects sometimes accidentally include this key
|
|
67
|
+
if (key === OID_KEY)
|
|
68
|
+
continue;
|
|
69
|
+
const error = validateEntityField({
|
|
57
70
|
field: subField,
|
|
58
71
|
value: value[key],
|
|
59
72
|
fieldPath: [...fieldPath, key],
|
|
60
73
|
depth: depth !== undefined ? depth - 1 : undefined,
|
|
74
|
+
expectRefs,
|
|
75
|
+
hasPassedFirstLevel: true,
|
|
61
76
|
});
|
|
77
|
+
if (error) {
|
|
78
|
+
return error;
|
|
79
|
+
}
|
|
62
80
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
81
|
+
// check for unexpected keys
|
|
82
|
+
for (const key of Object.keys(value)) {
|
|
83
|
+
if (!field.properties[key]) {
|
|
84
|
+
return {
|
|
85
|
+
type: 'invalid-key',
|
|
86
|
+
fieldPath: [...fieldPath, key],
|
|
87
|
+
message: `Invalid unexpected field "${key}" on value ${formatField(fieldPath)}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
72
90
|
}
|
|
73
91
|
}
|
|
74
92
|
}
|
|
75
93
|
else if (field.type === 'array') {
|
|
76
|
-
if (
|
|
77
|
-
if (value
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
94
|
+
if (actuallyExpectRefs) {
|
|
95
|
+
if (!isObjectRef(value)) {
|
|
96
|
+
return {
|
|
97
|
+
type: 'invalid-type',
|
|
98
|
+
fieldPath,
|
|
99
|
+
message: `Expected ref for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
84
102
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
else {
|
|
104
|
+
if (!Array.isArray(value)) {
|
|
105
|
+
if (value === null && field.nullable)
|
|
106
|
+
return;
|
|
107
|
+
return {
|
|
108
|
+
type: 'invalid-value',
|
|
109
|
+
fieldPath,
|
|
110
|
+
message: `Expected array ${field.nullable ? 'or null ' : ''}for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
for (const item of value) {
|
|
114
|
+
const error = validateEntityField({
|
|
115
|
+
field: field.items,
|
|
116
|
+
value: item,
|
|
117
|
+
fieldPath: [...fieldPath, '[]'],
|
|
118
|
+
depth: depth !== undefined ? depth - 1 : undefined,
|
|
119
|
+
expectRefs,
|
|
120
|
+
hasPassedFirstLevel: true,
|
|
121
|
+
});
|
|
122
|
+
if (error) {
|
|
123
|
+
return error;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
92
126
|
}
|
|
93
127
|
}
|
|
94
128
|
else if (field.type === 'map') {
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
129
|
+
if (actuallyExpectRefs) {
|
|
130
|
+
if (!isObjectRef(value)) {
|
|
131
|
+
return {
|
|
132
|
+
type: 'invalid-type',
|
|
133
|
+
fieldPath,
|
|
134
|
+
message: `Expected ref for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
101
137
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
138
|
+
else {
|
|
139
|
+
if (!isObject(value)) {
|
|
140
|
+
return {
|
|
141
|
+
type: 'invalid-type',
|
|
142
|
+
fieldPath,
|
|
143
|
+
message: `Expected map for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
for (const [key, item] of Object.entries(value)) {
|
|
147
|
+
const error = validateEntityField({
|
|
148
|
+
field: field.values,
|
|
149
|
+
value: item,
|
|
150
|
+
fieldPath: [...fieldPath, key],
|
|
151
|
+
depth: depth !== undefined ? depth - 1 : undefined,
|
|
152
|
+
expectRefs,
|
|
153
|
+
hasPassedFirstLevel: true,
|
|
154
|
+
});
|
|
155
|
+
if (error) {
|
|
156
|
+
return error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
109
159
|
}
|
|
110
160
|
}
|
|
111
161
|
else if (field.type === 'string') {
|
|
@@ -113,14 +163,14 @@ export function validateEntityField({ field, value, fieldPath = [], depth, requi
|
|
|
113
163
|
return {
|
|
114
164
|
type: 'invalid-type',
|
|
115
165
|
fieldPath,
|
|
116
|
-
message: `Expected string ${field.nullable ? 'or null ' : ''}for field ${formatField(fieldPath)}, got ${value}`,
|
|
166
|
+
message: `Expected string ${field.nullable ? 'or null ' : ''}for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
117
167
|
};
|
|
118
168
|
}
|
|
119
169
|
if (field.options && !field.options.includes(value)) {
|
|
120
170
|
return {
|
|
121
171
|
type: 'invalid-value',
|
|
122
172
|
fieldPath,
|
|
123
|
-
message: `Expected one of ${field.options.join(', ')} for field ${formatField(fieldPath)}, got ${value}`,
|
|
173
|
+
message: `Expected one of ${field.options.join(', ')} for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
124
174
|
};
|
|
125
175
|
}
|
|
126
176
|
}
|
|
@@ -129,7 +179,7 @@ export function validateEntityField({ field, value, fieldPath = [], depth, requi
|
|
|
129
179
|
return {
|
|
130
180
|
type: 'invalid-type',
|
|
131
181
|
fieldPath,
|
|
132
|
-
message: `Expected boolean ${field.nullable ? 'or null ' : ''}for field ${formatField(fieldPath)}, got ${value}`,
|
|
182
|
+
message: `Expected boolean ${field.nullable ? 'or null ' : ''}for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
133
183
|
};
|
|
134
184
|
}
|
|
135
185
|
}
|
|
@@ -138,20 +188,40 @@ export function validateEntityField({ field, value, fieldPath = [], depth, requi
|
|
|
138
188
|
return {
|
|
139
189
|
type: 'invalid-type',
|
|
140
190
|
fieldPath,
|
|
141
|
-
message: `Expected number ${field.nullable ? 'or null ' : ''}for field ${formatField(fieldPath)}, got ${value}`,
|
|
191
|
+
message: `Expected number ${field.nullable ? 'or null ' : ''}for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
142
192
|
};
|
|
143
193
|
}
|
|
144
194
|
}
|
|
145
195
|
else if (field.type === 'file') {
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
196
|
+
if (actuallyExpectRefs) {
|
|
197
|
+
if (!isFileRef(value)) {
|
|
198
|
+
return {
|
|
199
|
+
type: 'invalid-type',
|
|
200
|
+
fieldPath,
|
|
201
|
+
message: `Expected file ref for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
if (!isFile(value) && !isFileData(value)) {
|
|
207
|
+
return {
|
|
208
|
+
type: 'invalid-type',
|
|
209
|
+
fieldPath,
|
|
210
|
+
message: `Expected file ${field.nullable ? 'or null ' : ''}for field ${formatField(fieldPath)}, got ${safeStringify(value)}`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
152
213
|
}
|
|
153
214
|
}
|
|
154
215
|
}
|
|
216
|
+
function safeStringify(value) {
|
|
217
|
+
try {
|
|
218
|
+
return JSON.stringify(value);
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
// for example, recursive objects can't be stringified
|
|
222
|
+
return String(value);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
155
225
|
function formatField(fieldPath) {
|
|
156
226
|
if (fieldPath.length === 0)
|
|
157
227
|
return 'root';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/schema/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../src/schema/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createFileRef } from '../files.js';
|
|
3
|
+
import { createRef } from '../oids.js';
|
|
4
|
+
import { schema, validateEntityField } from './index.js';
|
|
5
|
+
describe('validation', () => {
|
|
6
|
+
it('validates shallowly with refs', () => {
|
|
7
|
+
const fieldSchema = schema.fields.object({
|
|
8
|
+
fields: {
|
|
9
|
+
obj: schema.fields.object({
|
|
10
|
+
fields: {},
|
|
11
|
+
}),
|
|
12
|
+
file: schema.fields.file(),
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
expect(validateEntityField({
|
|
16
|
+
field: fieldSchema,
|
|
17
|
+
value: {
|
|
18
|
+
obj: createRef('some/id:1'),
|
|
19
|
+
file: createFileRef('somefile'),
|
|
20
|
+
},
|
|
21
|
+
fieldPath: [],
|
|
22
|
+
expectRefs: true,
|
|
23
|
+
})).toBeUndefined();
|
|
24
|
+
expect(validateEntityField({
|
|
25
|
+
field: fieldSchema,
|
|
26
|
+
value: {
|
|
27
|
+
obj: createRef('some/id:1'),
|
|
28
|
+
file: createRef('some/object:1'),
|
|
29
|
+
},
|
|
30
|
+
fieldPath: [],
|
|
31
|
+
expectRefs: true,
|
|
32
|
+
})).toMatchInlineSnapshot(`
|
|
33
|
+
{
|
|
34
|
+
"fieldPath": [
|
|
35
|
+
"file",
|
|
36
|
+
],
|
|
37
|
+
"message": "Expected file ref for field file, got {"@@type":"ref","id":"some/object:1"}",
|
|
38
|
+
"type": "invalid-type",
|
|
39
|
+
}
|
|
40
|
+
`);
|
|
41
|
+
expect(validateEntityField({
|
|
42
|
+
field: fieldSchema,
|
|
43
|
+
value: {
|
|
44
|
+
obj: 'foo',
|
|
45
|
+
file: createFileRef('somefile'),
|
|
46
|
+
},
|
|
47
|
+
fieldPath: [],
|
|
48
|
+
expectRefs: true,
|
|
49
|
+
})).toMatchInlineSnapshot(`
|
|
50
|
+
{
|
|
51
|
+
"fieldPath": [
|
|
52
|
+
"obj",
|
|
53
|
+
],
|
|
54
|
+
"message": "Expected ref for field obj, got "foo"",
|
|
55
|
+
"type": "invalid-type",
|
|
56
|
+
}
|
|
57
|
+
`);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=validation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.test.js","sourceRoot":"","sources":["../../../src/schema/validation.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEzD,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACxC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YACxC,MAAM,EAAE;gBACP,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;oBACzB,MAAM,EAAE,EAAE;iBACV,CAAC;gBACF,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;aAC1B;SACD,CAAC,CAAC;QACH,MAAM,CACL,mBAAmB,CAAC;YACnB,KAAK,EAAE,WAAW;YAClB,KAAK,EAAE;gBACN,GAAG,EAAE,SAAS,CAAC,WAAW,CAAC;gBAC3B,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC;aAC/B;YACD,SAAS,EAAE,EAAE;YACb,UAAU,EAAE,IAAI;SAChB,CAAC,CACF,CAAC,aAAa,EAAE,CAAC;QAClB,MAAM,CACL,mBAAmB,CAAC;YACnB,KAAK,EAAE,WAAW;YAClB,KAAK,EAAE;gBACN,GAAG,EAAE,SAAS,CAAC,WAAW,CAAC;gBAC3B,IAAI,EAAE,SAAS,CAAC,eAAe,CAAC;aAChC;YACD,SAAS,EAAE,EAAE;YACb,UAAU,EAAE,IAAI;SAChB,CAAC,CACF,CAAC,qBAAqB,CAAC;;;;;;;;GAQvB,CAAC,CAAC;QACH,MAAM,CACL,mBAAmB,CAAC;YACnB,KAAK,EAAE,WAAW;YAClB,KAAK,EAAE;gBACN,GAAG,EAAE,KAAK;gBACV,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC;aAC/B;YACD,SAAS,EAAE,EAAE;YACb,UAAU,EAAE,IAAI;SAChB,CAAC,CACF,CAAC,qBAAqB,CAAC;;;;;;;;GAQvB,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
package/dist/esm/undo.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AuthorizationKey } from './authz.js';
|
|
1
2
|
import { ObjectIdentifier } from './oids.js';
|
|
2
3
|
import { Operation } from './operation.js';
|
|
3
|
-
export declare function getUndoOperations(oid: ObjectIdentifier, initial: any, operations: Operation[], getNow: () => string): Operation[];
|
|
4
|
+
export declare function getUndoOperations(oid: ObjectIdentifier, initial: any, operations: Operation[], getNow: () => string, getSubId?: () => string, authz?: AuthorizationKey): Operation[];
|
package/dist/esm/undo.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { applyPatch } from './operation.js';
|
|
2
|
+
import { PatchCreator } from './patch.js';
|
|
3
|
+
import { isRef } from './refs.js';
|
|
2
4
|
import { cloneDeep } from './utils.js';
|
|
3
|
-
export function getUndoOperations(oid, initial, operations, getNow) {
|
|
5
|
+
export function getUndoOperations(oid, initial, operations, getNow, getSubId, authz) {
|
|
6
|
+
const patchCreator = new PatchCreator(getNow, getSubId);
|
|
4
7
|
if (initial === undefined || initial === null) {
|
|
5
8
|
// if the initial state is nothing, then the undo is to delete everything.
|
|
6
9
|
// there's nothing else to worry about!
|
|
@@ -11,6 +14,7 @@ export function getUndoOperations(oid, initial, operations, getNow) {
|
|
|
11
14
|
data: {
|
|
12
15
|
op: 'delete',
|
|
13
16
|
},
|
|
17
|
+
authz,
|
|
14
18
|
},
|
|
15
19
|
];
|
|
16
20
|
}
|
|
@@ -20,203 +24,93 @@ export function getUndoOperations(oid, initial, operations, getNow) {
|
|
|
20
24
|
let state = cloneDeep(initial);
|
|
21
25
|
const undoOperations = [];
|
|
22
26
|
for (const operation of operations) {
|
|
23
|
-
const undo = getUndoOperation(oid, state, operation,
|
|
27
|
+
const undo = getUndoOperation(oid, state, operation, patchCreator, authz);
|
|
24
28
|
undoOperations.unshift(...undo);
|
|
25
29
|
applyPatch(state, operation.data);
|
|
26
30
|
}
|
|
27
31
|
return undoOperations;
|
|
28
32
|
}
|
|
29
|
-
function getUndoOperation(oid, initial, operation,
|
|
33
|
+
function getUndoOperation(oid, initial, operation, patchCreator, authz) {
|
|
34
|
+
var _a, _b;
|
|
30
35
|
const data = operation.data;
|
|
31
36
|
switch (data.op) {
|
|
32
37
|
case 'set':
|
|
38
|
+
if (initial[data.name] === undefined) {
|
|
39
|
+
return patchCreator.createRemove(oid, data.name, authz);
|
|
40
|
+
}
|
|
41
|
+
return patchCreator.createSet(oid, data.name, initial[data.name], authz);
|
|
33
42
|
case 'remove':
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
data: {
|
|
39
|
-
op: 'set',
|
|
40
|
-
name: data.name,
|
|
41
|
-
value: initial[data.name],
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
];
|
|
43
|
+
if (initial[data.name] === undefined) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
return patchCreator.createSet(oid, data.name, initial[data.name], authz);
|
|
45
47
|
case 'list-insert':
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
timestamp: getNow(),
|
|
50
|
-
data: {
|
|
51
|
-
op: 'list-delete',
|
|
52
|
-
index: data.index,
|
|
53
|
-
count: 1,
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
];
|
|
48
|
+
if (data.value === undefined && ((_a = data.values) === null || _a === void 0 ? void 0 : _a.length) === 0)
|
|
49
|
+
return [];
|
|
50
|
+
return patchCreator.createListDelete(oid, data.index, data.value ? 1 : ((_b = data.values) === null || _b === void 0 ? void 0 : _b.length) || 0, authz);
|
|
57
51
|
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
|
-
];
|
|
52
|
+
return patchCreator.createListInsertMany(oid, data.index, initial.slice(data.index, data.index + data.count), authz);
|
|
69
53
|
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
|
-
];
|
|
54
|
+
return patchCreator.createListMoveByRef(oid, data.value, findItemRefIndex(initial, data.value), authz);
|
|
81
55
|
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
|
-
];
|
|
56
|
+
return patchCreator.createListMoveByIndex(oid, data.to, data.from, authz);
|
|
93
57
|
case 'delete':
|
|
94
|
-
return
|
|
95
|
-
{
|
|
96
|
-
oid,
|
|
97
|
-
timestamp: getNow(),
|
|
98
|
-
data: {
|
|
99
|
-
op: 'initialize',
|
|
100
|
-
value: initial,
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
];
|
|
58
|
+
return patchCreator.createInitialize(initial, oid, authz, true);
|
|
104
59
|
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
|
-
];
|
|
60
|
+
return patchCreator.createListRemove(oid, data.value, 'last', authz);
|
|
117
61
|
case 'list-remove':
|
|
118
62
|
if (data.only === 'last') {
|
|
119
|
-
const index = initial
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
op: 'list-insert',
|
|
126
|
-
index,
|
|
127
|
-
values: [data.value],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
];
|
|
63
|
+
const index = findItemRefIndex(initial, data.value, 0, 'backward');
|
|
64
|
+
if (index === -1) {
|
|
65
|
+
// if the value isn't in the list, then there's nothing to undo.
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
return patchCreator.createListInsert(oid, index, data.value, authz);
|
|
131
69
|
}
|
|
132
70
|
else if (data.only === 'first') {
|
|
133
|
-
const index = initial
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
op: 'list-insert',
|
|
140
|
-
index,
|
|
141
|
-
values: [data.value],
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
];
|
|
71
|
+
const index = findItemRefIndex(initial, data.value);
|
|
72
|
+
if (index === -1) {
|
|
73
|
+
// if the value isn't in the list, then there's nothing to undo.
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
return patchCreator.createListInsert(oid, index, data.value, authz);
|
|
145
77
|
}
|
|
146
78
|
else {
|
|
147
79
|
// find all instances of value and restore them at their
|
|
148
80
|
// original index
|
|
149
81
|
const indexesOfValue = [];
|
|
150
|
-
let index = initial
|
|
82
|
+
let index = findItemRefIndex(initial, data.value);
|
|
151
83
|
while (index !== -1) {
|
|
152
84
|
indexesOfValue.push(index);
|
|
153
|
-
index = initial
|
|
85
|
+
index = findItemRefIndex(initial, data.value, index + 1);
|
|
154
86
|
}
|
|
155
|
-
return indexesOfValue.
|
|
156
|
-
oid,
|
|
157
|
-
timestamp: getNow(),
|
|
158
|
-
data: {
|
|
159
|
-
op: 'list-insert',
|
|
160
|
-
index,
|
|
161
|
-
value: data.value,
|
|
162
|
-
},
|
|
163
|
-
}));
|
|
87
|
+
return indexesOfValue.flatMap((index) => patchCreator.createListInsert(oid, index, data.value, authz));
|
|
164
88
|
}
|
|
165
89
|
case 'list-add':
|
|
166
90
|
// this one is kind of ambiguous. in theory the set may have
|
|
167
91
|
// already included the value and so no change happened. but
|
|
168
92
|
// basically we infer the intent is to remove what was meant
|
|
169
93
|
// 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
|
-
];
|
|
94
|
+
return patchCreator.createListRemove(oid, data.value, 'last', authz);
|
|
181
95
|
case 'list-set':
|
|
182
|
-
if (initial[data.index]) {
|
|
183
|
-
return [
|
|
184
|
-
{
|
|
185
|
-
oid,
|
|
186
|
-
timestamp: getNow(),
|
|
187
|
-
data: {
|
|
188
|
-
op: 'list-set',
|
|
189
|
-
index: data.index,
|
|
190
|
-
value: initial[data.index],
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
];
|
|
96
|
+
if (initial[data.index] !== undefined) {
|
|
97
|
+
return patchCreator.createListSet(oid, data.index, initial[data.index], authz);
|
|
194
98
|
}
|
|
195
|
-
return
|
|
196
|
-
{
|
|
197
|
-
oid,
|
|
198
|
-
timestamp: getNow(),
|
|
199
|
-
data: {
|
|
200
|
-
op: 'list-delete',
|
|
201
|
-
index: data.index,
|
|
202
|
-
count: 1,
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
];
|
|
99
|
+
return patchCreator.createListDelete(oid, data.index, 1, authz);
|
|
206
100
|
case 'initialize':
|
|
207
|
-
return
|
|
208
|
-
{
|
|
209
|
-
oid,
|
|
210
|
-
timestamp: getNow(),
|
|
211
|
-
data: {
|
|
212
|
-
op: 'delete',
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
];
|
|
101
|
+
return patchCreator.createDelete(oid, authz);
|
|
216
102
|
case 'touch':
|
|
217
103
|
return [];
|
|
218
104
|
default:
|
|
219
105
|
throw new Error(`Cannot undo operation type: ${data.op}`);
|
|
220
106
|
}
|
|
221
107
|
}
|
|
108
|
+
function findItemRefIndex(list, item, from = 0, dir = 'forward') {
|
|
109
|
+
const method = dir === 'forward' ? 'findIndex' : 'findLastIndex';
|
|
110
|
+
// for object items, attempt to find the matching item by OID.
|
|
111
|
+
if (isRef(item)) {
|
|
112
|
+
return list[method]((i, idx) => isRef(i) && i.id === item.id && idx >= from);
|
|
113
|
+
}
|
|
114
|
+
return list[method]((i, idx) => i === item && idx >= from);
|
|
115
|
+
}
|
|
222
116
|
//# sourceMappingURL=undo.js.map
|