@mica-js/sdk 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +311 -2
- package/dist/build/cli.d.ts +22 -0
- package/dist/build/cli.js +211 -0
- package/dist/build/config.d.ts +16 -0
- package/dist/build/config.js +92 -0
- package/dist/build/content.d.ts +14 -0
- package/dist/build/content.js +159 -0
- package/dist/build/prepare.d.ts +51 -0
- package/dist/build/prepare.js +306 -0
- package/dist/build/project.d.ts +4 -0
- package/dist/build/project.js +30 -0
- package/dist/build/vite.d.ts +7 -0
- package/dist/build/vite.js +60 -0
- package/dist/ddl.d.ts +2 -0
- package/dist/ddl.js +1 -0
- package/dist/device.d.ts +1 -0
- package/dist/device.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/metadata/index.d.ts +5 -0
- package/dist/metadata/index.js +2 -0
- package/dist/metadata/schema.d.ts +10 -0
- package/dist/metadata/schema.js +392 -0
- package/dist/metadata/types.d.ts +192 -0
- package/dist/metadata/types.js +7 -0
- package/dist/metadata/validate.d.ts +25 -0
- package/dist/metadata/validate.js +283 -0
- package/dist/model.d.ts +1 -0
- package/dist/model.js +1 -0
- package/dist/widget.d.ts +1 -0
- package/dist/widget.js +1 -0
- package/package.json +74 -3
- package/templates/basic/mica.config.ts +3 -0
- package/templates/basic/package.json +14 -0
- package/templates/basic/src/devices/resistor/device.json +30 -0
- package/templates/basic/src/devices/resistor/index.ts +8 -0
- package/templates/basic/src/devices/resistor/model.ddl +6 -0
- package/templates/basic/src/widgets/lamp/index.ts +22 -0
- package/templates/basic/src/widgets/lamp/widget.json +9 -0
- package/templates/basic/tsconfig.json +14 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
const DRAFT_2020_12 = 'https://json-schema.org/draft/2020-12/schema';
|
|
2
|
+
const PROPERTY_TYPES = ['number', 'boolean', 'string', 'enum', 'bytes'];
|
|
3
|
+
const PORT_PAYLOAD_TYPES = [
|
|
4
|
+
'boolean',
|
|
5
|
+
'number',
|
|
6
|
+
'string',
|
|
7
|
+
'vec2d',
|
|
8
|
+
'vec3d',
|
|
9
|
+
'bytes',
|
|
10
|
+
'bytes<rgb565>',
|
|
11
|
+
'bytes<segments>',
|
|
12
|
+
];
|
|
13
|
+
const PORT_DIRECTIONS = ['input', 'output'];
|
|
14
|
+
const PORT_DELIVERIES = ['level', 'message'];
|
|
15
|
+
const GRAPHIC_FILLS = ['none', 'foreground', 'background'];
|
|
16
|
+
const TEXT_ALIGNS = ['left', 'center', 'right'];
|
|
17
|
+
const SYMBOL_PIN_DIRECTIONS = ['left', 'right', 'up', 'down'];
|
|
18
|
+
const EDITOR_TYPES = ['number', 'text', 'textarea', 'checkbox', 'file', 'select'];
|
|
19
|
+
const GRAPHIC_TYPES = ['line', 'polyline', 'rect', 'circle', 'arc', 'text'];
|
|
20
|
+
function stringEnum(values) {
|
|
21
|
+
return { type: 'string', enum: [...values] };
|
|
22
|
+
}
|
|
23
|
+
function objectSchema(properties, required = []) {
|
|
24
|
+
return {
|
|
25
|
+
type: 'object',
|
|
26
|
+
additionalProperties: false,
|
|
27
|
+
...(required.length === 0 ? {} : { required: [...required] }),
|
|
28
|
+
properties,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function stringMap(values, description) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'object',
|
|
34
|
+
description,
|
|
35
|
+
propertyNames: { minLength: 1 },
|
|
36
|
+
additionalProperties: values,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Applies `then` only when the sibling discriminator field equals `value`. */
|
|
40
|
+
function whenEquals(field, value, then) {
|
|
41
|
+
return {
|
|
42
|
+
if: { required: [field], properties: { [field]: { const: value } } },
|
|
43
|
+
then,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function whenType(typeName, then) {
|
|
47
|
+
return whenEquals('type', typeName, then);
|
|
48
|
+
}
|
|
49
|
+
function pointSchema() {
|
|
50
|
+
return {
|
|
51
|
+
type: 'array',
|
|
52
|
+
description: 'A point expressed as [x, y].',
|
|
53
|
+
prefixItems: [{ type: 'number' }, { type: 'number' }],
|
|
54
|
+
items: false,
|
|
55
|
+
minItems: 2,
|
|
56
|
+
maxItems: 2,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function propertySchema() {
|
|
60
|
+
return {
|
|
61
|
+
type: 'object',
|
|
62
|
+
description: 'A persistent configuration value.',
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
required: ['type', 'required'],
|
|
65
|
+
properties: {
|
|
66
|
+
type: stringEnum(PROPERTY_TYPES),
|
|
67
|
+
required: { type: 'boolean' },
|
|
68
|
+
nullable: { type: 'boolean' },
|
|
69
|
+
default: { type: ['number', 'boolean', 'string', 'null'] },
|
|
70
|
+
title: { type: 'string' },
|
|
71
|
+
description: { type: 'string' },
|
|
72
|
+
unit: { type: 'string' },
|
|
73
|
+
group: { type: 'string' },
|
|
74
|
+
order: { type: 'integer', minimum: 0 },
|
|
75
|
+
editor: { $ref: '#/$defs/editor' },
|
|
76
|
+
enumName: { type: 'string', minLength: 1 },
|
|
77
|
+
values: {
|
|
78
|
+
type: 'array',
|
|
79
|
+
minItems: 1,
|
|
80
|
+
uniqueItems: true,
|
|
81
|
+
items: { type: 'string', minLength: 1 },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
allOf: [
|
|
85
|
+
{
|
|
86
|
+
$comment: 'A required value has no default.',
|
|
87
|
+
if: { required: ['required'], properties: { required: { const: true } } },
|
|
88
|
+
then: { properties: { default: false } },
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
$comment: 'Bytes defaults need an encoding that is not decided yet.',
|
|
92
|
+
if: { required: ['type'], properties: { type: { const: 'bytes' } } },
|
|
93
|
+
then: { properties: { default: false } },
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
$comment: 'An explicit null default requires nullable: true.',
|
|
97
|
+
if: {
|
|
98
|
+
required: ['default'],
|
|
99
|
+
properties: { default: { const: null }, type: { not: { const: 'bytes' } } },
|
|
100
|
+
},
|
|
101
|
+
then: { required: ['nullable'], properties: { nullable: { const: true } } },
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
$comment: 'Default values must match the declared type; membership in enum values is checked in code.',
|
|
105
|
+
if: { required: ['type'], properties: { type: { const: 'number' } } },
|
|
106
|
+
then: { properties: { default: { type: ['number', 'null'] } } },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
if: { required: ['type'], properties: { type: { const: 'boolean' } } },
|
|
110
|
+
then: { properties: { default: { type: ['boolean', 'null'] } } },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
if: { required: ['type'], properties: { type: { const: 'string' } } },
|
|
114
|
+
then: { properties: { default: { type: ['string', 'null'] } } },
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
if: { required: ['type'], properties: { type: { const: 'enum' } } },
|
|
118
|
+
then: {
|
|
119
|
+
required: ['enumName', 'values'],
|
|
120
|
+
properties: {
|
|
121
|
+
default: { type: ['string', 'null'] },
|
|
122
|
+
enumName: { type: 'string', minLength: 1 },
|
|
123
|
+
values: { type: 'array' },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
$comment: 'Only enum properties carry an enum name and a value list.',
|
|
129
|
+
if: { required: ['type'], properties: { type: { not: { const: 'enum' } } } },
|
|
130
|
+
then: { properties: { enumName: false, values: false } },
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function editorSchema() {
|
|
136
|
+
return {
|
|
137
|
+
type: 'object',
|
|
138
|
+
description: 'Editor hints for a property value.',
|
|
139
|
+
required: ['kind'],
|
|
140
|
+
properties: { kind: stringEnum(EDITOR_TYPES) },
|
|
141
|
+
allOf: [
|
|
142
|
+
whenEquals('kind', 'number', objectSchema({
|
|
143
|
+
kind: { type: 'string', const: 'number' },
|
|
144
|
+
min: { type: 'number' },
|
|
145
|
+
max: { type: 'number' },
|
|
146
|
+
step: { type: 'number', exclusiveMinimum: 0 },
|
|
147
|
+
})),
|
|
148
|
+
whenEquals('kind', 'text', objectSchema({ kind: { type: 'string', const: 'text' } })),
|
|
149
|
+
whenEquals('kind', 'textarea', objectSchema({ kind: { type: 'string', const: 'textarea' } })),
|
|
150
|
+
whenEquals('kind', 'checkbox', objectSchema({ kind: { type: 'string', const: 'checkbox' } })),
|
|
151
|
+
whenEquals('kind', 'file', objectSchema({
|
|
152
|
+
kind: { type: 'string', const: 'file' },
|
|
153
|
+
extensions: {
|
|
154
|
+
type: 'array',
|
|
155
|
+
description: 'Allowed dotted filename extensions, matched case-insensitively by the host.',
|
|
156
|
+
uniqueItems: true,
|
|
157
|
+
items: { type: 'string', pattern: '^\\.[A-Za-z0-9]+(?:[.-][A-Za-z0-9]+)*$' },
|
|
158
|
+
},
|
|
159
|
+
maxSize: {
|
|
160
|
+
type: 'integer',
|
|
161
|
+
exclusiveMinimum: 0,
|
|
162
|
+
description: 'Maximum file size in bytes.',
|
|
163
|
+
},
|
|
164
|
+
})),
|
|
165
|
+
whenEquals('kind', 'select', objectSchema({
|
|
166
|
+
kind: { type: 'string', const: 'select' },
|
|
167
|
+
labels: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
description: 'Option value to display label.',
|
|
170
|
+
propertyNames: { minLength: 1 },
|
|
171
|
+
additionalProperties: { type: 'string' },
|
|
172
|
+
},
|
|
173
|
+
}, ['labels'])),
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function portSchema() {
|
|
178
|
+
return {
|
|
179
|
+
type: 'object',
|
|
180
|
+
description: 'A topic port.',
|
|
181
|
+
additionalProperties: false,
|
|
182
|
+
required: ['direction', 'type'],
|
|
183
|
+
properties: {
|
|
184
|
+
title: { type: 'string' },
|
|
185
|
+
description: { type: 'string' },
|
|
186
|
+
direction: stringEnum(PORT_DIRECTIONS),
|
|
187
|
+
type: stringEnum(PORT_PAYLOAD_TYPES),
|
|
188
|
+
delivery: stringEnum(PORT_DELIVERIES),
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function pinSchema() {
|
|
193
|
+
return {
|
|
194
|
+
type: 'object',
|
|
195
|
+
description: 'A physical pin.',
|
|
196
|
+
additionalProperties: false,
|
|
197
|
+
properties: {
|
|
198
|
+
label: { type: 'string' },
|
|
199
|
+
description: { type: 'string' },
|
|
200
|
+
group: { type: 'string' },
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function boundsSchema() {
|
|
205
|
+
return {
|
|
206
|
+
...objectSchema({
|
|
207
|
+
x: { type: 'number' },
|
|
208
|
+
y: { type: 'number' },
|
|
209
|
+
width: { type: 'number', exclusiveMinimum: 0 },
|
|
210
|
+
height: { type: 'number', exclusiveMinimum: 0 },
|
|
211
|
+
}, ['x', 'y', 'width', 'height']),
|
|
212
|
+
description: 'The symbol body box.',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function graphicSchema() {
|
|
216
|
+
return {
|
|
217
|
+
type: 'object',
|
|
218
|
+
description: 'A symbol graphic.',
|
|
219
|
+
required: ['type'],
|
|
220
|
+
properties: { type: stringEnum(GRAPHIC_TYPES) },
|
|
221
|
+
allOf: [
|
|
222
|
+
whenType('line', { $ref: '#/$defs/lineGraphic' }),
|
|
223
|
+
whenType('polyline', { $ref: '#/$defs/polylineGraphic' }),
|
|
224
|
+
whenType('rect', { $ref: '#/$defs/rectGraphic' }),
|
|
225
|
+
whenType('circle', { $ref: '#/$defs/circleGraphic' }),
|
|
226
|
+
whenType('arc', { $ref: '#/$defs/arcGraphic' }),
|
|
227
|
+
whenType('text', { $ref: '#/$defs/textGraphic' }),
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function symbolPinSchema() {
|
|
232
|
+
return objectSchema({
|
|
233
|
+
position: { $ref: '#/$defs/point' },
|
|
234
|
+
direction: stringEnum(SYMBOL_PIN_DIRECTIONS),
|
|
235
|
+
label: { $ref: '#/$defs/pinLabel' },
|
|
236
|
+
}, ['position', 'direction']);
|
|
237
|
+
}
|
|
238
|
+
function buildDefs(kind) {
|
|
239
|
+
const fill = stringEnum(GRAPHIC_FILLS);
|
|
240
|
+
const strokeWidth = { type: 'number', exclusiveMinimum: 0 };
|
|
241
|
+
const pointRef = { $ref: '#/$defs/point' };
|
|
242
|
+
const defs = {
|
|
243
|
+
point: pointSchema(),
|
|
244
|
+
editor: editorSchema(),
|
|
245
|
+
property: propertySchema(),
|
|
246
|
+
properties: stringMap({ $ref: '#/$defs/property' }, 'Property key to property description.'),
|
|
247
|
+
port: portSchema(),
|
|
248
|
+
ports: stringMap({ $ref: '#/$defs/port' }, 'Port key to topic port description.'),
|
|
249
|
+
};
|
|
250
|
+
if (kind === 'widget')
|
|
251
|
+
return defs;
|
|
252
|
+
return {
|
|
253
|
+
...defs,
|
|
254
|
+
pin: pinSchema(),
|
|
255
|
+
pins: stringMap({ $ref: '#/$defs/pin' }, 'Pin key to physical pin description.'),
|
|
256
|
+
bounds: boundsSchema(),
|
|
257
|
+
graphic: graphicSchema(),
|
|
258
|
+
lineGraphic: objectSchema({
|
|
259
|
+
type: { type: 'string', const: 'line' },
|
|
260
|
+
from: pointRef,
|
|
261
|
+
to: pointRef,
|
|
262
|
+
fill,
|
|
263
|
+
strokeWidth,
|
|
264
|
+
}, ['type', 'from', 'to']),
|
|
265
|
+
polylineGraphic: objectSchema({
|
|
266
|
+
type: { type: 'string', const: 'polyline' },
|
|
267
|
+
points: { type: 'array', minItems: 2, items: pointRef },
|
|
268
|
+
closed: { type: 'boolean' },
|
|
269
|
+
fill,
|
|
270
|
+
strokeWidth,
|
|
271
|
+
}, ['type', 'points']),
|
|
272
|
+
rectGraphic: objectSchema({
|
|
273
|
+
type: { type: 'string', const: 'rect' },
|
|
274
|
+
x: { type: 'number' },
|
|
275
|
+
y: { type: 'number' },
|
|
276
|
+
width: { type: 'number', exclusiveMinimum: 0 },
|
|
277
|
+
height: { type: 'number', exclusiveMinimum: 0 },
|
|
278
|
+
fill,
|
|
279
|
+
strokeWidth,
|
|
280
|
+
}, ['type', 'x', 'y', 'width', 'height']),
|
|
281
|
+
circleGraphic: objectSchema({
|
|
282
|
+
type: { type: 'string', const: 'circle' },
|
|
283
|
+
center: pointRef,
|
|
284
|
+
radius: { type: 'number', exclusiveMinimum: 0 },
|
|
285
|
+
fill,
|
|
286
|
+
strokeWidth,
|
|
287
|
+
}, ['type', 'center', 'radius']),
|
|
288
|
+
arcGraphic: objectSchema({
|
|
289
|
+
type: { type: 'string', const: 'arc' },
|
|
290
|
+
center: pointRef,
|
|
291
|
+
radius: { type: 'number', exclusiveMinimum: 0 },
|
|
292
|
+
startAngle: { type: 'number' },
|
|
293
|
+
sweepAngle: {
|
|
294
|
+
type: 'number',
|
|
295
|
+
minimum: -360,
|
|
296
|
+
maximum: 360,
|
|
297
|
+
not: { const: 0 },
|
|
298
|
+
},
|
|
299
|
+
fill,
|
|
300
|
+
strokeWidth,
|
|
301
|
+
}, ['type', 'center', 'radius', 'startAngle', 'sweepAngle']),
|
|
302
|
+
textGraphic: objectSchema({
|
|
303
|
+
type: { type: 'string', const: 'text' },
|
|
304
|
+
position: pointRef,
|
|
305
|
+
text: { type: 'string' },
|
|
306
|
+
align: stringEnum(TEXT_ALIGNS),
|
|
307
|
+
fontSize: { type: 'number', exclusiveMinimum: 0 },
|
|
308
|
+
fill,
|
|
309
|
+
strokeWidth,
|
|
310
|
+
}, ['type', 'position', 'text', 'fontSize']),
|
|
311
|
+
pinLabel: objectSchema({
|
|
312
|
+
visible: { type: 'boolean' },
|
|
313
|
+
position: pointRef,
|
|
314
|
+
align: stringEnum(TEXT_ALIGNS),
|
|
315
|
+
}, ['visible']),
|
|
316
|
+
symbolPin: symbolPinSchema(),
|
|
317
|
+
designatorAnnotation: objectSchema({
|
|
318
|
+
position: pointRef,
|
|
319
|
+
align: stringEnum(TEXT_ALIGNS),
|
|
320
|
+
}, ['position']),
|
|
321
|
+
annotations: objectSchema({
|
|
322
|
+
designator: { $ref: '#/$defs/designatorAnnotation' },
|
|
323
|
+
}),
|
|
324
|
+
symbol: objectSchema({
|
|
325
|
+
bounds: { $ref: '#/$defs/bounds' },
|
|
326
|
+
graphics: { type: 'array', items: { $ref: '#/$defs/graphic' } },
|
|
327
|
+
pins: stringMap({ $ref: '#/$defs/symbolPin' }, 'Pin key to symbol pin placement.'),
|
|
328
|
+
annotations: { $ref: '#/$defs/annotations' },
|
|
329
|
+
}, ['bounds', 'graphics', 'pins']),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function headerProperties() {
|
|
333
|
+
return {
|
|
334
|
+
id: { type: 'string', pattern: '^[A-Za-z0-9-]+$' },
|
|
335
|
+
version: { type: 'string', minLength: 1 },
|
|
336
|
+
displayName: { type: 'string', minLength: 1 },
|
|
337
|
+
description: { type: 'string' },
|
|
338
|
+
license: { type: 'string' },
|
|
339
|
+
tags: {
|
|
340
|
+
type: 'array',
|
|
341
|
+
uniqueItems: true,
|
|
342
|
+
items: { type: 'string', minLength: 1 },
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/** Metadata of a single device, independent of any package manifest or DDL source. */
|
|
347
|
+
export const deviceMetadataSchema = {
|
|
348
|
+
$schema: DRAFT_2020_12,
|
|
349
|
+
title: 'Mica device metadata',
|
|
350
|
+
type: 'object',
|
|
351
|
+
additionalProperties: false,
|
|
352
|
+
required: ['kind', 'id', 'version', 'displayName'],
|
|
353
|
+
properties: {
|
|
354
|
+
kind: { type: 'string', const: 'device' },
|
|
355
|
+
...headerProperties(),
|
|
356
|
+
templates: {
|
|
357
|
+
type: 'array',
|
|
358
|
+
description: 'Platform project template IDs in display priority order.',
|
|
359
|
+
uniqueItems: true,
|
|
360
|
+
items: { type: 'string', minLength: 1 },
|
|
361
|
+
},
|
|
362
|
+
recommends: objectSchema({
|
|
363
|
+
widgets: {
|
|
364
|
+
type: 'array',
|
|
365
|
+
description: 'Recommended full widget IDs in display priority order; not dependencies.',
|
|
366
|
+
uniqueItems: true,
|
|
367
|
+
items: { type: 'string', minLength: 1 },
|
|
368
|
+
},
|
|
369
|
+
}),
|
|
370
|
+
designatorPrefix: { type: 'string', pattern: '^\\S+$' },
|
|
371
|
+
properties: { $ref: '#/$defs/properties' },
|
|
372
|
+
pins: { $ref: '#/$defs/pins' },
|
|
373
|
+
ports: { $ref: '#/$defs/ports' },
|
|
374
|
+
symbol: { $ref: '#/$defs/symbol' },
|
|
375
|
+
},
|
|
376
|
+
$defs: buildDefs('device'),
|
|
377
|
+
};
|
|
378
|
+
/** Metadata of a single widget, independent of any package manifest or DDL source. */
|
|
379
|
+
export const widgetMetadataSchema = {
|
|
380
|
+
$schema: DRAFT_2020_12,
|
|
381
|
+
title: 'Mica widget metadata',
|
|
382
|
+
type: 'object',
|
|
383
|
+
additionalProperties: false,
|
|
384
|
+
required: ['kind', 'id', 'version', 'displayName'],
|
|
385
|
+
properties: {
|
|
386
|
+
kind: { type: 'string', const: 'widget' },
|
|
387
|
+
...headerProperties(),
|
|
388
|
+
properties: { $ref: '#/$defs/properties' },
|
|
389
|
+
ports: { $ref: '#/$defs/ports' },
|
|
390
|
+
},
|
|
391
|
+
$defs: buildDefs('widget'),
|
|
392
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript shapes for individual device and widget metadata.
|
|
3
|
+
*
|
|
4
|
+
* The types describe the same contract as the JSON schemas in `schema.ts`; runtime validation
|
|
5
|
+
* remains authoritative because a metadata file is untrusted JSON.
|
|
6
|
+
*/
|
|
7
|
+
export type MetadataKind = 'device' | 'widget';
|
|
8
|
+
/** A two-dimensional point, serialized as `[x, y]`. */
|
|
9
|
+
export type Point = readonly [x: number, y: number];
|
|
10
|
+
export type PropertyType = 'number' | 'boolean' | 'string' | 'enum' | 'bytes';
|
|
11
|
+
export type PortDirection = 'input' | 'output';
|
|
12
|
+
export type PortPayloadType = 'boolean' | 'number' | 'string' | 'vec2d' | 'vec3d' | 'bytes' | 'bytes<rgb565>' | 'bytes<segments>';
|
|
13
|
+
export type PortDelivery = 'level' | 'message';
|
|
14
|
+
export type GraphicFill = 'none' | 'foreground' | 'background';
|
|
15
|
+
export type TextAlign = 'left' | 'center' | 'right';
|
|
16
|
+
export type PinDirection = 'left' | 'right' | 'up' | 'down';
|
|
17
|
+
export interface NumberEditor {
|
|
18
|
+
readonly kind: 'number';
|
|
19
|
+
readonly min?: number;
|
|
20
|
+
readonly max?: number;
|
|
21
|
+
readonly step?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface TextEditor {
|
|
24
|
+
readonly kind: 'text';
|
|
25
|
+
}
|
|
26
|
+
export interface TextareaEditor {
|
|
27
|
+
readonly kind: 'textarea';
|
|
28
|
+
}
|
|
29
|
+
export interface CheckboxEditor {
|
|
30
|
+
readonly kind: 'checkbox';
|
|
31
|
+
}
|
|
32
|
+
export interface FileEditor {
|
|
33
|
+
readonly kind: 'file';
|
|
34
|
+
readonly extensions?: readonly string[];
|
|
35
|
+
readonly maxSize?: number;
|
|
36
|
+
}
|
|
37
|
+
export interface SelectEditor {
|
|
38
|
+
readonly kind: 'select';
|
|
39
|
+
readonly labels: Readonly<Record<string, string>>;
|
|
40
|
+
}
|
|
41
|
+
export type PropertyEditor = NumberEditor | TextEditor | TextareaEditor | CheckboxEditor | FileEditor | SelectEditor;
|
|
42
|
+
interface PropertyCommon {
|
|
43
|
+
/** `true` values have no default; `false` values must declare one. */
|
|
44
|
+
readonly required: boolean;
|
|
45
|
+
readonly nullable?: boolean;
|
|
46
|
+
readonly title?: string;
|
|
47
|
+
readonly description?: string;
|
|
48
|
+
readonly unit?: string;
|
|
49
|
+
readonly group?: string;
|
|
50
|
+
readonly order?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface NumberProperty extends PropertyCommon {
|
|
53
|
+
readonly type: 'number';
|
|
54
|
+
readonly default?: number | null;
|
|
55
|
+
readonly editor?: NumberEditor;
|
|
56
|
+
}
|
|
57
|
+
export interface BooleanProperty extends PropertyCommon {
|
|
58
|
+
readonly type: 'boolean';
|
|
59
|
+
readonly default?: boolean | null;
|
|
60
|
+
readonly editor?: CheckboxEditor;
|
|
61
|
+
}
|
|
62
|
+
export interface StringProperty extends PropertyCommon {
|
|
63
|
+
readonly type: 'string';
|
|
64
|
+
readonly default?: string | null;
|
|
65
|
+
readonly editor?: TextEditor | TextareaEditor;
|
|
66
|
+
}
|
|
67
|
+
export interface EnumProperty extends PropertyCommon {
|
|
68
|
+
readonly type: 'enum';
|
|
69
|
+
readonly enumName: string;
|
|
70
|
+
readonly values: readonly string[];
|
|
71
|
+
readonly default?: string | null;
|
|
72
|
+
readonly editor?: SelectEditor;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Bytes values may omit the default, but bytes defaults are rejected: the byte encoding is not
|
|
76
|
+
* decided yet, so a default cannot be expressed as plain JSON.
|
|
77
|
+
*/
|
|
78
|
+
export interface BytesProperty extends PropertyCommon {
|
|
79
|
+
readonly type: 'bytes';
|
|
80
|
+
readonly editor?: FileEditor;
|
|
81
|
+
}
|
|
82
|
+
export type PropertyMetadata = NumberProperty | BooleanProperty | StringProperty | EnumProperty | BytesProperty;
|
|
83
|
+
export interface PortMetadata {
|
|
84
|
+
readonly title?: string;
|
|
85
|
+
readonly description?: string;
|
|
86
|
+
readonly direction: PortDirection;
|
|
87
|
+
readonly type: PortPayloadType;
|
|
88
|
+
readonly delivery?: PortDelivery;
|
|
89
|
+
}
|
|
90
|
+
export interface PinMetadata {
|
|
91
|
+
readonly label?: string;
|
|
92
|
+
readonly description?: string;
|
|
93
|
+
readonly group?: string;
|
|
94
|
+
}
|
|
95
|
+
interface GraphicCommon {
|
|
96
|
+
readonly fill?: GraphicFill;
|
|
97
|
+
readonly strokeWidth?: number;
|
|
98
|
+
}
|
|
99
|
+
export interface LineGraphic extends GraphicCommon {
|
|
100
|
+
readonly type: 'line';
|
|
101
|
+
readonly from: Point;
|
|
102
|
+
readonly to: Point;
|
|
103
|
+
}
|
|
104
|
+
export interface PolylineGraphic extends GraphicCommon {
|
|
105
|
+
readonly type: 'polyline';
|
|
106
|
+
readonly points: readonly Point[];
|
|
107
|
+
readonly closed?: boolean;
|
|
108
|
+
}
|
|
109
|
+
export interface RectGraphic extends GraphicCommon {
|
|
110
|
+
readonly type: 'rect';
|
|
111
|
+
readonly x: number;
|
|
112
|
+
readonly y: number;
|
|
113
|
+
readonly width: number;
|
|
114
|
+
readonly height: number;
|
|
115
|
+
}
|
|
116
|
+
export interface CircleGraphic extends GraphicCommon {
|
|
117
|
+
readonly type: 'circle';
|
|
118
|
+
readonly center: Point;
|
|
119
|
+
readonly radius: number;
|
|
120
|
+
}
|
|
121
|
+
export interface ArcGraphic extends GraphicCommon {
|
|
122
|
+
readonly type: 'arc';
|
|
123
|
+
readonly center: Point;
|
|
124
|
+
readonly radius: number;
|
|
125
|
+
readonly startAngle: number;
|
|
126
|
+
/** Degrees in `[-360, 360]`, excluding `0`. */
|
|
127
|
+
readonly sweepAngle: number;
|
|
128
|
+
}
|
|
129
|
+
export interface TextGraphic extends GraphicCommon {
|
|
130
|
+
readonly type: 'text';
|
|
131
|
+
readonly position: Point;
|
|
132
|
+
readonly text: string;
|
|
133
|
+
readonly align?: TextAlign;
|
|
134
|
+
readonly fontSize: number;
|
|
135
|
+
}
|
|
136
|
+
export type Graphic = LineGraphic | PolylineGraphic | RectGraphic | CircleGraphic | ArcGraphic | TextGraphic;
|
|
137
|
+
export interface SymbolBounds {
|
|
138
|
+
readonly x: number;
|
|
139
|
+
readonly y: number;
|
|
140
|
+
readonly width: number;
|
|
141
|
+
readonly height: number;
|
|
142
|
+
}
|
|
143
|
+
export interface PinLabelMetadata {
|
|
144
|
+
readonly visible: boolean;
|
|
145
|
+
readonly position?: Point;
|
|
146
|
+
readonly align?: TextAlign;
|
|
147
|
+
}
|
|
148
|
+
export interface SymbolPinMetadata {
|
|
149
|
+
readonly position: Point;
|
|
150
|
+
readonly direction: PinDirection;
|
|
151
|
+
readonly label?: PinLabelMetadata;
|
|
152
|
+
}
|
|
153
|
+
export interface DesignatorAnnotationMetadata {
|
|
154
|
+
readonly position: Point;
|
|
155
|
+
readonly align?: TextAlign;
|
|
156
|
+
}
|
|
157
|
+
export interface SymbolAnnotationsMetadata {
|
|
158
|
+
readonly designator?: DesignatorAnnotationMetadata;
|
|
159
|
+
}
|
|
160
|
+
export interface SymbolMetadata {
|
|
161
|
+
readonly bounds: SymbolBounds;
|
|
162
|
+
readonly graphics: readonly Graphic[];
|
|
163
|
+
readonly pins: Readonly<Record<string, SymbolPinMetadata>>;
|
|
164
|
+
readonly annotations?: SymbolAnnotationsMetadata;
|
|
165
|
+
}
|
|
166
|
+
interface MetadataHeader {
|
|
167
|
+
readonly id: string;
|
|
168
|
+
readonly version: string;
|
|
169
|
+
readonly displayName: string;
|
|
170
|
+
readonly description?: string;
|
|
171
|
+
readonly license?: string;
|
|
172
|
+
readonly tags?: readonly string[];
|
|
173
|
+
}
|
|
174
|
+
export interface DeviceMetadata extends MetadataHeader {
|
|
175
|
+
readonly kind: 'device';
|
|
176
|
+
readonly templates?: readonly string[];
|
|
177
|
+
readonly recommends?: {
|
|
178
|
+
readonly widgets?: readonly string[];
|
|
179
|
+
};
|
|
180
|
+
readonly designatorPrefix?: string;
|
|
181
|
+
readonly properties?: Readonly<Record<string, PropertyMetadata>>;
|
|
182
|
+
readonly pins?: Readonly<Record<string, PinMetadata>>;
|
|
183
|
+
readonly ports?: Readonly<Record<string, PortMetadata>>;
|
|
184
|
+
readonly symbol?: SymbolMetadata;
|
|
185
|
+
}
|
|
186
|
+
export interface WidgetMetadata extends MetadataHeader {
|
|
187
|
+
readonly kind: 'widget';
|
|
188
|
+
readonly properties?: Readonly<Record<string, PropertyMetadata>>;
|
|
189
|
+
readonly ports?: Readonly<Record<string, PortMetadata>>;
|
|
190
|
+
}
|
|
191
|
+
export type Metadata = DeviceMetadata | WidgetMetadata;
|
|
192
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { MetadataKind } from './types.js';
|
|
2
|
+
/** One validation problem, addressed by a dot path such as `properties.led.type`. */
|
|
3
|
+
export interface MetadataIssue {
|
|
4
|
+
/** Path of the offending value; `''` addresses the metadata root. */
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly message: string;
|
|
7
|
+
/** JSON Schema keyword, or `expectedId` for the cross-file id check. */
|
|
8
|
+
readonly keyword: string;
|
|
9
|
+
}
|
|
10
|
+
/** Thrown by {@link validateMetadata} when a metadata document is rejected. */
|
|
11
|
+
export declare class MetadataValidationError extends Error {
|
|
12
|
+
readonly kind: MetadataKind;
|
|
13
|
+
readonly issues: readonly MetadataIssue[];
|
|
14
|
+
readonly metadataId: string | undefined;
|
|
15
|
+
constructor(kind: MetadataKind, issues: readonly MetadataIssue[], metadataId: string | undefined);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validates one device or widget metadata document.
|
|
19
|
+
*
|
|
20
|
+
* @param input Untrusted JSON value of the metadata file.
|
|
21
|
+
* @param kind Metadata kind that selects the contract.
|
|
22
|
+
* @param expectedId Id the caller expects; a mismatch is reported at `id`.
|
|
23
|
+
* @throws {MetadataValidationError} with the path of every rejected value.
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateMetadata(input: unknown, kind: MetadataKind, expectedId?: string): void;
|