libpetri 1.5.1 → 1.8.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.
@@ -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-C3Jy5HCt.js';
3
- import { E as EventStore, N as NetEvent } from '../event-store-Y8q_wapJ.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.
@@ -34,6 +34,8 @@ type DebugCommand = {
34
34
  readonly type: 'listSessions';
35
35
  readonly limit?: number;
36
36
  readonly activeOnly?: boolean;
37
+ /** Optional tag filter (AND semantics). Empty or missing matches all. (libpetri 1.6.0+) */
38
+ readonly tagFilter?: Readonly<Record<string, string>>;
37
39
  } | {
38
40
  readonly type: 'seek';
39
41
  readonly sessionId: string;
@@ -94,11 +96,29 @@ interface SessionSummary {
94
96
  readonly startTime: string;
95
97
  readonly active: boolean;
96
98
  readonly eventCount: number;
99
+ /** User-defined session tags. Empty object if none. (libpetri 1.6.0+) */
100
+ readonly tags?: Readonly<Record<string, string>>;
101
+ /** ISO-8601 end time, present only for completed sessions. (libpetri 1.6.0+) */
102
+ readonly endTime?: string;
103
+ /** Session duration in milliseconds, present only for completed sessions. (libpetri 1.6.0+) */
104
+ readonly durationMs?: number;
97
105
  }
98
106
  interface TokenInfo {
99
107
  readonly id: string | null;
100
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
+ */
101
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;
102
122
  readonly timestamp: string | null;
103
123
  }
104
124
  interface NetEventInfo {
@@ -320,6 +340,15 @@ interface DebugSession {
320
340
  readonly startTime: number;
321
341
  readonly active: boolean;
322
342
  readonly importedStructure: NetStructure | null;
343
+ /** Stamped on first `complete()`. Undefined while the session is active. (libpetri 1.6.0+) */
344
+ readonly endTime?: number;
345
+ /**
346
+ * Per-session tag storage, mutated in place by the registry. The reference is
347
+ * readonly but the underlying object is not — prefer
348
+ * {@link DebugSessionRegistry.tag}/{@link DebugSessionRegistry.tagsFor}
349
+ * over direct access.
350
+ */
351
+ readonly tags: Record<string, string>;
323
352
  }
324
353
  /** Builds the net structure from a session's stored place and transition info. */
325
354
  declare function buildNetStructure(session: DebugSession): NetStructure;
@@ -333,26 +362,54 @@ declare class DebugSessionRegistry {
333
362
  /**
334
363
  * Registers a new debug session for the given Petri net.
335
364
  * Generates DOT diagram and extracts net structure.
365
+ *
366
+ * @param sessionId unique session id
367
+ * @param net the Petri net being executed
368
+ * @param tags optional user-defined tags (libpetri 1.6.0+) — e.g. `{channel: 'voice'}`
336
369
  */
337
- register(sessionId: string, net: PetriNet): DebugSession;
370
+ register(sessionId: string, net: PetriNet, tags?: Readonly<Record<string, string>>): DebugSession;
338
371
  /**
339
372
  * Marks a session as completed (no longer active) and notifies completion listeners.
373
+ *
374
+ * <p>Stamps `endTime = Date.now()` on the first completion. Idempotent: subsequent
375
+ * calls preserve the existing endTime. (libpetri 1.6.0+)
340
376
  */
341
377
  complete(sessionId: string): void;
342
- /** Removes a session from the registry. */
378
+ /** Removes a session from the registry. Tags die with the session. */
343
379
  remove(sessionId: string): DebugSession | undefined;
344
380
  /** Returns a session by ID. */
345
381
  getSession(sessionId: string): DebugSession | undefined;
382
+ /**
383
+ * Sets or overwrites a single tag on a session. (libpetri 1.6.0+)
384
+ *
385
+ * Tags accumulate until the session is removed. Setting a key that already
386
+ * exists replaces its value.
387
+ *
388
+ * If `sessionId` is not a currently-registered session the call is a no-op.
389
+ * A tag write that races with {@link remove} is harmless — the write lands on
390
+ * the now-orphaned session object and is garbage collected along with it.
391
+ */
392
+ tag(sessionId: string, key: string, value: string): void;
393
+ /**
394
+ * Returns a snapshot of the tags attached to a session. Returns an empty
395
+ * object if the session has no tags or does not exist. (libpetri 1.6.0+)
396
+ */
397
+ tagsFor(sessionId: string): Readonly<Record<string, string>>;
346
398
  /** Lists sessions, ordered by start time (most recent first). */
347
- listSessions(limit: number): readonly DebugSession[];
399
+ listSessions(limit: number, tagFilter?: Readonly<Record<string, string>>): readonly DebugSession[];
348
400
  /** Lists only active sessions. */
349
- listActiveSessions(limit: number): readonly DebugSession[];
401
+ listActiveSessions(limit: number, tagFilter?: Readonly<Record<string, string>>): readonly DebugSession[];
350
402
  /** Total number of sessions. */
351
403
  get size(): number;
352
404
  /**
353
405
  * Registers an imported (archived) session as an inactive, read-only session.
406
+ *
407
+ * @param endTime when the original session ended (libpetri 1.6.0+)
408
+ * @param tags user-defined tags attached to the imported session (libpetri 1.6.0+)
354
409
  */
355
- registerImported(sessionId: string, netName: string, dotDiagram: string, structure: NetStructure, eventStore: DebugEventStore, startTime: number): DebugSession;
410
+ registerImported(sessionId: string, netName: string, dotDiagram: string, structure: NetStructure, eventStore: DebugEventStore, startTime: number, endTime?: number, tags?: Readonly<Record<string, string>>): DebugSession;
411
+ /** AND-match: all filter entries must exactly match the session's tags. */
412
+ private matchesTagFilter;
356
413
  /** Notifies all completion listeners. Exceptions are caught and logged. */
357
414
  private notifyCompletionListeners;
358
415
  /** Evicts oldest inactive sessions if at capacity. */
@@ -388,6 +445,7 @@ declare class DebugProtocolHandler {
388
445
  /** Handles a command from a connected client. */
389
446
  handleCommand(clientId: string, command: DebugCommand): void;
390
447
  private handleListSessions;
448
+ private toProtocolSummary;
391
449
  private handleSubscribe;
392
450
  private subscribeLive;
393
451
  private subscribeReplay;
@@ -441,7 +499,23 @@ declare class MarkingCache {
441
499
 
442
500
  /** Converts a NetEvent to a serializable NetEventInfo. */
443
501
  declare function toEventInfo(event: NetEvent, compact?: boolean): NetEventInfo;
444
- /** 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
+ */
445
519
  declare function tokenInfo(token: Token<unknown>): TokenInfo;
446
520
  /** Converts a Token to compact TokenInfo (type only, no value). */
447
521
  declare function compactTokenInfo(token: Token<unknown>): TokenInfo;
@@ -468,19 +542,115 @@ declare class DebugAwareEventStore implements EventStore {
468
542
 
469
543
  /**
470
544
  * Metadata header for a session archive file.
545
+ *
546
+ * Discriminated union across format versions so callers can pattern-match on
547
+ * `archive.version` to access v2-only fields with type narrowing:
548
+ *
549
+ * ```ts
550
+ * const archive = reader.readMetadata(bytes);
551
+ * if (archive.version === 2) {
552
+ * console.log(archive.tags, archive.endTime, archive.metadata.hasErrors);
553
+ * }
554
+ * ```
555
+ *
556
+ * ## Version contract
557
+ *
558
+ * - **v1** (libpetri 1.5.x–1.6.x): original format. Header carries `sessionId`,
559
+ * `netName`, `dotDiagram`, `startTime`, `eventCount`, and net `structure`.
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.
567
+ *
568
+ * The {@link SessionArchiveReader} peeks the `version` field via a lenient JSON
569
+ * parse and dispatches to the correct concrete type. All three versions coexist
570
+ * in the same storage bucket.
471
571
  */
472
572
 
473
- interface SessionArchive {
474
- readonly version: number;
573
+ /** Common fields shared by v1 and v2 archive headers. */
574
+ interface SessionArchiveBase {
475
575
  readonly sessionId: string;
476
576
  readonly netName: string;
477
577
  readonly dotDiagram: string;
578
+ /** ISO-8601 instant the session started. */
478
579
  readonly startTime: string;
479
580
  readonly eventCount: number;
480
581
  readonly structure: NetStructure;
481
582
  }
482
- /** Current archive format version. */
483
- declare const CURRENT_VERSION = 1;
583
+ /** Legacy v1 archive header (libpetri 1.5.x–1.6.x). */
584
+ interface SessionArchiveV1 extends SessionArchiveBase {
585
+ readonly version: 1;
586
+ }
587
+ /**
588
+ * v2 archive header (libpetri 1.7.0+). Adds end time, tags, and pre-computed
589
+ * metadata so listing tools and samplers can filter/aggregate without scanning
590
+ * the event body.
591
+ */
592
+ interface SessionArchiveV2 extends SessionArchiveBase {
593
+ readonly version: 2;
594
+ /** ISO-8601 instant the session ended. Undefined for sessions archived while still active. */
595
+ readonly endTime?: string;
596
+ /** User-defined session tags (e.g., `{channel: "voice"}`). Always present; may be empty. */
597
+ readonly tags: Readonly<Record<string, string>>;
598
+ /** Pre-computed aggregate stats. Always present; `emptyMetadata()` for no-event sessions. */
599
+ readonly metadata: SessionMetadata;
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
+ }
612
+ /**
613
+ * Discriminated union of all supported archive header versions.
614
+ *
615
+ * Type-narrowing example:
616
+ * ```ts
617
+ * if (archive.version >= 2) {
618
+ * // TS knows archive has tags / endTime / metadata here.
619
+ * }
620
+ * ```
621
+ */
622
+ type SessionArchive = SessionArchiveV1 | SessionArchiveV2 | SessionArchiveV3;
623
+ /** Version written by default by {@link SessionArchiveWriter.write} (latest supported). */
624
+ declare const CURRENT_VERSION = 3;
625
+ /**
626
+ * Pre-computed aggregate statistics attached to a v2 session archive header.
627
+ *
628
+ * Computed once during archive write by a single-pass scan of the event store.
629
+ * Readers can answer `hasErrors`, histogram, and first/last timestamp queries
630
+ * without iterating the event stream — enabling cheap triage, sampling, and
631
+ * listing of many archives.
632
+ *
633
+ * For v1 archives (no pre-computed metadata), callers can recompute on-demand
634
+ * via {@link computeMetadata}.
635
+ */
636
+ interface SessionMetadata {
637
+ /**
638
+ * Count of events per `NetEvent` subtype name (PascalCase, matching the
639
+ * wire format used by `NetEventInfo.type` — e.g. `TransitionStarted -> 412`).
640
+ * Keys are stored in alphabetical order for deterministic JSON output.
641
+ */
642
+ readonly eventTypeHistogram: Readonly<Record<string, number>>;
643
+ /** ISO-8601 timestamp of the oldest event, or undefined if the session had no events. */
644
+ readonly firstEventTime?: string;
645
+ /** ISO-8601 timestamp of the newest event, or undefined if the session had no events. */
646
+ readonly lastEventTime?: string;
647
+ /**
648
+ * True if the session contains at least one error-signal event
649
+ * (`TransitionFailed`, `TransitionTimedOut`, `ActionTimedOut`, or
650
+ * a `LogMessage` at level `ERROR`).
651
+ */
652
+ readonly hasErrors: boolean;
653
+ }
484
654
 
485
655
  /**
486
656
  * File-system backed storage for session archives.
@@ -501,21 +671,73 @@ declare class FileSessionArchiveStorage implements SessionArchiveStorage {
501
671
  * Writes a debug session to a length-prefixed binary archive format.
502
672
  *
503
673
  * Format (inside gzip):
504
- * [4 bytes: metadata JSON length][N bytes: metadata JSON]
505
- * [4 bytes: event JSON length][N bytes: event JSON]
674
+ * `[4 bytes: metadata JSON length][N bytes: metadata JSON]`
675
+ * `[4 bytes: event JSON length][N bytes: event JSON]`
506
676
  * ...
507
677
  * (EOF terminates the stream)
678
+ *
679
+ * ## Format selection
680
+ *
681
+ * `write()` defaults to {@link CURRENT_VERSION} (v3 as of libpetri 1.8.0).
682
+ * Callers that need to emit legacy archives — compatibility tests or readers
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.
508
696
  */
509
697
 
510
698
  declare class SessionArchiveWriter {
511
699
  /**
512
- * Writes a complete session archive and returns the compressed bytes.
700
+ * Writes a complete session archive in the current format (v3 as of 1.8.0)
701
+ * and returns the compressed bytes.
513
702
  */
514
703
  write(session: DebugSession): Buffer;
704
+ /**
705
+ * Writes a session in the legacy v1 format. Use only for compatibility
706
+ * testing or when producing archives for consumers pinned to libpetri ≤ 1.6.1.
707
+ */
708
+ writeV1(session: DebugSession): Buffer;
709
+ /**
710
+ * Writes a session in the v2 format — richer header with `endTime`, `tags`,
711
+ * and pre-computed {@link SessionMetadata}.
712
+ *
713
+ * Two passes over the event store: one to compute metadata, one to serialize
714
+ * events. `DebugEventStore` stores events in a plain readonly array and its
715
+ * `[Symbol.iterator]()` returns a fresh array iterator each call, so both
716
+ * passes walk the same sequence from the start.
717
+ */
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;
725
+ /**
726
+ * Shared framing logic: length-prefixed header JSON, then length-prefixed
727
+ * event JSON, then gzip. Both v1 and v2 archives use the identical event
728
+ * wire format, so the body loop is version-agnostic.
729
+ */
730
+ private writeFramed;
515
731
  }
516
732
 
517
733
  /**
518
734
  * Reads session archives from length-prefixed binary format.
735
+ *
736
+ * Handles both v1 (libpetri 1.5.x–1.6.x) and v2 (libpetri 1.7.0+) archives via
737
+ * a lenient "version probe": parse the header JSON once, switch on the
738
+ * `version` field, narrow to the correct concrete type, and normalize missing
739
+ * optional fields to their defaults. Events inside the body use the same wire
740
+ * format across versions, so the event read path is shared.
519
741
  */
520
742
 
521
743
  interface ImportedSession {