libpetri 1.7.0 → 1.8.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.
@@ -20,7 +20,7 @@ var EDGE_STYLES = {
20
20
  reset: { color: "#fd7e14", style: "bold", arrowhead: "normal", penwidth: 2 }
21
21
  };
22
22
  var FONT = { family: "Helvetica,Arial,sans-serif", nodeSize: 12, edgeSize: 10 };
23
- var GRAPH = { nodesep: 0.5, ranksep: 0.75, forcelabels: true, overlap: false };
23
+ var GRAPH = { nodesep: 0.5, ranksep: 0.75, forcelabels: true, overlap: false, outputorder: "edgesfirst", splines: "curved" };
24
24
  function nodeStyle(category) {
25
25
  return NODE_STYLES[category];
26
26
  }
@@ -157,7 +157,9 @@ function mapToGraph(net, config = DEFAULT_DOT_CONFIG) {
157
157
  ranksep: String(GRAPH.ranksep),
158
158
  forcelabels: String(GRAPH.forcelabels),
159
159
  overlap: String(GRAPH.overlap),
160
- fontname: FONT.family
160
+ fontname: FONT.family,
161
+ outputorder: GRAPH.outputorder,
162
+ splines: GRAPH.splines
161
163
  },
162
164
  nodeDefaults: {
163
165
  fontname: FONT.family,
@@ -411,4 +413,4 @@ export {
411
413
  renderDot,
412
414
  dotExport
413
415
  };
414
- //# sourceMappingURL=chunk-7EKL7INR.js.map
416
+ //# sourceMappingURL=chunk-JNHADILD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/export/styles.ts","../src/export/petri-net-mapper.ts","../src/export/dot-renderer.ts","../src/export/dot-exporter.ts"],"sourcesContent":["// GENERATED from spec/petri-net-styles.json — do not edit manually.\n// Regenerate with: scripts/generate-styles.sh\n\n/**\n * Style loader for Petri net visualization.\n *\n * Reads the shared style definition from `spec/petri-net-styles.json` and\n * exposes typed accessors for node and edge visual properties.\n *\n * @module export/styles\n */\n\nimport type { NodeShape, EdgeLineStyle, ArrowHead } from './graph.js';\n\n// ======================== Style Types ========================\n\nexport interface NodeVisual {\n readonly shape: NodeShape;\n readonly fill: string;\n readonly stroke: string;\n readonly penwidth: number;\n readonly style?: string;\n readonly height?: number;\n readonly width?: number;\n}\n\nexport interface EdgeVisual {\n readonly color: string;\n readonly style: EdgeLineStyle;\n readonly arrowhead: ArrowHead;\n readonly penwidth?: number;\n}\n\nexport interface FontStyle {\n readonly family: string;\n readonly nodeSize: number;\n readonly edgeSize: number;\n}\n\nexport interface GraphStyle {\n readonly nodesep: number;\n readonly ranksep: number;\n readonly forcelabels: boolean;\n readonly overlap: boolean;\n readonly outputorder: string;\n readonly splines: string;\n}\n\n// ======================== Inline Style Data ========================\n\n// Inlined from spec/petri-net-styles.json to avoid runtime JSON import issues.\n// Keep in sync with the spec file.\n\nconst NODE_STYLES: Record<NodeCategory, NodeVisual> = {\n place: { shape: 'circle', fill: '#FFFFFF', stroke: '#333333', penwidth: 1.5, width: 0.35 },\n start: { shape: 'circle', fill: '#d4edda', stroke: '#28a745', penwidth: 2.0, width: 0.35 },\n end: { shape: 'doublecircle', fill: '#cce5ff', stroke: '#004085', penwidth: 2.0, width: 0.35 },\n environment: { shape: 'circle', fill: '#f8d7da', stroke: '#721c24', penwidth: 2.0, style: 'dashed', width: 0.35 },\n transition: { shape: 'box', fill: '#fff3cd', stroke: '#856404', penwidth: 1.0, height: 0.4, width: 0.8 },\n};\n\nconst EDGE_STYLES: Record<EdgeCategory, EdgeVisual> = {\n input: { color: '#333333', style: 'solid', arrowhead: 'normal' },\n output: { color: '#333333', style: 'solid', arrowhead: 'normal' },\n inhibitor: { color: '#dc3545', style: 'solid', arrowhead: 'odot' },\n read: { color: '#6c757d', style: 'dashed', arrowhead: 'normal' },\n reset: { color: '#fd7e14', style: 'bold', arrowhead: 'normal', penwidth: 2.0 },\n};\n\nexport const FONT: FontStyle = { family: 'Helvetica,Arial,sans-serif', nodeSize: 12, edgeSize: 10 };\n\nexport const GRAPH: GraphStyle = { nodesep: 0.5, ranksep: 0.75, forcelabels: true, overlap: false, outputorder: 'edgesfirst', splines: 'curved' };\n\n// ======================== Public API ========================\n\nexport type NodeCategory = 'place' | 'start' | 'end' | 'environment' | 'transition';\nexport type EdgeCategory = 'input' | 'output' | 'inhibitor' | 'read' | 'reset';\n\n/** Returns the visual style for the given node category. */\nexport function nodeStyle(category: NodeCategory): NodeVisual {\n return NODE_STYLES[category];\n}\n\n/** Returns the visual style for the given edge/arc type. */\nexport function edgeStyle(arcType: EdgeCategory): EdgeVisual {\n return EDGE_STYLES[arcType];\n}\n","/**\n * Maps a PetriNet definition to a format-agnostic Graph.\n *\n * This is where all the Petri net semantics live. The mapper understands\n * places, transitions, arcs, timing, and priority. It produces a Graph\n * that can be rendered to DOT (or any other format) without Petri net knowledge.\n *\n * @module export/petri-net-mapper\n */\n\nimport type { PetriNet } from '../core/petri-net.js';\nimport type { Transition } from '../core/transition.js';\nimport type { Out } from '../core/out.js';\nimport { earliest, latest, hasDeadline } from '../core/timing.js';\nimport type { Graph, GraphNode, GraphEdge, RankDir } from './graph.js';\nimport { nodeStyle, edgeStyle, FONT, GRAPH } from './styles.js';\nimport type { NodeCategory } from './styles.js';\n\n// ======================== Configuration ========================\n\nexport interface DotConfig {\n readonly direction: RankDir;\n readonly showTypes: boolean;\n readonly showIntervals: boolean;\n readonly showPriority: boolean;\n readonly environmentPlaces?: ReadonlySet<string>;\n}\n\nexport const DEFAULT_DOT_CONFIG: DotConfig = {\n direction: 'TB',\n showTypes: true,\n showIntervals: true,\n showPriority: true,\n};\n\n// ======================== Public API ========================\n\n/** Sanitizes a name for use as a graph node ID. */\nexport function sanitize(name: string): string {\n return name.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\n/** Maps a PetriNet to a format-agnostic Graph. */\nexport function mapToGraph(net: PetriNet, config: DotConfig = DEFAULT_DOT_CONFIG): Graph {\n const places = analyzePlaces(net);\n const envNames = config.environmentPlaces ?? new Set<string>();\n\n const nodes: GraphNode[] = [];\n const edges: GraphEdge[] = [];\n\n // Place nodes\n for (const [name, info] of places) {\n const category = placeCategory(info, envNames.has(name));\n const style = nodeStyle(category);\n nodes.push({\n id: 'p_' + sanitize(name),\n label: '',\n shape: style.shape,\n fill: style.fill,\n stroke: style.stroke,\n penwidth: style.penwidth,\n semanticId: name,\n style: style.style,\n width: style.width,\n attrs: { xlabel: name, fixedsize: 'true' },\n });\n }\n\n // Transition nodes\n for (const t of net.transitions) {\n const style = nodeStyle('transition');\n nodes.push({\n id: 't_' + sanitize(t.name),\n label: transitionLabel(t, config),\n shape: style.shape,\n fill: style.fill,\n stroke: style.stroke,\n penwidth: style.penwidth,\n semanticId: t.name,\n height: style.height,\n width: style.width,\n });\n }\n\n // Edges\n for (const t of net.transitions) {\n const tid = 't_' + sanitize(t.name);\n\n // Input arcs from inputSpecs\n for (const spec of t.inputSpecs) {\n const pid = 'p_' + sanitize(spec.place.name);\n const inputStyle = edgeStyle('input');\n let label: string | undefined;\n switch (spec.type) {\n case 'exactly':\n label = `\\u00d7${spec.count}`;\n break;\n case 'all':\n label = '*';\n break;\n case 'at-least':\n label = `\\u2265${spec.minimum}`;\n break;\n }\n edges.push({\n from: pid,\n to: tid,\n label,\n color: inputStyle.color,\n style: inputStyle.style,\n arrowhead: inputStyle.arrowhead,\n arcType: 'input',\n });\n }\n\n // Output arcs from outputSpec\n if (t.outputSpec !== null) {\n edges.push(...outputEdges(tid, t.outputSpec, null));\n }\n\n // Inhibitor arcs\n for (const inh of t.inhibitors) {\n const pid = 'p_' + sanitize(inh.place.name);\n const inhStyle = edgeStyle('inhibitor');\n edges.push({\n from: pid,\n to: tid,\n color: inhStyle.color,\n style: inhStyle.style,\n arrowhead: inhStyle.arrowhead,\n arcType: 'inhibitor',\n });\n }\n\n // Read arcs\n for (const r of t.reads) {\n const pid = 'p_' + sanitize(r.place.name);\n const readStyle = edgeStyle('read');\n edges.push({\n from: pid,\n to: tid,\n label: 'read',\n color: readStyle.color,\n style: readStyle.style,\n arrowhead: readStyle.arrowhead,\n arcType: 'read',\n });\n }\n\n // Reset arcs (only those without matching output)\n const outputPlaceNames = t.outputSpec !== null\n ? new Set([...t.outputPlaces()].map(p => p.name))\n : new Set<string>();\n for (const rst of t.resets) {\n if (!outputPlaceNames.has(rst.place.name)) {\n const pid = 'p_' + sanitize(rst.place.name);\n const resetStyle = edgeStyle('reset');\n edges.push({\n from: tid,\n to: pid,\n label: 'reset',\n color: resetStyle.color,\n style: resetStyle.style,\n arrowhead: resetStyle.arrowhead,\n penwidth: resetStyle.penwidth,\n arcType: 'reset',\n });\n }\n }\n }\n\n return {\n id: sanitize(net.name),\n rankdir: config.direction,\n nodes,\n edges,\n subgraphs: [],\n graphAttrs: {\n nodesep: String(GRAPH.nodesep),\n ranksep: String(GRAPH.ranksep),\n forcelabels: String(GRAPH.forcelabels),\n overlap: String(GRAPH.overlap),\n fontname: FONT.family,\n outputorder: GRAPH.outputorder,\n splines: GRAPH.splines,\n },\n nodeDefaults: {\n fontname: FONT.family,\n fontsize: String(FONT.nodeSize),\n },\n edgeDefaults: {\n fontname: FONT.family,\n fontsize: String(FONT.edgeSize),\n },\n };\n}\n\n// ======================== Place Analysis ========================\n\ninterface PlaceInfo {\n hasIncoming: boolean;\n hasOutgoing: boolean;\n}\n\nfunction analyzePlaces(net: PetriNet): Map<string, PlaceInfo> {\n const map = new Map<string, PlaceInfo>();\n\n function ensure(name: string): PlaceInfo {\n let info = map.get(name);\n if (!info) {\n info = { hasIncoming: false, hasOutgoing: false };\n map.set(name, info);\n }\n return info;\n }\n\n for (const t of net.transitions) {\n for (const spec of t.inputSpecs) {\n ensure(spec.place.name).hasOutgoing = true;\n }\n if (t.outputSpec !== null) {\n for (const p of t.outputPlaces()) {\n ensure(p.name).hasIncoming = true;\n }\n }\n for (const inh of t.inhibitors) {\n ensure(inh.place.name);\n }\n for (const r of t.reads) {\n ensure(r.place.name).hasOutgoing = true;\n }\n for (const rst of t.resets) {\n ensure(rst.place.name);\n }\n }\n\n return map;\n}\n\nfunction placeCategory(info: PlaceInfo, isEnvironment: boolean): NodeCategory {\n if (isEnvironment) return 'environment';\n if (!info.hasIncoming) return 'start';\n if (!info.hasOutgoing) return 'end';\n return 'place';\n}\n\n// ======================== Helpers ========================\n\nfunction transitionLabel(t: Transition, config: DotConfig): string {\n const parts = [t.name];\n\n if (config.showIntervals) {\n const e = earliest(t.timing);\n const l = latest(t.timing);\n const max = hasDeadline(t.timing) ? String(l) : '\\u221e';\n parts.push(`[${e}, ${max}]ms`);\n }\n\n if (config.showPriority && t.priority !== 0) {\n parts.push(`prio=${t.priority}`);\n }\n\n return parts.join(' ');\n}\n\nfunction outputEdges(transitionId: string, out: Out, branchLabel: string | null): GraphEdge[] {\n const outStyle = edgeStyle('output');\n\n switch (out.type) {\n case 'place': {\n const pid = 'p_' + sanitize(out.place.name);\n return [{\n from: transitionId,\n to: pid,\n label: branchLabel ?? undefined,\n color: outStyle.color,\n style: outStyle.style,\n arrowhead: outStyle.arrowhead,\n arcType: 'output',\n }];\n }\n\n case 'forward-input': {\n const pid = 'p_' + sanitize(out.to.name);\n const label = (branchLabel ? branchLabel + ' ' : '') + '\\u27f5' + out.from.name;\n return [{\n from: transitionId,\n to: pid,\n label,\n color: outStyle.color,\n style: 'dashed' as const,\n arrowhead: outStyle.arrowhead,\n arcType: 'output',\n }];\n }\n\n case 'and':\n return out.children.flatMap(c => outputEdges(transitionId, c, branchLabel));\n\n case 'xor': {\n const edges: GraphEdge[] = [];\n for (const child of out.children) {\n const label = inferBranchLabel(child);\n edges.push(...outputEdges(transitionId, child, label));\n }\n return edges;\n }\n\n case 'timeout':\n return outputEdges(transitionId, out.child, `\\u23f1${out.afterMs}ms`);\n }\n}\n\nfunction inferBranchLabel(out: Out): string | null {\n switch (out.type) {\n case 'place': return out.place.name;\n case 'timeout': return `\\u23f1${out.afterMs}ms`;\n case 'forward-input': return out.to.name;\n case 'and':\n case 'xor':\n return null;\n }\n}\n","/**\n * Renders a Graph to DOT (Graphviz) string.\n *\n * Pure function with zero Petri net knowledge. Operates solely on the\n * format-agnostic Graph model.\n *\n * @module export/dot-renderer\n */\n\nimport type { Graph, GraphNode, GraphEdge, Subgraph } from './graph.js';\n\n// ======================== Public API ========================\n\n/** Renders a Graph to a DOT string suitable for Graphviz. */\nexport function renderDot(graph: Graph): string {\n const lines: string[] = [];\n\n lines.push(`digraph ${quoteId(graph.id)} {`);\n\n // Graph attributes\n lines.push(` rankdir=${graph.rankdir};`);\n for (const [key, value] of Object.entries(graph.graphAttrs)) {\n lines.push(` ${key}=${quoteAttr(value)};`);\n }\n\n // Node defaults\n if (Object.keys(graph.nodeDefaults).length > 0) {\n lines.push(` node [${formatAttrs(graph.nodeDefaults)}];`);\n }\n\n // Edge defaults\n if (Object.keys(graph.edgeDefaults).length > 0) {\n lines.push(` edge [${formatAttrs(graph.edgeDefaults)}];`);\n }\n\n lines.push('');\n\n // Subgraphs\n for (const sg of graph.subgraphs) {\n lines.push(...renderSubgraph(sg, ' '));\n lines.push('');\n }\n\n // Nodes\n for (const node of graph.nodes) {\n lines.push(` ${renderNode(node)}`);\n }\n\n if (graph.nodes.length > 0) {\n lines.push('');\n }\n\n // Edges\n for (const edge of graph.edges) {\n lines.push(` ${renderEdge(edge)}`);\n }\n\n lines.push('}');\n\n return lines.join('\\n');\n}\n\n// ======================== Internal Rendering ========================\n\nfunction renderNode(node: GraphNode): string {\n const attrs: Record<string, string> = {\n label: node.label,\n shape: node.shape,\n style: node.style ? `\"filled,${node.style}\"` : 'filled',\n fillcolor: node.fill,\n color: node.stroke,\n penwidth: String(node.penwidth),\n };\n\n if (node.height !== undefined) {\n attrs['height'] = String(node.height);\n }\n if (node.width !== undefined) {\n attrs['width'] = String(node.width);\n }\n\n // Merge extra attrs\n if (node.attrs) {\n for (const [key, value] of Object.entries(node.attrs)) {\n attrs[key] = value;\n }\n }\n\n return `${quoteId(node.id)} [${formatNodeAttrs(attrs)}];`;\n}\n\nfunction renderEdge(edge: GraphEdge): string {\n const attrs: Record<string, string> = {\n color: edge.color,\n style: edge.style,\n arrowhead: edge.arrowhead,\n };\n\n if (edge.label !== undefined) {\n attrs['label'] = edge.label;\n }\n if (edge.penwidth !== undefined) {\n attrs['penwidth'] = String(edge.penwidth);\n }\n\n // Merge extra attrs\n if (edge.attrs) {\n for (const [key, value] of Object.entries(edge.attrs)) {\n attrs[key] = value;\n }\n }\n\n return `${quoteId(edge.from)} -> ${quoteId(edge.to)} [${formatNodeAttrs(attrs)}];`;\n}\n\nfunction renderSubgraph(sg: Subgraph, indent: string): string[] {\n const lines: string[] = [];\n lines.push(`${indent}subgraph ${quoteId('cluster_' + sg.id)} {`);\n\n if (sg.label !== undefined) {\n lines.push(`${indent} label=${quoteAttr(sg.label)};`);\n }\n\n if (sg.attrs) {\n for (const [key, value] of Object.entries(sg.attrs)) {\n lines.push(`${indent} ${key}=${quoteAttr(value)};`);\n }\n }\n\n for (const node of sg.nodes) {\n lines.push(`${indent} ${renderNode(node)}`);\n }\n\n lines.push(`${indent}}`);\n return lines;\n}\n\n// ======================== DOT Quoting ========================\n\n/** Quotes a DOT identifier. Always quotes to be safe with special chars. */\nfunction quoteId(id: string): string {\n // DOT keywords that must be quoted\n if (/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(id) && !isDotKeyword(id)) {\n return id;\n }\n return `\"${escapeDot(id)}\"`;\n}\n\n/** Quotes a DOT attribute value. */\nfunction quoteAttr(value: string): string {\n // Numbers don't need quoting\n if (/^-?\\d+(\\.\\d+)?$/.test(value)) {\n return value;\n }\n return `\"${escapeDot(value)}\"`;\n}\n\n/** Escapes special characters for DOT strings. */\nfunction escapeDot(s: string): string {\n return s.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"');\n}\n\n/**\n * Formats node/edge attributes where certain values (like style with commas)\n * need special handling.\n */\nfunction formatNodeAttrs(attrs: Record<string, string>): string {\n return Object.entries(attrs)\n .map(([key, value]) => {\n // Style values that are already pre-quoted (contain the quote char)\n if (value.startsWith('\"') && value.endsWith('\"')) {\n return `${key}=${value}`;\n }\n return `${key}=${quoteAttr(value)}`;\n })\n .join(', ');\n}\n\n/** Formats simple key=value attributes. */\nfunction formatAttrs(attrs: Readonly<Record<string, string>>): string {\n return Object.entries(attrs)\n .map(([key, value]) => `${key}=${quoteAttr(value)}`)\n .join(', ');\n}\n\n/** DOT language keywords that must be quoted when used as identifiers. */\nfunction isDotKeyword(id: string): boolean {\n const lower = id.toLowerCase();\n return lower === 'graph' || lower === 'digraph' || lower === 'subgraph'\n || lower === 'node' || lower === 'edge' || lower === 'strict';\n}\n","/**\n * Convenience function for exporting a PetriNet to DOT format.\n *\n * @module export/dot-exporter\n */\n\nimport type { PetriNet } from '../core/petri-net.js';\nimport type { DotConfig } from './petri-net-mapper.js';\nimport { mapToGraph } from './petri-net-mapper.js';\nimport { renderDot } from './dot-renderer.js';\n\n/**\n * Exports a PetriNet to DOT (Graphviz) format.\n *\n * @param net the Petri net to export\n * @param config optional export configuration\n * @returns DOT string suitable for rendering with Graphviz\n */\nexport function dotExport(net: PetriNet, config?: DotConfig): string {\n return renderDot(mapToGraph(net, config));\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import { Writable } from 'node:stream';
2
- import { a as PetriNet, b as Transition, T as Token } from '../petri-net-DrTpTRNy.js';
3
- import { E as EventStore, N as NetEvent } from '../event-store-DePCZb33.js';
2
+ import { a as PetriNet, b as Transition, T as Token } from '../petri-net-D-GN9g_D.js';
3
+ import { E as EventStore, N as NetEvent } from '../event-store-BnyHh3TF.js';
4
4
 
5
5
  /**
6
6
  * Commands sent from debug UI client to server via WebSocket.
@@ -106,7 +106,19 @@ interface SessionSummary {
106
106
  interface TokenInfo {
107
107
  readonly id: string | null;
108
108
  readonly type: string;
109
+ /**
110
+ * `String(value)` form — stable display field that the bundled debug UI relies on.
111
+ * Always populated unless compact mode strips values.
112
+ */
109
113
  readonly value: string | null;
114
+ /**
115
+ * Structured JSON representation of the token value when the value is a plain
116
+ * JSON-friendly object / enum-like string / primitive. Populated alongside `value`
117
+ * (not instead of) so LLM-facing consumers can project typed fields without parsing
118
+ * the stringified form. Omitted from the wire when absent.
119
+ * (libpetri 1.8.0+)
120
+ */
121
+ readonly structured?: unknown;
110
122
  readonly timestamp: string | null;
111
123
  }
112
124
  interface NetEventInfo {
@@ -487,7 +499,23 @@ declare class MarkingCache {
487
499
 
488
500
  /** Converts a NetEvent to a serializable NetEventInfo. */
489
501
  declare function toEventInfo(event: NetEvent, compact?: boolean): NetEventInfo;
490
- /** Converts a Token to serializable TokenInfo with full value. */
502
+ /**
503
+ * Converts a Token to a serializable {@link TokenInfo}.
504
+ *
505
+ * @remarks
506
+ * The emitted `type` is `value.constructor.name` for objects and `typeof value`
507
+ * for primitives — a *simple name*, not a fully-qualified type identifier.
508
+ * TypeScript has no portable FQN, so cross-language replay from TypeScript
509
+ * archives into a Java reader loses the original type identity (Java's
510
+ * `Class.forName` will fail on simple names) and falls through to the
511
+ * `Token<JsonNode>` graceful-degradation path. The `structured` payload
512
+ * survives intact across languages.
513
+ *
514
+ * The v3 archive body format described in [EVT-025](../../../spec/08-events-observability.md)
515
+ * always emits `structured` alongside `value` so the bundled debug UI keeps
516
+ * rendering while LLM-facing consumers get typed fields. See {@link structuredValue}
517
+ * for the projection rules.
518
+ */
491
519
  declare function tokenInfo(token: Token<unknown>): TokenInfo;
492
520
  /** Converts a Token to compact TokenInfo (type only, no value). */
493
521
  declare function compactTokenInfo(token: Token<unknown>): TokenInfo;
@@ -529,14 +557,17 @@ declare class DebugAwareEventStore implements EventStore {
529
557
  *
530
558
  * - **v1** (libpetri 1.5.x–1.6.x): original format. Header carries `sessionId`,
531
559
  * `netName`, `dotDiagram`, `startTime`, `eventCount`, and net `structure`.
532
- * - **v2** (libpetri 1.7.0+): adds `endTime`, user-defined `tags`, and pre-computed
533
- * {@link SessionMetadata} (event-type histogram, first/last event timestamps,
534
- * hasErrors). Events inside v2 archives are serialized the same way as in v1 —
535
- * only the header is enriched.
560
+ * - **v2** (libpetri 1.7.x): adds `endTime`, user-defined `tags`, and pre-computed
561
+ * {@link SessionMetadata}. Events inside v2 archives use the legacy `toString`-based
562
+ * token format types are erased on disk.
563
+ * - **v3** (libpetri 1.8.0+): same header shape as v2. Differs in the event body —
564
+ * token values are serialized with a `structured` JSON payload in addition to the
565
+ * legacy `value` string, so consumers that understand the original shape can
566
+ * surface typed fields without parsing the `toString` form.
536
567
  *
537
568
  * The {@link SessionArchiveReader} peeks the `version` field via a lenient JSON
538
- * parse and dispatches to the correct concrete type. Both v1 and v2 archives
539
- * remain readable and may coexist in the same storage bucket.
569
+ * parse and dispatches to the correct concrete type. All three versions coexist
570
+ * in the same storage bucket.
540
571
  */
541
572
 
542
573
  /** Common fields shared by v1 and v2 archive headers. */
@@ -567,19 +598,30 @@ interface SessionArchiveV2 extends SessionArchiveBase {
567
598
  /** Pre-computed aggregate stats. Always present; `emptyMetadata()` for no-event sessions. */
568
599
  readonly metadata: SessionMetadata;
569
600
  }
601
+ /**
602
+ * v3 archive header (libpetri 1.8.0+). Structurally identical to `SessionArchiveV2`;
603
+ * the version bump signals that the event body carries `structured` token payloads
604
+ * alongside the legacy `value` string.
605
+ */
606
+ interface SessionArchiveV3 extends SessionArchiveBase {
607
+ readonly version: 3;
608
+ readonly endTime?: string;
609
+ readonly tags: Readonly<Record<string, string>>;
610
+ readonly metadata: SessionMetadata;
611
+ }
570
612
  /**
571
613
  * Discriminated union of all supported archive header versions.
572
614
  *
573
615
  * Type-narrowing example:
574
616
  * ```ts
575
- * if (archive.version === 2) {
576
- * // TS knows archive is SessionArchiveV2 here — tags / endTime / metadata typed.
617
+ * if (archive.version >= 2) {
618
+ * // TS knows archive has tags / endTime / metadata here.
577
619
  * }
578
620
  * ```
579
621
  */
580
- type SessionArchive = SessionArchiveV1 | SessionArchiveV2;
622
+ type SessionArchive = SessionArchiveV1 | SessionArchiveV2 | SessionArchiveV3;
581
623
  /** Version written by default by {@link SessionArchiveWriter.write} (latest supported). */
582
- declare const CURRENT_VERSION = 2;
624
+ declare const CURRENT_VERSION = 3;
583
625
  /**
584
626
  * Pre-computed aggregate statistics attached to a v2 session archive header.
585
627
  *
@@ -636,17 +678,26 @@ declare class FileSessionArchiveStorage implements SessionArchiveStorage {
636
678
  *
637
679
  * ## Format selection
638
680
  *
639
- * `write()` defaults to {@link CURRENT_VERSION} (v2 as of libpetri 1.7.0).
681
+ * `write()` defaults to {@link CURRENT_VERSION} (v3 as of libpetri 1.8.0).
640
682
  * Callers that need to emit legacy archives — compatibility tests or readers
641
- * pinned to libpetri 1.6.1 — can call `writeV1()`. v2 archives cost one extra
642
- * pass over the event store to pre-compute {@link SessionMetadata}; the savings
643
- * at read time (no event scan needed for hasErrors / histogram queries) pay it
644
- * back the first time a caller lists or samples a bucket of sessions.
683
+ * pinned to older libpetri versions — can call `writeV1()` or `writeV2()`.
684
+ *
685
+ * Note: all writers now emit the v3 token body format (structured alongside
686
+ * value-string) regardless of header version. A 1.8.0+ writer cannot produce
687
+ * byte-for-byte 1.7.x event bodies.
688
+ *
689
+ * Cross-language note: the `type` field in each serialized `TokenInfo` is
690
+ * `value.constructor.name` — a simple name, not an FQN. Replaying a TypeScript
691
+ * archive through the Java reader therefore cannot reconstruct the original
692
+ * typed token (Java needs an FQN to `Class.forName`); Java falls through to
693
+ * `Token<JsonNode>` preserving the `structured` JSON payload. See
694
+ * {@link tokenInfo} for the full asymmetry and the [EVT-025](../../../../spec/08-events-observability.md)
695
+ * spec entry for the full wire-format contract.
645
696
  */
646
697
 
647
698
  declare class SessionArchiveWriter {
648
699
  /**
649
- * Writes a complete session archive in the current format (v2 as of 1.7.0)
700
+ * Writes a complete session archive in the current format (v3 as of 1.8.0)
650
701
  * and returns the compressed bytes.
651
702
  */
652
703
  write(session: DebugSession): Buffer;
@@ -665,6 +716,12 @@ declare class SessionArchiveWriter {
665
716
  * passes walk the same sequence from the start.
666
717
  */
667
718
  writeV2(session: DebugSession): Buffer;
719
+ /**
720
+ * Writes a session in the v3 format — same header shape as v2, with version=3
721
+ * signalling that token payloads carry a `structured` field alongside the
722
+ * legacy `value` string (see {@link tokenInfo}).
723
+ */
724
+ writeV3(session: DebugSession): Buffer;
668
725
  /**
669
726
  * Shared framing logic: length-prefixed header JSON, then length-prefixed
670
727
  * event JSON, then gzip. Both v1 and v2 archives use the identical event
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dotExport,
3
3
  sanitize
4
- } from "../chunk-7EKL7INR.js";
4
+ } from "../chunk-JNHADILD.js";
5
5
  import "../chunk-FN773SSE.js";
6
6
 
7
7
  // src/debug/debug-command.ts
@@ -118,7 +118,7 @@ var DebugEventStore = class {
118
118
  };
119
119
 
120
120
  // src/debug/archive/session-archive.ts
121
- var CURRENT_VERSION = 2;
121
+ var CURRENT_VERSION = 3;
122
122
  var MIN_SUPPORTED_VERSION = 1;
123
123
  function emptyMetadata() {
124
124
  return { eventTypeHistogram: {}, hasErrors: false };
@@ -174,6 +174,14 @@ function parseHeader(metaJson) {
174
174
  metadata: v2.metadata ?? emptyMetadata()
175
175
  };
176
176
  }
177
+ case 3: {
178
+ const v3 = raw;
179
+ return {
180
+ ...v3,
181
+ tags: v3.tags ?? {},
182
+ metadata: v3.metadata ?? emptyMetadata()
183
+ };
184
+ }
177
185
  default:
178
186
  throw new Error(
179
187
  `Unsupported archive version: ${raw.version} (reader supports ${MIN_SUPPORTED_VERSION}..${CURRENT_VERSION})`
@@ -238,10 +246,8 @@ function eventInfoToNetEvent(info) {
238
246
  }
239
247
  }
240
248
  function infoToToken(t) {
241
- return {
242
- value: t.value,
243
- createdAt: t.timestamp ? new Date(t.timestamp).getTime() : Date.now()
244
- };
249
+ const createdAt = t.timestamp ? new Date(t.timestamp).getTime() : Date.now();
250
+ return t.structured === void 0 ? { value: t.value, createdAt } : { value: t.value, createdAt, structured: t.structured };
245
251
  }
246
252
 
247
253
  // src/debug/place-analysis.ts
@@ -710,12 +716,30 @@ function tokenInfo(token) {
710
716
  const value = token.value;
711
717
  const type = value != null ? typeof value === "object" ? value.constructor.name : typeof value : "null";
712
718
  const fullValue = value != null ? String(value) : "null";
713
- return {
719
+ const info = {
714
720
  id: null,
715
721
  type,
716
722
  value: fullValue,
717
723
  timestamp: new Date(token.createdAt).toISOString()
718
724
  };
725
+ const structured = structuredValue(value);
726
+ return structured === void 0 ? info : { ...info, structured };
727
+ }
728
+ function structuredValue(value) {
729
+ if (value == null) return void 0;
730
+ const t = typeof value;
731
+ if (t === "string" || t === "number" || t === "boolean") return value;
732
+ if (t === "bigint") return String(value);
733
+ if (t === "symbol" || t === "function") return void 0;
734
+ try {
735
+ const cloned = JSON.parse(JSON.stringify(value));
736
+ if (cloned && typeof cloned === "object" && !Array.isArray(cloned) && Object.keys(cloned).length === 0) {
737
+ return void 0;
738
+ }
739
+ return cloned;
740
+ } catch {
741
+ return void 0;
742
+ }
719
743
  }
720
744
  function compactTokenInfo(token) {
721
745
  const value = token.value;
@@ -1548,11 +1572,11 @@ function isErrorEvent(event) {
1548
1572
  // src/debug/archive/session-archive-writer.ts
1549
1573
  var SessionArchiveWriter = class {
1550
1574
  /**
1551
- * Writes a complete session archive in the current format (v2 as of 1.7.0)
1575
+ * Writes a complete session archive in the current format (v3 as of 1.8.0)
1552
1576
  * and returns the compressed bytes.
1553
1577
  */
1554
1578
  write(session) {
1555
- return this.writeV2(session);
1579
+ return this.writeV3(session);
1556
1580
  }
1557
1581
  /**
1558
1582
  * Writes a session in the legacy v1 format. Use only for compatibility
@@ -1598,6 +1622,27 @@ var SessionArchiveWriter = class {
1598
1622
  };
1599
1623
  return this.writeFramed(header, session);
1600
1624
  }
1625
+ /**
1626
+ * Writes a session in the v3 format — same header shape as v2, with version=3
1627
+ * signalling that token payloads carry a `structured` field alongside the
1628
+ * legacy `value` string (see {@link tokenInfo}).
1629
+ */
1630
+ writeV3(session) {
1631
+ const metadata = computeMetadata(session.eventStore);
1632
+ const header = {
1633
+ version: 3,
1634
+ sessionId: session.sessionId,
1635
+ netName: session.netName,
1636
+ dotDiagram: session.dotDiagram,
1637
+ startTime: new Date(session.startTime).toISOString(),
1638
+ endTime: session.endTime !== void 0 ? new Date(session.endTime).toISOString() : void 0,
1639
+ eventCount: session.eventStore.eventCount(),
1640
+ tags: { ...session.tags },
1641
+ metadata,
1642
+ structure: buildNetStructure(session)
1643
+ };
1644
+ return this.writeFramed(header, session);
1645
+ }
1601
1646
  /**
1602
1647
  * Shared framing logic: length-prefixed header JSON, then length-prefixed
1603
1648
  * event JSON, then gzip. Both v1 and v2 archives use the identical event