@schemd/core 0.1.2 → 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/README.md CHANGED
@@ -10,18 +10,18 @@ Schemd has no runtime dependencies, browser layout pass, or DOM requirement. It
10
10
  npm install @schemd/core
11
11
  ```
12
12
 
13
- Node.js 24+ is required. Install `marked` too if you use Markdown fences.
13
+ Node.js 24+ is required.
14
14
 
15
15
  ## Quick start
16
16
 
17
17
  ```ts
18
- import { parseSchematic, parseSchematicFence, renderSchematic } from "@schemd/core";
18
+ import { compileSchematic, parseSchematicFence } from "@schemd/core";
19
19
 
20
20
  const fence = parseSchematicFence(
21
21
  'schemd bounds="640x260" title="Sensor input"',
22
22
  )!;
23
23
 
24
- const diagram = parseSchematic(`
24
+ const { svg, document, metrics } = compileSchematic(`
25
25
  port:VIN "Input" at (60, 130) #blue
26
26
  resistor:R1 "10 k\\Omega" at (220, 130) #amber
27
27
  capacitor:C1 "100 nF" at (400, 130) #cyan
@@ -29,8 +29,6 @@ capacitor:C1 "100 nF" at (400, 130) #cyan
29
29
  VIN.out -> R1.in #blue [ortho]
30
30
  R1.out -> C1.in #amber [ortho]
31
31
  `, fence);
32
-
33
- const html = renderSchematic(diagram, fence);
34
32
  ```
35
33
 
36
34
  The DSL is intentionally small:
@@ -55,6 +53,6 @@ Class, actor, use-case, state, lifeline, note, package, initial, and final nodes
55
53
 
56
54
  Use `mode: "embedded-css"` for built-in styling or `mode: "full"` for interaction metadata. The default output stays compact and static.
57
55
 
58
- For Markdown, use `schematicMarkedExtension()` from the main package or the `@schemd/core/marked` subpath.
56
+ Markdown belongs at the host boundary. Detect `schemd` fences in your server-side Markdown renderer and pass their text to `compileSchematic`; core does not ship or require a Markdown parser.
59
57
 
60
58
  [Documentation](https://johnowolabiidogun.dev/tools/schemd/docs/overview) · [Issues](https://github.com/Sirneij/schemd/issues) · [MIT](https://github.com/Sirneij/schemd/blob/main/LICENSE)
@@ -0,0 +1,16 @@
1
+ import type { CompileSchematicOptions, SchematicDocument } from './types.js';
2
+ /** Small, allocation-bounded compilation counters. */
3
+ export interface SchematicCompilationMetrics {
4
+ readonly sourceCharacters: number;
5
+ readonly components: number;
6
+ readonly connections: number;
7
+ readonly svgBytes: number;
8
+ }
9
+ /** Validated AST, rendered SVG, and useful host-side counters. */
10
+ export interface SchematicCompilation {
11
+ readonly document: SchematicDocument;
12
+ readonly svg: string;
13
+ readonly metrics: SchematicCompilationMetrics;
14
+ }
15
+ /** Parse and render a schematic with one stable public call. */
16
+ export declare function compileSchematic(source: string, options: CompileSchematicOptions): SchematicCompilation;
@@ -0,0 +1,17 @@
1
+ import { utf8ByteLength } from './limits.js';
2
+ import { parseSchematic } from './parser.js';
3
+ import { renderSchematic } from './renderer.js';
4
+ export function compileSchematic(source, options) {
5
+ const document = parseSchematic(source, options);
6
+ const svg = renderSchematic(document, options);
7
+ return {
8
+ document,
9
+ svg,
10
+ metrics: {
11
+ sourceCharacters: source.length,
12
+ components: document.components.length,
13
+ connections: document.connections.length,
14
+ svgBytes: utf8ByteLength(svg)
15
+ }
16
+ };
17
+ }
package/dist/index.d.ts CHANGED
@@ -1,16 +1,14 @@
1
1
  /**
2
2
  * Stable public entry point for the `schemd` server-side compiler.
3
3
  *
4
- * All runtime exports are dependency-free. The Marked integration imports
5
- * Marked contracts as types only, so applications pay for no Markdown runtime
6
- * unless they already provide one at the host boundary.
4
+ * All runtime exports are dependency-free and safe to run without a DOM.
7
5
  *
8
6
  * @packageDocumentation
9
7
  */
10
8
  export { parseSchematic, parseSchematicColor, parseSchematicFence } from './parser.js';
11
9
  export { renderSchematic } from './renderer.js';
12
- export { schematicMarkedExtension } from './marked-extension.js';
10
+ export { compileSchematic, type SchematicCompilation, type SchematicCompilationMetrics } from './compiler.js';
13
11
  export { mathLabelGlyphLength, mathLabelTextWidth, mathLabelText, parseMathLabel, renderMathLabelTspans, type MathLabelSegment, type MathLabelSegmentKind } from './math-label.js';
14
12
  export { MAX_SCHEMATIC_COMPONENTS, MAX_SCHEMATIC_CONNECTIONS, MAX_SCHEMATIC_WIRE_CROSSINGS, MAX_SCHEMATIC_SOURCE_CHARACTERS, MAX_SCHEMATIC_SVG_OUTPUT_BYTES, SCHEMATIC_LIMITS } from './limits.js';
15
13
  export { classicalGateHeight, componentObstacleRectangle, componentRectangle, componentTextAnchors, distributedCoordinate, enumerateComponentPorts, positionIcPin, PORT_HOTSPOT_RADIUS, resolvePortPoint, resolvePortGeometry, routeConnections, routeConnection, SCHEMATIC_BRIDGE_RADIUS, SCHEMATIC_OBSTACLE_CLEARANCE, validateDocumentGeometry, type RoutedConnection, type ComponentTextAnchors, type ComponentPort, type IcPinSide, type SchematicRectangle } from './layout.js';
16
- export { ANALOG_KINDS, COMPONENT_KINDS, CLASSICAL_GATE_KINDS, DIODE_TYPES, GROUND_STYLES, PASSIVE_KINDS, QUANTUM_GATE_KINDS, SCHEMATIC_SIGNAL_MARKERS, SEMANTIC_COLORS, TRANSISTOR_TYPES, UML_COMPONENT_KINDS, UML_RELATION_KINDS, SCHEMD_OUTPUT_MODES, SchematicSyntaxError, type AnalogKind, type CompileSchematicOptions, type ClassicalGateComponent, type ClassicalGateKind, type ComponentKind, type DiodeComponent, type DiodeType, type GroundComponent, type GroundStyle, type IcComponent, type IntegratedCircuitComponent, type IntegratedCircuitPins, type PassiveComponent, type PassiveKind, type PortComponent, type SemanticColor, type SchematicBounds, type SchematicComponent, type SchematicConnection, type SchematicColor, type SchematicDocument, type SchematicEndpoint, type SchematicFence, type SchematicSignalMarker, type SchematicMarkedOptions, type SchematicPoint, type SchemdOutputMode, type TransistorComponent, type TransistorType, type QuantumGateComponent, type QuantumGateKind, type SchematicRelationKind, type UmlActorComponent, type UmlClassComponent, type UmlComponent, type UmlComponentKind, type UmlPseudostateComponent, type UmlRelationKind, type UmlSizedComponent, type UmlStateComponent } from './types.js';
14
+ export { ANALOG_KINDS, COMPONENT_KINDS, CLASSICAL_GATE_KINDS, DIODE_TYPES, GROUND_STYLES, PASSIVE_KINDS, QUANTUM_GATE_KINDS, SCHEMATIC_SIGNAL_MARKERS, SEMANTIC_COLORS, TRANSISTOR_TYPES, UML_COMPONENT_KINDS, UML_RELATION_KINDS, SCHEMD_OUTPUT_MODES, SCHEMD_SEMANTIC_HOOKS, SchematicSyntaxError, type AnalogKind, type CompileSchematicOptions, type ClassicalGateComponent, type ClassicalGateKind, type ComponentKind, type DiodeComponent, type DiodeType, type GroundComponent, type GroundStyle, type IcComponent, type IntegratedCircuitComponent, type IntegratedCircuitPins, type PassiveComponent, type PassiveKind, type PortComponent, type SemanticColor, type SchematicBounds, type SchematicComponent, type SchematicConnection, type SchematicColor, type SchematicDocument, type SchematicEndpoint, type SchematicFence, type SchematicSignalMarker, type SchematicSemanticHook, type SchematicPoint, type SchemdOutputMode, type TransistorComponent, type TransistorType, type QuantumGateComponent, type QuantumGateKind, type SchematicRelationKind, type UmlActorComponent, type UmlClassComponent, type UmlComponent, type UmlComponentKind, type UmlPseudostateComponent, type UmlRelationKind, type UmlSizedComponent, type UmlStateComponent } from './types.js';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { parseSchematic, parseSchematicColor, parseSchematicFence } from './parser.js';
2
2
  export { renderSchematic } from './renderer.js';
3
- export { schematicMarkedExtension } from './marked-extension.js';
3
+ export { compileSchematic } from './compiler.js';
4
4
  export { mathLabelGlyphLength, mathLabelTextWidth, mathLabelText, parseMathLabel, renderMathLabelTspans } from './math-label.js';
5
5
  export { MAX_SCHEMATIC_COMPONENTS, MAX_SCHEMATIC_CONNECTIONS, MAX_SCHEMATIC_WIRE_CROSSINGS, MAX_SCHEMATIC_SOURCE_CHARACTERS, MAX_SCHEMATIC_SVG_OUTPUT_BYTES, SCHEMATIC_LIMITS } from './limits.js';
6
6
  export { classicalGateHeight, componentObstacleRectangle, componentRectangle, componentTextAnchors, distributedCoordinate, enumerateComponentPorts, positionIcPin, PORT_HOTSPOT_RADIUS, resolvePortPoint, resolvePortGeometry, routeConnections, routeConnection, SCHEMATIC_BRIDGE_RADIUS, SCHEMATIC_OBSTACLE_CLEARANCE, validateDocumentGeometry } from './layout.js';
7
- export { ANALOG_KINDS, COMPONENT_KINDS, CLASSICAL_GATE_KINDS, DIODE_TYPES, GROUND_STYLES, PASSIVE_KINDS, QUANTUM_GATE_KINDS, SCHEMATIC_SIGNAL_MARKERS, SEMANTIC_COLORS, TRANSISTOR_TYPES, UML_COMPONENT_KINDS, UML_RELATION_KINDS, SCHEMD_OUTPUT_MODES, SchematicSyntaxError } from './types.js';
7
+ export { ANALOG_KINDS, COMPONENT_KINDS, CLASSICAL_GATE_KINDS, DIODE_TYPES, GROUND_STYLES, PASSIVE_KINDS, QUANTUM_GATE_KINDS, SCHEMATIC_SIGNAL_MARKERS, SEMANTIC_COLORS, TRANSISTOR_TYPES, UML_COMPONENT_KINDS, UML_RELATION_KINDS, SCHEMD_OUTPUT_MODES, SCHEMD_SEMANTIC_HOOKS, SchematicSyntaxError } from './types.js';
package/dist/limits.d.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  /**
2
- * Hard compiler budgets shared by the parser, renderer, and Marked integration.
2
+ * Hard compiler budgets shared by the parser and renderer.
3
3
  *
4
4
  * `parseSchematic` and `renderSchematic` enforce these limits for one diagram.
5
- * `schematicMarkedExtension` additionally enforces them cumulatively across all
6
- * schematic fences encountered during one Marked document pass.
7
5
  *
8
6
  * @packageDocumentation
9
7
  */
package/dist/renderer.js CHANGED
@@ -1,12 +1,22 @@
1
1
  import { classicalGateHeight, componentTextAnchors, distributedCoordinate, enumerateComponentPorts, positionIcPin, PORT_HOTSPOT_RADIUS, routeConnections, validateDocumentGeometry } from './layout.js';
2
2
  import { MAX_SCHEMATIC_SVG_OUTPUT_BYTES, utf8ByteLength } from './limits.js';
3
3
  import { assertParsedSchematicDocument } from './parser.js';
4
- import { CLASSICAL_GATE_KINDS, SCHEMD_OUTPUT_MODES, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
4
+ import { CLASSICAL_GATE_KINDS, SCHEMD_OUTPUT_MODES, SCHEMD_SEMANTIC_HOOKS, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
5
5
  import { mathLabelTextWidth, renderMathLabelTspans } from './math-label.js';
6
6
  export const MAX_SVG_OUTPUT_BYTES = MAX_SCHEMATIC_SVG_OUTPUT_BYTES;
7
7
  const STATIC_SVG_STYLES = '.schematic-token{fill:none;stroke:var(--schematic-vector,var(--schematic-vector-fallback,currentColor));stroke-linecap:round;stroke-linejoin:round;stroke-width:var(--schematic-stroke-width,1.65);vector-effect:non-scaling-stroke}.schematic-node-fill{fill:var(--schematic-vector,var(--schematic-vector-fallback,currentColor))}.schematic-designator,.schematic-label,.schematic-gate-symbol,.schematic-quantum-detail,.schematic-pin-label,.schematic-uml-text,.schematic-connection-label{fill:currentColor}.schematic-uml-row{font-size:12px}.schematic-uml-stereotype{font-size:11px}.schematic-connection-label{font-size:11px;paint-order:stroke;stroke:var(--schematic-surface,#fff);stroke-width:4;stroke-linejoin:round}.schematic-surface{fill:var(--schematic-surface,transparent)}.schematic-grid-line{fill:none;stroke:var(--schematic-grid,currentColor);stroke-width:1;opacity:.12;vector-effect:non-scaling-stroke}';
8
8
  const HOOK_SVG_STYLES = '.schematic-port-hotspot{fill:transparent!important;stroke:transparent!important;stroke-width:8;vector-effect:non-scaling-stroke;pointer-events:all;outline:none}.schematic-port-hotspot:focus-visible{fill:var(--schematic-vector,var(--schematic-vector-fallback,currentColor))!important;fill-opacity:.22!important;stroke:var(--schematic-vector,var(--schematic-vector-fallback,currentColor))!important;stroke-width:2}';
9
9
  const INTERACTIVE_SVG_STYLES = '.schematic-component,.schematic-wire{outline:none;transition:opacity .2s ease,filter .2s ease}.schematic-component .schematic-token,.schematic-wire .schematic-token{transition:stroke .2s ease,fill .2s ease,color .2s ease,opacity .2s ease,stroke-width .2s ease}.schematic-glow-layer{opacity:0;pointer-events:none;transition:opacity .2s ease}.schematic-component:hover>.schematic-glow-layer,.schematic-component:focus>.schematic-glow-layer,.schematic-component:focus-within>.schematic-glow-layer,.schematic-component.is-hovered>.schematic-glow-layer,.schematic-component.is-active>.schematic-glow-layer,.schematic-component.is-selected>.schematic-glow-layer,.schematic-wire:hover>.schematic-glow-layer,.schematic-wire:focus>.schematic-glow-layer,.schematic-wire.is-hovered>.schematic-glow-layer,.schematic-wire.is-active>.schematic-glow-layer,.schematic-wire.is-selected>.schematic-glow-layer{opacity:1}.schematic-component.is-degraded,.schematic-wire.is-degraded{opacity:.45}.schematic-component.is-degraded>.schematic-glow-layer,.schematic-wire.is-degraded>.schematic-glow-layer{opacity:0}.schematic-component:hover .schematic-token,.schematic-component:focus .schematic-token,.schematic-component:focus-within .schematic-token,.schematic-component.is-hovered .schematic-token,.schematic-component.is-active .schematic-token,.schematic-component.is-selected .schematic-token,.schematic-wire:hover .schematic-token,.schematic-wire:focus .schematic-token,.schematic-wire.is-hovered .schematic-token,.schematic-wire.is-active .schematic-token,.schematic-wire.is-selected .schematic-token{stroke-width:var(--schematic-interactive-stroke-width,2.25)}.schematic-port-hotspot{fill:var(--schematic-vector,var(--schematic-vector-fallback,currentColor))!important;fill-opacity:0;transition:fill-opacity .2s ease,opacity .2s ease}.schematic-port-hotspot:hover,.schematic-port-hotspot:focus,.schematic-port-hotspot.is-hovered,.schematic-port-hotspot.is-active,.schematic-port-hotspot.is-selected{fill-opacity:.2}.schematic-port-hotspot.is-degraded{opacity:.45}@media(prefers-reduced-motion:reduce){.schematic-component,.schematic-wire,.schematic-token,.schematic-glow-layer,.schematic-port-hotspot{transition:none}}';
10
+ const NODE_HOOK = 1;
11
+ const PORT_HOOK = 2;
12
+ const WIRE_HOOK = 4;
13
+ function semanticHookBit(hook) {
14
+ if (hook === 'nodes')
15
+ return NODE_HOOK;
16
+ if (hook === 'ports')
17
+ return PORT_HOOK;
18
+ return WIRE_HOOK;
19
+ }
10
20
  function validXmlCodePoint(codePoint) {
11
21
  if (codePoint === 0x09 || codePoint === 0x0a || codePoint === 0x0d)
12
22
  return true;
@@ -82,9 +92,24 @@ function normalizeCompileOptions(value) {
82
92
  throw new SchematicSyntaxError('Render mode must be one of: default, embedded-css, or full.');
83
93
  }
84
94
  const normalizedMode = (mode ?? 'default');
95
+ const rawSemanticHooks = candidate.semanticHooks;
96
+ let semanticHookMask = NODE_HOOK | PORT_HOOK | WIRE_HOOK;
97
+ if (rawSemanticHooks !== undefined) {
98
+ if (!Array.isArray(rawSemanticHooks)) {
99
+ throw new SchematicSyntaxError('Render semanticHooks must be an array.');
100
+ }
101
+ semanticHookMask = 0;
102
+ for (const hook of rawSemanticHooks) {
103
+ if (typeof hook !== 'string' ||
104
+ !SCHEMD_SEMANTIC_HOOKS.includes(hook)) {
105
+ throw new SchematicSyntaxError('Render semanticHooks may contain only: nodes, ports, or wires.');
106
+ }
107
+ semanticHookMask |= semanticHookBit(hook);
108
+ }
109
+ }
85
110
  return idPrefix === undefined
86
- ? { bounds: { width, height }, title, mode: normalizedMode }
87
- : { bounds: { width, height }, title, idPrefix, mode: normalizedMode };
111
+ ? { bounds: { width, height }, title, mode: normalizedMode, semanticHookMask }
112
+ : { bounds: { width, height }, title, idPrefix, mode: normalizedMode, semanticHookMask };
88
113
  }
89
114
  function svgNumber(value) {
90
115
  return String(Number(value.toFixed(3)));
@@ -516,19 +541,18 @@ function componentPortMarkup(component) {
516
541
  })
517
542
  .join('');
518
543
  }
519
- function componentMarkup(component, index, idPrefix, glowId, mode, symbolId) {
544
+ function componentMarkup(component, index, idPrefix, glowId, mode, nodeHooks, portHooks, symbolId) {
520
545
  const label = escapeXml(component.label);
521
546
  const renderedLabel = renderMathLabelTspans(component.label);
522
547
  const anchors = componentTextAnchors(component);
523
548
  const id = escapeXml(component.id);
524
549
  const vectorId = `${idPrefix}-node-${index}-vector`;
525
550
  const styles = mode === 'embedded-css' || mode === 'full';
526
- const hooks = mode === 'full';
527
- const dataAttributes = hooks
551
+ const dataAttributes = nodeHooks
528
552
  ? ` data-node-id="${id}" data-node-kind="${component.kind}" data-node-label="${label}" data-component="${id}" data-kind="${component.kind}"`
529
553
  : '';
530
554
  let accessibility = '';
531
- if (styles || hooks) {
555
+ if (styles) {
532
556
  const metadata = componentMetadata(component);
533
557
  const ariaLabel = escapeXml(`${component.id}, ${component.kind}, ${component.label}${metadata === '' ? '' : `, ${metadata}`}`);
534
558
  accessibility = ` tabindex="0" role="group" aria-label="${ariaLabel}"`;
@@ -537,7 +561,7 @@ function componentMarkup(component, index, idPrefix, glowId, mode, symbolId) {
537
561
  const glow = styles
538
562
  ? `<use class="schematic-glow-layer" href="#${vectorId}" filter="url(#${glowId})" aria-hidden="true" pointer-events="none" />`
539
563
  : '';
540
- const hotspots = hooks ? componentPortMarkup(component) : '';
564
+ const hotspots = portHooks ? componentPortMarkup(component) : '';
541
565
  const vector = symbolId === undefined
542
566
  ? `<g${vectorIdAttribute} class="schematic-component-vector">${componentShape(component)}</g>`
543
567
  : `<use${vectorIdAttribute} ${colorAttributes(component.color, 'schematic-component-vector')} href="#${symbolId}" />`;
@@ -563,6 +587,9 @@ export function renderSchematic(document, options) {
563
587
  const glowId = `${safePrefix}-schematic-glow-filter`;
564
588
  const styles = normalized.mode === 'embedded-css' || normalized.mode === 'full';
565
589
  const hooks = normalized.mode === 'full';
590
+ const nodeHooks = hooks && (normalized.semanticHookMask & NODE_HOOK) !== 0;
591
+ const portHooks = hooks && (normalized.semanticHookMask & PORT_HOOK) !== 0;
592
+ const wireHooks = hooks && (normalized.semanticHookMask & WIRE_HOOK) !== 0;
566
593
  const reusableSymbols = new Map();
567
594
  for (const component of document.components) {
568
595
  const key = reusableSymbolKey(component);
@@ -574,7 +601,7 @@ export function renderSchematic(document, options) {
574
601
  const componentCount = document.components.length;
575
602
  const description = `${componentCount} component${componentCount === 1 ? '' : 's'} and ${document.connections.length} connection${document.connections.length === 1 ? '' : 's'}.`;
576
603
  const writer = new BoundedSvgWriter();
577
- const hookStyles = hooks ? HOOK_SVG_STYLES : '';
604
+ const hookStyles = portHooks ? HOOK_SVG_STYLES : '';
578
605
  const interactionStyles = styles ? INTERACTIVE_SVG_STYLES : '';
579
606
  const glowFilter = styles
580
607
  ? `<filter id="${glowId}" x="-30%" y="-30%" width="160%" height="160%" color-interpolation-filters="sRGB"><feGaussianBlur stdDeviation="2.2" result="blur" /><feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge></filter>`
@@ -587,7 +614,7 @@ export function renderSchematic(document, options) {
587
614
  : '';
588
615
  const definitions = `${embeddedDefinitions}${markerDefinitions}${symbolDefinitions}`;
589
616
  writer.append(`<figure class="schematic-frame"${hooks ? ' data-schematic' : ''}><svg class="schematic-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${normalized.bounds.width} ${normalized.bounds.height}" width="${normalized.bounds.width}" height="${normalized.bounds.height}" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" role="img" aria-labelledby="${titleId} ${descriptionId}" preserveAspectRatio="xMidYMid meet"><title id="${titleId}">${title}</title><desc id="${descriptionId}">${description}</desc>${definitions === '' ? '' : `<defs>${definitions}</defs>`}${styles ? `<rect class="schematic-surface" width="100%" height="100%" /><rect class="schematic-grid" width="100%" height="100%" fill="url(#${gridId})" />` : ''}<g class="schematic-vectors">`);
590
- if (hooks) {
617
+ if (wireHooks) {
591
618
  for (const [index, connection] of document.connections.entries()) {
592
619
  writer.append(connectionMarkup(connection, routedConnections[index], index, safePrefix, glowId));
593
620
  }
@@ -598,7 +625,7 @@ export function renderSchematic(document, options) {
598
625
  for (const [index, component] of document.components.entries()) {
599
626
  const key = reusableSymbolKey(component);
600
627
  const symbolId = key === undefined ? undefined : reusableSymbols.get(key)?.id;
601
- writer.append(componentMarkup(component, index, safePrefix, glowId, normalized.mode, symbolId));
628
+ writer.append(componentMarkup(component, index, safePrefix, glowId, normalized.mode, nodeHooks, portHooks, symbolId));
602
629
  }
603
630
  writer.append(`</g></svg><figcaption>${title}</figcaption></figure>`);
604
631
  return writer.finish();
package/dist/types.d.ts CHANGED
@@ -261,6 +261,10 @@ export interface SchematicDocument {
261
261
  export declare const SCHEMD_OUTPUT_MODES: readonly ["default", "embedded-css", "full"];
262
262
  /** Static, CSS-enhanced, or fully attributed SVG output mode. */
263
263
  export type SchemdOutputMode = (typeof SCHEMD_OUTPUT_MODES)[number];
264
+ /** Optional semantic payloads emitted by `full` mode. */
265
+ export declare const SCHEMD_SEMANTIC_HOOKS: readonly ["nodes", "ports", "wires"];
266
+ /** A selectable delegated-interaction payload. */
267
+ export type SchematicSemanticHook = (typeof SCHEMD_SEMANTIC_HOOKS)[number];
264
268
  /** Marker primitives that can terminate or originate a signal trace. */
265
269
  export declare const SCHEMATIC_SIGNAL_MARKERS: readonly ["none", "arrow", "open-arrow", "dot", "triangle", "diamond", "diamond-filled"];
266
270
  /** A validated connection marker selection. */
@@ -271,17 +275,8 @@ export interface CompileSchematicOptions extends SchematicFence {
271
275
  idPrefix?: string;
272
276
  /** Markup and interaction budget for the generated SVG. */
273
277
  mode?: SchemdOutputMode;
274
- }
275
- /** Configuration accepted by the type-only Marked extension factory. */
276
- export interface SchematicMarkedOptions {
277
- /** Accessible title used when the fence omits one. */
278
- defaultTitle?: string;
279
- /** Fixed output mode for every schemd fence in a Markdown pass. */
280
- mode?: SchemdOutputMode;
281
- /** Synchronous mode resolver for hosts with request-scoped rendering state. */
282
- resolveMode?: () => SchemdOutputMode;
283
- /** Optional safe fallback renderer for bounded syntax failures. */
284
- onError?: (error: SchematicSyntaxError, source: string) => string;
278
+ /** Full-mode metadata to emit. Defaults to nodes, ports, and wires. */
279
+ semanticHooks?: readonly SchematicSemanticHook[];
285
280
  }
286
281
  /**
287
282
  * Deterministic syntax or geometry failure with optional source location.
package/dist/types.js CHANGED
@@ -38,6 +38,7 @@ export const UML_RELATION_KINDS = [
38
38
  'extend'
39
39
  ];
40
40
  export const SCHEMD_OUTPUT_MODES = ['default', 'embedded-css', 'full'];
41
+ export const SCHEMD_SEMANTIC_HOOKS = ['nodes', 'ports', 'wires'];
41
42
  export const SCHEMATIC_SIGNAL_MARKERS = [
42
43
  'none',
43
44
  'arrow',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schemd/core",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Zero-dependency text-to-SVG compiler for schematics and UML.",
5
5
  "homepage": "https://johnowolabiidogun.dev/tools/schemd/docs/overview",
6
6
  "bugs": {
@@ -32,6 +32,10 @@
32
32
  "types": "./dist/renderer.d.ts",
33
33
  "import": "./dist/renderer.js"
34
34
  },
35
+ "./compiler": {
36
+ "types": "./dist/compiler.d.ts",
37
+ "import": "./dist/compiler.js"
38
+ },
35
39
  "./layout": {
36
40
  "types": "./dist/layout.d.ts",
37
41
  "import": "./dist/layout.js"
@@ -40,10 +44,6 @@
40
44
  "types": "./dist/math-label.d.ts",
41
45
  "import": "./dist/math-label.js"
42
46
  },
43
- "./marked": {
44
- "types": "./dist/marked-extension.d.ts",
45
- "import": "./dist/marked-extension.js"
46
- },
47
47
  "./types": {
48
48
  "types": "./dist/types.d.ts",
49
49
  "import": "./dist/types.js"
@@ -56,17 +56,8 @@
56
56
  "test": "vitest run",
57
57
  "test:coverage": "vitest run --coverage"
58
58
  },
59
- "peerDependencies": {
60
- "marked": ">=17 <19"
61
- },
62
- "peerDependenciesMeta": {
63
- "marked": {
64
- "optional": true
65
- }
66
- },
67
59
  "devDependencies": {
68
60
  "@vitest/coverage-v8": "^4.1.10",
69
- "marked": "^18.0.6",
70
61
  "typescript": "^6.0.3",
71
62
  "vitest": "^4.1.10"
72
63
  },
@@ -75,7 +66,6 @@
75
66
  },
76
67
  "keywords": [
77
68
  "schemd",
78
- "marked",
79
69
  "schematic",
80
70
  "svg",
81
71
  "compiler",
@@ -1,23 +0,0 @@
1
- /**
2
- * Type-only Marked adapter for compiling fenced `schemd` diagrams during SSR.
3
- *
4
- * The module imports no Marked runtime. Its factory returns ordinary extension
5
- * hooks to a host-owned parser and enforces both per-diagram compiler ceilings
6
- * and cumulative limits across each Markdown document pass.
7
- *
8
- * @packageDocumentation
9
- */
10
- import type { MarkedExtension } from 'marked';
11
- import { type SchematicMarkedOptions } from './types.js';
12
- /**
13
- * Create a bounded Marked extension that compiles `schemd` fences into inline SVG.
14
- *
15
- * The returned extension keeps aggregate per-document budgets in closure state,
16
- * resets them from Marked's `preprocess` hook, and recognizes only the canonical
17
- * `schemd` language identifier.
18
- *
19
- * @param options - Output mode, accessible-title fallback, dynamic mode resolver,
20
- * and trusted server-side diagnostic renderer.
21
- * @returns A Marked extension suitable for a server-owned `Marked` instance.
22
- */
23
- export declare function schematicMarkedExtension(options?: SchematicMarkedOptions): MarkedExtension;
@@ -1,75 +0,0 @@
1
- import { MAX_SCHEMATIC_COMPONENTS, MAX_SCHEMATIC_CONNECTIONS, MAX_SCHEMATIC_SOURCE_CHARACTERS, MAX_SCHEMATIC_SVG_OUTPUT_BYTES, utf8ByteLength } from './limits.js';
2
- import { parseSchematic, parseSchematicFence } from './parser.js';
3
- import { renderSchematic } from './renderer.js';
4
- import { SchematicSyntaxError } from './types.js';
5
- class SchematicDocumentBudgetError extends SchematicSyntaxError {
6
- }
7
- function emptyDocumentBudget() {
8
- return { sourceCharacters: 0, components: 0, connections: 0, svgOutputBytes: 0 };
9
- }
10
- function consumeBudget(used, amount, limit, unit) {
11
- if (amount > limit - used) {
12
- throw new SchematicDocumentBudgetError(`Schematic document exceeds the cumulative ${limit.toLocaleString('en-US')} ${unit} limit.`);
13
- }
14
- return used + amount;
15
- }
16
- function escapeHtml(value) {
17
- return value
18
- .replaceAll('&', '&amp;')
19
- .replaceAll('<', '&lt;')
20
- .replaceAll('>', '&gt;')
21
- .replaceAll('"', '&quot;')
22
- .replaceAll("'", '&#39;');
23
- }
24
- function defaultErrorRenderer(error, source) {
25
- return `<figure class="schematic-error" role="group" aria-label="Schematic compilation error"><figcaption>${escapeHtml(error.message)}</figcaption><pre><code class="language-schemd">${escapeHtml(source)}</code></pre></figure>`;
26
- }
27
- export function schematicMarkedExtension(options = {}) {
28
- let diagramIndex = 0;
29
- let budget = emptyDocumentBudget();
30
- let budgetError;
31
- return {
32
- hooks: {
33
- preprocess(source) {
34
- diagramIndex = 0;
35
- budget = emptyDocumentBudget();
36
- budgetError = undefined;
37
- return source;
38
- }
39
- },
40
- renderer: {
41
- code(token) {
42
- if (!token.lang || !/^schemd(?:\s|$)/i.test(token.lang.trim())) {
43
- return false;
44
- }
45
- try {
46
- if (budgetError)
47
- throw budgetError;
48
- budget.sourceCharacters = consumeBudget(budget.sourceCharacters, token.text.length, MAX_SCHEMATIC_SOURCE_CHARACTERS, 'schematic source character');
49
- const fence = parseSchematicFence(token.lang, options.defaultTitle);
50
- diagramIndex += 1;
51
- const document = parseSchematic(token.text, fence);
52
- const nextComponents = consumeBudget(budget.components, document.components.length, MAX_SCHEMATIC_COMPONENTS, 'component');
53
- const nextConnections = consumeBudget(budget.connections, document.connections.length, MAX_SCHEMATIC_CONNECTIONS, 'connection');
54
- const html = renderSchematic(document, {
55
- ...fence,
56
- idPrefix: `schematic-${diagramIndex}`,
57
- mode: options.resolveMode?.() ?? options.mode ?? 'default'
58
- });
59
- const nextSvgOutputBytes = consumeBudget(budget.svgOutputBytes, utf8ByteLength(html), MAX_SCHEMATIC_SVG_OUTPUT_BYTES, 'compiled SVG byte');
60
- budget.components = nextComponents;
61
- budget.connections = nextConnections;
62
- budget.svgOutputBytes = nextSvgOutputBytes;
63
- return html;
64
- }
65
- catch (error) {
66
- if (!(error instanceof SchematicSyntaxError))
67
- throw error;
68
- if (error instanceof SchematicDocumentBudgetError)
69
- budgetError = error;
70
- return (options.onError ?? defaultErrorRenderer)(error, token.text);
71
- }
72
- }
73
- }
74
- };
75
- }