@grayhaven/nerve 0.1.0 → 0.2.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.d.ts CHANGED
@@ -12,6 +12,15 @@ import { Schema } from 'effect';
12
12
  */
13
13
  type Units = "mm" | "in";
14
14
  type ConnectorGender = "plug" | "receptacle" | "hermaphroditic";
15
+ /** Datasheet/source provenance and verification state (PRD §30, §38). */
16
+ interface PartProvenance {
17
+ readonly source?: string;
18
+ readonly datasheet?: string;
19
+ /** "verified" requires a review pass; library seed data is "inspired-by". */
20
+ readonly verification: "unverified" | "inspired-by" | "verified";
21
+ /** ISO date of last verification. */
22
+ readonly lastVerified?: string;
23
+ }
15
24
  /**
16
25
  * Component master data for a connector housing (PRD §9.2, §30).
17
26
  * Instances reference a part; parts live in libraries such as
@@ -32,10 +41,19 @@ interface ConnectorPart {
32
41
  readonly matingMpn?: string;
33
42
  readonly compatibleTerminals?: ReadonlyArray<string>;
34
43
  readonly compatibleSeals?: ReadonlyArray<string>;
44
+ readonly compatibleBackshells?: ReadonlyArray<string>;
35
45
  readonly wireGaugeRange?: {
36
46
  readonly min: string;
37
47
  readonly max: string;
38
48
  };
49
+ /** Environmentally sealed housing: every populated cavity needs a seal. */
50
+ readonly sealed?: boolean;
51
+ readonly currentLimitA?: number;
52
+ readonly voltageLimitV?: number;
53
+ readonly crimpTool?: string;
54
+ readonly insertionTool?: string;
55
+ readonly extractionTool?: string;
56
+ readonly provenance?: PartProvenance;
39
57
  }
40
58
  /** A reference to a specific pin/cavity on a connector instance. */
41
59
  interface PinRef {
@@ -58,6 +76,10 @@ interface ConnectorInstance {
58
76
  readonly ref: string;
59
77
  readonly part: ConnectorPart;
60
78
  readonly pins: Readonly<Record<string, string>>;
79
+ /** Terminal MPN per pin (PRD §30). */
80
+ readonly terminals: Readonly<Record<string, string>>;
81
+ /** Seal MPN per pin. */
82
+ readonly seals: Readonly<Record<string, string>>;
61
83
  /** Build a `PinRef` for a pin on this connector. */
62
84
  pin(pin: string | number): PinRef;
63
85
  }
@@ -183,9 +205,16 @@ interface HarnessDesign {
183
205
  * diagnostics instead of throwing mid-definition.
184
206
  */
185
207
 
186
- /** Place a connector in the harness: `connector("J1", MolexMicroFit["43025-0800"], { pins: {...} })`. */
208
+ /**
209
+ * Per-pin part assignment: a map of pin → MPN, or a single MPN applied to
210
+ * every assigned pin (the common case — one terminal type per housing).
211
+ */
212
+ type PinPartAssignment = string | Readonly<Record<string | number, string>>;
213
+ /** Place a connector in the harness: `connector("J1", MolexMicroFit["43025-0800"], { pins: {...}, terminals: "43030-0007" })`. */
187
214
  declare const connector: (ref: string, part: ConnectorPart, opts: {
188
215
  readonly pins: PinAssignments;
216
+ readonly terminals?: PinPartAssignment;
217
+ readonly seals?: PinPartAssignment;
189
218
  }) => ConnectorInstance;
190
219
  /** Anything a wire can terminate on: a pin ref, a splice (def or ref). */
191
220
  type EndpointInput = PinRef | SpliceDef | {
@@ -270,9 +299,21 @@ declare const Hir: Schema.Struct<{
270
299
  min: typeof Schema.String;
271
300
  max: typeof Schema.String;
272
301
  }>>;
302
+ sealed: Schema.optional<typeof Schema.Boolean>;
303
+ compatibleTerminals: Schema.optional<Schema.Array$<typeof Schema.String>>;
304
+ compatibleSeals: Schema.optional<Schema.Array$<typeof Schema.String>>;
305
+ crimpTool: Schema.optional<typeof Schema.String>;
306
+ provenance: Schema.optional<Schema.Struct<{
307
+ source: Schema.optional<typeof Schema.String>;
308
+ datasheet: Schema.optional<typeof Schema.String>;
309
+ verification: Schema.Literal<["unverified", "inspired-by", "verified"]>;
310
+ lastVerified: Schema.optional<typeof Schema.String>;
311
+ }>>;
273
312
  pins: Schema.Array$<Schema.Struct<{
274
313
  pin: typeof Schema.String;
275
314
  signal: Schema.optional<typeof Schema.String>;
315
+ terminal: Schema.optional<typeof Schema.String>;
316
+ seal: Schema.optional<typeof Schema.String>;
276
317
  }>>;
277
318
  }>>;
278
319
  wires: Schema.Array$<Schema.Struct<{
@@ -379,9 +420,21 @@ declare const HirConnector: Schema.Struct<{
379
420
  min: typeof Schema.String;
380
421
  max: typeof Schema.String;
381
422
  }>>;
423
+ sealed: Schema.optional<typeof Schema.Boolean>;
424
+ compatibleTerminals: Schema.optional<Schema.Array$<typeof Schema.String>>;
425
+ compatibleSeals: Schema.optional<Schema.Array$<typeof Schema.String>>;
426
+ crimpTool: Schema.optional<typeof Schema.String>;
427
+ provenance: Schema.optional<Schema.Struct<{
428
+ source: Schema.optional<typeof Schema.String>;
429
+ datasheet: Schema.optional<typeof Schema.String>;
430
+ verification: Schema.Literal<["unverified", "inspired-by", "verified"]>;
431
+ lastVerified: Schema.optional<typeof Schema.String>;
432
+ }>>;
382
433
  pins: Schema.Array$<Schema.Struct<{
383
434
  pin: typeof Schema.String;
384
435
  signal: Schema.optional<typeof Schema.String>;
436
+ terminal: Schema.optional<typeof Schema.String>;
437
+ seal: Schema.optional<typeof Schema.String>;
385
438
  }>>;
386
439
  }>;
387
440
  type HirConnector = Schema.Schema.Type<typeof HirConnector>;
@@ -537,8 +590,8 @@ declare const decodeHir: (u: unknown, overrideOptions?: effect_SchemaAST.ParseOp
537
590
  }[];
538
591
  readonly schemaVersion: "0.1.0";
539
592
  readonly connectors: readonly {
540
- readonly ref: string;
541
593
  readonly mpn: string;
594
+ readonly ref: string;
542
595
  readonly manufacturer?: string | undefined;
543
596
  readonly family?: string | undefined;
544
597
  readonly description?: string | undefined;
@@ -548,9 +601,21 @@ declare const decodeHir: (u: unknown, overrideOptions?: effect_SchemaAST.ParseOp
548
601
  readonly min: string;
549
602
  readonly max: string;
550
603
  } | undefined;
604
+ readonly sealed?: boolean | undefined;
605
+ readonly compatibleTerminals?: readonly string[] | undefined;
606
+ readonly compatibleSeals?: readonly string[] | undefined;
607
+ readonly crimpTool?: string | undefined;
608
+ readonly provenance?: {
609
+ readonly source?: string | undefined;
610
+ readonly datasheet?: string | undefined;
611
+ readonly verification: "unverified" | "inspired-by" | "verified";
612
+ readonly lastVerified?: string | undefined;
613
+ } | undefined;
551
614
  readonly pins: readonly {
552
615
  readonly pin: string;
553
616
  readonly signal?: string | undefined;
617
+ readonly terminal?: string | undefined;
618
+ readonly seal?: string | undefined;
554
619
  }[];
555
620
  }[];
556
621
  readonly cables: readonly {
@@ -591,8 +656,8 @@ declare const decodeHir: (u: unknown, overrideOptions?: effect_SchemaAST.ParseOp
591
656
  readonly quantity?: number | undefined;
592
657
  }[];
593
658
  readonly bom: readonly {
594
- readonly quantity: number;
595
659
  readonly mpn: string;
660
+ readonly quantity: number;
596
661
  readonly manufacturer?: string | undefined;
597
662
  readonly description?: string | undefined;
598
663
  readonly notes?: string | undefined;
@@ -655,8 +720,8 @@ declare const decodeHirEffect: (u: unknown, overrideOptions?: effect_SchemaAST.P
655
720
  }[];
656
721
  readonly schemaVersion: "0.1.0";
657
722
  readonly connectors: readonly {
658
- readonly ref: string;
659
723
  readonly mpn: string;
724
+ readonly ref: string;
660
725
  readonly manufacturer?: string | undefined;
661
726
  readonly family?: string | undefined;
662
727
  readonly description?: string | undefined;
@@ -666,9 +731,21 @@ declare const decodeHirEffect: (u: unknown, overrideOptions?: effect_SchemaAST.P
666
731
  readonly min: string;
667
732
  readonly max: string;
668
733
  } | undefined;
734
+ readonly sealed?: boolean | undefined;
735
+ readonly compatibleTerminals?: readonly string[] | undefined;
736
+ readonly compatibleSeals?: readonly string[] | undefined;
737
+ readonly crimpTool?: string | undefined;
738
+ readonly provenance?: {
739
+ readonly source?: string | undefined;
740
+ readonly datasheet?: string | undefined;
741
+ readonly verification: "unverified" | "inspired-by" | "verified";
742
+ readonly lastVerified?: string | undefined;
743
+ } | undefined;
669
744
  readonly pins: readonly {
670
745
  readonly pin: string;
671
746
  readonly signal?: string | undefined;
747
+ readonly terminal?: string | undefined;
748
+ readonly seal?: string | undefined;
672
749
  }[];
673
750
  }[];
674
751
  readonly cables: readonly {
@@ -709,8 +786,8 @@ declare const decodeHirEffect: (u: unknown, overrideOptions?: effect_SchemaAST.P
709
786
  readonly quantity?: number | undefined;
710
787
  }[];
711
788
  readonly bom: readonly {
712
- readonly quantity: number;
713
789
  readonly mpn: string;
790
+ readonly quantity: number;
714
791
  readonly manufacturer?: string | undefined;
715
792
  readonly description?: string | undefined;
716
793
  readonly notes?: string | undefined;
@@ -773,8 +850,8 @@ declare const encodeHir: (a: {
773
850
  }[];
774
851
  readonly schemaVersion: "0.1.0";
775
852
  readonly connectors: readonly {
776
- readonly ref: string;
777
853
  readonly mpn: string;
854
+ readonly ref: string;
778
855
  readonly manufacturer?: string | undefined;
779
856
  readonly family?: string | undefined;
780
857
  readonly description?: string | undefined;
@@ -784,9 +861,21 @@ declare const encodeHir: (a: {
784
861
  readonly min: string;
785
862
  readonly max: string;
786
863
  } | undefined;
864
+ readonly sealed?: boolean | undefined;
865
+ readonly compatibleTerminals?: readonly string[] | undefined;
866
+ readonly compatibleSeals?: readonly string[] | undefined;
867
+ readonly crimpTool?: string | undefined;
868
+ readonly provenance?: {
869
+ readonly source?: string | undefined;
870
+ readonly datasheet?: string | undefined;
871
+ readonly verification: "unverified" | "inspired-by" | "verified";
872
+ readonly lastVerified?: string | undefined;
873
+ } | undefined;
787
874
  readonly pins: readonly {
788
875
  readonly pin: string;
789
876
  readonly signal?: string | undefined;
877
+ readonly terminal?: string | undefined;
878
+ readonly seal?: string | undefined;
790
879
  }[];
791
880
  }[];
792
881
  readonly cables: readonly {
@@ -827,8 +916,8 @@ declare const encodeHir: (a: {
827
916
  readonly quantity?: number | undefined;
828
917
  }[];
829
918
  readonly bom: readonly {
830
- readonly quantity: number;
831
919
  readonly mpn: string;
920
+ readonly quantity: number;
832
921
  readonly manufacturer?: string | undefined;
833
922
  readonly description?: string | undefined;
834
923
  readonly notes?: string | undefined;
@@ -889,12 +978,14 @@ declare const encodeHir: (a: {
889
978
  }[];
890
979
  readonly schemaVersion: "0.1.0";
891
980
  readonly connectors: readonly {
892
- readonly ref: string;
893
981
  readonly mpn: string;
982
+ readonly ref: string;
894
983
  readonly pinCount: number;
895
984
  readonly pins: readonly {
896
985
  readonly pin: string;
897
986
  readonly signal?: string | undefined;
987
+ readonly terminal?: string | undefined;
988
+ readonly seal?: string | undefined;
898
989
  }[];
899
990
  readonly manufacturer?: string | undefined;
900
991
  readonly family?: string | undefined;
@@ -904,6 +995,16 @@ declare const encodeHir: (a: {
904
995
  readonly min: string;
905
996
  readonly max: string;
906
997
  } | undefined;
998
+ readonly sealed?: boolean | undefined;
999
+ readonly compatibleTerminals?: readonly string[] | undefined;
1000
+ readonly compatibleSeals?: readonly string[] | undefined;
1001
+ readonly crimpTool?: string | undefined;
1002
+ readonly provenance?: {
1003
+ readonly verification: "unverified" | "inspired-by" | "verified";
1004
+ readonly source?: string | undefined;
1005
+ readonly datasheet?: string | undefined;
1006
+ readonly lastVerified?: string | undefined;
1007
+ } | undefined;
907
1008
  }[];
908
1009
  readonly cables: readonly {
909
1010
  readonly id: string;
@@ -943,8 +1044,8 @@ declare const encodeHir: (a: {
943
1044
  readonly quantity?: number | undefined;
944
1045
  }[];
945
1046
  readonly bom: readonly {
946
- readonly quantity: number;
947
1047
  readonly mpn: string;
1048
+ readonly quantity: number;
948
1049
  readonly unitOfMeasure: string;
949
1050
  readonly usedBy: readonly string[];
950
1051
  readonly manufacturer?: string | undefined;
@@ -1052,11 +1153,47 @@ declare const runRules: (hir: Hir, rules: ReadonlyArray<Rule>, config?: RuleConf
1052
1153
  * ```
1053
1154
  */
1054
1155
 
1156
+ /** Lifecycle risk per PRD §29 supplier/lifecycle fields. */
1157
+ type PartLifecycle = "active" | "nrnd" | "obsolete";
1158
+ interface PartCost {
1159
+ readonly unitCost: number;
1160
+ readonly supplier?: string;
1161
+ readonly leadTimeDays?: number;
1162
+ readonly lifecycle?: PartLifecycle;
1163
+ }
1164
+ /**
1165
+ * Cost and quote model (PRD §29). Prices are organization data, not design
1166
+ * data — they live in config (or a future part-data provider, §42), never
1167
+ * in the HIR.
1168
+ */
1169
+ interface CostModel {
1170
+ readonly currency?: string;
1171
+ readonly laborRatePerHour: number;
1172
+ /** Fraction of material lost to scrap, e.g. 0.05. */
1173
+ readonly scrapFactor?: number;
1174
+ /** First-pass yield, e.g. 0.97 — per-unit cost divides by this. */
1175
+ readonly yield?: number;
1176
+ /** Days at/above which a part is flagged long-lead. Default 60. */
1177
+ readonly longLeadThresholdDays?: number;
1178
+ /** Part costs by MPN (connectors, terminals, seals, splice parts...). */
1179
+ readonly parts?: Readonly<Record<string, PartCost>>;
1180
+ /** Wire cost per meter by gauge, e.g. { "18AWG": 0.22 }. */
1181
+ readonly wireCostPerMeter?: Readonly<Record<string, number>>;
1182
+ readonly defaultWireCostPerMeter?: number;
1183
+ readonly sleeveCostPerMeter?: number;
1184
+ readonly labelUnitCost?: number;
1185
+ readonly spliceUnitCost?: number;
1186
+ /** Fallback for parts with no entry; also flags the line as unpriced. */
1187
+ readonly defaultPartCost?: number;
1188
+ }
1055
1189
  interface NerveConfig {
1056
1190
  readonly units?: Units;
1057
1191
  readonly defaultWireTolerance?: number;
1058
1192
  readonly outputDir?: string;
1059
1193
  readonly rules?: RuleConfig;
1194
+ readonly costing?: CostModel;
1195
+ /** Plugin module paths (relative to the harness file) — rule packs etc. (PRD §40). */
1196
+ readonly plugins?: ReadonlyArray<string>;
1060
1197
  readonly exports?: {
1061
1198
  readonly csv?: boolean;
1062
1199
  readonly svg?: boolean;
@@ -1101,6 +1238,31 @@ interface VariantOptions {
1101
1238
  /** Derive a variant: `variant(base, { id: "...-long", wires: { override: { W1: { length: 800 } } } })`. */
1102
1239
  declare const variant: (base: HarnessDesign, opts: VariantOptions) => HarnessDesign;
1103
1240
 
1241
+ /**
1242
+ * Plugin SDK (PRD §40).
1243
+ *
1244
+ * Typed extension boundary. The contract:
1245
+ * - plugins declare which HIR schema versions they support — the compiler
1246
+ * refuses mismatches with a diagnostic instead of undefined behavior,
1247
+ * - plugin rules report through the standard typed diagnostic channel,
1248
+ * - plugins MUST NOT mutate the HIR (rules receive it read-only),
1249
+ * - plugins are plain modules, executable locally and in CI.
1250
+ *
1251
+ * First plugin surface: rule packs (the §38 standards-pack vehicle).
1252
+ * Importers/exporters/renderers join as their host seams stabilize.
1253
+ */
1254
+
1255
+ interface NervePlugin {
1256
+ readonly name: string;
1257
+ readonly version?: string;
1258
+ /** HIR schema versions this plugin understands. */
1259
+ readonly hirSchemaVersions: ReadonlyArray<string>;
1260
+ readonly rules?: ReadonlyArray<Rule>;
1261
+ }
1262
+ /** Identity helper for typed plugin modules: `export default definePlugin({...})`. */
1263
+ declare const definePlugin: (plugin: NervePlugin) => NervePlugin;
1264
+ declare const isNervePlugin: (value: unknown) => value is NervePlugin;
1265
+
1104
1266
  /**
1105
1267
  * Revision diff (PRD §21, DoD #9).
1106
1268
  *
@@ -1146,4 +1308,4 @@ declare const isEmptyDiff: (d: HirDiff) => boolean;
1146
1308
  /** Human-readable diff (git-diff-flavored prefixes). */
1147
1309
  declare const formatDiff: (d: HirDiff) => string;
1148
1310
 
1149
- export { type BranchDef, type BranchProps, type CableDef, type CableProps, Codes, type CompileResult, type ConnectorGender, type ConnectorInstance, type ConnectorPart, type Diagnostic, DiagnosticSeverity, type EndpointInput, type EntityChange, type FieldChange, HIR_SCHEMA_VERSION, type HarnessDesign, type HarnessProps, Hir, HirBomItem, HirBranch, HirCable, HirConnector, type HirDiff, HirEndpoint, HirLabel, HirPinRef, HirSplice, HirSpliceRef, HirWire, type LabelDef, type LabelProps, type NerveConfig, type PinAssignments, type PinRef, type PinoutChange, type Rule, type RuleConfig, type RuleContext, type RuleOptions, type RuleReport, type SectionDiff, type SpliceDef, type SpliceProps, type SpliceRef, type Units, type VariantOptions, type WireDef, type WireEndpoint, type WireProps, branch, cable, compileDesign, connector, decodeHir, decodeHirEffect, defineConfig, diffHir, encodeHir, endpointLabel, formatDiff, harness, hasErrors, isEmptyDiff, isPinEndpoint, label, refs, rule, runRules, splice, variant, wire };
1311
+ export { type BranchDef, type BranchProps, type CableDef, type CableProps, Codes, type CompileResult, type ConnectorGender, type ConnectorInstance, type ConnectorPart, type CostModel, type Diagnostic, DiagnosticSeverity, type EndpointInput, type EntityChange, type FieldChange, HIR_SCHEMA_VERSION, type HarnessDesign, type HarnessProps, Hir, HirBomItem, HirBranch, HirCable, HirConnector, type HirDiff, HirEndpoint, HirLabel, HirPinRef, HirSplice, HirSpliceRef, HirWire, type LabelDef, type LabelProps, type NerveConfig, type NervePlugin, type PartCost, type PartLifecycle, type PartProvenance, type PinAssignments, type PinPartAssignment, type PinRef, type PinoutChange, type Rule, type RuleConfig, type RuleContext, type RuleOptions, type RuleReport, type SectionDiff, type SpliceDef, type SpliceProps, type SpliceRef, type Units, type VariantOptions, type WireDef, type WireEndpoint, type WireProps, branch, cable, compileDesign, connector, decodeHir, decodeHirEffect, defineConfig, definePlugin, diffHir, encodeHir, endpointLabel, formatDiff, harness, hasErrors, isEmptyDiff, isNervePlugin, isPinEndpoint, label, refs, rule, runRules, splice, variant, wire };
package/dist/index.js CHANGED
@@ -1,5 +1,14 @@
1
1
  // src/dsl.ts
2
2
  var toRef = (target) => typeof target === "string" ? target : target.ref;
3
+ var expandPinParts = (assignment, pins) => {
4
+ if (assignment === void 0) return {};
5
+ if (typeof assignment === "string") {
6
+ return Object.fromEntries(Object.keys(pins).map((pin) => [pin, assignment]));
7
+ }
8
+ return Object.fromEntries(
9
+ Object.entries(assignment).map(([pin, mpn]) => [String(pin), mpn])
10
+ );
11
+ };
3
12
  var connector = (ref, part, opts) => {
4
13
  const pins = {};
5
14
  for (const [pin, signal] of Object.entries(opts.pins)) {
@@ -10,6 +19,8 @@ var connector = (ref, part, opts) => {
10
19
  ref,
11
20
  part,
12
21
  pins,
22
+ terminals: expandPinParts(opts.terminals, pins),
23
+ seals: expandPinParts(opts.seals, pins),
13
24
  pin: (pin) => ({
14
25
  kind: "pin-ref",
15
26
  connector: ref,
@@ -107,7 +118,15 @@ var HirSpliceRef = Schema.Struct({
107
118
  var HirEndpoint = Schema.Union(HirPinRef, HirSpliceRef);
108
119
  var HirPin = Schema.Struct({
109
120
  pin: Schema.String,
110
- signal: Schema.optional(Schema.String)
121
+ signal: Schema.optional(Schema.String),
122
+ terminal: Schema.optional(Schema.String),
123
+ seal: Schema.optional(Schema.String)
124
+ });
125
+ var HirProvenance = Schema.Struct({
126
+ source: Schema.optional(Schema.String),
127
+ datasheet: Schema.optional(Schema.String),
128
+ verification: Schema.Literal("unverified", "inspired-by", "verified"),
129
+ lastVerified: Schema.optional(Schema.String)
111
130
  });
112
131
  var HirConnector = Schema.Struct({
113
132
  ref: Schema.String,
@@ -120,6 +139,11 @@ var HirConnector = Schema.Struct({
120
139
  wireGaugeRange: Schema.optional(
121
140
  Schema.Struct({ min: Schema.String, max: Schema.String })
122
141
  ),
142
+ sealed: Schema.optional(Schema.Boolean),
143
+ compatibleTerminals: Schema.optional(Schema.Array(Schema.String)),
144
+ compatibleSeals: Schema.optional(Schema.Array(Schema.String)),
145
+ crimpTool: Schema.optional(Schema.String),
146
+ provenance: Schema.optional(HirProvenance),
123
147
  pins: Schema.Array(HirPin)
124
148
  });
125
149
  var HirWire = Schema.Struct({
@@ -278,7 +302,19 @@ var compileDesign = (design) => {
278
302
  gender: c.part.gender,
279
303
  pinCount: c.part.pinCount,
280
304
  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 }))
305
+ sealed: c.part.sealed,
306
+ compatibleTerminals: c.part.compatibleTerminals ? [...c.part.compatibleTerminals] : void 0,
307
+ compatibleSeals: c.part.compatibleSeals ? [...c.part.compatibleSeals] : void 0,
308
+ crimpTool: c.part.crimpTool,
309
+ provenance: c.part.provenance ? { ...c.part.provenance } : void 0,
310
+ pins: Object.entries(c.pins).sort(([a], [b]) => comparePins(a, b)).map(
311
+ ([pin, signal]) => compact({
312
+ pin,
313
+ signal,
314
+ terminal: c.terminals[pin],
315
+ seal: c.seals[pin]
316
+ })
317
+ )
282
318
  })
283
319
  );
284
320
  const pinExists = (connectorRef, pin) => {
@@ -524,25 +560,39 @@ var compileDesign = (design) => {
524
560
  }
525
561
  labels.sort((a, b) => compareStrings(a.id, b.id));
526
562
  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);
563
+ const bomAdd = (mpn, category, usedBy, extra = {}) => {
564
+ const existing = bomByMpn.get(mpn);
529
565
  if (existing) {
530
- existing.usedBy.push(refs.connector(c.ref));
566
+ existing.qty += 1;
567
+ existing.usedBy.push(usedBy);
531
568
  } 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)]
569
+ bomByMpn.set(mpn, {
570
+ item: compact({ mpn, category, unitOfMeasure: "ea", ...extra }),
571
+ usedBy: [usedBy],
572
+ qty: 1
542
573
  });
543
574
  }
575
+ };
576
+ for (const c of [...connectorByRef.values()].sort((a, b) => compareStrings(a.ref, b.ref))) {
577
+ bomAdd(c.part.mpn, "connector", refs.connector(c.ref), {
578
+ manufacturer: c.part.manufacturer,
579
+ description: c.part.description
580
+ });
581
+ for (const [pin, terminal] of Object.entries(c.terminals).sort(
582
+ ([a], [b]) => comparePins(a, b)
583
+ )) {
584
+ bomAdd(terminal, "terminal", refs.pin(c.ref, pin));
585
+ }
586
+ for (const [pin, seal] of Object.entries(c.seals).sort(
587
+ ([a], [b]) => comparePins(a, b)
588
+ )) {
589
+ bomAdd(seal, "seal", refs.pin(c.ref, pin));
590
+ }
591
+ }
592
+ for (const s of splices) {
593
+ if (s.part !== void 0) bomAdd(s.part, "splice", refs.splice(s.id));
544
594
  }
545
- const bom = [...bomByMpn.values()].map(({ item, usedBy }) => ({ ...item, quantity: usedBy.length, usedBy })).sort((a, b) => compareStrings(a.mpn, b.mpn));
595
+ const bom = [...bomByMpn.values()].map(({ item, usedBy, qty }) => ({ ...item, quantity: qty, usedBy })).sort((a, b) => compareStrings(a.mpn, b.mpn));
546
596
  diagnostics.sort(
547
597
  (a, b) => compareStrings(a.target ?? "", b.target ?? "") || compareStrings(a.code, b.code) || compareStrings(a.message, b.message)
548
598
  );
@@ -603,6 +653,10 @@ var variant = (base, opts) => ({
603
653
  cables: apply(base.cables, opts.cables)
604
654
  });
605
655
 
656
+ // src/plugin.ts
657
+ var definePlugin = (plugin) => plugin;
658
+ var isNervePlugin = (value) => typeof value === "object" && value !== null && typeof value.name === "string" && Array.isArray(value.hirSchemaVersions);
659
+
606
660
  // src/diff.ts
607
661
  var show = (v) => v === void 0 ? "(none)" : typeof v === "string" ? v : JSON.stringify(v);
608
662
  var fieldChanges = (a, b, fields) => {
@@ -781,6 +835,7 @@ export {
781
835
  decodeHir,
782
836
  decodeHirEffect,
783
837
  defineConfig,
838
+ definePlugin,
784
839
  diffHir,
785
840
  encodeHir,
786
841
  endpointLabel,
@@ -788,6 +843,7 @@ export {
788
843
  harness,
789
844
  hasErrors,
790
845
  isEmptyDiff,
846
+ isNervePlugin,
791
847
  isPinEndpoint,
792
848
  label,
793
849
  refs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grayhaven/nerve",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Grayhaven Nerve core: domain model, DSL builders, HIR types and schema, compiler interface, validation primitives.",
6
6
  "exports": {
@@ -15,7 +15,7 @@
15
15
  "devDependencies": {
16
16
  "typescript": "^5.8.3",
17
17
  "vitest": "^3.2.4",
18
- "@grayhaven/nerve-rules": "0.1.0"
18
+ "@grayhaven/nerve-rules": "0.2.0"
19
19
  },
20
20
  "license": "Apache-2.0",
21
21
  "files": [