@mica-js/core 0.1.0 → 0.1.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.
@@ -1,74 +1,39 @@
1
- /**
2
- * Applies a canvas command to a copy of `canvas`.
3
- *
4
- * @throws {SchematicError} when the command is invalid; the input canvas is never mutated.
5
- */
6
- export declare function applyCanvasCommand(canvas: CanvasDocument, command: CanvasCommand, options?: ApplyCanvasCommandOptions): CanvasDocument;
7
-
8
- export declare interface ApplyCanvasCommandOptions {
9
- /** Bound schematic; symbol references are validated against it when provided. */
10
- readonly schematic?: SchematicDocument;
11
- }
12
-
13
- /** Where an image / binary property value lives (#7). */
14
- declare type AssetMode = 'file' | 'inline' | 'store';
15
-
16
- /**
17
- * Reference to a project asset instead of inline bytes (#7): `file` is a project-root relative
18
- * POSIX path, `inline` is base64 text and `store` is a host resource identifier.
19
- */
20
- declare interface AssetReference {
21
- readonly src: string;
22
- readonly mode: AssetMode;
23
- }
24
-
25
1
  /**
26
2
  * Schema version of the `.cnv` canvas contract (see x-botlab/mica-js#67).
27
3
  *
28
- * A canvas is a 2D layout: one `.cnv` binds exactly one `.sch` (see x-botlab/mica-js#6) and keeps
29
- * a placement per symbol id. Schematic connections stay in the `.sch`; the canvas only adds 2D
30
- * coordinates and rotation.
4
+ * A canvas is an independent 2D plane: it records widget instances with their placement, property
5
+ * overrides and port-to-symbol bindings. Electrical topology stays in the `.sch` the canvas binds
6
+ * (see x-botlab/mica-js#6); the two coordinate spaces are unrelated.
31
7
  */
32
8
  export declare const CANVAS_VERSION = 1;
33
9
 
34
- export declare type CanvasCommand = {
35
- readonly type: 'set-placement';
36
- readonly symbolId: string;
37
- readonly x: number;
38
- readonly y: number;
39
- readonly rotation?: number;
40
- } | {
41
- readonly type: 'remove-placement';
42
- readonly symbolId: string;
43
- } | {
44
- readonly type: 'bind-schematic';
45
- readonly schematic: string;
46
- };
47
-
48
- export declare interface CanvasDocument {
49
- readonly version: CanvasVersion;
50
- /** Canvas identity, unique within one project's `.cnv` files. */
51
- readonly id: string;
52
- /** Project-root relative POSIX path of the bound `.sch`. */
53
- readonly schematic: string;
54
- /** Symbol id → placement; ids are stable, so renaming a designator keeps the entry. */
55
- readonly layout: Readonly<Record<string, CanvasPlacement>>;
10
+ /** Endpoint of a widget port: a port of one symbol instance in the bound schematic. */
11
+ export declare interface CanvasBindingTarget {
12
+ readonly symbol: string;
13
+ readonly port: string;
56
14
  }
57
15
 
58
- export declare interface CanvasLayoutEntry {
59
- readonly symbolId: string;
60
- readonly placement: CanvasPlacement;
16
+ /** Root of a `.cnv` file; the project-root relative path is the canvas identity. */
17
+ export declare interface CanvasDocument {
18
+ readonly version: typeof CANVAS_VERSION;
19
+ /** Project-root relative POSIX path of the `.sch` the canvas draws and simulates against. */
20
+ readonly schematic?: string;
21
+ /** Widget instances in drawing order; later instances paint above earlier ones. */
22
+ readonly widgets: readonly CanvasWidgetInstance[];
61
23
  }
62
24
 
63
- /** 2D placement of one symbol in a canvas. */
64
- export declare interface CanvasPlacement {
65
- readonly x: number;
66
- readonly y: number;
67
- readonly rotation: number;
25
+ /** One widget placed on a canvas; the order in `widgets` is the drawing order (#67). */
26
+ export declare interface CanvasWidgetInstance extends WidgetPlacement {
27
+ /** Stable identity: UUID v4, unique within the document. */
28
+ readonly id: string;
29
+ /** Widget reference `"<package>:<widget>"`; built-ins use the reserved `builtin:` namespace. */
30
+ readonly widget: string;
31
+ /** Property overrides keyed by the widget definition; values follow that definition (#7). */
32
+ readonly properties?: Readonly<Record<string, unknown>>;
33
+ /** Port key → endpoint; only bound ports take part in messaging (#10). */
34
+ readonly bindings?: Readonly<Record<string, CanvasBindingTarget>>;
68
35
  }
69
36
 
70
- export declare type CanvasVersion = typeof CANVAS_VERSION;
71
-
72
37
  export declare interface Diagnostic {
73
38
  readonly severity: DiagnosticSeverity;
74
39
  readonly code: DiagnosticCode;
@@ -80,7 +45,7 @@ export declare interface Diagnostic {
80
45
  readonly related?: readonly DiagnosticRelated[];
81
46
  }
82
47
 
83
- export declare type DiagnosticCode = 'json/invalid' | 'json/not-object' | 'schema/unsupported-version' | 'schema/missing-field' | 'schema/invalid-type' | 'schema/invalid-value' | 'schema/unknown-field' | 'id/duplicate' | 'device/invalid-reference' | 'device/unresolved' | 'device/invalid-package' | 'device/duplicate-package' | 'device/reserved-namespace' | 'device/duplicate-device' | 'device/invalid-description' | 'device/unknown-pin' | 'device/no-symbol' | 'wire/invalid-points' | 'wire/not-orthogonal' | 'junction/unknown-wire' | 'junction/duplicate-wire' | 'junction/off-wire' | 'symbol/missing-net' | 'symbol/unsupported-field' | 'symbol/designator-duplicate' | 'net/naming-conflict' | 'canvas/unknown-symbol' | 'edit/unknown-entity' | 'edit/invalid-command';
48
+ export declare type DiagnosticCode = 'json/invalid' | 'json/not-object' | 'schema/unsupported-version' | 'schema/missing-field' | 'schema/invalid-type' | 'schema/invalid-value' | 'schema/unknown-field' | 'id/duplicate' | 'device/invalid-reference' | 'device/unresolved' | 'device/invalid-package' | 'device/duplicate-package' | 'device/reserved-namespace' | 'device/duplicate-device' | 'device/invalid-description' | 'device/unknown-pin' | 'device/no-symbol' | 'wire/invalid-points' | 'wire/not-orthogonal' | 'junction/unknown-wire' | 'junction/duplicate-wire' | 'junction/off-wire' | 'symbol/missing-net' | 'symbol/unsupported-field' | 'symbol/designator-duplicate' | 'net/naming-conflict' | 'canvas/unknown-symbol' | 'canvas/unknown-widget' | 'canvas/unknown-property' | 'canvas/unknown-port' | 'canvas/unknown-schematic' | 'canvas/unknown-target-port' | 'topic/type-mismatch' | 'type/unknown' | 'edit/unknown-entity' | 'edit/invalid-command';
84
49
 
85
50
  export declare interface DiagnosticEntity {
86
51
  readonly kind: 'symbol' | 'wire' | 'junction';
@@ -93,54 +58,9 @@ export declare interface DiagnosticRelated {
93
58
 
94
59
  export declare type DiagnosticSeverity = 'error' | 'warning';
95
60
 
96
- /** Parses a canvas and validates its layout against the bound schematic. @throws {SchematicError} */
97
- export declare function loadCanvas(source: string | CanvasDocument, options?: LoadCanvasOptions): LoadedCanvas;
98
-
99
- export declare interface LoadCanvasOptions {
100
- /** Bound schematic; layout keys are validated against its symbols when provided. */
101
- readonly schematic?: SchematicDocument;
102
- }
103
-
104
- export declare interface LoadedCanvas {
105
- readonly document: CanvasDocument;
106
- /** Layout entries sorted by symbol id, so loading is deterministic. */
107
- readonly placements: readonly CanvasLayoutEntry[];
108
- }
109
-
110
61
  /** @throws {SchematicError} when the input does not match the `.cnv` contract. */
111
62
  export declare function parseCanvas(input: string | unknown): CanvasDocument;
112
63
 
113
- declare interface Placement {
114
- readonly x: number;
115
- readonly y: number;
116
- readonly rotation: number;
117
- }
118
-
119
- declare interface Point {
120
- readonly x: number;
121
- readonly y: number;
122
- }
123
-
124
- /**
125
- * Instance property value stored in `symbol.properties` (see #6 / #7): a scalar, a resource
126
- * reference for `image` / `binary` properties, or `null` for an explicitly cleared nullable one.
127
- */
128
- declare type PropertyValue = string | number | boolean | AssetReference | null;
129
-
130
- /**
131
- * Schema version of the `.sch` contract (see x-botlab/mica-js#6).
132
- *
133
- * 2D placement lives in the separate `.cnv` canvas contract (see x-botlab/mica-js#67 and
134
- * `@mica-js/core/canvas`).
135
- */
136
- declare const SCHEMATIC_VERSION = 1;
137
-
138
- /** Root of a `.sch` file; the file name is the schematic's identity. */
139
- declare interface SchematicDocument {
140
- readonly version: SchematicVersion;
141
- readonly sheet: SchematicSheet;
142
- }
143
-
144
64
  /**
145
65
  * Thrown when input violates the schematic format contract.
146
66
  * `diagnostics` carries every problem found, so callers can report them all at once.
@@ -152,50 +72,21 @@ export declare class SchematicError extends Error {
152
72
  get code(): DiagnosticCode | undefined;
153
73
  }
154
74
 
155
- declare interface SchematicJunction {
156
- readonly id: string;
75
+ /**
76
+ * Serializes with a fixed field order and 2-space indentation, matching the example in #67.
77
+ * `properties` and `bindings` are written whenever the document carries them, even when empty, so
78
+ * a parse / serialize round trip keeps the original shape.
79
+ */
80
+ export declare function serializeCanvas(document: CanvasDocument): string;
81
+
82
+ /** Placement and size of one widget instance, in logical units. */
83
+ export declare interface WidgetPlacement {
157
84
  readonly x: number;
158
85
  readonly y: number;
159
- readonly wires: readonly string[];
160
- }
161
-
162
- declare interface SchematicSheet {
163
- readonly symbols: readonly SchematicSymbol[];
164
- readonly wires: readonly SchematicWire[];
165
- readonly junctions: readonly SchematicJunction[];
166
- }
167
-
168
- declare interface SchematicSymbol extends Placement {
169
- /** Stable identity used by selections, undo and `.view` layout keys. */
170
- readonly id: string;
171
- readonly device: string;
172
- /** Instance property overrides; absent means "use device defaults". */
173
- readonly properties?: Readonly<Record<string, PropertyValue>>;
174
- /**
175
- * Reference designator of a component (`U1`, `R2`); defaults to {@link id}.
176
- * Only components carry one — the format rejects it elsewhere.
177
- */
178
- readonly designator?: string;
179
- /**
180
- * Net name of a net label, power or ground symbol (`VCC`, `N1`). It is the text drawn next to
181
- * the anchor and the key same-named ports merge on. Only net-naming symbols carry one.
182
- */
183
- readonly net?: string;
184
- /**
185
- * Reference designator anchor in symbol-local coordinates (unrotated, unplaced).
186
- * Absent means the default position at the body's top-left corner.
187
- */
188
- readonly reference?: Point;
189
- }
190
-
191
- declare type SchematicVersion = typeof SCHEMATIC_VERSION;
192
-
193
- declare interface SchematicWire {
194
- readonly id: string;
195
- readonly points: readonly Point[];
86
+ readonly width: number;
87
+ readonly height: number;
88
+ /** Clockwise rotation in degrees around the instance center. */
89
+ readonly rotation: number;
196
90
  }
197
91
 
198
- /** Serializes with a fixed field order and 2-space indentation, matching the example in #67. */
199
- export declare function serializeCanvas(canvas: CanvasDocument): string;
200
-
201
92
  export { }
@@ -1,15 +1,38 @@
1
- import { c as readObjectField, d as readStringField, f as report, h as SchematicError, i as isRecord, m as parseInput, n as forbidUnknownFields, o as readIntegerField, p as toPointer, r as hasErrors, s as readNumberField } from "../validate-COqYIMQg.js";
1
+ import { a as readArrayField, d as readStringField, f as report, h as SchematicError, i as isRecord, m as parseInput, n as forbidUnknownFields, o as readIntegerField, r as hasErrors, s as readNumberField } from "../validate-DuALfDe0.js";
2
2
  //#region src/canvas/format/types.ts
3
3
  /**
4
4
  * Schema version of the `.cnv` canvas contract (see x-botlab/mica-js#67).
5
5
  *
6
- * A canvas is a 2D layout: one `.cnv` binds exactly one `.sch` (see x-botlab/mica-js#6) and keeps
7
- * a placement per symbol id. Schematic connections stay in the `.sch`; the canvas only adds 2D
8
- * coordinates and rotation.
6
+ * A canvas is an independent 2D plane: it records widget instances with their placement, property
7
+ * overrides and port-to-symbol bindings. Electrical topology stays in the `.sch` the canvas binds
8
+ * (see x-botlab/mica-js#6); the two coordinate spaces are unrelated.
9
9
  */
10
10
  var CANVAS_VERSION = 1;
11
11
  //#endregion
12
+ //#region src/format/uuid.ts
13
+ var UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
14
+ /** Strict UUID v4 check: 8-4-4-4-12 hex with the version and variant bits fixed. */
15
+ function isUuidV4(value) {
16
+ return UUID_V4.test(value);
17
+ }
18
+ //#endregion
12
19
  //#region src/canvas/format/parse.ts
20
+ /**
21
+ * `"<package>:<widget>"`; `builtin` matches the package pattern, so the same expression also
22
+ * accepts the reserved `builtin:<widget>` form (#67).
23
+ */
24
+ var WIDGET_REFERENCE = /^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*$/;
25
+ var WIDGET_FIELDS = [
26
+ "id",
27
+ "widget",
28
+ "x",
29
+ "y",
30
+ "width",
31
+ "height",
32
+ "rotation",
33
+ "properties",
34
+ "bindings"
35
+ ];
13
36
  /** @throws {SchematicError} when the input does not match the `.cnv` contract. */
14
37
  function parseCanvas(input) {
15
38
  const diagnostics = [];
@@ -26,20 +49,20 @@ function readDocument(value, diagnostics) {
26
49
  }
27
50
  forbidUnknownFields(value, [
28
51
  "version",
29
- "id",
30
52
  "schematic",
31
- "layout"
53
+ "widgets"
32
54
  ], [], diagnostics);
33
55
  const version = readVersion(value, diagnostics);
34
- const id = readStringField(value, "id", [], diagnostics, { nonEmpty: true });
35
- const schematic = readStringField(value, "schematic", [], diagnostics, { nonEmpty: true });
36
- const layout = readLayout(readObjectField(value, "layout", [], diagnostics), diagnostics);
37
- if (version === void 0 || id === void 0 || schematic === void 0 || layout === void 0) return;
38
- return {
56
+ const schematic = readSchematic(value, diagnostics);
57
+ const widgets = readWidgets(readArrayField(value, "widgets", [], diagnostics), diagnostics);
58
+ if (version === void 0 || widgets === void 0) return;
59
+ return schematic === void 0 ? {
60
+ version,
61
+ widgets
62
+ } : {
39
63
  version,
40
- id,
41
64
  schematic,
42
- layout
65
+ widgets
43
66
  };
44
67
  }
45
68
  function readVersion(value, diagnostics) {
@@ -51,173 +74,193 @@ function readVersion(value, diagnostics) {
51
74
  }
52
75
  return 1;
53
76
  }
54
- function readLayout(value, diagnostics) {
55
- if (value === void 0) return;
56
- const layout = Object.create(null);
57
- for (const symbolId of Object.keys(value)) {
58
- const path = ["layout", symbolId];
59
- if (symbolId.length === 0) {
60
- report(diagnostics, "schema/invalid-value", "Layout symbol id must not be empty.", path);
61
- continue;
77
+ function readSchematic(value, diagnostics) {
78
+ if (!hasField(value, "schematic")) return;
79
+ return readStringField(value, "schematic", [], diagnostics, { nonEmpty: true });
80
+ }
81
+ function readWidgets(raw, diagnostics) {
82
+ if (raw === void 0) return;
83
+ const widgets = [];
84
+ const seen = /* @__PURE__ */ new Set();
85
+ raw.forEach((item, index) => {
86
+ const path = ["widgets", String(index)];
87
+ const widget = readWidget(item, path, diagnostics);
88
+ if (widget === void 0) return;
89
+ if (seen.has(widget.id)) {
90
+ report(diagnostics, "id/duplicate", `Duplicate widget id "${widget.id}".`, [...path, "id"]);
91
+ return;
62
92
  }
63
- const placement = readPlacement(value[symbolId], path, diagnostics);
64
- if (placement !== void 0) layout[symbolId] = placement;
65
- }
66
- return layout;
93
+ seen.add(widget.id);
94
+ widgets.push(widget);
95
+ });
96
+ return widgets;
67
97
  }
68
- function readPlacement(value, path, diagnostics) {
98
+ function readWidget(value, path, diagnostics) {
69
99
  if (!isRecord(value)) {
70
- report(diagnostics, "schema/invalid-type", "Placement must be an object.", path);
100
+ report(diagnostics, "schema/invalid-type", "Widget instance must be an object.", path);
71
101
  return;
72
102
  }
73
- forbidUnknownFields(value, [
74
- "x",
75
- "y",
76
- "rotation"
77
- ], path, diagnostics);
103
+ forbidUnknownFields(value, WIDGET_FIELDS, path, diagnostics);
104
+ const id = readId(value, path, diagnostics);
105
+ const widget = readWidgetReference(value, path, diagnostics);
106
+ const placement = readPlacement(value, path, diagnostics);
107
+ const hasProperties = hasField(value, "properties");
108
+ const properties = hasProperties ? readProperties(value["properties"], [...path, "properties"], diagnostics) : void 0;
109
+ const hasBindings = hasField(value, "bindings");
110
+ const bindings = hasBindings ? readBindings(value["bindings"], [...path, "bindings"], diagnostics) : void 0;
111
+ if (id === void 0 || widget === void 0 || placement === void 0 || hasProperties && properties === void 0 || hasBindings && bindings === void 0) return;
112
+ return {
113
+ id,
114
+ widget,
115
+ ...placement,
116
+ ...properties === void 0 ? {} : { properties },
117
+ ...bindings === void 0 ? {} : { bindings }
118
+ };
119
+ }
120
+ function readId(value, path, diagnostics) {
121
+ const id = readStringField(value, "id", path, diagnostics, { nonEmpty: true });
122
+ if (id === void 0) return;
123
+ if (!isUuidV4(id)) {
124
+ report(diagnostics, "schema/invalid-value", "Field \"id\" must be a UUID v4.", [...path, "id"]);
125
+ return;
126
+ }
127
+ return id;
128
+ }
129
+ function readWidgetReference(value, path, diagnostics) {
130
+ const widget = readStringField(value, "widget", path, diagnostics, { nonEmpty: true });
131
+ if (widget === void 0) return;
132
+ if (!WIDGET_REFERENCE.test(widget)) {
133
+ report(diagnostics, "schema/invalid-value", "Field \"widget\" must be \"<package>:<widget>\" or \"builtin:<widget>\".", [...path, "widget"]);
134
+ return;
135
+ }
136
+ return widget;
137
+ }
138
+ function readPlacement(value, path, diagnostics) {
78
139
  const x = readNumberField(value, "x", path, diagnostics);
79
140
  const y = readNumberField(value, "y", path, diagnostics);
141
+ const width = readPositiveNumber(value, "width", path, diagnostics);
142
+ const height = readPositiveNumber(value, "height", path, diagnostics);
80
143
  const rotation = readNumberField(value, "rotation", path, diagnostics);
81
- if (x === void 0 || y === void 0 || rotation === void 0) return;
144
+ if (x === void 0 || y === void 0 || width === void 0 || height === void 0 || rotation === void 0) return;
82
145
  return {
83
146
  x,
84
147
  y,
148
+ width,
149
+ height,
85
150
  rotation
86
151
  };
87
152
  }
88
- //#endregion
89
- //#region src/canvas/format/serialize.ts
90
- /** Serializes with a fixed field order and 2-space indentation, matching the example in #67. */
91
- function serializeCanvas(canvas) {
92
- const layout = Object.create(null);
93
- for (const symbolId of Object.keys(canvas.layout).sort()) {
94
- const placement = canvas.layout[symbolId];
95
- if (placement === void 0) continue;
96
- layout[symbolId] = {
97
- x: placement.x,
98
- y: placement.y,
99
- rotation: placement.rotation
100
- };
153
+ function readPositiveNumber(value, key, path, diagnostics) {
154
+ const field = readNumberField(value, key, path, diagnostics);
155
+ if (field === void 0) return;
156
+ if (field <= 0) {
157
+ report(diagnostics, "schema/invalid-value", `Field "${key}" must be a positive number.`, [...path, key]);
158
+ return;
101
159
  }
102
- const serializable = {
103
- version: canvas.version,
104
- id: canvas.id,
105
- schematic: canvas.schematic,
106
- layout
107
- };
108
- return `${JSON.stringify(serializable, null, 2)}\n`;
160
+ return field;
109
161
  }
110
- //#endregion
111
- //#region src/canvas/load.ts
112
- /** Parses a canvas and validates its layout against the bound schematic. @throws {SchematicError} */
113
- function loadCanvas(source, options = {}) {
114
- const document = parseCanvas(source);
115
- const diagnostics = [];
116
- const symbolIds = options.schematic === void 0 ? void 0 : new Set(options.schematic.sheet.symbols.map((symbol) => symbol.id));
117
- const placements = [];
118
- for (const symbolId of Object.keys(document.layout).sort()) {
119
- const placement = document.layout[symbolId];
120
- if (placement === void 0) continue;
121
- if (symbolIds !== void 0 && !symbolIds.has(symbolId)) {
122
- diagnostics.push({
123
- severity: "error",
124
- code: "canvas/unknown-symbol",
125
- message: `Canvas layout references symbol ${JSON.stringify(symbolId)} which is absent from the bound schematic.`,
126
- pointer: toPointer(["layout", symbolId])
127
- });
162
+ /**
163
+ * Property values are opaque to the format: their types come from the widget definition (#7), so
164
+ * only the map shape and key spelling are checked here.
165
+ */
166
+ function readProperties(value, path, diagnostics) {
167
+ if (!isRecord(value)) {
168
+ report(diagnostics, "schema/invalid-type", "Field \"properties\" must be an object.", path);
169
+ return;
170
+ }
171
+ const properties = Object.create(null);
172
+ let failed = false;
173
+ for (const [key, entry] of Object.entries(value)) {
174
+ if (key.length === 0) {
175
+ report(diagnostics, "schema/invalid-value", "Property keys must not be empty.", path);
176
+ failed = true;
177
+ continue;
178
+ }
179
+ properties[key] = clonePropertyValue(entry);
180
+ }
181
+ return failed ? void 0 : properties;
182
+ }
183
+ /** Values are copied so the parsed document never aliases the caller's input object. */
184
+ function clonePropertyValue(value) {
185
+ if (Array.isArray(value)) return value.map(clonePropertyValue);
186
+ const prototype = isRecord(value) ? Object.getPrototypeOf(value) : void 0;
187
+ if (prototype !== Object.prototype && prototype !== null) return value;
188
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, clonePropertyValue(entry)]));
189
+ }
190
+ function readBindings(value, path, diagnostics) {
191
+ if (!isRecord(value)) {
192
+ report(diagnostics, "schema/invalid-type", "Field \"bindings\" must be an object.", path);
193
+ return;
194
+ }
195
+ const bindings = Object.create(null);
196
+ let failed = false;
197
+ for (const [port, entry] of Object.entries(value)) {
198
+ if (port.length === 0) {
199
+ report(diagnostics, "schema/invalid-value", "Binding port keys must not be empty.", path);
200
+ failed = true;
201
+ continue;
202
+ }
203
+ const target = readBindingTarget(entry, [...path, port], diagnostics);
204
+ if (target === void 0) {
205
+ failed = true;
128
206
  continue;
129
207
  }
130
- placements.push({
131
- symbolId,
132
- placement
133
- });
208
+ bindings[port] = target;
209
+ }
210
+ return failed ? void 0 : bindings;
211
+ }
212
+ function readBindingTarget(value, path, diagnostics) {
213
+ if (!isRecord(value)) {
214
+ report(diagnostics, "schema/invalid-type", "Binding target must be an object.", path);
215
+ return;
134
216
  }
135
- if (diagnostics.length > 0) throw new SchematicError(diagnostics);
217
+ forbidUnknownFields(value, ["symbol", "port"], path, diagnostics);
218
+ const symbol = readStringField(value, "symbol", path, diagnostics, { nonEmpty: true });
219
+ const port = readStringField(value, "port", path, diagnostics, { nonEmpty: true });
220
+ if (symbol === void 0 || port === void 0) return;
136
221
  return {
137
- document,
138
- placements
222
+ symbol,
223
+ port
139
224
  };
140
225
  }
226
+ function hasField(value, key) {
227
+ return Object.prototype.hasOwnProperty.call(value, key);
228
+ }
141
229
  //#endregion
142
- //#region src/canvas/edit.ts
230
+ //#region src/canvas/format/serialize.ts
143
231
  /**
144
- * Applies a canvas command to a copy of `canvas`.
145
- *
146
- * @throws {SchematicError} when the command is invalid; the input canvas is never mutated.
232
+ * Serializes with a fixed field order and 2-space indentation, matching the example in #67.
233
+ * `properties` and `bindings` are written whenever the document carries them, even when empty, so
234
+ * a parse / serialize round trip keeps the original shape.
147
235
  */
148
- function applyCanvasCommand(canvas, command, options = {}) {
149
- const diagnostics = [];
150
- const layout = { ...canvas.layout };
151
- switch (command.type) {
152
- case "set-placement": {
153
- if (command.symbolId.length === 0) {
154
- diagnostics.push({
155
- severity: "error",
156
- code: "schema/invalid-value",
157
- message: "Placement symbol id must not be empty.",
158
- pointer: toPointer(["layout"])
159
- });
160
- break;
161
- }
162
- requireKnownSymbol(command.symbolId, options, diagnostics);
163
- const existing = layout[command.symbolId];
164
- layout[command.symbolId] = {
165
- x: command.x,
166
- y: command.y,
167
- rotation: command.rotation ?? existing?.rotation ?? 0
168
- };
169
- break;
170
- }
171
- case "remove-placement":
172
- if (!Object.prototype.hasOwnProperty.call(layout, command.symbolId)) {
173
- diagnostics.push({
174
- severity: "error",
175
- code: "edit/unknown-entity",
176
- message: `Canvas has no placement for symbol "${command.symbolId}".`,
177
- pointer: toPointer(["layout", command.symbolId])
178
- });
179
- break;
180
- }
181
- delete layout[command.symbolId];
182
- break;
183
- case "bind-schematic":
184
- if (command.schematic.length === 0) {
185
- diagnostics.push({
186
- severity: "error",
187
- code: "schema/invalid-value",
188
- message: "Bound schematic reference must not be empty.",
189
- pointer: toPointer(["schematic"])
190
- });
191
- break;
192
- }
193
- if (diagnostics.length === 0) return {
194
- ...canvas,
195
- schematic: command.schematic
196
- };
197
- break;
198
- default: diagnostics.push({
199
- severity: "error",
200
- code: "edit/invalid-command",
201
- message: `Unsupported canvas command ${JSON.stringify(command.type)}.`
202
- });
203
- }
204
- if (diagnostics.length > 0) throw new SchematicError(diagnostics);
205
- return {
206
- ...canvas,
207
- layout
208
- };
236
+ function serializeCanvas(document) {
237
+ const serializable = { version: document.version };
238
+ if (document.schematic !== void 0) serializable["schematic"] = document.schematic;
239
+ serializable["widgets"] = document.widgets.map(serializeWidget);
240
+ return `${JSON.stringify(serializable, null, 2)}\n`;
209
241
  }
210
- function requireKnownSymbol(symbolId, options, diagnostics) {
211
- if (options.schematic === void 0) return;
212
- if (options.schematic.sheet.symbols.some((symbol) => symbol.id === symbolId)) return;
213
- diagnostics.push({
214
- severity: "error",
215
- code: "canvas/unknown-symbol",
216
- message: `Canvas layout references symbol ${JSON.stringify(symbolId)} which is absent from the bound schematic.`,
217
- pointer: toPointer(["layout", symbolId])
218
- });
242
+ function serializeWidget(widget) {
243
+ const serialized = {
244
+ id: widget.id,
245
+ widget: widget.widget,
246
+ x: widget.x,
247
+ y: widget.y,
248
+ width: widget.width,
249
+ height: widget.height,
250
+ rotation: widget.rotation
251
+ };
252
+ if (widget.properties !== void 0) serialized["properties"] = { ...widget.properties };
253
+ if (widget.bindings !== void 0) {
254
+ const bindings = Object.create(null);
255
+ for (const [port, target] of Object.entries(widget.bindings)) bindings[port] = {
256
+ symbol: target.symbol,
257
+ port: target.port
258
+ };
259
+ serialized["bindings"] = bindings;
260
+ }
261
+ return serialized;
219
262
  }
220
263
  //#endregion
221
- export { CANVAS_VERSION, SchematicError, applyCanvasCommand, loadCanvas, parseCanvas, serializeCanvas };
264
+ export { CANVAS_VERSION, SchematicError, parseCanvas, serializeCanvas };
222
265
 
223
266
  //# sourceMappingURL=index.js.map