@verdant-web/common 1.16.2 → 2.0.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 +1 -0
- package/dist/cjs/EventSubscriber.js +3 -0
- package/dist/cjs/EventSubscriber.js.map +1 -1
- package/dist/cjs/batching.d.ts +1 -0
- package/dist/cjs/batching.js +6 -0
- package/dist/cjs/batching.js.map +1 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/memo.d.ts +6 -0
- package/dist/cjs/memo.js +25 -0
- package/dist/cjs/memo.js.map +1 -0
- package/dist/cjs/memo.test.d.ts +1 -0
- package/dist/cjs/memo.test.js +29 -0
- package/dist/cjs/memo.test.js.map +1 -0
- package/dist/cjs/migration.js +4 -0
- package/dist/cjs/migration.js.map +1 -1
- package/dist/cjs/oids.d.ts +1 -0
- package/dist/cjs/oids.js +5 -1
- package/dist/cjs/oids.js.map +1 -1
- package/dist/cjs/operation.d.ts +7 -4
- package/dist/cjs/operation.js +7 -4
- package/dist/cjs/operation.js.map +1 -1
- package/dist/cjs/patch.d.ts +2 -1
- package/dist/cjs/patch.js.map +1 -1
- package/dist/cjs/refs.d.ts +1 -1
- package/dist/cjs/schema/children.d.ts +2 -0
- package/dist/cjs/schema/children.js +24 -0
- package/dist/cjs/schema/children.js.map +1 -0
- package/dist/cjs/schema/fields.d.ts +3 -0
- package/dist/cjs/schema/fields.js +33 -2
- package/dist/cjs/schema/fields.js.map +1 -1
- package/dist/cjs/schema/index.d.ts +1 -0
- package/dist/cjs/schema/index.js +1 -0
- package/dist/cjs/schema/index.js.map +1 -1
- package/dist/cjs/schema/validation.d.ts +14 -3
- package/dist/cjs/schema/validation.js +88 -30
- package/dist/cjs/schema/validation.js.map +1 -1
- package/dist/cjs/timestamp.d.ts +2 -1
- package/dist/cjs/timestamp.js +6 -4
- package/dist/cjs/timestamp.js.map +1 -1
- package/dist/cjs/undo.js +3 -1
- package/dist/cjs/undo.js.map +1 -1
- package/dist/esm/EventSubscriber.d.ts +1 -0
- package/dist/esm/EventSubscriber.js +3 -0
- package/dist/esm/EventSubscriber.js.map +1 -1
- package/dist/esm/batching.d.ts +1 -0
- package/dist/esm/batching.js +6 -0
- package/dist/esm/batching.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/memo.d.ts +6 -0
- package/dist/esm/memo.js +21 -0
- package/dist/esm/memo.js.map +1 -0
- package/dist/esm/memo.test.d.ts +1 -0
- package/dist/esm/memo.test.js +27 -0
- package/dist/esm/memo.test.js.map +1 -0
- package/dist/esm/migration.js +5 -1
- package/dist/esm/migration.js.map +1 -1
- package/dist/esm/oids.d.ts +1 -0
- package/dist/esm/oids.js +3 -0
- package/dist/esm/oids.js.map +1 -1
- package/dist/esm/operation.d.ts +7 -4
- package/dist/esm/operation.js +5 -2
- package/dist/esm/operation.js.map +1 -1
- package/dist/esm/patch.d.ts +2 -1
- package/dist/esm/patch.js.map +1 -1
- package/dist/esm/refs.d.ts +1 -1
- package/dist/esm/schema/children.d.ts +2 -0
- package/dist/esm/schema/children.js +20 -0
- package/dist/esm/schema/children.js.map +1 -0
- package/dist/esm/schema/fields.d.ts +3 -0
- package/dist/esm/schema/fields.js +30 -1
- package/dist/esm/schema/fields.js.map +1 -1
- package/dist/esm/schema/index.d.ts +1 -0
- package/dist/esm/schema/index.js +1 -0
- package/dist/esm/schema/index.js.map +1 -1
- package/dist/esm/schema/validation.d.ts +14 -3
- package/dist/esm/schema/validation.js +88 -30
- package/dist/esm/schema/validation.js.map +1 -1
- package/dist/esm/timestamp.d.ts +2 -1
- package/dist/esm/timestamp.js +4 -3
- package/dist/esm/timestamp.js.map +1 -1
- package/dist/esm/undo.js +3 -1
- package/dist/esm/undo.js.map +1 -1
- package/dist/tsconfig-cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/EventSubscriber.ts +3 -0
- package/src/batching.ts +7 -0
- package/src/index.ts +1 -0
- package/src/memo.test.ts +31 -0
- package/src/memo.ts +25 -0
- package/src/migration.ts +8 -0
- package/src/oids.ts +4 -0
- package/src/operation.ts +14 -5
- package/src/patch.ts +2 -1
- package/src/refs.ts +1 -1
- package/src/schema/children.ts +19 -0
- package/src/schema/fields.ts +27 -1
- package/src/schema/index.ts +1 -0
- package/src/schema/validation.ts +130 -45
- package/src/timestamp.ts +5 -3
- package/src/undo.ts +3 -1
package/src/schema/validation.ts
CHANGED
|
@@ -1,97 +1,182 @@
|
|
|
1
|
-
import { LEGACY_OID_KEY, OID_KEY } from '../oids.js';
|
|
2
1
|
import { isObject } from '../utils.js';
|
|
3
|
-
import {
|
|
2
|
+
import { hasDefault, isNullable } from './fields.js';
|
|
3
|
+
import { StorageFieldSchema, StorageFieldsSchema } from './types.js';
|
|
4
4
|
|
|
5
5
|
export function validateEntity(
|
|
6
|
-
schema:
|
|
6
|
+
schema: StorageFieldsSchema,
|
|
7
7
|
entity: any,
|
|
8
|
-
):
|
|
8
|
+
): EntityValidationProblem | void {
|
|
9
9
|
for (const [key, value] of Object.entries(entity)) {
|
|
10
|
-
if (!schema
|
|
11
|
-
return
|
|
10
|
+
if (!schema[key]) {
|
|
11
|
+
return {
|
|
12
|
+
type: 'invalid-key',
|
|
13
|
+
fieldPath: [key],
|
|
14
|
+
message: `Invalid field "${key}"`,
|
|
15
|
+
};
|
|
12
16
|
}
|
|
13
17
|
if (value) {
|
|
14
|
-
const err = validateEntityField(
|
|
18
|
+
const err = validateEntityField({
|
|
19
|
+
field: schema[key],
|
|
20
|
+
value,
|
|
21
|
+
fieldPath: [key],
|
|
22
|
+
});
|
|
15
23
|
if (err) return err;
|
|
16
24
|
}
|
|
17
25
|
}
|
|
18
26
|
return;
|
|
19
27
|
}
|
|
20
28
|
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
export type EntityValidationProblem = {
|
|
30
|
+
type:
|
|
31
|
+
| 'null'
|
|
32
|
+
| 'no-default'
|
|
33
|
+
| 'invalid-type'
|
|
34
|
+
| 'invalid-value'
|
|
35
|
+
| 'invalid-key';
|
|
36
|
+
fieldPath: (string | number)[];
|
|
37
|
+
message: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export function validateEntityField({
|
|
41
|
+
field,
|
|
42
|
+
value,
|
|
43
|
+
fieldPath = [],
|
|
44
|
+
depth,
|
|
45
|
+
requireDefaults,
|
|
46
|
+
}: {
|
|
47
|
+
field: StorageFieldSchema;
|
|
48
|
+
value: any;
|
|
49
|
+
fieldPath: (string | number)[];
|
|
50
|
+
depth?: number;
|
|
51
|
+
requireDefaults?: boolean;
|
|
52
|
+
}): EntityValidationProblem | undefined {
|
|
53
|
+
if (depth !== undefined && depth <= 0) return;
|
|
54
|
+
|
|
55
|
+
if (isNullable(field) && value === null) return;
|
|
56
|
+
if (value === null) {
|
|
57
|
+
if (requireDefaults || !hasDefault(field)) {
|
|
58
|
+
return {
|
|
59
|
+
type: 'no-default',
|
|
60
|
+
fieldPath,
|
|
61
|
+
message: `Invalid null value for field ${formatField(fieldPath)}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
26
66
|
if (field.type === 'object') {
|
|
27
67
|
if (!isObject(value)) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
68
|
+
return {
|
|
69
|
+
type: 'invalid-type',
|
|
70
|
+
fieldPath,
|
|
71
|
+
message: `Expected object${
|
|
72
|
+
field.nullable ? ' or null' : ''
|
|
73
|
+
} for field ${formatField(fieldPath)}, got ${value}`,
|
|
74
|
+
};
|
|
32
75
|
}
|
|
33
76
|
for (const [key, subField] of Object.entries(field.properties)) {
|
|
34
77
|
if (value[key]) {
|
|
35
|
-
validateEntityField(
|
|
78
|
+
validateEntityField({
|
|
79
|
+
field: subField,
|
|
80
|
+
value: value[key],
|
|
81
|
+
fieldPath: [...fieldPath, key],
|
|
82
|
+
depth: depth !== undefined ? depth - 1 : undefined,
|
|
83
|
+
});
|
|
36
84
|
}
|
|
37
85
|
}
|
|
86
|
+
// check for unexpected keys
|
|
38
87
|
for (const key of Object.keys(value)) {
|
|
39
88
|
if (!field.properties[key]) {
|
|
40
|
-
return
|
|
89
|
+
return {
|
|
90
|
+
type: 'invalid-key',
|
|
91
|
+
fieldPath: [...fieldPath, key],
|
|
92
|
+
message: `Invalid unexpected field "${key}" on value ${formatField(
|
|
93
|
+
fieldPath,
|
|
94
|
+
)}`,
|
|
95
|
+
};
|
|
41
96
|
}
|
|
42
97
|
}
|
|
43
98
|
} else if (field.type === 'array') {
|
|
44
99
|
if (!Array.isArray(value)) {
|
|
45
100
|
if (value === null && field.nullable) return;
|
|
46
|
-
return
|
|
47
|
-
|
|
48
|
-
|
|
101
|
+
return {
|
|
102
|
+
type: 'invalid-value',
|
|
103
|
+
fieldPath,
|
|
104
|
+
message: `Expected array${
|
|
105
|
+
field.nullable ? ' or null' : ''
|
|
106
|
+
} for field ${formatField(fieldPath)}, got ${value}`,
|
|
107
|
+
};
|
|
49
108
|
}
|
|
50
109
|
for (const item of value) {
|
|
51
|
-
validateEntityField(
|
|
110
|
+
validateEntityField({
|
|
111
|
+
field: field.items,
|
|
112
|
+
value: item,
|
|
113
|
+
fieldPath: [...fieldPath, '[]'],
|
|
114
|
+
depth: depth !== undefined ? depth - 1 : undefined,
|
|
115
|
+
});
|
|
52
116
|
}
|
|
53
117
|
} else if (field.type === 'map') {
|
|
54
118
|
if (!isObject(value)) {
|
|
55
|
-
return
|
|
119
|
+
return {
|
|
120
|
+
type: 'invalid-type',
|
|
121
|
+
fieldPath,
|
|
122
|
+
message: `Expected map for field ${formatField(
|
|
123
|
+
fieldPath,
|
|
124
|
+
)}, got ${value}`,
|
|
125
|
+
};
|
|
56
126
|
}
|
|
57
127
|
for (const [key, item] of Object.entries(value)) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
128
|
+
validateEntityField({
|
|
129
|
+
field: field.values,
|
|
130
|
+
value: item,
|
|
131
|
+
fieldPath: [...fieldPath, key],
|
|
132
|
+
depth: depth !== undefined ? depth - 1 : undefined,
|
|
133
|
+
});
|
|
61
134
|
}
|
|
62
135
|
} else if (field.type === 'string') {
|
|
63
136
|
if (typeof value !== 'string') {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
137
|
+
return {
|
|
138
|
+
type: 'invalid-type',
|
|
139
|
+
fieldPath,
|
|
140
|
+
message: `Expected string ${
|
|
141
|
+
field.nullable ? ' or null' : ''
|
|
142
|
+
} for field ${formatField(fieldPath)}, got ${value}`,
|
|
143
|
+
};
|
|
68
144
|
}
|
|
69
145
|
if (field.options && !field.options.includes(value)) {
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
146
|
+
return {
|
|
147
|
+
type: 'invalid-value',
|
|
148
|
+
fieldPath,
|
|
149
|
+
message: `Expected one of ${field.options.join(
|
|
150
|
+
', ',
|
|
151
|
+
)} for field ${formatField(fieldPath)}, got ${value}`,
|
|
152
|
+
};
|
|
73
153
|
}
|
|
74
154
|
} else if (field.type === 'boolean') {
|
|
75
155
|
if (typeof value !== 'boolean') {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
156
|
+
return {
|
|
157
|
+
type: 'invalid-type',
|
|
158
|
+
fieldPath,
|
|
159
|
+
message: `Expected boolean ${
|
|
160
|
+
field.nullable ? ' or null' : ''
|
|
161
|
+
} for field ${formatField(fieldPath)}, got ${value}`,
|
|
162
|
+
};
|
|
80
163
|
}
|
|
81
164
|
} else if (field.type === 'number') {
|
|
82
165
|
if (typeof value !== 'number') {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
166
|
+
return {
|
|
167
|
+
type: 'invalid-type',
|
|
168
|
+
fieldPath,
|
|
169
|
+
message: `Expected number ${
|
|
170
|
+
field.nullable ? ' or null' : ''
|
|
171
|
+
} for field ${formatField(fieldPath)}, got ${value}`,
|
|
172
|
+
};
|
|
87
173
|
}
|
|
88
174
|
} else if (field.type === 'file') {
|
|
89
|
-
|
|
90
|
-
return `Expected file for field ${formatField(fieldPath)}}`;
|
|
91
|
-
}
|
|
175
|
+
// TODO: how?
|
|
92
176
|
}
|
|
93
177
|
}
|
|
94
178
|
|
|
95
|
-
function formatField(fieldPath: string[]) {
|
|
179
|
+
function formatField(fieldPath: (string | number)[]) {
|
|
180
|
+
if (fieldPath.length === 0) return 'root';
|
|
96
181
|
return fieldPath.join('.');
|
|
97
182
|
}
|
package/src/timestamp.ts
CHANGED
|
@@ -84,9 +84,7 @@ export class HybridLogicalClockTimestampProvider implements TimestampProvider {
|
|
|
84
84
|
})
|
|
85
85
|
);
|
|
86
86
|
};
|
|
87
|
-
getWallClockTime =
|
|
88
|
-
return deserializeHlcTimestamp(timestamp.slice(VERSION_BLOCK_LENGTH)).time;
|
|
89
|
-
};
|
|
87
|
+
getWallClockTime = getWallClockTime;
|
|
90
88
|
}
|
|
91
89
|
|
|
92
90
|
class ClockDriftError extends Error {
|
|
@@ -290,3 +288,7 @@ export function getTimestampSchemaVersion(timestamp: string): number {
|
|
|
290
288
|
export function compareTimestampSchemaVersions(a: string, b: string) {
|
|
291
289
|
return getTimestampSchemaVersion(a) - getTimestampSchemaVersion(b);
|
|
292
290
|
}
|
|
291
|
+
|
|
292
|
+
export function getWallClockTime(timestamp: string): number {
|
|
293
|
+
return deserializeHlcTimestamp(timestamp.slice(VERSION_BLOCK_LENGTH)).time;
|
|
294
|
+
}
|
package/src/undo.ts
CHANGED
|
@@ -8,7 +8,7 @@ export function getUndoOperations(
|
|
|
8
8
|
operations: Operation[],
|
|
9
9
|
getNow: () => string,
|
|
10
10
|
): Operation[] {
|
|
11
|
-
if (initial === undefined) {
|
|
11
|
+
if (initial === undefined || initial === null) {
|
|
12
12
|
// if the initial state is nothing, then the undo is to delete everything.
|
|
13
13
|
// there's nothing else to worry about!
|
|
14
14
|
return [
|
|
@@ -199,6 +199,8 @@ function getUndoOperation(
|
|
|
199
199
|
},
|
|
200
200
|
},
|
|
201
201
|
];
|
|
202
|
+
case 'touch':
|
|
203
|
+
return [];
|
|
202
204
|
default:
|
|
203
205
|
throw new Error(`Cannot undo operation type: ${(data as any).op}`);
|
|
204
206
|
}
|