@mica-js/core 0.0.0-stage → 0.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DigitalEcho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,61 @@
1
- # Temporary Holding Version
1
+ # @mica-js/core
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Mica 仿真内核(虚拟时间与确定性事件调度),并提供原理图 `.sch` 与画布 `.cnv` 的文档格式子路径。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pnpm add @mica-js/core
9
+ ```
10
+
11
+ ## 入口
12
+
13
+ | 入口 | 内容 |
14
+ | ------------------------- | ------------------------------------------------- |
15
+ | `@mica-js/core` | 仿真内核(虚拟时间与事件调度) |
16
+ | `@mica-js/core/schematic` | 原理图 `.sch`:解析、拓扑推导、编辑命令与属性系统 |
17
+ | `@mica-js/core/canvas` | 画布 `.cnv`:与 `.sch` 绑定的 2D 摆放(`layout`) |
18
+
19
+ 文件合同:`.sch` 见 [#6](https://github.com/x-botlab/mica-js/issues/6),`.cnv` 见 [#67](https://github.com/x-botlab/mica-js/issues/67)。
20
+
21
+ ## 原理图(`./schematic`)
22
+
23
+ ```ts
24
+ import { loadSchematic, parseSchematic, serializeSchematic } from '@mica-js/core/schematic';
25
+
26
+ const loaded = loadSchematic(text, { devices }); // { document, topology }
27
+ const document = parseSchematic(text); // 解析 + 校验,失败抛 SchematicError
28
+ serializeSchematic(document); // 固定字段顺序的 .sch JSON
29
+ ```
30
+
31
+ - 文件名即原理图标识,顶层没有 `id`;`symbols` / `wires` / `junctions` 使用不透明的稳定 `id`。
32
+ - 拓扑按位号、网名与连接关系合并网络;纯绘图分量保留为几何数据。
33
+ - 编辑命令、undo/redo、命中测试、导线段拖动与交点合并都是纯函数,编辑器只做编排。
34
+ - `createDeviceRegistry` 把器件的 `designatorPrefix` 与引脚声明合成为符号几何与位号。
35
+ - 属性(#7):`string` / `number` / `integer` / `boolean` / `enum` / `color` / `image` / `binary`;`image` / `binary` 的值是资源引用 `{ src, mode }`(`file` 为工程根相对路径,`inline` 为 base64,`store` 为宿主资源标识)。
36
+
37
+ ## 画布(`./canvas`)
38
+
39
+ ```ts
40
+ import { loadCanvas, parseCanvas, serializeCanvas } from '@mica-js/core/canvas';
41
+
42
+ const canvas = parseCanvas(text); // version / id / schematic / layout
43
+ const { placements } = loadCanvas(canvas, { schematic: document }); // layout 键须属于该 .sch
44
+ serializeCanvas(canvas);
45
+ ```
46
+
47
+ `layout` 以 symbol 的稳定 `id` 为键,值为 `{ x, y, rotation }`;引用绑定原理图中不存在的实例时报 `canvas/unknown-symbol`。原理图编辑器不依赖这个入口。
48
+
49
+ ## 开发
50
+
51
+ ```bash
52
+ pnpm --filter @mica-js/core run test
53
+ pnpm --filter @mica-js/core run typecheck
54
+ pnpm --filter @mica-js/core run build
55
+ ```
56
+
57
+ ## 相关提案
58
+
59
+ - 原理图格式:[#6](https://github.com/x-botlab/mica-js/issues/6)
60
+ - 属性定义:[#7](https://github.com/x-botlab/mica-js/issues/7)
61
+ - 画布格式:[#67](https://github.com/x-botlab/mica-js/issues/67)
@@ -0,0 +1,201 @@
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
+ /**
26
+ * Schema version of the `.cnv` canvas contract (see x-botlab/mica-js#67).
27
+ *
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.
31
+ */
32
+ export declare const CANVAS_VERSION = 1;
33
+
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>>;
56
+ }
57
+
58
+ export declare interface CanvasLayoutEntry {
59
+ readonly symbolId: string;
60
+ readonly placement: CanvasPlacement;
61
+ }
62
+
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;
68
+ }
69
+
70
+ export declare type CanvasVersion = typeof CANVAS_VERSION;
71
+
72
+ export declare interface Diagnostic {
73
+ readonly severity: DiagnosticSeverity;
74
+ readonly code: DiagnosticCode;
75
+ /** Human readable English message; the `code` is the stable contract. */
76
+ readonly message: string;
77
+ /** JSON Pointer to the offending member, e.g. `/sheet/symbols/0/id`. */
78
+ readonly pointer?: string;
79
+ readonly entity?: DiagnosticEntity;
80
+ readonly related?: readonly DiagnosticRelated[];
81
+ }
82
+
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';
84
+
85
+ export declare interface DiagnosticEntity {
86
+ readonly kind: 'symbol' | 'wire' | 'junction';
87
+ readonly id: string;
88
+ }
89
+
90
+ export declare interface DiagnosticRelated {
91
+ readonly pointer?: string;
92
+ }
93
+
94
+ export declare type DiagnosticSeverity = 'error' | 'warning';
95
+
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
+ /** @throws {SchematicError} when the input does not match the `.cnv` contract. */
111
+ export declare function parseCanvas(input: string | unknown): CanvasDocument;
112
+
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
+ /**
145
+ * Thrown when input violates the schematic format contract.
146
+ * `diagnostics` carries every problem found, so callers can report them all at once.
147
+ */
148
+ export declare class SchematicError extends Error {
149
+ readonly diagnostics: readonly Diagnostic[];
150
+ constructor(diagnostics: readonly Diagnostic[]);
151
+ /** Code of the first diagnostic, for coarse programmatic handling. */
152
+ get code(): DiagnosticCode | undefined;
153
+ }
154
+
155
+ declare interface SchematicJunction {
156
+ readonly id: string;
157
+ readonly x: number;
158
+ 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[];
196
+ }
197
+
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
+ export { }
@@ -0,0 +1,223 @@
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";
2
+ //#region src/canvas/format/types.ts
3
+ /**
4
+ * Schema version of the `.cnv` canvas contract (see x-botlab/mica-js#67).
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.
9
+ */
10
+ var CANVAS_VERSION = 1;
11
+ //#endregion
12
+ //#region src/canvas/format/parse.ts
13
+ /** @throws {SchematicError} when the input does not match the `.cnv` contract. */
14
+ function parseCanvas(input) {
15
+ const diagnostics = [];
16
+ const parsed = parseInput(input, diagnostics);
17
+ if (!parsed.ok) throw new SchematicError(diagnostics);
18
+ const value = readDocument(parsed.value, diagnostics);
19
+ if (value === void 0 || hasErrors(diagnostics)) throw new SchematicError(diagnostics);
20
+ return value;
21
+ }
22
+ function readDocument(value, diagnostics) {
23
+ if (!isRecord(value)) {
24
+ report(diagnostics, "json/not-object", "Canvas must be a JSON object.", []);
25
+ return;
26
+ }
27
+ forbidUnknownFields(value, [
28
+ "version",
29
+ "id",
30
+ "schematic",
31
+ "layout"
32
+ ], [], diagnostics);
33
+ 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 {
39
+ version,
40
+ id,
41
+ schematic,
42
+ layout
43
+ };
44
+ }
45
+ function readVersion(value, diagnostics) {
46
+ const version = readIntegerField(value, "version", [], diagnostics);
47
+ if (version === void 0) return;
48
+ if (version !== 1) {
49
+ report(diagnostics, "schema/unsupported-version", `Unsupported version ${version}; expected 1.`, ["version"]);
50
+ return;
51
+ }
52
+ return 1;
53
+ }
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;
62
+ }
63
+ const placement = readPlacement(value[symbolId], path, diagnostics);
64
+ if (placement !== void 0) layout[symbolId] = placement;
65
+ }
66
+ return layout;
67
+ }
68
+ function readPlacement(value, path, diagnostics) {
69
+ if (!isRecord(value)) {
70
+ report(diagnostics, "schema/invalid-type", "Placement must be an object.", path);
71
+ return;
72
+ }
73
+ forbidUnknownFields(value, [
74
+ "x",
75
+ "y",
76
+ "rotation"
77
+ ], path, diagnostics);
78
+ const x = readNumberField(value, "x", path, diagnostics);
79
+ const y = readNumberField(value, "y", path, diagnostics);
80
+ const rotation = readNumberField(value, "rotation", path, diagnostics);
81
+ if (x === void 0 || y === void 0 || rotation === void 0) return;
82
+ return {
83
+ x,
84
+ y,
85
+ rotation
86
+ };
87
+ }
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
+ };
101
+ }
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`;
109
+ }
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
+ });
128
+ continue;
129
+ }
130
+ placements.push({
131
+ symbolId,
132
+ placement
133
+ });
134
+ }
135
+ if (diagnostics.length > 0) throw new SchematicError(diagnostics);
136
+ return {
137
+ document,
138
+ placements
139
+ };
140
+ }
141
+ //#endregion
142
+ //#region src/canvas/edit.ts
143
+ /**
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.
147
+ */
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
+ };
209
+ }
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
+ });
219
+ }
220
+ //#endregion
221
+ export { CANVAS_VERSION, SchematicError, applyCanvasCommand, loadCanvas, parseCanvas, serializeCanvas };
222
+
223
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/canvas/format/types.ts","../../src/canvas/format/parse.ts","../../src/canvas/format/serialize.ts","../../src/canvas/load.ts","../../src/canvas/edit.ts"],"sourcesContent":["/**\n * Schema version of the `.cnv` canvas contract (see x-botlab/mica-js#67).\n *\n * A canvas is a 2D layout: one `.cnv` binds exactly one `.sch` (see x-botlab/mica-js#6) and keeps\n * a placement per symbol id. Schematic connections stay in the `.sch`; the canvas only adds 2D\n * coordinates and rotation.\n */\nexport const CANVAS_VERSION = 1;\n\nexport type CanvasVersion = typeof CANVAS_VERSION;\n\n/** 2D placement of one symbol in a canvas. */\nexport interface CanvasPlacement {\n readonly x: number;\n readonly y: number;\n readonly rotation: number;\n}\n\nexport interface CanvasDocument {\n readonly version: CanvasVersion;\n /** Canvas identity, unique within one project's `.cnv` files. */\n readonly id: string;\n /** Project-root relative POSIX path of the bound `.sch`. */\n readonly schematic: string;\n /** Symbol id → placement; ids are stable, so renaming a designator keeps the entry. */\n readonly layout: Readonly<Record<string, CanvasPlacement>>;\n}\n","import type { Diagnostic } from '../../schematic/format/diagnostics';\nimport { SchematicError } from '../../schematic/format/errors';\nimport { parseInput } from '../../schematic/format/input';\nimport {\n forbidUnknownFields,\n hasErrors,\n isRecord,\n readIntegerField,\n readNumberField,\n readObjectField,\n readStringField,\n report,\n} from '../../schematic/format/validate';\nimport type { CanvasDocument, CanvasPlacement } from './types';\nimport { CANVAS_VERSION } from './types';\n\n/** @throws {SchematicError} when the input does not match the `.cnv` contract. */\nexport function parseCanvas(input: string | unknown): CanvasDocument {\n const diagnostics: Diagnostic[] = [];\n const parsed = parseInput(input, diagnostics);\n if (!parsed.ok) {\n throw new SchematicError(diagnostics);\n }\n const value = readDocument(parsed.value, diagnostics);\n if (value === undefined || hasErrors(diagnostics)) {\n throw new SchematicError(diagnostics);\n }\n return value;\n}\n\nfunction readDocument(value: unknown, diagnostics: Diagnostic[]): CanvasDocument | undefined {\n if (!isRecord(value)) {\n report(diagnostics, 'json/not-object', 'Canvas must be a JSON object.', []);\n return undefined;\n }\n forbidUnknownFields(value, ['version', 'id', 'schematic', 'layout'], [], diagnostics);\n const version = readVersion(value, diagnostics);\n const id = readStringField(value, 'id', [], diagnostics, { nonEmpty: true });\n const schematic = readStringField(value, 'schematic', [], diagnostics, { nonEmpty: true });\n const layout = readLayout(readObjectField(value, 'layout', [], diagnostics), diagnostics);\n if (\n version === undefined ||\n id === undefined ||\n schematic === undefined ||\n layout === undefined\n ) {\n return undefined;\n }\n return { version, id, schematic, layout };\n}\n\nfunction readVersion(\n value: Record<string, unknown>,\n diagnostics: Diagnostic[],\n): typeof CANVAS_VERSION | undefined {\n const version = readIntegerField(value, 'version', [], diagnostics);\n if (version === undefined) {\n return undefined;\n }\n if (version !== CANVAS_VERSION) {\n report(\n diagnostics,\n 'schema/unsupported-version',\n `Unsupported version ${version}; expected ${CANVAS_VERSION}.`,\n ['version'],\n );\n return undefined;\n }\n return CANVAS_VERSION;\n}\n\nfunction readLayout(\n value: Record<string, unknown> | undefined,\n diagnostics: Diagnostic[],\n): Readonly<Record<string, CanvasPlacement>> | undefined {\n if (value === undefined) {\n return undefined;\n }\n const layout: Record<string, CanvasPlacement> = Object.create(null);\n for (const symbolId of Object.keys(value)) {\n const path = ['layout', symbolId];\n if (symbolId.length === 0) {\n report(diagnostics, 'schema/invalid-value', 'Layout symbol id must not be empty.', path);\n continue;\n }\n const placement = readPlacement(value[symbolId], path, diagnostics);\n if (placement !== undefined) {\n layout[symbolId] = placement;\n }\n }\n return layout;\n}\n\nfunction readPlacement(\n value: unknown,\n path: readonly string[],\n diagnostics: Diagnostic[],\n): CanvasPlacement | undefined {\n if (!isRecord(value)) {\n report(diagnostics, 'schema/invalid-type', 'Placement must be an object.', path);\n return undefined;\n }\n forbidUnknownFields(value, ['x', 'y', 'rotation'], path, diagnostics);\n const x = readNumberField(value, 'x', path, diagnostics);\n const y = readNumberField(value, 'y', path, diagnostics);\n const rotation = readNumberField(value, 'rotation', path, diagnostics);\n if (x === undefined || y === undefined || rotation === undefined) {\n return undefined;\n }\n return { x, y, rotation };\n}\n","import type { CanvasDocument, CanvasPlacement } from './types';\n\n/** Serializes with a fixed field order and 2-space indentation, matching the example in #67. */\nexport function serializeCanvas(canvas: CanvasDocument): string {\n const layout: Record<string, CanvasPlacement> = Object.create(null);\n for (const symbolId of Object.keys(canvas.layout).sort()) {\n const placement = canvas.layout[symbolId];\n if (placement === undefined) {\n continue;\n }\n layout[symbolId] = { x: placement.x, y: placement.y, rotation: placement.rotation };\n }\n const serializable = {\n version: canvas.version,\n id: canvas.id,\n schematic: canvas.schematic,\n layout,\n };\n return `${JSON.stringify(serializable, null, 2)}\\n`;\n}\n","import type { Diagnostic } from '../schematic/format/diagnostics';\nimport { SchematicError } from '../schematic/format/errors';\nimport type { SchematicDocument } from '../schematic/format/types';\nimport { toPointer } from '../schematic/format/validate';\nimport { parseCanvas } from './format/parse';\nimport type { CanvasDocument, CanvasPlacement } from './format/types';\n\nexport interface LoadCanvasOptions {\n /** Bound schematic; layout keys are validated against its symbols when provided. */\n readonly schematic?: SchematicDocument;\n}\n\nexport interface CanvasLayoutEntry {\n readonly symbolId: string;\n readonly placement: CanvasPlacement;\n}\n\nexport interface LoadedCanvas {\n readonly document: CanvasDocument;\n /** Layout entries sorted by symbol id, so loading is deterministic. */\n readonly placements: readonly CanvasLayoutEntry[];\n}\n\n/** Parses a canvas and validates its layout against the bound schematic. @throws {SchematicError} */\nexport function loadCanvas(\n source: string | CanvasDocument,\n options: LoadCanvasOptions = {},\n): LoadedCanvas {\n const document = parseCanvas(source);\n const diagnostics: Diagnostic[] = [];\n\n const symbolIds =\n options.schematic === undefined\n ? undefined\n : new Set(options.schematic.sheet.symbols.map((symbol) => symbol.id));\n\n const placements: CanvasLayoutEntry[] = [];\n for (const symbolId of Object.keys(document.layout).sort()) {\n const placement = document.layout[symbolId];\n if (placement === undefined) {\n continue;\n }\n if (symbolIds !== undefined && !symbolIds.has(symbolId)) {\n diagnostics.push({\n severity: 'error',\n code: 'canvas/unknown-symbol',\n message: `Canvas layout references symbol ${JSON.stringify(symbolId)} which is absent from the bound schematic.`,\n pointer: toPointer(['layout', symbolId]),\n });\n continue;\n }\n placements.push({ symbolId, placement });\n }\n\n if (diagnostics.length > 0) {\n throw new SchematicError(diagnostics);\n }\n return { document, placements };\n}\n","import type { Diagnostic } from '../schematic/format/diagnostics';\nimport { SchematicError } from '../schematic/format/errors';\nimport type { SchematicDocument } from '../schematic/format/types';\nimport { toPointer } from '../schematic/format/validate';\nimport type { CanvasDocument, CanvasPlacement } from './format/types';\n\nexport type CanvasCommand =\n | {\n readonly type: 'set-placement';\n readonly symbolId: string;\n readonly x: number;\n readonly y: number;\n readonly rotation?: number;\n }\n | { readonly type: 'remove-placement'; readonly symbolId: string }\n | { readonly type: 'bind-schematic'; readonly schematic: string };\n\nexport interface ApplyCanvasCommandOptions {\n /** Bound schematic; symbol references are validated against it when provided. */\n readonly schematic?: SchematicDocument;\n}\n\n/**\n * Applies a canvas command to a copy of `canvas`.\n *\n * @throws {SchematicError} when the command is invalid; the input canvas is never mutated.\n */\nexport function applyCanvasCommand(\n canvas: CanvasDocument,\n command: CanvasCommand,\n options: ApplyCanvasCommandOptions = {},\n): CanvasDocument {\n const diagnostics: Diagnostic[] = [];\n const layout: Record<string, CanvasPlacement> = { ...canvas.layout };\n\n switch (command.type) {\n case 'set-placement': {\n if (command.symbolId.length === 0) {\n diagnostics.push({\n severity: 'error',\n code: 'schema/invalid-value',\n message: 'Placement symbol id must not be empty.',\n pointer: toPointer(['layout']),\n });\n break;\n }\n requireKnownSymbol(command.symbolId, options, diagnostics);\n const existing = layout[command.symbolId];\n layout[command.symbolId] = {\n x: command.x,\n y: command.y,\n rotation: command.rotation ?? existing?.rotation ?? 0,\n };\n break;\n }\n case 'remove-placement': {\n if (!Object.prototype.hasOwnProperty.call(layout, command.symbolId)) {\n diagnostics.push({\n severity: 'error',\n code: 'edit/unknown-entity',\n message: `Canvas has no placement for symbol \"${command.symbolId}\".`,\n pointer: toPointer(['layout', command.symbolId]),\n });\n break;\n }\n delete layout[command.symbolId];\n break;\n }\n case 'bind-schematic': {\n if (command.schematic.length === 0) {\n diagnostics.push({\n severity: 'error',\n code: 'schema/invalid-value',\n message: 'Bound schematic reference must not be empty.',\n pointer: toPointer(['schematic']),\n });\n break;\n }\n if (diagnostics.length === 0) {\n return { ...canvas, schematic: command.schematic };\n }\n break;\n }\n default: {\n diagnostics.push({\n severity: 'error',\n code: 'edit/invalid-command',\n message: `Unsupported canvas command ${JSON.stringify((command as { type?: unknown }).type)}.`,\n });\n }\n }\n\n if (diagnostics.length > 0) {\n throw new SchematicError(diagnostics);\n }\n return { ...canvas, layout };\n}\n\nfunction requireKnownSymbol(\n symbolId: string,\n options: ApplyCanvasCommandOptions,\n diagnostics: Diagnostic[],\n): void {\n if (options.schematic === undefined) {\n return;\n }\n if (options.schematic.sheet.symbols.some((symbol) => symbol.id === symbolId)) {\n return;\n }\n diagnostics.push({\n severity: 'error',\n code: 'canvas/unknown-symbol',\n message: `Canvas layout references symbol ${JSON.stringify(symbolId)} which is absent from the bound schematic.`,\n pointer: toPointer(['layout', symbolId]),\n });\n}\n"],"mappings":";;;;;;;;;AAOA,IAAa,iBAAiB;;;;ACU9B,SAAgB,YAAY,OAAyC;CACnE,MAAM,cAA4B,CAAC;CACnC,MAAM,SAAS,WAAW,OAAO,WAAW;CAC5C,IAAI,CAAC,OAAO,IACV,MAAM,IAAI,eAAe,WAAW;CAEtC,MAAM,QAAQ,aAAa,OAAO,OAAO,WAAW;CACpD,IAAI,UAAU,KAAA,KAAa,UAAU,WAAW,GAC9C,MAAM,IAAI,eAAe,WAAW;CAEtC,OAAO;AACT;AAEA,SAAS,aAAa,OAAgB,aAAuD;CAC3F,IAAI,CAAC,SAAS,KAAK,GAAG;EACpB,OAAO,aAAa,mBAAmB,iCAAiC,CAAC,CAAC;EAC1E;CACF;CACA,oBAAoB,OAAO;EAAC;EAAW;EAAM;EAAa;CAAQ,GAAG,CAAC,GAAG,WAAW;CACpF,MAAM,UAAU,YAAY,OAAO,WAAW;CAC9C,MAAM,KAAK,gBAAgB,OAAO,MAAM,CAAC,GAAG,aAAa,EAAE,UAAU,KAAK,CAAC;CAC3E,MAAM,YAAY,gBAAgB,OAAO,aAAa,CAAC,GAAG,aAAa,EAAE,UAAU,KAAK,CAAC;CACzF,MAAM,SAAS,WAAW,gBAAgB,OAAO,UAAU,CAAC,GAAG,WAAW,GAAG,WAAW;CACxF,IACE,YAAY,KAAA,KACZ,OAAO,KAAA,KACP,cAAc,KAAA,KACd,WAAW,KAAA,GAEX;CAEF,OAAO;EAAE;EAAS;EAAI;EAAW;CAAO;AAC1C;AAEA,SAAS,YACP,OACA,aACmC;CACnC,MAAM,UAAU,iBAAiB,OAAO,WAAW,CAAC,GAAG,WAAW;CAClE,IAAI,YAAY,KAAA,GACd;CAEF,IAAI,YAAA,GAA4B;EAC9B,OACE,aACA,8BACA,uBAAuB,QAAQ,gBAC/B,CAAC,SAAS,CACZ;EACA;CACF;CACA,OAAA;AACF;AAEA,SAAS,WACP,OACA,aACuD;CACvD,IAAI,UAAU,KAAA,GACZ;CAEF,MAAM,SAA0C,OAAO,OAAO,IAAI;CAClE,KAAK,MAAM,YAAY,OAAO,KAAK,KAAK,GAAG;EACzC,MAAM,OAAO,CAAC,UAAU,QAAQ;EAChC,IAAI,SAAS,WAAW,GAAG;GACzB,OAAO,aAAa,wBAAwB,uCAAuC,IAAI;GACvF;EACF;EACA,MAAM,YAAY,cAAc,MAAM,WAAW,MAAM,WAAW;EAClE,IAAI,cAAc,KAAA,GAChB,OAAO,YAAY;CAEvB;CACA,OAAO;AACT;AAEA,SAAS,cACP,OACA,MACA,aAC6B;CAC7B,IAAI,CAAC,SAAS,KAAK,GAAG;EACpB,OAAO,aAAa,uBAAuB,gCAAgC,IAAI;EAC/E;CACF;CACA,oBAAoB,OAAO;EAAC;EAAK;EAAK;CAAU,GAAG,MAAM,WAAW;CACpE,MAAM,IAAI,gBAAgB,OAAO,KAAK,MAAM,WAAW;CACvD,MAAM,IAAI,gBAAgB,OAAO,KAAK,MAAM,WAAW;CACvD,MAAM,WAAW,gBAAgB,OAAO,YAAY,MAAM,WAAW;CACrE,IAAI,MAAM,KAAA,KAAa,MAAM,KAAA,KAAa,aAAa,KAAA,GACrD;CAEF,OAAO;EAAE;EAAG;EAAG;CAAS;AAC1B;;;;AC3GA,SAAgB,gBAAgB,QAAgC;CAC9D,MAAM,SAA0C,OAAO,OAAO,IAAI;CAClE,KAAK,MAAM,YAAY,OAAO,KAAK,OAAO,MAAM,CAAC,CAAC,KAAK,GAAG;EACxD,MAAM,YAAY,OAAO,OAAO;EAChC,IAAI,cAAc,KAAA,GAChB;EAEF,OAAO,YAAY;GAAE,GAAG,UAAU;GAAG,GAAG,UAAU;GAAG,UAAU,UAAU;EAAS;CACpF;CACA,MAAM,eAAe;EACnB,SAAS,OAAO;EAChB,IAAI,OAAO;EACX,WAAW,OAAO;EAClB;CACF;CACA,OAAO,GAAG,KAAK,UAAU,cAAc,MAAM,CAAC,EAAE;AAClD;;;;ACKA,SAAgB,WACd,QACA,UAA6B,CAAC,GAChB;CACd,MAAM,WAAW,YAAY,MAAM;CACnC,MAAM,cAA4B,CAAC;CAEnC,MAAM,YACJ,QAAQ,cAAc,KAAA,IAClB,KAAA,IACA,IAAI,IAAI,QAAQ,UAAU,MAAM,QAAQ,KAAK,WAAW,OAAO,EAAE,CAAC;CAExE,MAAM,aAAkC,CAAC;CACzC,KAAK,MAAM,YAAY,OAAO,KAAK,SAAS,MAAM,CAAC,CAAC,KAAK,GAAG;EAC1D,MAAM,YAAY,SAAS,OAAO;EAClC,IAAI,cAAc,KAAA,GAChB;EAEF,IAAI,cAAc,KAAA,KAAa,CAAC,UAAU,IAAI,QAAQ,GAAG;GACvD,YAAY,KAAK;IACf,UAAU;IACV,MAAM;IACN,SAAS,mCAAmC,KAAK,UAAU,QAAQ,EAAE;IACrE,SAAS,UAAU,CAAC,UAAU,QAAQ,CAAC;GACzC,CAAC;GACD;EACF;EACA,WAAW,KAAK;GAAE;GAAU;EAAU,CAAC;CACzC;CAEA,IAAI,YAAY,SAAS,GACvB,MAAM,IAAI,eAAe,WAAW;CAEtC,OAAO;EAAE;EAAU;CAAW;AAChC;;;;;;;;AC/BA,SAAgB,mBACd,QACA,SACA,UAAqC,CAAC,GACtB;CAChB,MAAM,cAA4B,CAAC;CACnC,MAAM,SAA0C,EAAE,GAAG,OAAO,OAAO;CAEnE,QAAQ,QAAQ,MAAhB;EACE,KAAK,iBAAiB;GACpB,IAAI,QAAQ,SAAS,WAAW,GAAG;IACjC,YAAY,KAAK;KACf,UAAU;KACV,MAAM;KACN,SAAS;KACT,SAAS,UAAU,CAAC,QAAQ,CAAC;IAC/B,CAAC;IACD;GACF;GACA,mBAAmB,QAAQ,UAAU,SAAS,WAAW;GACzD,MAAM,WAAW,OAAO,QAAQ;GAChC,OAAO,QAAQ,YAAY;IACzB,GAAG,QAAQ;IACX,GAAG,QAAQ;IACX,UAAU,QAAQ,YAAY,UAAU,YAAY;GACtD;GACA;EACF;EACA,KAAK;GACH,IAAI,CAAC,OAAO,UAAU,eAAe,KAAK,QAAQ,QAAQ,QAAQ,GAAG;IACnE,YAAY,KAAK;KACf,UAAU;KACV,MAAM;KACN,SAAS,uCAAuC,QAAQ,SAAS;KACjE,SAAS,UAAU,CAAC,UAAU,QAAQ,QAAQ,CAAC;IACjD,CAAC;IACD;GACF;GACA,OAAO,OAAO,QAAQ;GACtB;EAEF,KAAK;GACH,IAAI,QAAQ,UAAU,WAAW,GAAG;IAClC,YAAY,KAAK;KACf,UAAU;KACV,MAAM;KACN,SAAS;KACT,SAAS,UAAU,CAAC,WAAW,CAAC;IAClC,CAAC;IACD;GACF;GACA,IAAI,YAAY,WAAW,GACzB,OAAO;IAAE,GAAG;IAAQ,WAAW,QAAQ;GAAU;GAEnD;EAEF,SACE,YAAY,KAAK;GACf,UAAU;GACV,MAAM;GACN,SAAS,8BAA8B,KAAK,UAAW,QAA+B,IAAI,EAAE;EAC9F,CAAC;CAEL;CAEA,IAAI,YAAY,SAAS,GACvB,MAAM,IAAI,eAAe,WAAW;CAEtC,OAAO;EAAE,GAAG;EAAQ;CAAO;AAC7B;AAEA,SAAS,mBACP,UACA,SACA,aACM;CACN,IAAI,QAAQ,cAAc,KAAA,GACxB;CAEF,IAAI,QAAQ,UAAU,MAAM,QAAQ,MAAM,WAAW,OAAO,OAAO,QAAQ,GACzE;CAEF,YAAY,KAAK;EACf,UAAU;EACV,MAAM;EACN,SAAS,mCAAmC,KAAK,UAAU,QAAQ,EAAE;EACrE,SAAS,UAAU,CAAC,UAAU,QAAQ,CAAC;CACzC,CAAC;AACH"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Placeholder export so the build and test pipeline has something to verify.
3
+ * Replace with the virtual time and event scheduler API.
4
+ */
5
+ export declare const MICA_CORE_VERSION = "0.0.0";
6
+
7
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ //#region src/index.ts
2
+ /**
3
+ * Placeholder export so the build and test pipeline has something to verify.
4
+ * Replace with the virtual time and event scheduler API.
5
+ */
6
+ var MICA_CORE_VERSION = "0.0.0";
7
+ //#endregion
8
+ export { MICA_CORE_VERSION };
9
+
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Placeholder export so the build and test pipeline has something to verify.\n * Replace with the virtual time and event scheduler API.\n */\nexport const MICA_CORE_VERSION = '0.0.0';\n"],"mappings":";;;;;AAIA,IAAa,oBAAoB"}