libpetri 0.5.3 → 0.6.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/debug/index.d.ts +52 -21
- package/dist/debug/index.js +287 -183
- package/dist/debug/index.js.map +1 -1
- package/dist/index.js +7 -7
- package/dist/index.js.map +1 -1
- package/dist/verification/index.d.ts +200 -6
- package/dist/verification/index.js +902 -0
- package/dist/verification/index.js.map +1 -1
- package/package.json +1 -1
package/dist/debug/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Writable } from 'node:stream';
|
|
1
2
|
import { a as PetriNet, b as Transition, T as Token } from '../petri-net-C3Jy5HCt.js';
|
|
2
3
|
import { E as EventStore, N as NetEvent } from '../event-store-Y8q_wapJ.js';
|
|
3
|
-
import { Writable } from 'node:stream';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Commands sent from debug UI client to server via WebSocket.
|
|
@@ -66,6 +66,17 @@ type DebugCommand = {
|
|
|
66
66
|
} | {
|
|
67
67
|
readonly type: 'listBreakpoints';
|
|
68
68
|
readonly sessionId: string;
|
|
69
|
+
} | {
|
|
70
|
+
readonly type: 'listArchives';
|
|
71
|
+
readonly limit?: number;
|
|
72
|
+
readonly prefix?: string;
|
|
73
|
+
} | {
|
|
74
|
+
readonly type: 'importArchive';
|
|
75
|
+
readonly sessionId: string;
|
|
76
|
+
} | {
|
|
77
|
+
readonly type: 'uploadArchive';
|
|
78
|
+
readonly fileName: string;
|
|
79
|
+
readonly data: string;
|
|
69
80
|
};
|
|
70
81
|
declare function eventFilterAll(): EventFilter;
|
|
71
82
|
|
|
@@ -110,6 +121,12 @@ interface NetStructure {
|
|
|
110
121
|
readonly places: readonly PlaceInfo[];
|
|
111
122
|
readonly transitions: readonly TransitionInfo[];
|
|
112
123
|
}
|
|
124
|
+
interface ArchiveSummary {
|
|
125
|
+
readonly sessionId: string;
|
|
126
|
+
readonly key: string;
|
|
127
|
+
readonly sizeBytes: number;
|
|
128
|
+
readonly lastModified: string;
|
|
129
|
+
}
|
|
113
130
|
type DebugResponse = {
|
|
114
131
|
readonly type: 'sessionList';
|
|
115
132
|
readonly sessions: readonly SessionSummary[];
|
|
@@ -177,8 +194,35 @@ type DebugResponse = {
|
|
|
177
194
|
readonly code: string;
|
|
178
195
|
readonly message: string;
|
|
179
196
|
readonly sessionId: string | null;
|
|
197
|
+
} | {
|
|
198
|
+
readonly type: 'archiveList';
|
|
199
|
+
readonly archives: readonly ArchiveSummary[];
|
|
200
|
+
readonly storageAvailable: boolean;
|
|
201
|
+
} | {
|
|
202
|
+
readonly type: 'archiveImported';
|
|
203
|
+
readonly sessionId: string;
|
|
204
|
+
readonly netName: string;
|
|
205
|
+
readonly eventCount: number;
|
|
180
206
|
};
|
|
181
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Storage backend interface for session archives.
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
interface ArchivedSessionSummary {
|
|
213
|
+
readonly sessionId: string;
|
|
214
|
+
readonly key: string;
|
|
215
|
+
readonly sizeBytes: number;
|
|
216
|
+
readonly lastModified: number;
|
|
217
|
+
}
|
|
218
|
+
type OutputStreamConsumer = (out: Writable) => Promise<void>;
|
|
219
|
+
interface SessionArchiveStorage {
|
|
220
|
+
storeStreaming(sessionId: string, writer: OutputStreamConsumer): Promise<void>;
|
|
221
|
+
list(limit: number, prefix?: string): Promise<readonly ArchivedSessionSummary[]>;
|
|
222
|
+
retrieve(sessionId: string): Promise<Buffer>;
|
|
223
|
+
isAvailable(): boolean;
|
|
224
|
+
}
|
|
225
|
+
|
|
182
226
|
/**
|
|
183
227
|
* Analyze places for start/end/environment classification.
|
|
184
228
|
* TypeScript port of Java's PlaceAnalysis.
|
|
@@ -331,8 +375,9 @@ interface ComputedState {
|
|
|
331
375
|
}
|
|
332
376
|
declare class DebugProtocolHandler {
|
|
333
377
|
private readonly _sessionRegistry;
|
|
378
|
+
private readonly _archiveStorage;
|
|
334
379
|
private readonly _clients;
|
|
335
|
-
constructor(sessionRegistry: DebugSessionRegistry);
|
|
380
|
+
constructor(sessionRegistry: DebugSessionRegistry, archiveStorage?: SessionArchiveStorage | null);
|
|
336
381
|
/** Registers a new client connection. */
|
|
337
382
|
clientConnected(clientId: string, sink: ResponseSink): void;
|
|
338
383
|
/** Cleans up when a client disconnects. */
|
|
@@ -354,6 +399,10 @@ declare class DebugProtocolHandler {
|
|
|
354
399
|
private handleSetBreakpoint;
|
|
355
400
|
private handleClearBreakpoint;
|
|
356
401
|
private handleListBreakpoints;
|
|
402
|
+
private handleListArchives;
|
|
403
|
+
private handleImportArchive;
|
|
404
|
+
private handleUploadArchive;
|
|
405
|
+
private broadcastSessionList;
|
|
357
406
|
private send;
|
|
358
407
|
private sendError;
|
|
359
408
|
private sendInBatches;
|
|
@@ -430,24 +479,6 @@ interface SessionArchive {
|
|
|
430
479
|
/** Current archive format version. */
|
|
431
480
|
declare const CURRENT_VERSION = 1;
|
|
432
481
|
|
|
433
|
-
/**
|
|
434
|
-
* Storage backend interface for session archives.
|
|
435
|
-
*/
|
|
436
|
-
|
|
437
|
-
interface ArchivedSessionSummary {
|
|
438
|
-
readonly sessionId: string;
|
|
439
|
-
readonly key: string;
|
|
440
|
-
readonly sizeBytes: number;
|
|
441
|
-
readonly lastModified: number;
|
|
442
|
-
}
|
|
443
|
-
type OutputStreamConsumer = (out: Writable) => Promise<void>;
|
|
444
|
-
interface SessionArchiveStorage {
|
|
445
|
-
storeStreaming(sessionId: string, writer: OutputStreamConsumer): Promise<void>;
|
|
446
|
-
list(limit: number, prefix?: string): Promise<readonly ArchivedSessionSummary[]>;
|
|
447
|
-
retrieve(sessionId: string): Promise<Buffer>;
|
|
448
|
-
isAvailable(): boolean;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
482
|
/**
|
|
452
483
|
* File-system backed storage for session archives.
|
|
453
484
|
* Uses Node.js fs/promises — no external dependencies.
|
|
@@ -510,4 +541,4 @@ declare class SessionArchiveReader {
|
|
|
510
541
|
*/
|
|
511
542
|
declare function debugUiAssetPath(): Promise<string>;
|
|
512
543
|
|
|
513
|
-
export { type ArchivedSessionSummary, type BreakpointConfig, type BreakpointType, CURRENT_VERSION, type ComputedState, DEFAULT_MAX_EVENTS, DebugAwareEventStore, type DebugCommand, DebugEventStore, DebugProtocolHandler, type DebugResponse, type DebugSession, DebugSessionRegistry, type EventFilter, type EventStoreFactory, FileSessionArchiveStorage, type ImportedSession, MarkingCache, type NetEventInfo, type NetStructure, type OutputStreamConsumer, PlaceAnalysis, type PlaceAnalysisInfo, type PlaceInfo, type ResponseSink, SNAPSHOT_INTERVAL, type SessionArchive, SessionArchiveReader, type SessionArchiveStorage, SessionArchiveWriter, type SessionCompletionListener, type SessionSummary, type Subscription, type SubscriptionMode, type TokenInfo, type TransitionInfo, buildNetStructure, compactTokenInfo, convertMarking, debugUiAssetPath, eventFilterAll, toEventInfo, tokenInfo };
|
|
544
|
+
export { type ArchiveSummary, type ArchivedSessionSummary, type BreakpointConfig, type BreakpointType, CURRENT_VERSION, type ComputedState, DEFAULT_MAX_EVENTS, DebugAwareEventStore, type DebugCommand, DebugEventStore, DebugProtocolHandler, type DebugResponse, type DebugSession, DebugSessionRegistry, type EventFilter, type EventStoreFactory, FileSessionArchiveStorage, type ImportedSession, MarkingCache, type NetEventInfo, type NetStructure, type OutputStreamConsumer, PlaceAnalysis, type PlaceAnalysisInfo, type PlaceInfo, type ResponseSink, SNAPSHOT_INTERVAL, type SessionArchive, SessionArchiveReader, type SessionArchiveStorage, SessionArchiveWriter, type SessionCompletionListener, type SessionSummary, type Subscription, type SubscriptionMode, type TokenInfo, type TransitionInfo, buildNetStructure, compactTokenInfo, convertMarking, debugUiAssetPath, eventFilterAll, toEventInfo, tokenInfo };
|
package/dist/debug/index.js
CHANGED
|
@@ -9,76 +9,8 @@ function eventFilterAll() {
|
|
|
9
9
|
return { eventTypes: null, transitionNames: null, placeNames: null };
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
// src/debug/
|
|
13
|
-
|
|
14
|
-
_data;
|
|
15
|
-
constructor(data) {
|
|
16
|
-
this._data = data;
|
|
17
|
-
}
|
|
18
|
-
get data() {
|
|
19
|
-
return this._data;
|
|
20
|
-
}
|
|
21
|
-
isStart(placeName) {
|
|
22
|
-
const info = this._data.get(placeName);
|
|
23
|
-
return info != null && !info.hasIncoming;
|
|
24
|
-
}
|
|
25
|
-
isEnd(placeName) {
|
|
26
|
-
const info = this._data.get(placeName);
|
|
27
|
-
return info != null && !info.hasOutgoing;
|
|
28
|
-
}
|
|
29
|
-
/** Build place analysis from a PetriNet. */
|
|
30
|
-
static from(net) {
|
|
31
|
-
const data = /* @__PURE__ */ new Map();
|
|
32
|
-
function ensure(place) {
|
|
33
|
-
let info = data.get(place.name);
|
|
34
|
-
if (!info) {
|
|
35
|
-
info = { tokenType: "unknown", hasIncoming: false, hasOutgoing: false };
|
|
36
|
-
data.set(place.name, info);
|
|
37
|
-
}
|
|
38
|
-
return info;
|
|
39
|
-
}
|
|
40
|
-
for (const transition of net.transitions) {
|
|
41
|
-
for (const input of transition.inputSpecs) {
|
|
42
|
-
const info = ensure(input.place);
|
|
43
|
-
info.hasOutgoing = true;
|
|
44
|
-
}
|
|
45
|
-
if (transition.outputSpec) {
|
|
46
|
-
const outputPlaces = collectOutputPlaces(transition.outputSpec);
|
|
47
|
-
for (const place of outputPlaces) {
|
|
48
|
-
const info = ensure(place);
|
|
49
|
-
info.hasIncoming = true;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
for (const inh of transition.inhibitors) {
|
|
53
|
-
ensure(inh.place);
|
|
54
|
-
}
|
|
55
|
-
for (const read of transition.reads) {
|
|
56
|
-
const info = ensure(read.place);
|
|
57
|
-
info.hasOutgoing = true;
|
|
58
|
-
}
|
|
59
|
-
for (const reset of transition.resets) {
|
|
60
|
-
ensure(reset.place);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return new _PlaceAnalysis(data);
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
function collectOutputPlaces(out) {
|
|
67
|
-
const spec = out;
|
|
68
|
-
switch (spec.type) {
|
|
69
|
-
case "place":
|
|
70
|
-
return spec.place ? [spec.place] : [];
|
|
71
|
-
case "and":
|
|
72
|
-
case "xor":
|
|
73
|
-
return (spec.children ?? []).flatMap((c) => collectOutputPlaces(c));
|
|
74
|
-
case "timeout":
|
|
75
|
-
return spec.child ? collectOutputPlaces(spec.child) : [];
|
|
76
|
-
case "forward-input":
|
|
77
|
-
return spec.to ? [spec.to] : [];
|
|
78
|
-
default:
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
}
|
|
12
|
+
// src/debug/archive/session-archive-reader.ts
|
|
13
|
+
import { gunzipSync } from "zlib";
|
|
82
14
|
|
|
83
15
|
// src/debug/debug-event-store.ts
|
|
84
16
|
var DEFAULT_MAX_EVENTS = 1e4;
|
|
@@ -185,6 +117,188 @@ var DebugEventStore = class {
|
|
|
185
117
|
}
|
|
186
118
|
};
|
|
187
119
|
|
|
120
|
+
// src/debug/archive/session-archive.ts
|
|
121
|
+
var CURRENT_VERSION = 1;
|
|
122
|
+
|
|
123
|
+
// src/debug/archive/session-archive-reader.ts
|
|
124
|
+
var MAX_EVENT_SIZE = 10 * 1024 * 1024;
|
|
125
|
+
var SessionArchiveReader = class {
|
|
126
|
+
/** Reads only the metadata header from an archive. */
|
|
127
|
+
readMetadata(compressed) {
|
|
128
|
+
const data = gunzipSync(compressed);
|
|
129
|
+
const metaLen = data.readUInt32BE(0);
|
|
130
|
+
const metaJson = data.subarray(4, 4 + metaLen).toString("utf-8");
|
|
131
|
+
const metadata = JSON.parse(metaJson);
|
|
132
|
+
if (metadata.version !== CURRENT_VERSION) {
|
|
133
|
+
throw new Error(`Unsupported archive version: ${metadata.version} (expected ${CURRENT_VERSION})`);
|
|
134
|
+
}
|
|
135
|
+
return metadata;
|
|
136
|
+
}
|
|
137
|
+
/** Reads the full archive: metadata + all events into a DebugEventStore. */
|
|
138
|
+
readFull(compressed) {
|
|
139
|
+
const data = gunzipSync(compressed);
|
|
140
|
+
let offset = 0;
|
|
141
|
+
const metaLen = data.readUInt32BE(offset);
|
|
142
|
+
offset += 4;
|
|
143
|
+
const metaJson = data.subarray(offset, offset + metaLen).toString("utf-8");
|
|
144
|
+
offset += metaLen;
|
|
145
|
+
const metadata = JSON.parse(metaJson);
|
|
146
|
+
if (metadata.version !== CURRENT_VERSION) {
|
|
147
|
+
throw new Error(`Unsupported archive version: ${metadata.version} (expected ${CURRENT_VERSION})`);
|
|
148
|
+
}
|
|
149
|
+
const eventStore = new DebugEventStore(metadata.sessionId, Number.MAX_SAFE_INTEGER);
|
|
150
|
+
while (offset < data.length) {
|
|
151
|
+
if (offset + 4 > data.length) break;
|
|
152
|
+
const eventLen = data.readUInt32BE(offset);
|
|
153
|
+
offset += 4;
|
|
154
|
+
if (eventLen <= 0 || eventLen > MAX_EVENT_SIZE) {
|
|
155
|
+
throw new Error(`Invalid event size: ${eventLen}`);
|
|
156
|
+
}
|
|
157
|
+
if (offset + eventLen > data.length) break;
|
|
158
|
+
const eventJson = data.subarray(offset, offset + eventLen).toString("utf-8");
|
|
159
|
+
offset += eventLen;
|
|
160
|
+
const eventInfo = JSON.parse(eventJson);
|
|
161
|
+
const netEvent = eventInfoToNetEvent(eventInfo);
|
|
162
|
+
eventStore.append(netEvent);
|
|
163
|
+
}
|
|
164
|
+
return { metadata, eventStore };
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
function eventInfoToNetEvent(info) {
|
|
168
|
+
const timestamp = new Date(info.timestamp).getTime();
|
|
169
|
+
const d = info.details;
|
|
170
|
+
switch (info.type) {
|
|
171
|
+
case "ExecutionStarted":
|
|
172
|
+
return { type: "execution-started", timestamp, netName: d["netName"], executionId: d["executionId"] };
|
|
173
|
+
case "ExecutionCompleted":
|
|
174
|
+
return { type: "execution-completed", timestamp, netName: d["netName"], executionId: d["executionId"], totalDurationMs: d["totalDurationMs"] };
|
|
175
|
+
case "TransitionEnabled":
|
|
176
|
+
return { type: "transition-enabled", timestamp, transitionName: info.transitionName };
|
|
177
|
+
case "TransitionClockRestarted":
|
|
178
|
+
return { type: "transition-clock-restarted", timestamp, transitionName: info.transitionName };
|
|
179
|
+
case "TransitionStarted": {
|
|
180
|
+
const tokens = d["consumedTokens"].map((t) => infoToToken(t));
|
|
181
|
+
return { type: "transition-started", timestamp, transitionName: info.transitionName, consumedTokens: tokens };
|
|
182
|
+
}
|
|
183
|
+
case "TransitionCompleted": {
|
|
184
|
+
const tokens = d["producedTokens"].map((t) => infoToToken(t));
|
|
185
|
+
return { type: "transition-completed", timestamp, transitionName: info.transitionName, producedTokens: tokens, durationMs: d["durationMs"] };
|
|
186
|
+
}
|
|
187
|
+
case "TransitionFailed":
|
|
188
|
+
return { type: "transition-failed", timestamp, transitionName: info.transitionName, errorMessage: d["errorMessage"], exceptionType: d["exceptionType"] };
|
|
189
|
+
case "TransitionTimedOut":
|
|
190
|
+
return { type: "transition-timed-out", timestamp, transitionName: info.transitionName, deadlineMs: d["deadlineMs"], actualDurationMs: d["actualDurationMs"] };
|
|
191
|
+
case "ActionTimedOut":
|
|
192
|
+
return { type: "action-timed-out", timestamp, transitionName: info.transitionName, timeoutMs: d["timeoutMs"] };
|
|
193
|
+
case "TokenAdded": {
|
|
194
|
+
const t = d["token"];
|
|
195
|
+
return { type: "token-added", timestamp, placeName: info.placeName, token: infoToToken(t) };
|
|
196
|
+
}
|
|
197
|
+
case "TokenRemoved": {
|
|
198
|
+
const t = d["token"];
|
|
199
|
+
return { type: "token-removed", timestamp, placeName: info.placeName, token: infoToToken(t) };
|
|
200
|
+
}
|
|
201
|
+
case "MarkingSnapshot": {
|
|
202
|
+
const markingData = d["marking"];
|
|
203
|
+
const marking = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const [place, tokens] of Object.entries(markingData)) {
|
|
205
|
+
marking.set(place, tokens.map((t) => infoToToken(t)));
|
|
206
|
+
}
|
|
207
|
+
return { type: "marking-snapshot", timestamp, marking };
|
|
208
|
+
}
|
|
209
|
+
case "LogMessage":
|
|
210
|
+
return {
|
|
211
|
+
type: "log-message",
|
|
212
|
+
timestamp,
|
|
213
|
+
transitionName: info.transitionName,
|
|
214
|
+
logger: d["loggerName"],
|
|
215
|
+
level: d["level"],
|
|
216
|
+
message: d["message"],
|
|
217
|
+
error: d["throwable"] ?? null,
|
|
218
|
+
errorMessage: d["throwableMessage"] ?? null
|
|
219
|
+
};
|
|
220
|
+
default:
|
|
221
|
+
return { type: "transition-enabled", timestamp, transitionName: info.transitionName ?? "unknown" };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function infoToToken(t) {
|
|
225
|
+
return {
|
|
226
|
+
value: t.value,
|
|
227
|
+
createdAt: t.timestamp ? new Date(t.timestamp).getTime() : Date.now()
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/debug/place-analysis.ts
|
|
232
|
+
var PlaceAnalysis = class _PlaceAnalysis {
|
|
233
|
+
_data;
|
|
234
|
+
constructor(data) {
|
|
235
|
+
this._data = data;
|
|
236
|
+
}
|
|
237
|
+
get data() {
|
|
238
|
+
return this._data;
|
|
239
|
+
}
|
|
240
|
+
isStart(placeName) {
|
|
241
|
+
const info = this._data.get(placeName);
|
|
242
|
+
return info != null && !info.hasIncoming;
|
|
243
|
+
}
|
|
244
|
+
isEnd(placeName) {
|
|
245
|
+
const info = this._data.get(placeName);
|
|
246
|
+
return info != null && !info.hasOutgoing;
|
|
247
|
+
}
|
|
248
|
+
/** Build place analysis from a PetriNet. */
|
|
249
|
+
static from(net) {
|
|
250
|
+
const data = /* @__PURE__ */ new Map();
|
|
251
|
+
function ensure(place) {
|
|
252
|
+
let info = data.get(place.name);
|
|
253
|
+
if (!info) {
|
|
254
|
+
info = { tokenType: "unknown", hasIncoming: false, hasOutgoing: false };
|
|
255
|
+
data.set(place.name, info);
|
|
256
|
+
}
|
|
257
|
+
return info;
|
|
258
|
+
}
|
|
259
|
+
for (const transition of net.transitions) {
|
|
260
|
+
for (const input of transition.inputSpecs) {
|
|
261
|
+
const info = ensure(input.place);
|
|
262
|
+
info.hasOutgoing = true;
|
|
263
|
+
}
|
|
264
|
+
if (transition.outputSpec) {
|
|
265
|
+
const outputPlaces = collectOutputPlaces(transition.outputSpec);
|
|
266
|
+
for (const place of outputPlaces) {
|
|
267
|
+
const info = ensure(place);
|
|
268
|
+
info.hasIncoming = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
for (const inh of transition.inhibitors) {
|
|
272
|
+
ensure(inh.place);
|
|
273
|
+
}
|
|
274
|
+
for (const read of transition.reads) {
|
|
275
|
+
const info = ensure(read.place);
|
|
276
|
+
info.hasOutgoing = true;
|
|
277
|
+
}
|
|
278
|
+
for (const reset of transition.resets) {
|
|
279
|
+
ensure(reset.place);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return new _PlaceAnalysis(data);
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
function collectOutputPlaces(out) {
|
|
286
|
+
const spec = out;
|
|
287
|
+
switch (spec.type) {
|
|
288
|
+
case "place":
|
|
289
|
+
return spec.place ? [spec.place] : [];
|
|
290
|
+
case "and":
|
|
291
|
+
case "xor":
|
|
292
|
+
return (spec.children ?? []).flatMap((c) => collectOutputPlaces(c));
|
|
293
|
+
case "timeout":
|
|
294
|
+
return spec.child ? collectOutputPlaces(spec.child) : [];
|
|
295
|
+
case "forward-input":
|
|
296
|
+
return spec.to ? [spec.to] : [];
|
|
297
|
+
default:
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
188
302
|
// src/debug/debug-session-registry.ts
|
|
189
303
|
function buildNetStructure(session) {
|
|
190
304
|
if (session.importedStructure) {
|
|
@@ -562,9 +676,11 @@ function convertMarking(marking, compact = false) {
|
|
|
562
676
|
var BATCH_SIZE = 500;
|
|
563
677
|
var DebugProtocolHandler = class {
|
|
564
678
|
_sessionRegistry;
|
|
679
|
+
_archiveStorage;
|
|
565
680
|
_clients = /* @__PURE__ */ new Map();
|
|
566
|
-
constructor(sessionRegistry) {
|
|
681
|
+
constructor(sessionRegistry, archiveStorage) {
|
|
567
682
|
this._sessionRegistry = sessionRegistry;
|
|
683
|
+
this._archiveStorage = archiveStorage ?? null;
|
|
568
684
|
}
|
|
569
685
|
/** Registers a new client connection. */
|
|
570
686
|
clientConnected(clientId, sink) {
|
|
@@ -621,6 +737,15 @@ var DebugProtocolHandler = class {
|
|
|
621
737
|
case "listBreakpoints":
|
|
622
738
|
this.handleListBreakpoints(clientState, command);
|
|
623
739
|
break;
|
|
740
|
+
case "listArchives":
|
|
741
|
+
this.handleListArchives(clientState, command);
|
|
742
|
+
break;
|
|
743
|
+
case "importArchive":
|
|
744
|
+
this.handleImportArchive(clientState, command);
|
|
745
|
+
break;
|
|
746
|
+
case "uploadArchive":
|
|
747
|
+
this.handleUploadArchive(clientState, command);
|
|
748
|
+
break;
|
|
624
749
|
}
|
|
625
750
|
} catch (e) {
|
|
626
751
|
this.sendError(clientState, "COMMAND_ERROR", e instanceof Error ? e.message : String(e), null);
|
|
@@ -823,6 +948,97 @@ var DebugProtocolHandler = class {
|
|
|
823
948
|
const breakpoints = client.subscriptions.getBreakpoints(cmd.sessionId);
|
|
824
949
|
this.send(client, { type: "breakpointList", sessionId: cmd.sessionId, breakpoints });
|
|
825
950
|
}
|
|
951
|
+
// ======================== Archive Handlers ========================
|
|
952
|
+
async handleListArchives(client, cmd) {
|
|
953
|
+
if (!this._archiveStorage || !this._archiveStorage.isAvailable()) {
|
|
954
|
+
this.send(client, { type: "archiveList", archives: [], storageAvailable: false });
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
try {
|
|
958
|
+
const limit = cmd.limit ?? 50;
|
|
959
|
+
const archived = await this._archiveStorage.list(limit, cmd.prefix);
|
|
960
|
+
const summaries = archived.map((a) => ({
|
|
961
|
+
sessionId: a.sessionId,
|
|
962
|
+
key: a.key,
|
|
963
|
+
sizeBytes: a.sizeBytes,
|
|
964
|
+
lastModified: new Date(a.lastModified).toISOString()
|
|
965
|
+
}));
|
|
966
|
+
this.send(client, { type: "archiveList", archives: summaries, storageAvailable: true });
|
|
967
|
+
} catch (e) {
|
|
968
|
+
this.sendError(client, "ARCHIVE_LIST_ERROR", e instanceof Error ? e.message : String(e), null);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
async handleImportArchive(client, cmd) {
|
|
972
|
+
if (!this._archiveStorage || !this._archiveStorage.isAvailable()) {
|
|
973
|
+
this.sendError(client, "NO_ARCHIVE_STORAGE", "Archive storage is not configured", null);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
try {
|
|
977
|
+
const data = await this._archiveStorage.retrieve(cmd.sessionId);
|
|
978
|
+
const reader = new SessionArchiveReader();
|
|
979
|
+
const imported = reader.readFull(data);
|
|
980
|
+
const meta = imported.metadata;
|
|
981
|
+
this._sessionRegistry.registerImported(
|
|
982
|
+
meta.sessionId,
|
|
983
|
+
meta.netName,
|
|
984
|
+
meta.dotDiagram,
|
|
985
|
+
meta.structure,
|
|
986
|
+
imported.eventStore,
|
|
987
|
+
new Date(meta.startTime).getTime()
|
|
988
|
+
);
|
|
989
|
+
this.send(client, {
|
|
990
|
+
type: "archiveImported",
|
|
991
|
+
sessionId: meta.sessionId,
|
|
992
|
+
netName: meta.netName,
|
|
993
|
+
eventCount: meta.eventCount
|
|
994
|
+
});
|
|
995
|
+
this.broadcastSessionList();
|
|
996
|
+
} catch (e) {
|
|
997
|
+
this.sendError(client, "ARCHIVE_IMPORT_ERROR", e instanceof Error ? e.message : String(e), cmd.sessionId);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
async handleUploadArchive(client, cmd) {
|
|
1001
|
+
try {
|
|
1002
|
+
const decoded = Buffer.from(cmd.data, "base64");
|
|
1003
|
+
const reader = new SessionArchiveReader();
|
|
1004
|
+
const imported = reader.readFull(decoded);
|
|
1005
|
+
const meta = imported.metadata;
|
|
1006
|
+
this._sessionRegistry.registerImported(
|
|
1007
|
+
meta.sessionId,
|
|
1008
|
+
meta.netName,
|
|
1009
|
+
meta.dotDiagram,
|
|
1010
|
+
meta.structure,
|
|
1011
|
+
imported.eventStore,
|
|
1012
|
+
new Date(meta.startTime).getTime()
|
|
1013
|
+
);
|
|
1014
|
+
this.send(client, {
|
|
1015
|
+
type: "archiveImported",
|
|
1016
|
+
sessionId: meta.sessionId,
|
|
1017
|
+
netName: meta.netName,
|
|
1018
|
+
eventCount: meta.eventCount
|
|
1019
|
+
});
|
|
1020
|
+
this.broadcastSessionList();
|
|
1021
|
+
} catch (e) {
|
|
1022
|
+
this.sendError(client, "ARCHIVE_UPLOAD_ERROR", e instanceof Error ? e.message : String(e), null);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
broadcastSessionList() {
|
|
1026
|
+
const sessions = this._sessionRegistry.listSessions(50);
|
|
1027
|
+
const summaries = sessions.map((s) => ({
|
|
1028
|
+
sessionId: s.sessionId,
|
|
1029
|
+
netName: s.netName,
|
|
1030
|
+
startTime: new Date(s.startTime).toISOString(),
|
|
1031
|
+
active: s.active,
|
|
1032
|
+
eventCount: s.eventStore.eventCount()
|
|
1033
|
+
}));
|
|
1034
|
+
const response = { type: "sessionList", sessions: summaries };
|
|
1035
|
+
for (const clientState of this._clients.values()) {
|
|
1036
|
+
try {
|
|
1037
|
+
this.send(clientState, response);
|
|
1038
|
+
} catch {
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
826
1042
|
// ======================== Helper Methods ========================
|
|
827
1043
|
send(client, response) {
|
|
828
1044
|
client.sink(response);
|
|
@@ -1112,9 +1328,6 @@ var DebugAwareEventStore = class {
|
|
|
1112
1328
|
}
|
|
1113
1329
|
};
|
|
1114
1330
|
|
|
1115
|
-
// src/debug/archive/session-archive.ts
|
|
1116
|
-
var CURRENT_VERSION = 1;
|
|
1117
|
-
|
|
1118
1331
|
// src/debug/archive/file-session-archive-storage.ts
|
|
1119
1332
|
import { mkdir, readdir, readFile, stat, writeFile } from "fs/promises";
|
|
1120
1333
|
import { join, normalize, resolve } from "path";
|
|
@@ -1246,115 +1459,6 @@ var SessionArchiveWriter = class {
|
|
|
1246
1459
|
}
|
|
1247
1460
|
};
|
|
1248
1461
|
|
|
1249
|
-
// src/debug/archive/session-archive-reader.ts
|
|
1250
|
-
import { gunzipSync } from "zlib";
|
|
1251
|
-
var MAX_EVENT_SIZE = 10 * 1024 * 1024;
|
|
1252
|
-
var SessionArchiveReader = class {
|
|
1253
|
-
/** Reads only the metadata header from an archive. */
|
|
1254
|
-
readMetadata(compressed) {
|
|
1255
|
-
const data = gunzipSync(compressed);
|
|
1256
|
-
const metaLen = data.readUInt32BE(0);
|
|
1257
|
-
const metaJson = data.subarray(4, 4 + metaLen).toString("utf-8");
|
|
1258
|
-
const metadata = JSON.parse(metaJson);
|
|
1259
|
-
if (metadata.version !== CURRENT_VERSION) {
|
|
1260
|
-
throw new Error(`Unsupported archive version: ${metadata.version} (expected ${CURRENT_VERSION})`);
|
|
1261
|
-
}
|
|
1262
|
-
return metadata;
|
|
1263
|
-
}
|
|
1264
|
-
/** Reads the full archive: metadata + all events into a DebugEventStore. */
|
|
1265
|
-
readFull(compressed) {
|
|
1266
|
-
const data = gunzipSync(compressed);
|
|
1267
|
-
let offset = 0;
|
|
1268
|
-
const metaLen = data.readUInt32BE(offset);
|
|
1269
|
-
offset += 4;
|
|
1270
|
-
const metaJson = data.subarray(offset, offset + metaLen).toString("utf-8");
|
|
1271
|
-
offset += metaLen;
|
|
1272
|
-
const metadata = JSON.parse(metaJson);
|
|
1273
|
-
if (metadata.version !== CURRENT_VERSION) {
|
|
1274
|
-
throw new Error(`Unsupported archive version: ${metadata.version} (expected ${CURRENT_VERSION})`);
|
|
1275
|
-
}
|
|
1276
|
-
const eventStore = new DebugEventStore(metadata.sessionId, Number.MAX_SAFE_INTEGER);
|
|
1277
|
-
while (offset < data.length) {
|
|
1278
|
-
if (offset + 4 > data.length) break;
|
|
1279
|
-
const eventLen = data.readUInt32BE(offset);
|
|
1280
|
-
offset += 4;
|
|
1281
|
-
if (eventLen <= 0 || eventLen > MAX_EVENT_SIZE) {
|
|
1282
|
-
throw new Error(`Invalid event size: ${eventLen}`);
|
|
1283
|
-
}
|
|
1284
|
-
if (offset + eventLen > data.length) break;
|
|
1285
|
-
const eventJson = data.subarray(offset, offset + eventLen).toString("utf-8");
|
|
1286
|
-
offset += eventLen;
|
|
1287
|
-
const eventInfo = JSON.parse(eventJson);
|
|
1288
|
-
const netEvent = eventInfoToNetEvent(eventInfo);
|
|
1289
|
-
eventStore.append(netEvent);
|
|
1290
|
-
}
|
|
1291
|
-
return { metadata, eventStore };
|
|
1292
|
-
}
|
|
1293
|
-
};
|
|
1294
|
-
function eventInfoToNetEvent(info) {
|
|
1295
|
-
const timestamp = new Date(info.timestamp).getTime();
|
|
1296
|
-
const d = info.details;
|
|
1297
|
-
switch (info.type) {
|
|
1298
|
-
case "ExecutionStarted":
|
|
1299
|
-
return { type: "execution-started", timestamp, netName: d["netName"], executionId: d["executionId"] };
|
|
1300
|
-
case "ExecutionCompleted":
|
|
1301
|
-
return { type: "execution-completed", timestamp, netName: d["netName"], executionId: d["executionId"], totalDurationMs: d["totalDurationMs"] };
|
|
1302
|
-
case "TransitionEnabled":
|
|
1303
|
-
return { type: "transition-enabled", timestamp, transitionName: info.transitionName };
|
|
1304
|
-
case "TransitionClockRestarted":
|
|
1305
|
-
return { type: "transition-clock-restarted", timestamp, transitionName: info.transitionName };
|
|
1306
|
-
case "TransitionStarted": {
|
|
1307
|
-
const tokens = d["consumedTokens"].map((t) => infoToToken(t));
|
|
1308
|
-
return { type: "transition-started", timestamp, transitionName: info.transitionName, consumedTokens: tokens };
|
|
1309
|
-
}
|
|
1310
|
-
case "TransitionCompleted": {
|
|
1311
|
-
const tokens = d["producedTokens"].map((t) => infoToToken(t));
|
|
1312
|
-
return { type: "transition-completed", timestamp, transitionName: info.transitionName, producedTokens: tokens, durationMs: d["durationMs"] };
|
|
1313
|
-
}
|
|
1314
|
-
case "TransitionFailed":
|
|
1315
|
-
return { type: "transition-failed", timestamp, transitionName: info.transitionName, errorMessage: d["errorMessage"], exceptionType: d["exceptionType"] };
|
|
1316
|
-
case "TransitionTimedOut":
|
|
1317
|
-
return { type: "transition-timed-out", timestamp, transitionName: info.transitionName, deadlineMs: d["deadlineMs"], actualDurationMs: d["actualDurationMs"] };
|
|
1318
|
-
case "ActionTimedOut":
|
|
1319
|
-
return { type: "action-timed-out", timestamp, transitionName: info.transitionName, timeoutMs: d["timeoutMs"] };
|
|
1320
|
-
case "TokenAdded": {
|
|
1321
|
-
const t = d["token"];
|
|
1322
|
-
return { type: "token-added", timestamp, placeName: info.placeName, token: infoToToken(t) };
|
|
1323
|
-
}
|
|
1324
|
-
case "TokenRemoved": {
|
|
1325
|
-
const t = d["token"];
|
|
1326
|
-
return { type: "token-removed", timestamp, placeName: info.placeName, token: infoToToken(t) };
|
|
1327
|
-
}
|
|
1328
|
-
case "MarkingSnapshot": {
|
|
1329
|
-
const markingData = d["marking"];
|
|
1330
|
-
const marking = /* @__PURE__ */ new Map();
|
|
1331
|
-
for (const [place, tokens] of Object.entries(markingData)) {
|
|
1332
|
-
marking.set(place, tokens.map((t) => infoToToken(t)));
|
|
1333
|
-
}
|
|
1334
|
-
return { type: "marking-snapshot", timestamp, marking };
|
|
1335
|
-
}
|
|
1336
|
-
case "LogMessage":
|
|
1337
|
-
return {
|
|
1338
|
-
type: "log-message",
|
|
1339
|
-
timestamp,
|
|
1340
|
-
transitionName: info.transitionName,
|
|
1341
|
-
logger: d["loggerName"],
|
|
1342
|
-
level: d["level"],
|
|
1343
|
-
message: d["message"],
|
|
1344
|
-
error: d["throwable"] ?? null,
|
|
1345
|
-
errorMessage: d["throwableMessage"] ?? null
|
|
1346
|
-
};
|
|
1347
|
-
default:
|
|
1348
|
-
return { type: "transition-enabled", timestamp, transitionName: info.transitionName ?? "unknown" };
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
function infoToToken(t) {
|
|
1352
|
-
return {
|
|
1353
|
-
value: t.value,
|
|
1354
|
-
createdAt: t.timestamp ? new Date(t.timestamp).getTime() : Date.now()
|
|
1355
|
-
};
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
1462
|
// src/debug/index.ts
|
|
1359
1463
|
async function debugUiAssetPath() {
|
|
1360
1464
|
const dynamicImport = Function("m", "return import(m)");
|