@matter/model 0.12.0-alpha.0-20241228-9f74a0273 → 0.12.0-alpha.0-20241231-14ac774ba
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/aspects/Constraint.d.ts +24 -15
- package/dist/cjs/aspects/Constraint.d.ts.map +1 -1
- package/dist/cjs/aspects/Constraint.js +268 -198
- package/dist/cjs/aspects/Constraint.js.map +2 -2
- package/dist/cjs/common/FieldValue.d.ts +10 -4
- package/dist/cjs/common/FieldValue.d.ts.map +1 -1
- package/dist/cjs/common/FieldValue.js +1 -1
- package/dist/cjs/common/FieldValue.js.map +1 -1
- package/dist/cjs/common/Metatype.d.ts +19 -1
- package/dist/cjs/common/Metatype.d.ts.map +1 -1
- package/dist/cjs/common/Metatype.js +171 -170
- package/dist/cjs/common/Metatype.js.map +1 -1
- package/dist/cjs/common/Specification.d.ts +1 -1
- package/dist/cjs/common/Specification.d.ts.map +1 -1
- package/dist/cjs/logic/ModelDiff.d.ts +40 -0
- package/dist/cjs/logic/ModelDiff.d.ts.map +1 -0
- package/dist/cjs/logic/ModelDiff.js +119 -0
- package/dist/cjs/logic/ModelDiff.js.map +6 -0
- package/dist/cjs/logic/definition-validation/ValueValidator.js +1 -1
- package/dist/cjs/logic/definition-validation/ValueValidator.js.map +1 -1
- package/dist/cjs/logic/index.d.ts +1 -0
- package/dist/cjs/logic/index.d.ts.map +1 -1
- package/dist/cjs/logic/index.js +1 -0
- package/dist/cjs/logic/index.js.map +1 -1
- package/dist/cjs/parser/Lexer.d.ts +3 -3
- package/dist/cjs/parser/Lexer.d.ts.map +1 -1
- package/dist/cjs/parser/Lexer.js +35 -31
- package/dist/cjs/parser/Lexer.js.map +1 -1
- package/dist/cjs/parser/Token.d.ts +5 -2
- package/dist/cjs/parser/Token.d.ts.map +1 -1
- package/dist/cjs/parser/TokenStream.js +2 -2
- package/dist/esm/aspects/Constraint.d.ts +24 -15
- package/dist/esm/aspects/Constraint.d.ts.map +1 -1
- package/dist/esm/aspects/Constraint.js +269 -199
- package/dist/esm/aspects/Constraint.js.map +2 -2
- package/dist/esm/common/FieldValue.d.ts +10 -4
- package/dist/esm/common/FieldValue.d.ts.map +1 -1
- package/dist/esm/common/FieldValue.js +1 -1
- package/dist/esm/common/FieldValue.js.map +1 -1
- package/dist/esm/common/Metatype.d.ts +19 -1
- package/dist/esm/common/Metatype.d.ts.map +1 -1
- package/dist/esm/common/Metatype.js +171 -170
- package/dist/esm/common/Metatype.js.map +1 -1
- package/dist/esm/common/Specification.d.ts +1 -1
- package/dist/esm/common/Specification.d.ts.map +1 -1
- package/dist/esm/logic/ModelDiff.d.ts +40 -0
- package/dist/esm/logic/ModelDiff.d.ts.map +1 -0
- package/dist/esm/logic/ModelDiff.js +99 -0
- package/dist/esm/logic/ModelDiff.js.map +6 -0
- package/dist/esm/logic/definition-validation/ValueValidator.js +1 -1
- package/dist/esm/logic/definition-validation/ValueValidator.js.map +1 -1
- package/dist/esm/logic/index.d.ts +1 -0
- package/dist/esm/logic/index.d.ts.map +1 -1
- package/dist/esm/logic/index.js +1 -0
- package/dist/esm/logic/index.js.map +1 -1
- package/dist/esm/parser/Lexer.d.ts +3 -3
- package/dist/esm/parser/Lexer.d.ts.map +1 -1
- package/dist/esm/parser/Lexer.js +35 -31
- package/dist/esm/parser/Lexer.js.map +1 -1
- package/dist/esm/parser/Token.d.ts +5 -2
- package/dist/esm/parser/Token.d.ts.map +1 -1
- package/dist/esm/parser/TokenStream.js +2 -2
- package/package.json +4 -4
- package/src/aspects/Constraint.ts +340 -215
- package/src/common/FieldValue.ts +10 -5
- package/src/common/Metatype.ts +200 -181
- package/src/common/Specification.ts +1 -1
- package/src/logic/ModelDiff.ts +150 -0
- package/src/logic/definition-validation/ValueValidator.ts +1 -1
- package/src/logic/index.ts +1 -0
- package/src/parser/Lexer.ts +38 -40
- package/src/parser/Token.ts +11 -1
- package/src/parser/TokenStream.ts +2 -2
package/src/common/FieldValue.ts
CHANGED
|
@@ -47,6 +47,11 @@ export namespace FieldValue {
|
|
|
47
47
|
export const none = "none";
|
|
48
48
|
export type none = typeof none;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* A field value that allows type extension.
|
|
52
|
+
*/
|
|
53
|
+
export type Open = FieldValue | { type: string };
|
|
54
|
+
|
|
50
55
|
/**
|
|
51
56
|
* If a field value isn't a primitive type, it's an object with a type field indicating one of these types.
|
|
52
57
|
*/
|
|
@@ -55,7 +60,7 @@ export namespace FieldValue {
|
|
|
55
60
|
/**
|
|
56
61
|
* Test for one of the special placeholder types.
|
|
57
62
|
*/
|
|
58
|
-
export function is(value:
|
|
63
|
+
export function is(value: Open | undefined, type: Type) {
|
|
59
64
|
return value && (value as any).type === type;
|
|
60
65
|
}
|
|
61
66
|
|
|
@@ -150,7 +155,7 @@ export namespace FieldValue {
|
|
|
150
155
|
return `${(value as Celsius).value}°C`;
|
|
151
156
|
}
|
|
152
157
|
if (is(value, percent)) {
|
|
153
|
-
return `${(value as Percent).value}
|
|
158
|
+
return `${(value as Percent).value}%`;
|
|
154
159
|
}
|
|
155
160
|
if (is(value, properties)) {
|
|
156
161
|
return stringSerialize((value as Properties).properties) ?? "?";
|
|
@@ -161,7 +166,7 @@ export namespace FieldValue {
|
|
|
161
166
|
/**
|
|
162
167
|
* Given a type name as a hint, do our best to convert a field value to a number.
|
|
163
168
|
*/
|
|
164
|
-
export function numericValue(value:
|
|
169
|
+
export function numericValue(value: Open | undefined, typeName?: string) {
|
|
165
170
|
if (typeof value === "boolean") {
|
|
166
171
|
return value ? 1 : 0;
|
|
167
172
|
}
|
|
@@ -240,7 +245,7 @@ export namespace FieldValue {
|
|
|
240
245
|
/**
|
|
241
246
|
* Get the referenced name if the FieldValue is a reference.
|
|
242
247
|
*/
|
|
243
|
-
export function referenced(value:
|
|
248
|
+
export function referenced(value: Open | undefined) {
|
|
244
249
|
if (is(value, reference)) {
|
|
245
250
|
return (value as Reference).name;
|
|
246
251
|
}
|
|
@@ -254,7 +259,7 @@ export namespace FieldValue {
|
|
|
254
259
|
*
|
|
255
260
|
* @returns the cast value or FieldValue.Invalid if cast is not possible
|
|
256
261
|
*/
|
|
257
|
-
export function cast(type:
|
|
262
|
+
export function cast<const T extends Metatype>(type: T, value: any): FieldValue | FieldValue.Invalid | undefined {
|
|
258
263
|
if (value === undefined || value === null || type === "any") {
|
|
259
264
|
return value;
|
|
260
265
|
}
|
package/src/common/Metatype.ts
CHANGED
|
@@ -74,6 +74,27 @@ export namespace Metatype {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Map metatype value to JS type.
|
|
79
|
+
*/
|
|
80
|
+
export type Native<T> = T extends "boolean"
|
|
81
|
+
? boolean
|
|
82
|
+
: T extends "integer" | "float"
|
|
83
|
+
? number
|
|
84
|
+
: T extends "string"
|
|
85
|
+
? string
|
|
86
|
+
: T extends "bitmap" | "object"
|
|
87
|
+
? Record<string, unknown>
|
|
88
|
+
: T extends "array"
|
|
89
|
+
? unknown[]
|
|
90
|
+
: T extends "bytes"
|
|
91
|
+
? Uint8Array
|
|
92
|
+
: T extends "date"
|
|
93
|
+
? Date
|
|
94
|
+
: T extends "any"
|
|
95
|
+
? unknown
|
|
96
|
+
: never;
|
|
97
|
+
|
|
77
98
|
/**
|
|
78
99
|
* Functions that perform conversion of arbitrary values to a metatype.
|
|
79
100
|
*
|
|
@@ -82,243 +103,241 @@ export namespace Metatype {
|
|
|
82
103
|
*
|
|
83
104
|
* @throws {@link UnsupportedCastError} if the cast is deemed impossible
|
|
84
105
|
*/
|
|
85
|
-
export const
|
|
86
|
-
|
|
106
|
+
export function cast<const T extends `${Metatype}`>(type: T, value: unknown) {
|
|
107
|
+
const caster = cast[type];
|
|
108
|
+
return caster(value) as Native<T>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
cast.any = (value: unknown) => value;
|
|
112
|
+
|
|
113
|
+
cast.boolean = (value: unknown): boolean | null | undefined => {
|
|
114
|
+
if (typeof value === "boolean" || value === null || value === undefined) {
|
|
87
115
|
return value;
|
|
88
|
-
}
|
|
116
|
+
}
|
|
89
117
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
118
|
+
if (typeof value === "string") {
|
|
119
|
+
const normalized = value.toLowerCase().trim();
|
|
120
|
+
switch (normalized) {
|
|
121
|
+
case "":
|
|
122
|
+
case "0":
|
|
123
|
+
case "off":
|
|
124
|
+
case "no":
|
|
125
|
+
case "false":
|
|
126
|
+
return false;
|
|
127
|
+
|
|
128
|
+
case "1":
|
|
129
|
+
case "on":
|
|
130
|
+
case "yes":
|
|
131
|
+
case "true":
|
|
132
|
+
return true;
|
|
93
133
|
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
137
|
+
return !!value;
|
|
138
|
+
}
|
|
94
139
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
case "0":
|
|
100
|
-
case "off":
|
|
101
|
-
case "no":
|
|
102
|
-
case "false":
|
|
103
|
-
return false;
|
|
104
|
-
|
|
105
|
-
case "1":
|
|
106
|
-
case "on":
|
|
107
|
-
case "yes":
|
|
108
|
-
case "true":
|
|
109
|
-
return true;
|
|
140
|
+
if (ArrayBuffer.isView(value)) {
|
|
141
|
+
for (const byte of new Uint8Array(value.buffer)) {
|
|
142
|
+
if (byte) {
|
|
143
|
+
return true;
|
|
110
144
|
}
|
|
111
145
|
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
112
148
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
149
|
+
throw new UnsupportedCastError(`Cannot convert "${value}" to boolean`);
|
|
150
|
+
};
|
|
116
151
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
152
|
+
cast.bitmap = (value: any): number | bigint | Record<string, number> | null | undefined => {
|
|
153
|
+
if (value === null || value === undefined) {
|
|
154
|
+
return value;
|
|
155
|
+
}
|
|
125
156
|
|
|
126
|
-
|
|
127
|
-
|
|
157
|
+
if (typeof value === "string") {
|
|
158
|
+
value = cast.integer(value);
|
|
159
|
+
}
|
|
128
160
|
|
|
129
|
-
|
|
130
|
-
if (value
|
|
161
|
+
if (typeof value === "number") {
|
|
162
|
+
if (Number.isFinite(value)) {
|
|
131
163
|
return value;
|
|
132
164
|
}
|
|
165
|
+
} else if (typeof value === "bigint") {
|
|
166
|
+
return value;
|
|
167
|
+
} else if (isObject(value)) {
|
|
168
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, cast.integer(v)])) as Record<
|
|
169
|
+
string,
|
|
170
|
+
number
|
|
171
|
+
>;
|
|
172
|
+
}
|
|
133
173
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
174
|
+
throw new UnsupportedCastError(`Cannot convert "${value}" to bitmap`);
|
|
175
|
+
};
|
|
137
176
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
} else
|
|
177
|
+
cast.enum = (value: any): number | string | null | undefined => {
|
|
178
|
+
if (typeof value === "string") {
|
|
179
|
+
if (value.trim().match(/^(?:[0-9]+|0x[0-9a-f]+|0b[01]+)$/)) {
|
|
180
|
+
value = Number.parseInt(value);
|
|
181
|
+
} else {
|
|
143
182
|
return value;
|
|
144
|
-
} else if (isObject(value)) {
|
|
145
|
-
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, cast.integer(v)])) as Record<
|
|
146
|
-
string,
|
|
147
|
-
number
|
|
148
|
-
>;
|
|
149
183
|
}
|
|
184
|
+
}
|
|
150
185
|
|
|
151
|
-
|
|
152
|
-
|
|
186
|
+
if (typeof value === "number" && !Number.isNaN(value) && Number.isFinite(value)) {
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
153
189
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (value.trim().match(/^(?:[0-9]+|0x[0-9a-f]+|0b[01]+)$/)) {
|
|
157
|
-
value = Number.parseInt(value);
|
|
158
|
-
} else {
|
|
159
|
-
return value;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
190
|
+
throw new UnsupportedCastError(`Cannot convert "${value}" to an enum value`);
|
|
191
|
+
};
|
|
162
192
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
193
|
+
cast.integer = (value: any): number | bigint | null | undefined => {
|
|
194
|
+
if (value === null || value === undefined) {
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
166
197
|
|
|
167
|
-
|
|
168
|
-
|
|
198
|
+
switch (typeof value) {
|
|
199
|
+
case "number":
|
|
200
|
+
return Math.floor(value);
|
|
169
201
|
|
|
170
|
-
|
|
171
|
-
if (value === null || value === undefined) {
|
|
202
|
+
case "bigint":
|
|
172
203
|
return value;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
switch (typeof value) {
|
|
176
|
-
case "number":
|
|
177
|
-
return Math.floor(value);
|
|
178
|
-
|
|
179
|
-
case "bigint":
|
|
180
|
-
return value;
|
|
181
204
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
case "boolean":
|
|
206
|
+
return value ? 1 : 0;
|
|
207
|
+
}
|
|
185
208
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
209
|
+
if (value instanceof Date) {
|
|
210
|
+
return value.getTime();
|
|
211
|
+
}
|
|
189
212
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
213
|
+
if (typeof value === "string") {
|
|
214
|
+
try {
|
|
215
|
+
const big = BigInt(value);
|
|
216
|
+
const little = Number.parseInt(value);
|
|
217
|
+
if (big === BigInt(little)) {
|
|
218
|
+
return little;
|
|
219
|
+
}
|
|
220
|
+
return big;
|
|
221
|
+
} catch (e) {
|
|
222
|
+
if (!(e instanceof SyntaxError)) {
|
|
223
|
+
throw e;
|
|
202
224
|
}
|
|
203
225
|
}
|
|
226
|
+
}
|
|
204
227
|
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
throw new UnsupportedCastError(`Cannot convert "${value}" to an integer`);
|
|
229
|
+
};
|
|
207
230
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
231
|
+
cast.float = (value: any): number | null | undefined => {
|
|
232
|
+
if (typeof value === "number" || value === null || value === undefined) {
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
212
235
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
236
|
+
if (value instanceof Date) {
|
|
237
|
+
return value.getTime();
|
|
238
|
+
}
|
|
216
239
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
240
|
+
const number = Number(value);
|
|
241
|
+
if (!Number.isNaN(number) && Number.isFinite(value)) {
|
|
242
|
+
return number;
|
|
243
|
+
}
|
|
221
244
|
|
|
222
|
-
|
|
223
|
-
|
|
245
|
+
throw new UnsupportedCastError(`Cannot convert "${value}" to a float`);
|
|
246
|
+
};
|
|
224
247
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
248
|
+
cast.bytes = (value: any): Uint8Array | null | undefined => {
|
|
249
|
+
if (value === undefined || value === null || value instanceof Uint8Array) {
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
229
252
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
253
|
+
if (typeof value === "string") {
|
|
254
|
+
return Bytes.fromHex(value);
|
|
255
|
+
}
|
|
233
256
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
257
|
+
if (typeof value === "boolean") {
|
|
258
|
+
return new Uint8Array([value ? 1 : 0]);
|
|
259
|
+
}
|
|
237
260
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
261
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
262
|
+
return Bytes.fromHex(value.toString(16));
|
|
263
|
+
}
|
|
241
264
|
|
|
242
|
-
|
|
243
|
-
|
|
265
|
+
throw new UnsupportedCastError(`Cannot convert "${value}" to bytes`);
|
|
266
|
+
};
|
|
244
267
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
268
|
+
cast.array = (value: any): Array<unknown> | null | undefined => {
|
|
269
|
+
if (value === undefined || value === null || Array.isArray(value)) {
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
249
272
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
273
|
+
if (typeof value === "string") {
|
|
274
|
+
try {
|
|
275
|
+
const parsed = JSON.parse(value);
|
|
276
|
+
if (Array.isArray(parsed)) {
|
|
277
|
+
return parsed;
|
|
278
|
+
}
|
|
279
|
+
} catch (e) {
|
|
280
|
+
if (!(e instanceof SyntaxError)) {
|
|
281
|
+
throw e;
|
|
260
282
|
}
|
|
261
283
|
}
|
|
284
|
+
}
|
|
262
285
|
|
|
263
|
-
|
|
264
|
-
|
|
286
|
+
throw new UnsupportedCastError(`Cannot convert "${value}" to array`);
|
|
287
|
+
};
|
|
265
288
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
) {
|
|
271
|
-
return value;
|
|
272
|
-
}
|
|
289
|
+
cast.object = (value: any): Record<string, unknown> | null | undefined => {
|
|
290
|
+
if (value === undefined || (typeof value === "object" && !Array.isArray(value) && !(value instanceof Date))) {
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
273
293
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
294
|
+
if (typeof value === "string") {
|
|
295
|
+
try {
|
|
296
|
+
const parsed = JSON.parse(value);
|
|
297
|
+
return parsed;
|
|
298
|
+
} catch (e) {
|
|
299
|
+
if (!(e instanceof SyntaxError)) {
|
|
300
|
+
throw e;
|
|
282
301
|
}
|
|
283
302
|
}
|
|
303
|
+
}
|
|
284
304
|
|
|
285
|
-
|
|
286
|
-
|
|
305
|
+
throw new UnsupportedCastError(`Cannot convert "${value}" to object`);
|
|
306
|
+
};
|
|
287
307
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
308
|
+
cast.string = (value: any): string | null | undefined => {
|
|
309
|
+
if (value === undefined || value === null) {
|
|
310
|
+
return value;
|
|
311
|
+
}
|
|
292
312
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
313
|
+
if (typeof value === "string") {
|
|
314
|
+
return value;
|
|
315
|
+
}
|
|
296
316
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
317
|
+
if (value instanceof Date) {
|
|
318
|
+
return value.toISOString();
|
|
319
|
+
}
|
|
300
320
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
321
|
+
if (typeof value === "object" || Array.isArray(value)) {
|
|
322
|
+
return JSON.stringify(value);
|
|
323
|
+
}
|
|
304
324
|
|
|
305
|
-
|
|
306
|
-
|
|
325
|
+
return value.toString();
|
|
326
|
+
};
|
|
307
327
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
328
|
+
cast.date = (value: any): Date | null | undefined => {
|
|
329
|
+
if (value === undefined || value === null || value instanceof Date) {
|
|
330
|
+
return value;
|
|
331
|
+
}
|
|
312
332
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
333
|
+
if (typeof value === "number" || typeof value === "string") {
|
|
334
|
+
const date = new Date(value);
|
|
335
|
+
if (!Number.isNaN(date.getTime())) {
|
|
336
|
+
return date;
|
|
318
337
|
}
|
|
338
|
+
}
|
|
319
339
|
|
|
320
|
-
|
|
321
|
-
},
|
|
340
|
+
throw new UnexpectedDataError();
|
|
322
341
|
};
|
|
323
342
|
|
|
324
343
|
/**
|
|
@@ -44,7 +44,7 @@ export namespace Specification {
|
|
|
44
44
|
/**
|
|
45
45
|
* Matter specification version.
|
|
46
46
|
*/
|
|
47
|
-
export type Revision = `${number}.${number}`;
|
|
47
|
+
export type Revision = `${number}.${number}` | `${number}.${number}.${number}.${number}`;
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* The default specification revision for Matter.js.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2024 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ElementTag } from "#common/ElementTag.js";
|
|
8
|
+
import { Specification } from "#index.js";
|
|
9
|
+
import { Model } from "#models/Model.js";
|
|
10
|
+
import { Diagnostic } from "@matter/general";
|
|
11
|
+
import { ModelVariantTraversal, VariantDetail } from "./ModelVariantTraversal.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A high level summary of changes between two models.
|
|
15
|
+
*/
|
|
16
|
+
export type ModelDiff = ModelDiff.Add | ModelDiff.Delete | ModelDiff.List | ModelDiff.Summary;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Diff two models.
|
|
20
|
+
*/
|
|
21
|
+
export function ModelDiff(from: Model, to: Model, depth = 2) {
|
|
22
|
+
const traversal = new DiffTraversal(depth);
|
|
23
|
+
return traversal.traverse({ from, to });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class DiffTraversal extends ModelVariantTraversal<ModelDiff | undefined> {
|
|
27
|
+
#detailDepth: number;
|
|
28
|
+
#currentDepth = 0;
|
|
29
|
+
|
|
30
|
+
constructor(depth: number) {
|
|
31
|
+
super(Specification.REVISION, ["from", "to"]);
|
|
32
|
+
this.#detailDepth = depth;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected override visit(variants: VariantDetail, recurse: () => (ModelDiff | undefined)[]): ModelDiff | undefined {
|
|
36
|
+
if (variants.map.to === undefined) {
|
|
37
|
+
if (variants.map.from === undefined) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
kind: "delete",
|
|
42
|
+
tag: variants.tag,
|
|
43
|
+
name: variants.name,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (variants.map.from === undefined) {
|
|
48
|
+
return {
|
|
49
|
+
kind: "add",
|
|
50
|
+
tag: variants.tag,
|
|
51
|
+
name: variants.name,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (this.#currentDepth >= this.#detailDepth) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.#currentDepth++;
|
|
60
|
+
const children = recurse().filter(child => child !== undefined);
|
|
61
|
+
this.#currentDepth--;
|
|
62
|
+
|
|
63
|
+
if (!children.length) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.#currentDepth < this.#detailDepth - 1) {
|
|
68
|
+
return {
|
|
69
|
+
kind: "list",
|
|
70
|
+
tag: variants.tag,
|
|
71
|
+
name: variants.name,
|
|
72
|
+
children,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const changes = {} as ModelDiff.Summary["changes"];
|
|
77
|
+
for (const child of children) {
|
|
78
|
+
changes[child.tag] = (changes[child.tag] ?? 0) + 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
kind: "summary",
|
|
83
|
+
tag: variants.tag,
|
|
84
|
+
name: variants.name,
|
|
85
|
+
changes,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export namespace ModelDiff {
|
|
91
|
+
/**
|
|
92
|
+
* Convert a diff to a diagnostic for serialization.
|
|
93
|
+
*/
|
|
94
|
+
export function diagnosticOf(diff: ModelDiff | undefined): unknown {
|
|
95
|
+
const id = `${diff?.tag}#${diff?.name}`;
|
|
96
|
+
switch (diff?.kind) {
|
|
97
|
+
case "add":
|
|
98
|
+
return Diagnostic.added(id);
|
|
99
|
+
|
|
100
|
+
case "delete":
|
|
101
|
+
return Diagnostic.deleted(id);
|
|
102
|
+
|
|
103
|
+
case "list":
|
|
104
|
+
if (diff.children.length) {
|
|
105
|
+
return [id, Diagnostic.list(diff.children.map(diagnosticOf))];
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case "summary":
|
|
110
|
+
const changes = Object.entries(diff.changes).map(([tag, count]) => {
|
|
111
|
+
if (count < 0) {
|
|
112
|
+
return Diagnostic.deleted(`${-count} ${tag}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (count > 0) {
|
|
116
|
+
return Diagnostic.added(`${count} ${tag}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return Diagnostic.weak(`${0} ${tag}`);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
return [`${id}`, ...changes];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return Diagnostic.weak("(unchanged)");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface Identity {
|
|
129
|
+
name: string;
|
|
130
|
+
tag: ElementTag;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface Add extends Identity {
|
|
134
|
+
kind: "add";
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface Delete extends Identity {
|
|
138
|
+
kind: "delete";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface List extends Identity {
|
|
142
|
+
kind: "list";
|
|
143
|
+
children: ModelDiff[];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface Summary extends Identity {
|
|
147
|
+
kind: "summary";
|
|
148
|
+
changes: Record<ElementTag, number>;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -49,7 +49,7 @@ export class ValueValidator<T extends ValueModel> extends ModelValidator<T> {
|
|
|
49
49
|
private validateAspect(name: string) {
|
|
50
50
|
const aspect = (this.model as any)[name] as Aspect;
|
|
51
51
|
if (aspect?.errors) {
|
|
52
|
-
aspect.errors.forEach((e: DefinitionError) => this.model.error(e.code, e.message));
|
|
52
|
+
aspect.errors.forEach((e: DefinitionError) => this.model.error(e.code, `${e.source}: ${e.message}`));
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|