@grayhaven/nerve 0.1.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/dist/index.js ADDED
@@ -0,0 +1,799 @@
1
+ // src/dsl.ts
2
+ var toRef = (target) => typeof target === "string" ? target : target.ref;
3
+ var connector = (ref, part, opts) => {
4
+ const pins = {};
5
+ for (const [pin, signal] of Object.entries(opts.pins)) {
6
+ pins[String(pin)] = signal;
7
+ }
8
+ return {
9
+ kind: "connector",
10
+ ref,
11
+ part,
12
+ pins,
13
+ pin: (pin) => ({
14
+ kind: "pin-ref",
15
+ connector: ref,
16
+ pin: String(pin)
17
+ })
18
+ };
19
+ };
20
+ var toEndpoint = (input) => input.kind === "splice" ? { kind: "splice-ref", splice: input.id } : input;
21
+ var wire = (id, from, to, props = {}) => ({
22
+ kind: "wire",
23
+ id,
24
+ from: toEndpoint(from),
25
+ to: toEndpoint(to),
26
+ ...props
27
+ });
28
+ var splice = (id, props = {}) => ({
29
+ kind: "splice",
30
+ id,
31
+ ...props
32
+ });
33
+ var cable = (id, props = {}) => ({
34
+ kind: "cable",
35
+ id,
36
+ ...props
37
+ });
38
+ var branch = (id, props) => {
39
+ const { path, ...rest } = props;
40
+ return {
41
+ kind: "branch",
42
+ id,
43
+ path: path.map(toRef),
44
+ ...rest
45
+ };
46
+ };
47
+ var label = (id, props) => {
48
+ const { attachTo, offsetFrom, ...rest } = props;
49
+ return {
50
+ kind: "label",
51
+ id,
52
+ attachTo: toRef(attachTo),
53
+ ...offsetFrom !== void 0 ? { offsetFrom: toRef(offsetFrom) } : {},
54
+ ...rest
55
+ };
56
+ };
57
+ var harness = (id, props) => ({
58
+ kind: "harness",
59
+ id,
60
+ revision: props.revision,
61
+ units: props.units,
62
+ metadata: props.metadata ?? {},
63
+ connectors: props.connectors,
64
+ wires: props.wires,
65
+ branches: props.branches ?? [],
66
+ labels: props.labels ?? [],
67
+ splices: props.splices ?? [],
68
+ cables: props.cables ?? []
69
+ });
70
+
71
+ // src/diagnostics.ts
72
+ var DiagnosticSeverity = {
73
+ Error: "error",
74
+ Warning: "warning",
75
+ Info: "info"
76
+ };
77
+ var Codes = {
78
+ DuplicateConnectorRef: "HK-CONN-001",
79
+ UndefinedConnectorRef: "HK-CONN-002",
80
+ UndefinedPinRef: "HK-CONN-003",
81
+ DuplicateWireId: "HK-WIRE-001",
82
+ WireEndpointsIdentical: "HK-WIRE-002",
83
+ DuplicateBranchId: "HK-BRANCH-001",
84
+ BranchUndefinedEndpoint: "HK-BRANCH-002",
85
+ DuplicateLabelId: "HK-LABEL-001",
86
+ LabelUndefinedTarget: "HK-LABEL-002",
87
+ DuplicateSpliceId: "HK-SPLICE-001",
88
+ UndefinedSpliceRef: "HK-SPLICE-002",
89
+ SpliceTooFewWires: "HK-SPLICE-003",
90
+ SpliceUndefinedBranch: "HK-SPLICE-004",
91
+ DuplicateCableId: "HK-CABLE-001",
92
+ UndefinedCableRef: "HK-CABLE-002"
93
+ };
94
+ var hasErrors = (diagnostics) => diagnostics.some((d) => d.severity === DiagnosticSeverity.Error);
95
+
96
+ // src/hir/schema.ts
97
+ import { Schema } from "effect";
98
+ var HIR_SCHEMA_VERSION = "0.1.0";
99
+ var HirUnits = Schema.Literal("mm", "in");
100
+ var HirPinRef = Schema.Struct({
101
+ connector: Schema.String,
102
+ pin: Schema.String
103
+ });
104
+ var HirSpliceRef = Schema.Struct({
105
+ splice: Schema.String
106
+ });
107
+ var HirEndpoint = Schema.Union(HirPinRef, HirSpliceRef);
108
+ var HirPin = Schema.Struct({
109
+ pin: Schema.String,
110
+ signal: Schema.optional(Schema.String)
111
+ });
112
+ var HirConnector = Schema.Struct({
113
+ ref: Schema.String,
114
+ mpn: Schema.String,
115
+ manufacturer: Schema.optional(Schema.String),
116
+ family: Schema.optional(Schema.String),
117
+ description: Schema.optional(Schema.String),
118
+ gender: Schema.optional(Schema.Literal("plug", "receptacle", "hermaphroditic")),
119
+ pinCount: Schema.Number,
120
+ wireGaugeRange: Schema.optional(
121
+ Schema.Struct({ min: Schema.String, max: Schema.String })
122
+ ),
123
+ pins: Schema.Array(HirPin)
124
+ });
125
+ var HirWire = Schema.Struct({
126
+ id: Schema.String,
127
+ from: HirEndpoint,
128
+ to: HirEndpoint,
129
+ gauge: Schema.optional(Schema.String),
130
+ color: Schema.optional(Schema.String),
131
+ stripe: Schema.optional(Schema.String),
132
+ length: Schema.optional(Schema.Number),
133
+ lengthTolerance: Schema.optional(Schema.Number),
134
+ signal: Schema.optional(Schema.String),
135
+ insulation: Schema.optional(Schema.String),
136
+ voltageRating: Schema.optional(Schema.Number),
137
+ temperatureRating: Schema.optional(Schema.Number),
138
+ currentEstimate: Schema.optional(Schema.Number),
139
+ twistGroup: Schema.optional(Schema.String),
140
+ shieldGroup: Schema.optional(Schema.String),
141
+ cable: Schema.optional(Schema.String),
142
+ conductor: Schema.optional(Schema.String),
143
+ branch: Schema.optional(Schema.String),
144
+ notes: Schema.optional(Schema.String)
145
+ });
146
+ var HirSplice = Schema.Struct({
147
+ id: Schema.String,
148
+ type: Schema.optional(Schema.String),
149
+ part: Schema.optional(Schema.String),
150
+ branch: Schema.optional(Schema.String),
151
+ location: Schema.optional(Schema.Number),
152
+ notes: Schema.optional(Schema.String),
153
+ /** Wire IDs attached to this splice (computed by the compiler). */
154
+ wires: Schema.Array(Schema.String)
155
+ });
156
+ var HirCable = Schema.Struct({
157
+ id: Schema.String,
158
+ type: Schema.optional(Schema.String),
159
+ conductors: Schema.optional(Schema.Number),
160
+ shield: Schema.optional(Schema.String),
161
+ jacket: Schema.optional(Schema.String),
162
+ outerDiameter: Schema.optional(Schema.Number),
163
+ /** Longest member wire — the cable cut length (computed). */
164
+ cutLength: Schema.optional(Schema.Number),
165
+ notes: Schema.optional(Schema.String),
166
+ /** Member wire IDs (computed by the compiler). */
167
+ wires: Schema.Array(Schema.String)
168
+ });
169
+ var HirBranch = Schema.Struct({
170
+ id: Schema.String,
171
+ path: Schema.Array(Schema.String),
172
+ parent: Schema.optional(Schema.String),
173
+ sleeve: Schema.optional(Schema.String),
174
+ nominalLength: Schema.optional(Schema.Number),
175
+ breakoutDistance: Schema.optional(Schema.Number)
176
+ });
177
+ var HirLabel = Schema.Struct({
178
+ id: Schema.String,
179
+ text: Schema.String,
180
+ attachTo: Schema.String,
181
+ offsetFrom: Schema.optional(Schema.String),
182
+ distance: Schema.optional(Schema.Number),
183
+ material: Schema.optional(Schema.String),
184
+ quantity: Schema.optional(Schema.Number)
185
+ });
186
+ var HirBomItem = Schema.Struct({
187
+ internalPartId: Schema.optional(Schema.String),
188
+ mpn: Schema.String,
189
+ manufacturer: Schema.optional(Schema.String),
190
+ description: Schema.optional(Schema.String),
191
+ category: Schema.optional(Schema.String),
192
+ quantity: Schema.Number,
193
+ unitOfMeasure: Schema.String,
194
+ usedBy: Schema.Array(Schema.String),
195
+ notes: Schema.optional(Schema.String)
196
+ });
197
+ var HirDiagnostic = Schema.Struct({
198
+ code: Schema.String,
199
+ severity: Schema.Literal("error", "warning", "info"),
200
+ message: Schema.String,
201
+ target: Schema.optional(Schema.String)
202
+ });
203
+ var Hir = Schema.Struct({
204
+ schemaVersion: Schema.Literal(HIR_SCHEMA_VERSION),
205
+ harness: Schema.Struct({
206
+ id: Schema.String,
207
+ revision: Schema.String,
208
+ units: HirUnits,
209
+ metadata: Schema.Record({ key: Schema.String, value: Schema.String })
210
+ }),
211
+ connectors: Schema.Array(HirConnector),
212
+ wires: Schema.Array(HirWire),
213
+ cables: Schema.Array(HirCable),
214
+ branches: Schema.Array(HirBranch),
215
+ splices: Schema.Array(HirSplice),
216
+ labels: Schema.Array(HirLabel),
217
+ bom: Schema.Array(HirBomItem),
218
+ diagnostics: Schema.Array(HirDiagnostic),
219
+ layoutHints: Schema.Array(Schema.Unknown),
220
+ exports: Schema.Record({ key: Schema.String, value: Schema.Unknown })
221
+ });
222
+ var isPinEndpoint = (e) => "connector" in e;
223
+ var endpointLabel = (e) => isPinEndpoint(e) ? `${e.connector}.${e.pin}` : e.splice;
224
+ var decodeHir = Schema.decodeUnknownSync(Hir);
225
+ var decodeHirEffect = Schema.decodeUnknown(Hir);
226
+ var encodeHir = Schema.encodeSync(Hir);
227
+ var refs = {
228
+ connector: (ref) => `connector:${ref}`,
229
+ pin: (connector2, pin) => `connector:${connector2}.pin:${pin}`,
230
+ wire: (id) => `wire:${id}`,
231
+ branch: (id) => `branch:${id}`,
232
+ splice: (id) => `splice:${id}`,
233
+ label: (id) => `label:${id}`,
234
+ bom: (mpn) => `bom:${mpn}`
235
+ };
236
+
237
+ // src/compile.ts
238
+ var comparePins = (a, b) => {
239
+ const na = Number(a);
240
+ const nb = Number(b);
241
+ if (Number.isFinite(na) && Number.isFinite(nb)) return na - nb;
242
+ return a < b ? -1 : a > b ? 1 : 0;
243
+ };
244
+ var compareStrings = (a, b) => a < b ? -1 : a > b ? 1 : 0;
245
+ var compact = (obj) => {
246
+ const out = {};
247
+ for (const [key, value] of Object.entries(obj)) {
248
+ if (value !== void 0) out[key] = value;
249
+ }
250
+ return out;
251
+ };
252
+ var compileDesign = (design) => {
253
+ const diagnostics = [];
254
+ const report = (code, message, target, severity = DiagnosticSeverity.Error) => {
255
+ diagnostics.push(
256
+ target !== void 0 ? { code, severity, message, target } : { code, severity, message }
257
+ );
258
+ };
259
+ const connectorByRef = /* @__PURE__ */ new Map();
260
+ for (const c of design.connectors) {
261
+ if (connectorByRef.has(c.ref)) {
262
+ report(
263
+ Codes.DuplicateConnectorRef,
264
+ `Connector reference ${c.ref} is defined more than once.`,
265
+ refs.connector(c.ref)
266
+ );
267
+ continue;
268
+ }
269
+ connectorByRef.set(c.ref, c);
270
+ }
271
+ const connectors = [...connectorByRef.values()].sort((a, b) => compareStrings(a.ref, b.ref)).map(
272
+ (c) => compact({
273
+ ref: c.ref,
274
+ mpn: c.part.mpn,
275
+ manufacturer: c.part.manufacturer,
276
+ family: c.part.family,
277
+ description: c.part.description,
278
+ gender: c.part.gender,
279
+ pinCount: c.part.pinCount,
280
+ wireGaugeRange: c.part.wireGaugeRange ? { min: c.part.wireGaugeRange.min, max: c.part.wireGaugeRange.max } : void 0,
281
+ pins: Object.entries(c.pins).sort(([a], [b]) => comparePins(a, b)).map(([pin, signal]) => compact({ pin, signal }))
282
+ })
283
+ );
284
+ const pinExists = (connectorRef, pin) => {
285
+ const c = connectorByRef.get(connectorRef);
286
+ if (c === void 0) return false;
287
+ if (pin in c.pins) return true;
288
+ const n = Number(pin);
289
+ return Number.isInteger(n) && n >= 1 && n <= c.part.pinCount;
290
+ };
291
+ const spliceIds = /* @__PURE__ */ new Set();
292
+ for (const s of design.splices) {
293
+ if (spliceIds.has(s.id)) {
294
+ report(
295
+ Codes.DuplicateSpliceId,
296
+ `Splice ID ${s.id} is defined more than once.`,
297
+ refs.splice(s.id)
298
+ );
299
+ }
300
+ spliceIds.add(s.id);
301
+ }
302
+ const cableIds = /* @__PURE__ */ new Set();
303
+ for (const c of design.cables) {
304
+ if (cableIds.has(c.id)) {
305
+ report(
306
+ Codes.DuplicateCableId,
307
+ `Cable ID ${c.id} is defined more than once.`,
308
+ `cable:${c.id}`
309
+ );
310
+ }
311
+ cableIds.add(c.id);
312
+ }
313
+ const checkEndpoint = (owner, end) => {
314
+ if (!("connector" in end)) {
315
+ if (!spliceIds.has(end.splice)) {
316
+ report(
317
+ Codes.UndefinedSpliceRef,
318
+ `${owner} references undefined splice ${end.splice}.`,
319
+ refs.splice(end.splice)
320
+ );
321
+ }
322
+ return;
323
+ }
324
+ if (!connectorByRef.has(end.connector)) {
325
+ report(
326
+ Codes.UndefinedConnectorRef,
327
+ `${owner} references undefined connector ${end.connector}.`,
328
+ refs.connector(end.connector)
329
+ );
330
+ } else if (!pinExists(end.connector, end.pin)) {
331
+ report(
332
+ Codes.UndefinedPinRef,
333
+ `${owner} references pin ${end.pin} which does not exist on connector ${end.connector}.`,
334
+ refs.pin(end.connector, end.pin)
335
+ );
336
+ }
337
+ };
338
+ const toHirEndpoint = (e) => e.kind === "pin-ref" ? { connector: e.connector, pin: e.pin } : { splice: e.splice };
339
+ const wireIds = /* @__PURE__ */ new Set();
340
+ const wires = [];
341
+ for (const w of design.wires) {
342
+ if (wireIds.has(w.id)) {
343
+ report(
344
+ Codes.DuplicateWireId,
345
+ `Wire ID ${w.id} is defined more than once.`,
346
+ refs.wire(w.id)
347
+ );
348
+ continue;
349
+ }
350
+ wireIds.add(w.id);
351
+ const ownerLabel = `Wire ${w.id}`;
352
+ const from = toHirEndpoint(w.from);
353
+ const to = toHirEndpoint(w.to);
354
+ checkEndpoint(ownerLabel, from);
355
+ checkEndpoint(ownerLabel, to);
356
+ if (endpointLabel(from) === endpointLabel(to)) {
357
+ report(
358
+ Codes.WireEndpointsIdentical,
359
+ `Wire ${w.id} starts and ends on the same endpoint ${endpointLabel(from)}.`,
360
+ refs.wire(w.id)
361
+ );
362
+ }
363
+ if (w.cable !== void 0 && !cableIds.has(w.cable)) {
364
+ report(
365
+ Codes.UndefinedCableRef,
366
+ `Wire ${w.id} references undefined cable ${w.cable}.`,
367
+ refs.wire(w.id)
368
+ );
369
+ }
370
+ wires.push(
371
+ compact({
372
+ id: w.id,
373
+ from,
374
+ to,
375
+ gauge: w.gauge,
376
+ color: w.color,
377
+ stripe: w.stripe,
378
+ length: w.length,
379
+ lengthTolerance: w.lengthTolerance,
380
+ signal: w.signal,
381
+ insulation: w.insulation,
382
+ voltageRating: w.voltageRating,
383
+ temperatureRating: w.temperatureRating,
384
+ currentEstimate: w.currentEstimate,
385
+ twistGroup: w.twistGroup,
386
+ shieldGroup: w.shieldGroup,
387
+ cable: w.cable,
388
+ conductor: w.conductor !== void 0 ? String(w.conductor) : void 0,
389
+ notes: w.notes
390
+ })
391
+ );
392
+ }
393
+ wires.sort((a, b) => compareStrings(a.id, b.id));
394
+ const spliceWires = (id) => wires.filter(
395
+ (w) => !("connector" in w.from) && w.from.splice === id || !("connector" in w.to) && w.to.splice === id
396
+ ).map((w) => w.id);
397
+ const designBranchIds = new Set(design.branches.map((b) => b.id));
398
+ const seenSplices = /* @__PURE__ */ new Set();
399
+ const splices = [];
400
+ for (const s of design.splices) {
401
+ if (seenSplices.has(s.id)) continue;
402
+ seenSplices.add(s.id);
403
+ if (s.branch !== void 0 && !designBranchIds.has(s.branch)) {
404
+ report(
405
+ Codes.SpliceUndefinedBranch,
406
+ `Splice ${s.id} is located on undefined branch ${s.branch}.`,
407
+ refs.splice(s.id)
408
+ );
409
+ }
410
+ const attached = spliceWires(s.id);
411
+ if (attached.length < 2) {
412
+ report(
413
+ Codes.SpliceTooFewWires,
414
+ `Splice ${s.id} joins ${attached.length} wire(s); a splice needs at least 2.`,
415
+ refs.splice(s.id),
416
+ DiagnosticSeverity.Error
417
+ );
418
+ }
419
+ splices.push(
420
+ compact({
421
+ id: s.id,
422
+ type: s.type,
423
+ part: s.part,
424
+ branch: s.branch,
425
+ location: s.location,
426
+ notes: s.notes,
427
+ wires: attached
428
+ })
429
+ );
430
+ }
431
+ splices.sort((a, b) => compareStrings(a.id, b.id));
432
+ const seenCables = /* @__PURE__ */ new Set();
433
+ const cables = [];
434
+ for (const c of design.cables) {
435
+ if (seenCables.has(c.id)) continue;
436
+ seenCables.add(c.id);
437
+ const members = wires.filter((w) => w.cable === c.id);
438
+ const lengths = members.map((w) => w.length).filter((l) => l !== void 0);
439
+ cables.push(
440
+ compact({
441
+ id: c.id,
442
+ type: c.type,
443
+ conductors: c.conductors,
444
+ shield: c.shield,
445
+ jacket: c.jacket,
446
+ outerDiameter: c.outerDiameter,
447
+ cutLength: lengths.length > 0 ? Math.max(...lengths) : void 0,
448
+ notes: c.notes,
449
+ wires: members.map((w) => w.id)
450
+ })
451
+ );
452
+ }
453
+ cables.sort((a, b) => compareStrings(a.id, b.id));
454
+ const branchIds = /* @__PURE__ */ new Set();
455
+ const branches = [];
456
+ for (const b of design.branches) {
457
+ if (branchIds.has(b.id)) {
458
+ report(
459
+ Codes.DuplicateBranchId,
460
+ `Branch ID ${b.id} is defined more than once.`,
461
+ refs.branch(b.id)
462
+ );
463
+ continue;
464
+ }
465
+ branchIds.add(b.id);
466
+ for (const endpoint of b.path) {
467
+ if (!connectorByRef.has(endpoint)) {
468
+ report(
469
+ Codes.BranchUndefinedEndpoint,
470
+ `Branch ${b.id} path references undefined connector ${endpoint}.`,
471
+ refs.branch(b.id)
472
+ );
473
+ }
474
+ }
475
+ branches.push(
476
+ compact({
477
+ id: b.id,
478
+ path: [...b.path],
479
+ parent: b.parent,
480
+ sleeve: b.sleeve,
481
+ nominalLength: b.nominalLength,
482
+ breakoutDistance: b.breakoutDistance
483
+ })
484
+ );
485
+ }
486
+ branches.sort((a, b) => compareStrings(a.id, b.id));
487
+ const labelIds = /* @__PURE__ */ new Set();
488
+ const labels = [];
489
+ for (const l of design.labels) {
490
+ if (labelIds.has(l.id)) {
491
+ report(
492
+ Codes.DuplicateLabelId,
493
+ `Label ID ${l.id} is defined more than once.`,
494
+ refs.label(l.id)
495
+ );
496
+ continue;
497
+ }
498
+ labelIds.add(l.id);
499
+ if (!branchIds.has(l.attachTo) && !connectorByRef.has(l.attachTo)) {
500
+ report(
501
+ Codes.LabelUndefinedTarget,
502
+ `Label ${l.id} attaches to ${l.attachTo}, which is not a defined branch or connector.`,
503
+ refs.label(l.id)
504
+ );
505
+ }
506
+ if (l.offsetFrom !== void 0 && !connectorByRef.has(l.offsetFrom)) {
507
+ report(
508
+ Codes.LabelUndefinedTarget,
509
+ `Label ${l.id} is offset from ${l.offsetFrom}, which is not a defined connector.`,
510
+ refs.label(l.id)
511
+ );
512
+ }
513
+ labels.push(
514
+ compact({
515
+ id: l.id,
516
+ text: l.text,
517
+ attachTo: l.attachTo,
518
+ offsetFrom: l.offsetFrom,
519
+ distance: l.distance,
520
+ material: l.material,
521
+ quantity: l.quantity
522
+ })
523
+ );
524
+ }
525
+ labels.sort((a, b) => compareStrings(a.id, b.id));
526
+ const bomByMpn = /* @__PURE__ */ new Map();
527
+ for (const c of [...connectorByRef.values()].sort((a, b) => compareStrings(a.ref, b.ref))) {
528
+ const existing = bomByMpn.get(c.part.mpn);
529
+ if (existing) {
530
+ existing.usedBy.push(refs.connector(c.ref));
531
+ } else {
532
+ bomByMpn.set(c.part.mpn, {
533
+ item: compact({
534
+ mpn: c.part.mpn,
535
+ manufacturer: c.part.manufacturer,
536
+ description: c.part.description,
537
+ category: "connector",
538
+ quantity: 0,
539
+ unitOfMeasure: "ea"
540
+ }),
541
+ usedBy: [refs.connector(c.ref)]
542
+ });
543
+ }
544
+ }
545
+ const bom = [...bomByMpn.values()].map(({ item, usedBy }) => ({ ...item, quantity: usedBy.length, usedBy })).sort((a, b) => compareStrings(a.mpn, b.mpn));
546
+ diagnostics.sort(
547
+ (a, b) => compareStrings(a.target ?? "", b.target ?? "") || compareStrings(a.code, b.code) || compareStrings(a.message, b.message)
548
+ );
549
+ const hir = {
550
+ schemaVersion: HIR_SCHEMA_VERSION,
551
+ harness: {
552
+ id: design.id,
553
+ revision: design.revision,
554
+ units: design.units,
555
+ metadata: design.metadata
556
+ },
557
+ connectors,
558
+ wires,
559
+ cables,
560
+ branches,
561
+ splices,
562
+ labels,
563
+ bom,
564
+ diagnostics,
565
+ layoutHints: [],
566
+ exports: {}
567
+ };
568
+ return { hir, diagnostics };
569
+ };
570
+
571
+ // src/config.ts
572
+ var defineConfig = (config) => config;
573
+
574
+ // src/variant.ts
575
+ var apply = (items, mods) => {
576
+ if (mods === void 0) return items;
577
+ const removed = new Set(mods.remove ?? []);
578
+ const out = items.filter((item) => !removed.has(item.id)).map(
579
+ (item) => mods.override?.[item.id] !== void 0 ? { ...item, ...mods.override[item.id] } : item
580
+ );
581
+ return [...out, ...mods.add ?? []];
582
+ };
583
+ var applyConnectors = (items, mods) => {
584
+ if (mods === void 0) return items;
585
+ const removed = new Set(mods.remove ?? []);
586
+ return [...items.filter((c) => !removed.has(c.ref)), ...mods.add ?? []];
587
+ };
588
+ var variant = (base, opts) => ({
589
+ kind: "harness",
590
+ id: opts.id,
591
+ revision: opts.revision ?? base.revision,
592
+ units: base.units,
593
+ metadata: {
594
+ ...base.metadata,
595
+ ...opts.metadata,
596
+ variantOf: base.id
597
+ },
598
+ connectors: applyConnectors(base.connectors, opts.connectors),
599
+ wires: apply(base.wires, opts.wires),
600
+ branches: apply(base.branches, opts.branches),
601
+ labels: apply(base.labels, opts.labels),
602
+ splices: apply(base.splices, opts.splices),
603
+ cables: apply(base.cables, opts.cables)
604
+ });
605
+
606
+ // src/diff.ts
607
+ var show = (v) => v === void 0 ? "(none)" : typeof v === "string" ? v : JSON.stringify(v);
608
+ var fieldChanges = (a, b, fields) => {
609
+ const changes = [];
610
+ for (const [field, get] of fields) {
611
+ const from = get(a);
612
+ const to = get(b);
613
+ if (show(from) !== show(to)) changes.push({ field, from: show(from), to: show(to) });
614
+ }
615
+ return changes;
616
+ };
617
+ var sectionDiff = (as, bs, key, fields) => {
618
+ const aMap = new Map(as.map((x) => [key(x), x]));
619
+ const bMap = new Map(bs.map((x) => [key(x), x]));
620
+ const added = [...bMap.keys()].filter((k) => !aMap.has(k));
621
+ const removed = [...aMap.keys()].filter((k) => !bMap.has(k));
622
+ const changed = [];
623
+ for (const [k, a] of aMap) {
624
+ const b = bMap.get(k);
625
+ if (b === void 0) continue;
626
+ const changes = fieldChanges(a, b, fields);
627
+ if (changes.length > 0) changed.push({ id: k, changes });
628
+ }
629
+ return { added, removed, changed };
630
+ };
631
+ var diffHir = (a, b) => {
632
+ const pinouts = [];
633
+ const bConnectors = new Map(b.connectors.map((c) => [c.ref, c]));
634
+ for (const ca of a.connectors) {
635
+ const cb = bConnectors.get(ca.ref);
636
+ if (cb === void 0) continue;
637
+ const aPins = new Map(ca.pins.map((p) => [p.pin, p.signal]));
638
+ const bPins = new Map(cb.pins.map((p) => [p.pin, p.signal]));
639
+ for (const pin of /* @__PURE__ */ new Set([...aPins.keys(), ...bPins.keys()])) {
640
+ const from = aPins.get(pin);
641
+ const to = bPins.get(pin);
642
+ if (from !== to) pinouts.push({ connector: ca.ref, pin, from, to });
643
+ }
644
+ }
645
+ return {
646
+ harness: fieldChanges(a.harness, b.harness, [
647
+ ["id", (h) => h.id],
648
+ ["revision", (h) => h.revision],
649
+ ["units", (h) => h.units]
650
+ ]),
651
+ connectors: sectionDiff(a.connectors, b.connectors, (c) => c.ref, [
652
+ ["mpn", (c) => c.mpn],
653
+ ["pinCount", (c) => c.pinCount],
654
+ ["gender", (c) => c.gender]
655
+ ]),
656
+ pinouts,
657
+ wires: sectionDiff(a.wires, b.wires, (w) => w.id, [
658
+ ["from", (w) => endpointLabel(w.from)],
659
+ ["to", (w) => endpointLabel(w.to)],
660
+ ["gauge", (w) => w.gauge],
661
+ ["color", (w) => w.color],
662
+ ["length", (w) => w.length],
663
+ ["signal", (w) => w.signal],
664
+ ["twistGroup", (w) => w.twistGroup],
665
+ ["cable", (w) => w.cable]
666
+ ]),
667
+ splices: sectionDiff(a.splices, b.splices, (s) => s.id, [
668
+ ["type", (s) => s.type],
669
+ ["part", (s) => s.part],
670
+ ["branch", (s) => s.branch],
671
+ ["location", (s) => s.location],
672
+ ["wires", (s) => s.wires.join(", ")]
673
+ ]),
674
+ cables: sectionDiff(a.cables, b.cables, (c) => c.id, [
675
+ ["type", (c) => c.type],
676
+ ["conductors", (c) => c.conductors],
677
+ ["cutLength", (c) => c.cutLength],
678
+ ["wires", (c) => c.wires.join(", ")]
679
+ ]),
680
+ branches: sectionDiff(a.branches, b.branches, (br) => br.id, [
681
+ ["path", (br) => br.path.join(" \u2192 ")],
682
+ ["sleeve", (br) => br.sleeve],
683
+ ["nominalLength", (br) => br.nominalLength],
684
+ ["parent", (br) => br.parent]
685
+ ]),
686
+ labels: sectionDiff(a.labels, b.labels, (l) => l.id, [
687
+ ["text", (l) => l.text],
688
+ ["attachTo", (l) => l.attachTo],
689
+ ["offsetFrom", (l) => l.offsetFrom],
690
+ ["distance", (l) => l.distance]
691
+ ]),
692
+ bom: sectionDiff(a.bom, b.bom, (i) => i.mpn, [
693
+ ["quantity", (i) => i.quantity],
694
+ ["usedBy", (i) => i.usedBy.join(", ")]
695
+ ])
696
+ };
697
+ };
698
+ var isEmptyDiff = (d) => d.harness.length === 0 && d.pinouts.length === 0 && [d.connectors, d.wires, d.splices, d.cables, d.branches, d.labels, d.bom].every(
699
+ (s) => s.added.length === 0 && s.removed.length === 0 && s.changed.length === 0
700
+ );
701
+ var formatDiff = (d) => {
702
+ const lines = [];
703
+ const section = (title, s, prefix) => {
704
+ if (s.added.length === 0 && s.removed.length === 0 && s.changed.length === 0) return;
705
+ lines.push(`${title}:`);
706
+ for (const id of s.added) lines.push(` + ${prefix}:${id}`);
707
+ for (const id of s.removed) lines.push(` - ${prefix}:${id}`);
708
+ for (const c of s.changed) {
709
+ lines.push(` ~ ${prefix}:${c.id}`);
710
+ for (const f of c.changes) lines.push(` ${f.field}: ${f.from} -> ${f.to}`);
711
+ }
712
+ };
713
+ if (d.harness.length > 0) {
714
+ lines.push("harness:");
715
+ for (const f of d.harness) lines.push(` ~ ${f.field}: ${f.from} -> ${f.to}`);
716
+ }
717
+ section("connectors", d.connectors, "connector");
718
+ if (d.pinouts.length > 0) {
719
+ lines.push("pinouts:");
720
+ for (const p of d.pinouts) {
721
+ lines.push(` ~ connector:${p.connector}.pin:${p.pin}: ${show(p.from)} -> ${show(p.to)}`);
722
+ }
723
+ }
724
+ section("wires", d.wires, "wire");
725
+ section("splices", d.splices, "splice");
726
+ section("cables", d.cables, "cable");
727
+ section("branches", d.branches, "branch");
728
+ section("labels", d.labels, "label");
729
+ section("bom", d.bom, "bom");
730
+ return lines.length === 0 ? "No differences.\n" : lines.join("\n") + "\n";
731
+ };
732
+
733
+ // src/rules.ts
734
+ var rule = (name, run, options = {}) => ({
735
+ name,
736
+ code: options.code ?? `HK-RULE-${name.toUpperCase().replace(/[^A-Z0-9]+/g, "-")}`,
737
+ run
738
+ });
739
+ var runRules = (hir, rules, config = {}) => {
740
+ const diagnostics = [];
741
+ for (const r of rules) {
742
+ const override = config[r.name];
743
+ if (override === "off") continue;
744
+ r.run({
745
+ hir,
746
+ report: ({ severity, message, target, code }) => {
747
+ diagnostics.push({
748
+ code: code ?? r.code,
749
+ severity: override ?? severity,
750
+ message,
751
+ ...target !== void 0 ? { target } : {}
752
+ });
753
+ }
754
+ });
755
+ }
756
+ diagnostics.sort(
757
+ (a, b) => cmp(a.target ?? "", b.target ?? "") || cmp(a.code, b.code) || cmp(a.message, b.message)
758
+ );
759
+ return diagnostics;
760
+ };
761
+ var cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0;
762
+ export {
763
+ Codes,
764
+ DiagnosticSeverity,
765
+ HIR_SCHEMA_VERSION,
766
+ Hir,
767
+ HirBomItem,
768
+ HirBranch,
769
+ HirCable,
770
+ HirConnector,
771
+ HirEndpoint,
772
+ HirLabel,
773
+ HirPinRef,
774
+ HirSplice,
775
+ HirSpliceRef,
776
+ HirWire,
777
+ branch,
778
+ cable,
779
+ compileDesign,
780
+ connector,
781
+ decodeHir,
782
+ decodeHirEffect,
783
+ defineConfig,
784
+ diffHir,
785
+ encodeHir,
786
+ endpointLabel,
787
+ formatDiff,
788
+ harness,
789
+ hasErrors,
790
+ isEmptyDiff,
791
+ isPinEndpoint,
792
+ label,
793
+ refs,
794
+ rule,
795
+ runRules,
796
+ splice,
797
+ variant,
798
+ wire
799
+ };