@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/patch.ts
CHANGED
|
@@ -3,22 +3,24 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { AuthorizationKey } from './authz.js';
|
|
6
|
+
import { diffToPatches } from './diffing.js';
|
|
6
7
|
import { FileRef } from './files.js';
|
|
7
8
|
import { createRef, createSubOid, ObjectIdentifier } from './oids.js';
|
|
8
9
|
import {
|
|
9
|
-
diffToPatches,
|
|
10
10
|
initialToPatches,
|
|
11
11
|
ObjectRef,
|
|
12
12
|
Operation,
|
|
13
13
|
PropertyName,
|
|
14
|
+
PropertyValue,
|
|
15
|
+
shallowInitialToPatches,
|
|
14
16
|
} from './operation.js';
|
|
15
17
|
import { isRef } from './refs.js';
|
|
16
|
-
import { isObject } from './utils.js';
|
|
18
|
+
import { assert, isObject } from './utils.js';
|
|
17
19
|
|
|
18
20
|
export class PatchCreator {
|
|
19
21
|
constructor(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
readonly getNow: () => string,
|
|
23
|
+
readonly createSubId?: () => string,
|
|
22
24
|
) {}
|
|
23
25
|
|
|
24
26
|
isPrimitive = (value: any) => {
|
|
@@ -28,7 +30,13 @@ export class PatchCreator {
|
|
|
28
30
|
createDiff = (
|
|
29
31
|
from: any,
|
|
30
32
|
to: any,
|
|
31
|
-
options: {
|
|
33
|
+
options: {
|
|
34
|
+
mergeUnknownObjects?: boolean;
|
|
35
|
+
/** @deprecated use the equivalent 'merge' */
|
|
36
|
+
defaultUndefined?: boolean;
|
|
37
|
+
merge?: boolean;
|
|
38
|
+
authz?: AuthorizationKey;
|
|
39
|
+
} = {},
|
|
32
40
|
) => {
|
|
33
41
|
return diffToPatches(from, to, this.getNow, this.createSubId, [], options);
|
|
34
42
|
};
|
|
@@ -37,7 +45,17 @@ export class PatchCreator {
|
|
|
37
45
|
obj: any,
|
|
38
46
|
oid: ObjectIdentifier,
|
|
39
47
|
authz?: AuthorizationKey,
|
|
48
|
+
shallow?: boolean,
|
|
40
49
|
) => {
|
|
50
|
+
if (shallow) {
|
|
51
|
+
return shallowInitialToPatches(
|
|
52
|
+
obj,
|
|
53
|
+
oid,
|
|
54
|
+
this.getNow,
|
|
55
|
+
undefined,
|
|
56
|
+
authz ? { authz } : undefined,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
41
59
|
return initialToPatches(
|
|
42
60
|
obj,
|
|
43
61
|
oid,
|
|
@@ -52,6 +70,7 @@ export class PatchCreator {
|
|
|
52
70
|
oid: ObjectIdentifier,
|
|
53
71
|
key: PropertyName,
|
|
54
72
|
value: any,
|
|
73
|
+
authz?: AuthorizationKey,
|
|
55
74
|
): Operation[] => {
|
|
56
75
|
// incoming value must be normalized. if it's not a primitive, it and all sub-objects
|
|
57
76
|
// must be created
|
|
@@ -65,6 +84,7 @@ export class PatchCreator {
|
|
|
65
84
|
name: key,
|
|
66
85
|
value,
|
|
67
86
|
},
|
|
87
|
+
authz,
|
|
68
88
|
},
|
|
69
89
|
];
|
|
70
90
|
} else {
|
|
@@ -72,7 +92,16 @@ export class PatchCreator {
|
|
|
72
92
|
return [
|
|
73
93
|
// since we're setting a complex nested object, we can initialize it wholesale.
|
|
74
94
|
// no diffing to do.
|
|
75
|
-
...initialToPatches(
|
|
95
|
+
...initialToPatches(
|
|
96
|
+
value,
|
|
97
|
+
itemOid,
|
|
98
|
+
this.getNow,
|
|
99
|
+
this.createSubId,
|
|
100
|
+
undefined,
|
|
101
|
+
{
|
|
102
|
+
authz,
|
|
103
|
+
},
|
|
104
|
+
),
|
|
76
105
|
// then set the reference to the object
|
|
77
106
|
{
|
|
78
107
|
oid,
|
|
@@ -82,12 +111,17 @@ export class PatchCreator {
|
|
|
82
111
|
value: createRef(itemOid),
|
|
83
112
|
name: key,
|
|
84
113
|
},
|
|
114
|
+
authz,
|
|
85
115
|
},
|
|
86
116
|
];
|
|
87
117
|
}
|
|
88
118
|
};
|
|
89
119
|
|
|
90
|
-
createRemove = (
|
|
120
|
+
createRemove = (
|
|
121
|
+
oid: ObjectIdentifier,
|
|
122
|
+
key: PropertyName,
|
|
123
|
+
authz?: AuthorizationKey,
|
|
124
|
+
): Operation[] => {
|
|
91
125
|
return [
|
|
92
126
|
{
|
|
93
127
|
oid,
|
|
@@ -96,6 +130,7 @@ export class PatchCreator {
|
|
|
96
130
|
op: 'remove',
|
|
97
131
|
name: key,
|
|
98
132
|
},
|
|
133
|
+
authz,
|
|
99
134
|
},
|
|
100
135
|
];
|
|
101
136
|
};
|
|
@@ -104,7 +139,9 @@ export class PatchCreator {
|
|
|
104
139
|
oid: ObjectIdentifier,
|
|
105
140
|
index: number,
|
|
106
141
|
value: any,
|
|
142
|
+
authz?: AuthorizationKey,
|
|
107
143
|
): Operation[] => {
|
|
144
|
+
assert(index >= 0, 'List index must be non-negative');
|
|
108
145
|
if (this.isPrimitive(value)) {
|
|
109
146
|
return [
|
|
110
147
|
{
|
|
@@ -115,12 +152,22 @@ export class PatchCreator {
|
|
|
115
152
|
index,
|
|
116
153
|
value,
|
|
117
154
|
},
|
|
155
|
+
authz,
|
|
118
156
|
},
|
|
119
157
|
];
|
|
120
158
|
} else {
|
|
121
159
|
const itemOid = createSubOid(oid, this.createSubId);
|
|
122
160
|
return [
|
|
123
|
-
...initialToPatches(
|
|
161
|
+
...initialToPatches(
|
|
162
|
+
value,
|
|
163
|
+
itemOid,
|
|
164
|
+
this.getNow,
|
|
165
|
+
this.createSubId,
|
|
166
|
+
undefined,
|
|
167
|
+
{
|
|
168
|
+
authz,
|
|
169
|
+
},
|
|
170
|
+
),
|
|
124
171
|
{
|
|
125
172
|
oid,
|
|
126
173
|
timestamp: this.getNow(),
|
|
@@ -129,12 +176,17 @@ export class PatchCreator {
|
|
|
129
176
|
index,
|
|
130
177
|
value: createRef(itemOid),
|
|
131
178
|
},
|
|
179
|
+
authz,
|
|
132
180
|
},
|
|
133
181
|
];
|
|
134
182
|
}
|
|
135
183
|
};
|
|
136
184
|
|
|
137
|
-
createListPush = (
|
|
185
|
+
createListPush = (
|
|
186
|
+
oid: ObjectIdentifier,
|
|
187
|
+
value: any,
|
|
188
|
+
authz?: AuthorizationKey,
|
|
189
|
+
): Operation[] => {
|
|
138
190
|
if (this.isPrimitive(value)) {
|
|
139
191
|
return [
|
|
140
192
|
{
|
|
@@ -144,12 +196,15 @@ export class PatchCreator {
|
|
|
144
196
|
op: 'list-push',
|
|
145
197
|
value,
|
|
146
198
|
},
|
|
199
|
+
authz,
|
|
147
200
|
},
|
|
148
201
|
];
|
|
149
202
|
} else {
|
|
150
203
|
const itemOid = createSubOid(oid, this.createSubId);
|
|
151
204
|
return [
|
|
152
|
-
...initialToPatches(value, itemOid, this.getNow
|
|
205
|
+
...initialToPatches(value, itemOid, this.getNow, undefined, undefined, {
|
|
206
|
+
authz,
|
|
207
|
+
}),
|
|
153
208
|
{
|
|
154
209
|
oid,
|
|
155
210
|
timestamp: this.getNow(),
|
|
@@ -157,12 +212,17 @@ export class PatchCreator {
|
|
|
157
212
|
op: 'list-push',
|
|
158
213
|
value: createRef(itemOid),
|
|
159
214
|
},
|
|
215
|
+
authz,
|
|
160
216
|
},
|
|
161
217
|
];
|
|
162
218
|
}
|
|
163
219
|
};
|
|
164
220
|
|
|
165
|
-
createListAdd = (
|
|
221
|
+
createListAdd = (
|
|
222
|
+
oid: ObjectIdentifier,
|
|
223
|
+
value: any,
|
|
224
|
+
authz?: AuthorizationKey,
|
|
225
|
+
): Operation[] => {
|
|
166
226
|
if (!this.isPrimitive(value)) {
|
|
167
227
|
return [
|
|
168
228
|
{
|
|
@@ -172,6 +232,7 @@ export class PatchCreator {
|
|
|
172
232
|
op: 'list-add',
|
|
173
233
|
value: createRef(value),
|
|
174
234
|
},
|
|
235
|
+
authz,
|
|
175
236
|
},
|
|
176
237
|
];
|
|
177
238
|
} else {
|
|
@@ -183,6 +244,7 @@ export class PatchCreator {
|
|
|
183
244
|
op: 'list-add',
|
|
184
245
|
value,
|
|
185
246
|
},
|
|
247
|
+
authz,
|
|
186
248
|
},
|
|
187
249
|
];
|
|
188
250
|
}
|
|
@@ -192,7 +254,9 @@ export class PatchCreator {
|
|
|
192
254
|
oid: ObjectIdentifier,
|
|
193
255
|
index: number,
|
|
194
256
|
value: any,
|
|
257
|
+
authz?: AuthorizationKey,
|
|
195
258
|
): Operation[] => {
|
|
259
|
+
assert(index >= 0, 'List index must be non-negative');
|
|
196
260
|
if (this.isPrimitive(value)) {
|
|
197
261
|
return [
|
|
198
262
|
{
|
|
@@ -203,12 +267,24 @@ export class PatchCreator {
|
|
|
203
267
|
value,
|
|
204
268
|
index,
|
|
205
269
|
},
|
|
270
|
+
authz,
|
|
206
271
|
},
|
|
207
272
|
];
|
|
208
273
|
} else {
|
|
209
274
|
const itemOid = createSubOid(oid, this.createSubId);
|
|
210
275
|
return [
|
|
211
|
-
...initialToPatches(
|
|
276
|
+
...initialToPatches(
|
|
277
|
+
value,
|
|
278
|
+
itemOid,
|
|
279
|
+
this.getNow,
|
|
280
|
+
this.createSubId,
|
|
281
|
+
undefined,
|
|
282
|
+
authz
|
|
283
|
+
? {
|
|
284
|
+
authz,
|
|
285
|
+
}
|
|
286
|
+
: undefined,
|
|
287
|
+
),
|
|
212
288
|
{
|
|
213
289
|
oid,
|
|
214
290
|
timestamp: this.getNow(),
|
|
@@ -217,15 +293,55 @@ export class PatchCreator {
|
|
|
217
293
|
value: createRef(itemOid),
|
|
218
294
|
index,
|
|
219
295
|
},
|
|
296
|
+
authz,
|
|
220
297
|
},
|
|
221
298
|
];
|
|
222
299
|
}
|
|
223
300
|
};
|
|
224
301
|
|
|
302
|
+
createListInsertMany = (
|
|
303
|
+
oid: ObjectIdentifier,
|
|
304
|
+
index: number,
|
|
305
|
+
values: any[],
|
|
306
|
+
authz?: AuthorizationKey,
|
|
307
|
+
): Operation[] => {
|
|
308
|
+
assert(index >= 0, 'List index must be non-negative');
|
|
309
|
+
const operations: Operation[] = [];
|
|
310
|
+
const refs = values.map((value) => {
|
|
311
|
+
if (this.isPrimitive(value)) return value;
|
|
312
|
+
const subOid = createSubOid(oid, this.createSubId);
|
|
313
|
+
operations.push(
|
|
314
|
+
...initialToPatches(
|
|
315
|
+
value,
|
|
316
|
+
subOid,
|
|
317
|
+
this.getNow,
|
|
318
|
+
this.createSubId,
|
|
319
|
+
undefined,
|
|
320
|
+
{
|
|
321
|
+
authz,
|
|
322
|
+
},
|
|
323
|
+
),
|
|
324
|
+
);
|
|
325
|
+
return createRef(subOid);
|
|
326
|
+
});
|
|
327
|
+
operations.push({
|
|
328
|
+
oid,
|
|
329
|
+
timestamp: this.getNow(),
|
|
330
|
+
data: {
|
|
331
|
+
op: 'list-insert',
|
|
332
|
+
values: refs,
|
|
333
|
+
index,
|
|
334
|
+
},
|
|
335
|
+
authz,
|
|
336
|
+
});
|
|
337
|
+
return operations;
|
|
338
|
+
};
|
|
339
|
+
|
|
225
340
|
createListRemove = (
|
|
226
341
|
oid: ObjectIdentifier,
|
|
227
|
-
value:
|
|
342
|
+
value: PropertyValue,
|
|
228
343
|
only?: 'first' | 'last',
|
|
344
|
+
authz?: AuthorizationKey,
|
|
229
345
|
): Operation[] => {
|
|
230
346
|
return [
|
|
231
347
|
{
|
|
@@ -236,6 +352,7 @@ export class PatchCreator {
|
|
|
236
352
|
value,
|
|
237
353
|
only,
|
|
238
354
|
},
|
|
355
|
+
authz,
|
|
239
356
|
},
|
|
240
357
|
];
|
|
241
358
|
};
|
|
@@ -244,7 +361,10 @@ export class PatchCreator {
|
|
|
244
361
|
oid: ObjectIdentifier,
|
|
245
362
|
index: number,
|
|
246
363
|
count: number = 1,
|
|
364
|
+
authz?: AuthorizationKey,
|
|
247
365
|
): Operation[] => {
|
|
366
|
+
assert(index >= 0, 'List index must be non-negative');
|
|
367
|
+
assert(count > 0, 'Count must be positive and non-zero');
|
|
248
368
|
return [
|
|
249
369
|
{
|
|
250
370
|
oid,
|
|
@@ -254,6 +374,7 @@ export class PatchCreator {
|
|
|
254
374
|
index,
|
|
255
375
|
count,
|
|
256
376
|
},
|
|
377
|
+
authz,
|
|
257
378
|
},
|
|
258
379
|
];
|
|
259
380
|
};
|
|
@@ -262,7 +383,9 @@ export class PatchCreator {
|
|
|
262
383
|
oid: ObjectIdentifier,
|
|
263
384
|
value: ObjectRef | FileRef,
|
|
264
385
|
index: number,
|
|
386
|
+
authz?: AuthorizationKey,
|
|
265
387
|
): Operation[] => {
|
|
388
|
+
assert(index >= 0, 'List index must be non-negative');
|
|
266
389
|
return [
|
|
267
390
|
{
|
|
268
391
|
oid,
|
|
@@ -272,6 +395,7 @@ export class PatchCreator {
|
|
|
272
395
|
value,
|
|
273
396
|
index,
|
|
274
397
|
},
|
|
398
|
+
authz,
|
|
275
399
|
},
|
|
276
400
|
];
|
|
277
401
|
};
|
|
@@ -280,7 +404,10 @@ export class PatchCreator {
|
|
|
280
404
|
oid: ObjectIdentifier,
|
|
281
405
|
fromIndex: number,
|
|
282
406
|
toIndex: number,
|
|
407
|
+
authz?: AuthorizationKey,
|
|
283
408
|
): Operation[] => {
|
|
409
|
+
assert(fromIndex >= 0, 'List move from index must be non-negative');
|
|
410
|
+
assert(toIndex >= 0, 'List move to index must be non-negative');
|
|
284
411
|
return [
|
|
285
412
|
{
|
|
286
413
|
oid,
|
|
@@ -290,11 +417,15 @@ export class PatchCreator {
|
|
|
290
417
|
from: fromIndex,
|
|
291
418
|
to: toIndex,
|
|
292
419
|
},
|
|
420
|
+
authz,
|
|
293
421
|
},
|
|
294
422
|
];
|
|
295
423
|
};
|
|
296
424
|
|
|
297
|
-
createDelete = (
|
|
425
|
+
createDelete = (
|
|
426
|
+
oid: ObjectIdentifier,
|
|
427
|
+
authz?: AuthorizationKey,
|
|
428
|
+
): Operation[] => {
|
|
298
429
|
return [
|
|
299
430
|
{
|
|
300
431
|
oid,
|
|
@@ -302,6 +433,7 @@ export class PatchCreator {
|
|
|
302
433
|
data: {
|
|
303
434
|
op: 'delete',
|
|
304
435
|
},
|
|
436
|
+
authz,
|
|
305
437
|
},
|
|
306
438
|
];
|
|
307
439
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
|
|
6
|
+
describe('validation', () => {
|
|
7
|
+
it('validates shallowly with refs', () => {
|
|
8
|
+
const fieldSchema = schema.fields.object({
|
|
9
|
+
fields: {
|
|
10
|
+
obj: schema.fields.object({
|
|
11
|
+
fields: {},
|
|
12
|
+
}),
|
|
13
|
+
file: schema.fields.file(),
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
expect(
|
|
17
|
+
validateEntityField({
|
|
18
|
+
field: fieldSchema,
|
|
19
|
+
value: {
|
|
20
|
+
obj: createRef('some/id:1'),
|
|
21
|
+
file: createFileRef('somefile'),
|
|
22
|
+
},
|
|
23
|
+
fieldPath: [],
|
|
24
|
+
expectRefs: true,
|
|
25
|
+
}),
|
|
26
|
+
).toBeUndefined();
|
|
27
|
+
expect(
|
|
28
|
+
validateEntityField({
|
|
29
|
+
field: fieldSchema,
|
|
30
|
+
value: {
|
|
31
|
+
obj: createRef('some/id:1'),
|
|
32
|
+
file: createRef('some/object:1'),
|
|
33
|
+
},
|
|
34
|
+
fieldPath: [],
|
|
35
|
+
expectRefs: true,
|
|
36
|
+
}),
|
|
37
|
+
).toMatchInlineSnapshot(`
|
|
38
|
+
{
|
|
39
|
+
"fieldPath": [
|
|
40
|
+
"file",
|
|
41
|
+
],
|
|
42
|
+
"message": "Expected file ref for field file, got {"@@type":"ref","id":"some/object:1"}",
|
|
43
|
+
"type": "invalid-type",
|
|
44
|
+
}
|
|
45
|
+
`);
|
|
46
|
+
expect(
|
|
47
|
+
validateEntityField({
|
|
48
|
+
field: fieldSchema,
|
|
49
|
+
value: {
|
|
50
|
+
obj: 'foo',
|
|
51
|
+
file: createFileRef('somefile'),
|
|
52
|
+
},
|
|
53
|
+
fieldPath: [],
|
|
54
|
+
expectRefs: true,
|
|
55
|
+
}),
|
|
56
|
+
).toMatchInlineSnapshot(`
|
|
57
|
+
{
|
|
58
|
+
"fieldPath": [
|
|
59
|
+
"obj",
|
|
60
|
+
],
|
|
61
|
+
"message": "Expected ref for field obj, got "foo"",
|
|
62
|
+
"type": "invalid-type",
|
|
63
|
+
}
|
|
64
|
+
`);
|
|
65
|
+
});
|
|
66
|
+
});
|