@vessel-dsp/core 0.5.0 → 0.6.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/README.md +10 -0
- package/package.json +1 -1
- package/src/formats/document.ts +145 -3
- package/src/formats/interchange/parser.ts +667 -9
- package/src/formats/interchange/serializer.ts +96 -1
- package/src/index.ts +51 -1
- package/src/model/types.ts +302 -1
- package/src/model/validation.ts +579 -1
package/README.md
CHANGED
|
@@ -4,3 +4,13 @@ Headless Vessel DSP circuit, device, format conversion, and layout model APIs.
|
|
|
4
4
|
|
|
5
5
|
This package has no React, DOM rendering, AudioContext, or AudioWorklet
|
|
6
6
|
dependency.
|
|
7
|
+
|
|
8
|
+
`.vdsp` parsing supports `circuit-interchange/v2` and `circuit-interchange/v3`.
|
|
9
|
+
V3 documents preserve reviewed physical build metadata such as build scope,
|
|
10
|
+
mechanical envelopes, BOM rows, embedded part and footprint catalogs,
|
|
11
|
+
off-board wiring, panel drill placement, and board realizations for stripboard,
|
|
12
|
+
perfboard, breadboard-pattern protoboard, and fabricated PCB.
|
|
13
|
+
|
|
14
|
+
Conversions from v3 `.vdsp` to formats that cannot represent those physical
|
|
15
|
+
fields throw by default. Use `convertCircuitDocumentFileWithReport()` with
|
|
16
|
+
`lossPolicy: 'drop-with-diagnostics'` when intentional lossy export is needed.
|
package/package.json
CHANGED
package/src/formats/document.ts
CHANGED
|
@@ -38,6 +38,24 @@ export type ConvertCircuitDocumentFileOptions = Readonly<{
|
|
|
38
38
|
outputFilename?: string;
|
|
39
39
|
}>;
|
|
40
40
|
|
|
41
|
+
export type CircuitDocumentConversionLossPolicy = 'error' | 'drop-with-diagnostics';
|
|
42
|
+
|
|
43
|
+
export type CircuitDocumentConversionDiagnostic = Readonly<{
|
|
44
|
+
code: 'v3-data-dropped';
|
|
45
|
+
message: string;
|
|
46
|
+
field: string;
|
|
47
|
+
}>;
|
|
48
|
+
|
|
49
|
+
export type ConvertCircuitDocumentFileWithReportOptions = ConvertCircuitDocumentFileOptions & Readonly<{
|
|
50
|
+
lossPolicy?: CircuitDocumentConversionLossPolicy;
|
|
51
|
+
}>;
|
|
52
|
+
|
|
53
|
+
export type CircuitDocumentFileConversionReport = Readonly<{
|
|
54
|
+
output: string;
|
|
55
|
+
diagnostics: readonly CircuitDocumentConversionDiagnostic[];
|
|
56
|
+
droppedFields: readonly string[];
|
|
57
|
+
}>;
|
|
58
|
+
|
|
41
59
|
export type VdspSchemaValidationIssue = Readonly<{
|
|
42
60
|
code: 'vdsp-schema-invalid';
|
|
43
61
|
message: string;
|
|
@@ -190,6 +208,8 @@ export function serializeCircuitDocumentFile(
|
|
|
190
208
|
document: CircuitDocument,
|
|
191
209
|
options: SerializeCircuitDocumentFileOptions,
|
|
192
210
|
): string {
|
|
211
|
+
assertNoV3OnlyDataDrop(document, options.format);
|
|
212
|
+
|
|
193
213
|
switch (options.format) {
|
|
194
214
|
case 'vdsp':
|
|
195
215
|
case 'yaml':
|
|
@@ -212,12 +232,45 @@ export function convertCircuitDocumentFile(
|
|
|
212
232
|
source: string,
|
|
213
233
|
options: ConvertCircuitDocumentFileOptions,
|
|
214
234
|
): string {
|
|
215
|
-
return
|
|
235
|
+
return convertCircuitDocumentFileWithReport(source, options).output;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function convertCircuitDocumentFileWithReport(
|
|
239
|
+
source: string,
|
|
240
|
+
options: ConvertCircuitDocumentFileWithReportOptions,
|
|
241
|
+
): CircuitDocumentFileConversionReport {
|
|
242
|
+
const document = parseCircuitDocumentFile(source, {
|
|
216
243
|
filename: options.inputFilename,
|
|
217
|
-
})
|
|
244
|
+
});
|
|
245
|
+
const droppedFields = v3OnlyDroppedFields(document);
|
|
246
|
+
const targetOptions: SerializeCircuitDocumentFileOptions = {
|
|
218
247
|
format: options.outputFormat,
|
|
219
248
|
...(options.outputFilename === undefined ? {} : { filename: options.outputFilename }),
|
|
220
|
-
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
if (isVdspOutputFormat(options.outputFormat) || droppedFields.length === 0) {
|
|
252
|
+
return {
|
|
253
|
+
output: serializeCircuitDocumentFile(document, targetOptions),
|
|
254
|
+
diagnostics: [],
|
|
255
|
+
droppedFields: [],
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (options.lossPolicy !== 'drop-with-diagnostics') {
|
|
260
|
+
throw new Error(v3DropMessage(options.outputFormat, droppedFields));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const diagnostics = droppedFields.map((field): CircuitDocumentConversionDiagnostic => ({
|
|
264
|
+
code: 'v3-data-dropped',
|
|
265
|
+
field,
|
|
266
|
+
message: `Dropped v3-only field "${field}" while converting to ${options.outputFormat}`,
|
|
267
|
+
}));
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
output: serializeCircuitDocumentFile(stripV3OnlyData(document), targetOptions),
|
|
271
|
+
diagnostics,
|
|
272
|
+
droppedFields,
|
|
273
|
+
};
|
|
221
274
|
}
|
|
222
275
|
|
|
223
276
|
export function serializeVdspCircuitDocument(
|
|
@@ -261,6 +314,95 @@ function normalizeVdspFilename(filename: string | undefined, fallbackName: strin
|
|
|
261
314
|
return `${withoutExtension || 'untitled-preset'}${vdspFileExtension}`;
|
|
262
315
|
}
|
|
263
316
|
|
|
317
|
+
function assertNoV3OnlyDataDrop(document: CircuitDocument, format: CircuitDocumentFileFormat): void {
|
|
318
|
+
if (isVdspOutputFormat(format)) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const droppedFields = v3OnlyDroppedFields(document);
|
|
322
|
+
if (droppedFields.length > 0) {
|
|
323
|
+
throw new Error(v3DropMessage(format, droppedFields));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function v3DropMessage(format: CircuitDocumentFileFormat, droppedFields: readonly string[]): string {
|
|
328
|
+
return `Converting to ${format} would drop v3-only fields: ${droppedFields.join(', ')}`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function isVdspOutputFormat(format: CircuitDocumentFileFormat): boolean {
|
|
332
|
+
return format === 'vdsp' || format === 'yaml';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function v3OnlyDroppedFields(document: CircuitDocument): readonly string[] {
|
|
336
|
+
const fields: string[] = [];
|
|
337
|
+
if (document.mechanical !== undefined) {
|
|
338
|
+
fields.push('mechanical');
|
|
339
|
+
}
|
|
340
|
+
if (document.build !== undefined) {
|
|
341
|
+
fields.push('build');
|
|
342
|
+
}
|
|
343
|
+
if (document.bom !== undefined) {
|
|
344
|
+
fields.push('bom');
|
|
345
|
+
}
|
|
346
|
+
if (document.partProfiles !== undefined) {
|
|
347
|
+
fields.push('partProfiles');
|
|
348
|
+
}
|
|
349
|
+
if (document.footprints !== undefined) {
|
|
350
|
+
fields.push('footprints');
|
|
351
|
+
}
|
|
352
|
+
if (document.offBoardWiring !== undefined) {
|
|
353
|
+
fields.push('offBoardWiring');
|
|
354
|
+
}
|
|
355
|
+
if (document.panel?.faces.some((face) => face.geometry !== undefined) === true) {
|
|
356
|
+
fields.push('panel.faces[].geometry');
|
|
357
|
+
}
|
|
358
|
+
if (document.panel?.faces.some((face) => face.elements.some((element) => element.id !== undefined)) === true) {
|
|
359
|
+
fields.push('panel.faces[].elements[].id');
|
|
360
|
+
}
|
|
361
|
+
if (document.panel?.faces.some((face) => face.elements.some((element) => element.physical !== undefined)) === true) {
|
|
362
|
+
fields.push('panel.faces[].elements[].physical');
|
|
363
|
+
}
|
|
364
|
+
if (document.boards !== undefined) {
|
|
365
|
+
fields.push('boards');
|
|
366
|
+
}
|
|
367
|
+
return fields;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function stripV3OnlyData(document: CircuitDocument): CircuitDocument {
|
|
371
|
+
return {
|
|
372
|
+
metadata: document.metadata,
|
|
373
|
+
...(document.source === undefined ? {} : { source: document.source }),
|
|
374
|
+
...(document.device === undefined ? {} : { device: document.device }),
|
|
375
|
+
...(document.controlGroups === undefined ? {} : { controlGroups: document.controlGroups }),
|
|
376
|
+
...(document.controlContexts === undefined ? {} : { controlContexts: document.controlContexts }),
|
|
377
|
+
...(document.deviceInterface === undefined ? {} : { deviceInterface: document.deviceInterface }),
|
|
378
|
+
...(document.panel === undefined ? {} : {
|
|
379
|
+
panel: {
|
|
380
|
+
faces: document.panel.faces.map((face) => ({
|
|
381
|
+
id: face.id,
|
|
382
|
+
...(face.label === undefined ? {} : { label: face.label }),
|
|
383
|
+
layout: face.layout,
|
|
384
|
+
elements: face.elements.map((element) => ({
|
|
385
|
+
bind: element.bind,
|
|
386
|
+
kind: element.kind,
|
|
387
|
+
...(element.label === undefined ? {} : { label: element.label }),
|
|
388
|
+
...(element.interfaceControlId === undefined
|
|
389
|
+
? {}
|
|
390
|
+
: { interfaceControlId: element.interfaceControlId }),
|
|
391
|
+
grid: element.grid,
|
|
392
|
+
})),
|
|
393
|
+
})),
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
...(document.controlInterfaces === undefined ? {} : { controlInterfaces: document.controlInterfaces }),
|
|
397
|
+
...(document.controlOutputs === undefined ? {} : { controlOutputs: document.controlOutputs }),
|
|
398
|
+
components: document.components,
|
|
399
|
+
wires: document.wires,
|
|
400
|
+
directives: document.directives,
|
|
401
|
+
warnings: document.warnings,
|
|
402
|
+
rawAttributes: document.rawAttributes,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
264
406
|
function pathFromSchemaMessage(message: string): string | undefined {
|
|
265
407
|
const colonIndex = message.indexOf(':');
|
|
266
408
|
if (colonIndex <= 0) {
|