@principal-ai/principal-view-core 0.5.6

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.
Files changed (204) hide show
  1. package/README.md +126 -0
  2. package/dist/ConfigurationLoader.d.ts +76 -0
  3. package/dist/ConfigurationLoader.d.ts.map +1 -0
  4. package/dist/ConfigurationLoader.js +144 -0
  5. package/dist/ConfigurationLoader.js.map +1 -0
  6. package/dist/ConfigurationValidator.d.ts +31 -0
  7. package/dist/ConfigurationValidator.d.ts.map +1 -0
  8. package/dist/ConfigurationValidator.js +242 -0
  9. package/dist/ConfigurationValidator.js.map +1 -0
  10. package/dist/EventProcessor.d.ts +49 -0
  11. package/dist/EventProcessor.d.ts.map +1 -0
  12. package/dist/EventProcessor.js +215 -0
  13. package/dist/EventProcessor.js.map +1 -0
  14. package/dist/EventRecorderService.d.ts +305 -0
  15. package/dist/EventRecorderService.d.ts.map +1 -0
  16. package/dist/EventRecorderService.js +463 -0
  17. package/dist/EventRecorderService.js.map +1 -0
  18. package/dist/LibraryLoader.d.ts +63 -0
  19. package/dist/LibraryLoader.d.ts.map +1 -0
  20. package/dist/LibraryLoader.js +188 -0
  21. package/dist/LibraryLoader.js.map +1 -0
  22. package/dist/PathBasedEventProcessor.d.ts +90 -0
  23. package/dist/PathBasedEventProcessor.d.ts.map +1 -0
  24. package/dist/PathBasedEventProcessor.js +239 -0
  25. package/dist/PathBasedEventProcessor.js.map +1 -0
  26. package/dist/SessionManager.d.ts +194 -0
  27. package/dist/SessionManager.d.ts.map +1 -0
  28. package/dist/SessionManager.js +299 -0
  29. package/dist/SessionManager.js.map +1 -0
  30. package/dist/ValidationEngine.d.ts +31 -0
  31. package/dist/ValidationEngine.d.ts.map +1 -0
  32. package/dist/ValidationEngine.js +158 -0
  33. package/dist/ValidationEngine.js.map +1 -0
  34. package/dist/helpers/GraphInstrumentationHelper.d.ts +93 -0
  35. package/dist/helpers/GraphInstrumentationHelper.d.ts.map +1 -0
  36. package/dist/helpers/GraphInstrumentationHelper.js +248 -0
  37. package/dist/helpers/GraphInstrumentationHelper.js.map +1 -0
  38. package/dist/index.d.ts +33 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +34 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/rules/config.d.ts +57 -0
  43. package/dist/rules/config.d.ts.map +1 -0
  44. package/dist/rules/config.js +382 -0
  45. package/dist/rules/config.js.map +1 -0
  46. package/dist/rules/engine.d.ts +70 -0
  47. package/dist/rules/engine.d.ts.map +1 -0
  48. package/dist/rules/engine.js +252 -0
  49. package/dist/rules/engine.js.map +1 -0
  50. package/dist/rules/implementations/connection-type-references.d.ts +7 -0
  51. package/dist/rules/implementations/connection-type-references.d.ts.map +1 -0
  52. package/dist/rules/implementations/connection-type-references.js +104 -0
  53. package/dist/rules/implementations/connection-type-references.js.map +1 -0
  54. package/dist/rules/implementations/dead-end-states.d.ts +17 -0
  55. package/dist/rules/implementations/dead-end-states.d.ts.map +1 -0
  56. package/dist/rules/implementations/dead-end-states.js +72 -0
  57. package/dist/rules/implementations/dead-end-states.js.map +1 -0
  58. package/dist/rules/implementations/index.d.ts +24 -0
  59. package/dist/rules/implementations/index.d.ts.map +1 -0
  60. package/dist/rules/implementations/index.js +62 -0
  61. package/dist/rules/implementations/index.js.map +1 -0
  62. package/dist/rules/implementations/library-node-type-match.d.ts +17 -0
  63. package/dist/rules/implementations/library-node-type-match.d.ts.map +1 -0
  64. package/dist/rules/implementations/library-node-type-match.js +123 -0
  65. package/dist/rules/implementations/library-node-type-match.js.map +1 -0
  66. package/dist/rules/implementations/minimum-node-sources.d.ts +22 -0
  67. package/dist/rules/implementations/minimum-node-sources.d.ts.map +1 -0
  68. package/dist/rules/implementations/minimum-node-sources.js +54 -0
  69. package/dist/rules/implementations/minimum-node-sources.js.map +1 -0
  70. package/dist/rules/implementations/no-unknown-fields.d.ts +7 -0
  71. package/dist/rules/implementations/no-unknown-fields.d.ts.map +1 -0
  72. package/dist/rules/implementations/no-unknown-fields.js +211 -0
  73. package/dist/rules/implementations/no-unknown-fields.js.map +1 -0
  74. package/dist/rules/implementations/orphaned-edge-types.d.ts +7 -0
  75. package/dist/rules/implementations/orphaned-edge-types.d.ts.map +1 -0
  76. package/dist/rules/implementations/orphaned-edge-types.js +47 -0
  77. package/dist/rules/implementations/orphaned-edge-types.js.map +1 -0
  78. package/dist/rules/implementations/orphaned-node-types.d.ts +7 -0
  79. package/dist/rules/implementations/orphaned-node-types.d.ts.map +1 -0
  80. package/dist/rules/implementations/orphaned-node-types.js +50 -0
  81. package/dist/rules/implementations/orphaned-node-types.js.map +1 -0
  82. package/dist/rules/implementations/required-metadata.d.ts +7 -0
  83. package/dist/rules/implementations/required-metadata.d.ts.map +1 -0
  84. package/dist/rules/implementations/required-metadata.js +57 -0
  85. package/dist/rules/implementations/required-metadata.js.map +1 -0
  86. package/dist/rules/implementations/state-transition-references.d.ts +7 -0
  87. package/dist/rules/implementations/state-transition-references.d.ts.map +1 -0
  88. package/dist/rules/implementations/state-transition-references.js +135 -0
  89. package/dist/rules/implementations/state-transition-references.js.map +1 -0
  90. package/dist/rules/implementations/unreachable-states.d.ts +7 -0
  91. package/dist/rules/implementations/unreachable-states.d.ts.map +1 -0
  92. package/dist/rules/implementations/unreachable-states.js +80 -0
  93. package/dist/rules/implementations/unreachable-states.js.map +1 -0
  94. package/dist/rules/implementations/valid-action-patterns.d.ts +17 -0
  95. package/dist/rules/implementations/valid-action-patterns.d.ts.map +1 -0
  96. package/dist/rules/implementations/valid-action-patterns.js +109 -0
  97. package/dist/rules/implementations/valid-action-patterns.js.map +1 -0
  98. package/dist/rules/implementations/valid-color-format.d.ts +7 -0
  99. package/dist/rules/implementations/valid-color-format.d.ts.map +1 -0
  100. package/dist/rules/implementations/valid-color-format.js +91 -0
  101. package/dist/rules/implementations/valid-color-format.js.map +1 -0
  102. package/dist/rules/implementations/valid-edge-types.d.ts +7 -0
  103. package/dist/rules/implementations/valid-edge-types.d.ts.map +1 -0
  104. package/dist/rules/implementations/valid-edge-types.js +244 -0
  105. package/dist/rules/implementations/valid-edge-types.js.map +1 -0
  106. package/dist/rules/implementations/valid-node-types.d.ts +7 -0
  107. package/dist/rules/implementations/valid-node-types.d.ts.map +1 -0
  108. package/dist/rules/implementations/valid-node-types.js +175 -0
  109. package/dist/rules/implementations/valid-node-types.js.map +1 -0
  110. package/dist/rules/index.d.ts +28 -0
  111. package/dist/rules/index.d.ts.map +1 -0
  112. package/dist/rules/index.js +45 -0
  113. package/dist/rules/index.js.map +1 -0
  114. package/dist/rules/types.d.ts +309 -0
  115. package/dist/rules/types.d.ts.map +1 -0
  116. package/dist/rules/types.js +35 -0
  117. package/dist/rules/types.js.map +1 -0
  118. package/dist/types/canvas.d.ts +409 -0
  119. package/dist/types/canvas.d.ts.map +1 -0
  120. package/dist/types/canvas.js +70 -0
  121. package/dist/types/canvas.js.map +1 -0
  122. package/dist/types/index.d.ts +311 -0
  123. package/dist/types/index.d.ts.map +1 -0
  124. package/dist/types/index.js +13 -0
  125. package/dist/types/index.js.map +1 -0
  126. package/dist/types/library.d.ts +185 -0
  127. package/dist/types/library.d.ts.map +1 -0
  128. package/dist/types/library.js +15 -0
  129. package/dist/types/library.js.map +1 -0
  130. package/dist/types/path-based-config.d.ts +230 -0
  131. package/dist/types/path-based-config.d.ts.map +1 -0
  132. package/dist/types/path-based-config.js +9 -0
  133. package/dist/types/path-based-config.js.map +1 -0
  134. package/dist/utils/CanvasConverter.d.ts +118 -0
  135. package/dist/utils/CanvasConverter.d.ts.map +1 -0
  136. package/dist/utils/CanvasConverter.js +315 -0
  137. package/dist/utils/CanvasConverter.js.map +1 -0
  138. package/dist/utils/GraphConverter.d.ts +18 -0
  139. package/dist/utils/GraphConverter.d.ts.map +1 -0
  140. package/dist/utils/GraphConverter.js +61 -0
  141. package/dist/utils/GraphConverter.js.map +1 -0
  142. package/dist/utils/LibraryConverter.d.ts +113 -0
  143. package/dist/utils/LibraryConverter.d.ts.map +1 -0
  144. package/dist/utils/LibraryConverter.js +166 -0
  145. package/dist/utils/LibraryConverter.js.map +1 -0
  146. package/dist/utils/PathMatcher.d.ts +55 -0
  147. package/dist/utils/PathMatcher.d.ts.map +1 -0
  148. package/dist/utils/PathMatcher.js +172 -0
  149. package/dist/utils/PathMatcher.js.map +1 -0
  150. package/dist/utils/YamlParser.d.ts +36 -0
  151. package/dist/utils/YamlParser.d.ts.map +1 -0
  152. package/dist/utils/YamlParser.js +63 -0
  153. package/dist/utils/YamlParser.js.map +1 -0
  154. package/package.json +47 -0
  155. package/src/ConfigurationLoader.test.ts +490 -0
  156. package/src/ConfigurationLoader.ts +185 -0
  157. package/src/ConfigurationValidator.test.ts +200 -0
  158. package/src/ConfigurationValidator.ts +283 -0
  159. package/src/EventProcessor.test.ts +405 -0
  160. package/src/EventProcessor.ts +250 -0
  161. package/src/EventRecorderService.test.ts +541 -0
  162. package/src/EventRecorderService.ts +744 -0
  163. package/src/LibraryLoader.ts +215 -0
  164. package/src/PathBasedEventProcessor.test.ts +567 -0
  165. package/src/PathBasedEventProcessor.ts +332 -0
  166. package/src/SessionManager.test.ts +424 -0
  167. package/src/SessionManager.ts +470 -0
  168. package/src/ValidationEngine.test.ts +371 -0
  169. package/src/ValidationEngine.ts +196 -0
  170. package/src/helpers/GraphInstrumentationHelper.test.ts +340 -0
  171. package/src/helpers/GraphInstrumentationHelper.ts +326 -0
  172. package/src/index.ts +85 -0
  173. package/src/rules/config.test.ts +278 -0
  174. package/src/rules/config.ts +459 -0
  175. package/src/rules/engine.test.ts +332 -0
  176. package/src/rules/engine.ts +318 -0
  177. package/src/rules/implementations/connection-type-references.ts +117 -0
  178. package/src/rules/implementations/dead-end-states.ts +101 -0
  179. package/src/rules/implementations/index.ts +73 -0
  180. package/src/rules/implementations/library-node-type-match.ts +148 -0
  181. package/src/rules/implementations/minimum-node-sources.ts +82 -0
  182. package/src/rules/implementations/no-unknown-fields.ts +342 -0
  183. package/src/rules/implementations/orphaned-edge-types.ts +55 -0
  184. package/src/rules/implementations/orphaned-node-types.ts +58 -0
  185. package/src/rules/implementations/required-metadata.ts +64 -0
  186. package/src/rules/implementations/state-transition-references.ts +151 -0
  187. package/src/rules/implementations/unreachable-states.ts +94 -0
  188. package/src/rules/implementations/valid-action-patterns.ts +136 -0
  189. package/src/rules/implementations/valid-color-format.ts +140 -0
  190. package/src/rules/implementations/valid-edge-types.ts +258 -0
  191. package/src/rules/implementations/valid-node-types.ts +189 -0
  192. package/src/rules/index.ts +95 -0
  193. package/src/rules/types.ts +426 -0
  194. package/src/types/canvas.ts +496 -0
  195. package/src/types/index.ts +382 -0
  196. package/src/types/library.ts +233 -0
  197. package/src/types/path-based-config.ts +281 -0
  198. package/src/utils/CanvasConverter.ts +431 -0
  199. package/src/utils/GraphConverter.test.ts +195 -0
  200. package/src/utils/GraphConverter.ts +71 -0
  201. package/src/utils/LibraryConverter.ts +245 -0
  202. package/src/utils/PathMatcher.test.ts +148 -0
  203. package/src/utils/PathMatcher.ts +183 -0
  204. package/src/utils/YamlParser.ts +75 -0
@@ -0,0 +1,470 @@
1
+ /**
2
+ * SessionManager - Manages event recording sessions
3
+ *
4
+ * Sessions group events by test case or recording context.
5
+ * Each session contains a sequence of GraphEvents that can be
6
+ * replayed through the EventControllerPanel.
7
+ */
8
+
9
+ import type { GraphEvent } from './types';
10
+
11
+ /**
12
+ * Session status
13
+ */
14
+ export type SessionStatus = 'recording' | 'completed' | 'error';
15
+
16
+ /**
17
+ * Session result when completed
18
+ */
19
+ export type SessionResult = 'pass' | 'fail' | 'skip';
20
+
21
+ /**
22
+ * Metadata associated with a session
23
+ */
24
+ export interface SessionMetadata {
25
+ /** Test file path */
26
+ testFile?: string;
27
+
28
+ /** Test case name */
29
+ testName?: string;
30
+
31
+ /** Test suite name */
32
+ testSuite?: string;
33
+
34
+ /** Custom tags for filtering */
35
+ tags?: string[];
36
+
37
+ /** Test result (set when session completes) */
38
+ result?: SessionResult;
39
+
40
+ /** Error message if test failed */
41
+ error?: string;
42
+
43
+ /** Duration in milliseconds */
44
+ duration?: number;
45
+ }
46
+
47
+ /**
48
+ * An event recording session
49
+ */
50
+ export interface EventSession {
51
+ /** Unique session identifier */
52
+ id: string;
53
+
54
+ /** Human-readable name (e.g., test name) */
55
+ name: string;
56
+
57
+ /** When recording started */
58
+ startedAt: number;
59
+
60
+ /** When recording ended (undefined if still recording) */
61
+ endedAt?: number;
62
+
63
+ /** Status of the session */
64
+ status: SessionStatus;
65
+
66
+ /** Recorded events */
67
+ events: GraphEvent[];
68
+
69
+ /** Session metadata */
70
+ metadata: SessionMetadata;
71
+ }
72
+
73
+ /**
74
+ * Options for creating a session
75
+ */
76
+ export interface CreateSessionOptions {
77
+ /** Session name */
78
+ name: string;
79
+
80
+ /** Optional metadata */
81
+ metadata?: SessionMetadata;
82
+
83
+ /** Optional custom ID (auto-generated if not provided) */
84
+ id?: string;
85
+ }
86
+
87
+ /**
88
+ * Options for ending a session
89
+ */
90
+ export interface EndSessionOptions {
91
+ /** Test result */
92
+ result?: SessionResult;
93
+
94
+ /** Error message if failed */
95
+ error?: string;
96
+ }
97
+
98
+ /**
99
+ * Session change callback
100
+ */
101
+ export type SessionChangeCallback = (sessions: EventSession[]) => void;
102
+
103
+ /**
104
+ * SessionManager configuration
105
+ */
106
+ export interface SessionManagerConfig {
107
+ /** Maximum events per session (default: 10000) */
108
+ maxEventsPerSession?: number;
109
+
110
+ /** Maximum number of sessions to keep (default: 100) */
111
+ maxSessions?: number;
112
+
113
+ /** Auto-cleanup sessions older than this (ms, default: 3600000 = 1 hour) */
114
+ sessionRetention?: number;
115
+
116
+ /** Whether to auto-cleanup old sessions (default: true) */
117
+ autoCleanup?: boolean;
118
+ }
119
+
120
+ /**
121
+ * Manages event recording sessions
122
+ */
123
+ export class SessionManager {
124
+ private sessions: Map<string, EventSession> = new Map();
125
+ private activeSessionId: string | null = null;
126
+ private listeners: Set<SessionChangeCallback> = new Set();
127
+ private config: Required<SessionManagerConfig>;
128
+ private cleanupInterval: ReturnType<typeof setInterval> | null = null;
129
+
130
+ constructor(config: SessionManagerConfig = {}) {
131
+ this.config = {
132
+ maxEventsPerSession: config.maxEventsPerSession ?? 10000,
133
+ maxSessions: config.maxSessions ?? 100,
134
+ sessionRetention: config.sessionRetention ?? 3600000, // 1 hour
135
+ autoCleanup: config.autoCleanup ?? true,
136
+ };
137
+
138
+ // Start auto-cleanup if enabled
139
+ if (this.config.autoCleanup) {
140
+ this.startAutoCleanup();
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Generate a unique session ID
146
+ */
147
+ private generateId(): string {
148
+ return `session-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
149
+ }
150
+
151
+ /**
152
+ * Notify all listeners of session changes
153
+ */
154
+ private notifyListeners(): void {
155
+ const sessions = this.listSessions();
156
+ for (const listener of this.listeners) {
157
+ try {
158
+ listener(sessions);
159
+ } catch (error) {
160
+ console.error('Session listener error:', error);
161
+ }
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Start auto-cleanup interval
167
+ */
168
+ private startAutoCleanup(): void {
169
+ // Clean up every minute
170
+ this.cleanupInterval = setInterval(() => {
171
+ this.cleanupOldSessions();
172
+ }, 60000);
173
+ }
174
+
175
+ /**
176
+ * Stop auto-cleanup interval
177
+ */
178
+ private stopAutoCleanup(): void {
179
+ if (this.cleanupInterval) {
180
+ clearInterval(this.cleanupInterval);
181
+ this.cleanupInterval = null;
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Clean up sessions older than retention period
187
+ */
188
+ private cleanupOldSessions(): void {
189
+ const now = Date.now();
190
+ const cutoff = now - this.config.sessionRetention;
191
+
192
+ for (const [id, session] of this.sessions) {
193
+ // Don't cleanup active session
194
+ if (id === this.activeSessionId) continue;
195
+
196
+ // Cleanup completed/error sessions past retention
197
+ if (session.status !== 'recording' && session.startedAt < cutoff) {
198
+ this.sessions.delete(id);
199
+ }
200
+ }
201
+
202
+ // Enforce max sessions limit
203
+ if (this.sessions.size > this.config.maxSessions) {
204
+ const sortedSessions = Array.from(this.sessions.entries())
205
+ .filter(([id]) => id !== this.activeSessionId)
206
+ .sort((a, b) => a[1].startedAt - b[1].startedAt);
207
+
208
+ const toDelete = sortedSessions.slice(0, this.sessions.size - this.config.maxSessions);
209
+ for (const [id] of toDelete) {
210
+ this.sessions.delete(id);
211
+ }
212
+ }
213
+
214
+ this.notifyListeners();
215
+ }
216
+
217
+ /**
218
+ * Create a new session
219
+ */
220
+ createSession(options: CreateSessionOptions): EventSession {
221
+ const id = options.id || this.generateId();
222
+
223
+ // Check if session already exists
224
+ if (this.sessions.has(id)) {
225
+ throw new Error(`Session with ID "${id}" already exists`);
226
+ }
227
+
228
+ const session: EventSession = {
229
+ id,
230
+ name: options.name,
231
+ startedAt: Date.now(),
232
+ status: 'recording',
233
+ events: [],
234
+ metadata: options.metadata || {},
235
+ };
236
+
237
+ this.sessions.set(id, session);
238
+ this.activeSessionId = id;
239
+ this.notifyListeners();
240
+
241
+ return session;
242
+ }
243
+
244
+ /**
245
+ * Get session by ID
246
+ */
247
+ getSession(id: string): EventSession | undefined {
248
+ return this.sessions.get(id);
249
+ }
250
+
251
+ /**
252
+ * Get the currently active (recording) session
253
+ */
254
+ getActiveSession(): EventSession | undefined {
255
+ if (!this.activeSessionId) return undefined;
256
+ return this.sessions.get(this.activeSessionId);
257
+ }
258
+
259
+ /**
260
+ * List all sessions, sorted by start time (newest first)
261
+ */
262
+ listSessions(): EventSession[] {
263
+ return Array.from(this.sessions.values())
264
+ .sort((a, b) => b.startedAt - a.startedAt);
265
+ }
266
+
267
+ /**
268
+ * Add an event to a session
269
+ */
270
+ addEvent(sessionId: string, event: GraphEvent): void {
271
+ const session = this.sessions.get(sessionId);
272
+
273
+ if (!session) {
274
+ throw new Error(`Session "${sessionId}" not found`);
275
+ }
276
+
277
+ if (session.status !== 'recording') {
278
+ throw new Error(`Session "${sessionId}" is not recording (status: ${session.status})`);
279
+ }
280
+
281
+ // Check max events limit
282
+ if (session.events.length >= this.config.maxEventsPerSession) {
283
+ console.warn(`Session "${sessionId}" reached max events limit (${this.config.maxEventsPerSession})`);
284
+ return;
285
+ }
286
+
287
+ session.events.push(event);
288
+ this.notifyListeners();
289
+ }
290
+
291
+ /**
292
+ * Add an event to the active session (convenience method)
293
+ */
294
+ addEventToActive(event: GraphEvent): void {
295
+ if (!this.activeSessionId) {
296
+ throw new Error('No active session');
297
+ }
298
+ this.addEvent(this.activeSessionId, event);
299
+ }
300
+
301
+ /**
302
+ * End a session
303
+ */
304
+ endSession(sessionId: string, options: EndSessionOptions = {}): void {
305
+ const session = this.sessions.get(sessionId);
306
+
307
+ if (!session) {
308
+ throw new Error(`Session "${sessionId}" not found`);
309
+ }
310
+
311
+ if (session.status !== 'recording') {
312
+ throw new Error(`Session "${sessionId}" is not recording (status: ${session.status})`);
313
+ }
314
+
315
+ session.endedAt = Date.now();
316
+ session.status = 'completed';
317
+ session.metadata.result = options.result;
318
+ session.metadata.error = options.error;
319
+ session.metadata.duration = session.endedAt - session.startedAt;
320
+
321
+ // Clear active session if this was it
322
+ if (this.activeSessionId === sessionId) {
323
+ this.activeSessionId = null;
324
+ }
325
+
326
+ this.notifyListeners();
327
+ }
328
+
329
+ /**
330
+ * End the active session (convenience method)
331
+ */
332
+ endActiveSession(options: EndSessionOptions = {}): void {
333
+ if (!this.activeSessionId) {
334
+ throw new Error('No active session');
335
+ }
336
+ this.endSession(this.activeSessionId, options);
337
+ }
338
+
339
+ /**
340
+ * Mark a session as errored
341
+ */
342
+ errorSession(sessionId: string, error: string): void {
343
+ const session = this.sessions.get(sessionId);
344
+
345
+ if (!session) {
346
+ throw new Error(`Session "${sessionId}" not found`);
347
+ }
348
+
349
+ session.endedAt = Date.now();
350
+ session.status = 'error';
351
+ session.metadata.error = error;
352
+ session.metadata.duration = session.endedAt - session.startedAt;
353
+
354
+ if (this.activeSessionId === sessionId) {
355
+ this.activeSessionId = null;
356
+ }
357
+
358
+ this.notifyListeners();
359
+ }
360
+
361
+ /**
362
+ * Delete a session
363
+ */
364
+ deleteSession(id: string): boolean {
365
+ if (this.activeSessionId === id) {
366
+ this.activeSessionId = null;
367
+ }
368
+
369
+ const deleted = this.sessions.delete(id);
370
+ if (deleted) {
371
+ this.notifyListeners();
372
+ }
373
+ return deleted;
374
+ }
375
+
376
+ /**
377
+ * Clear all sessions
378
+ */
379
+ clearSessions(): void {
380
+ this.sessions.clear();
381
+ this.activeSessionId = null;
382
+ this.notifyListeners();
383
+ }
384
+
385
+ /**
386
+ * Export a session to JSON string
387
+ */
388
+ exportSession(id: string): string {
389
+ const session = this.sessions.get(id);
390
+
391
+ if (!session) {
392
+ throw new Error(`Session "${id}" not found`);
393
+ }
394
+
395
+ return JSON.stringify(session, null, 2);
396
+ }
397
+
398
+ /**
399
+ * Import a session from JSON string
400
+ */
401
+ importSession(json: string): EventSession {
402
+ const session = JSON.parse(json) as EventSession;
403
+
404
+ // Validate required fields
405
+ if (!session.id || !session.name || !session.events) {
406
+ throw new Error('Invalid session format: missing required fields');
407
+ }
408
+
409
+ // Generate new ID if session with same ID exists
410
+ if (this.sessions.has(session.id)) {
411
+ session.id = this.generateId();
412
+ }
413
+
414
+ // Mark as completed (imported sessions can't be recorded to)
415
+ session.status = 'completed';
416
+
417
+ this.sessions.set(session.id, session);
418
+ this.notifyListeners();
419
+
420
+ return session;
421
+ }
422
+
423
+ /**
424
+ * Subscribe to session changes
425
+ * Returns unsubscribe function
426
+ */
427
+ onSessionChange(callback: SessionChangeCallback): () => void {
428
+ this.listeners.add(callback);
429
+
430
+ // Immediately call with current sessions
431
+ callback(this.listSessions());
432
+
433
+ return () => {
434
+ this.listeners.delete(callback);
435
+ };
436
+ }
437
+
438
+ /**
439
+ * Get session statistics
440
+ */
441
+ getStats(): {
442
+ totalSessions: number;
443
+ activeSessions: number;
444
+ totalEvents: number;
445
+ oldestSession: number | null;
446
+ newestSession: number | null;
447
+ } {
448
+ const sessions = this.listSessions();
449
+ const activeSessions = sessions.filter(s => s.status === 'recording').length;
450
+ const totalEvents = sessions.reduce((sum, s) => sum + s.events.length, 0);
451
+
452
+ return {
453
+ totalSessions: sessions.length,
454
+ activeSessions,
455
+ totalEvents,
456
+ oldestSession: sessions.length > 0 ? sessions[sessions.length - 1].startedAt : null,
457
+ newestSession: sessions.length > 0 ? sessions[0].startedAt : null,
458
+ };
459
+ }
460
+
461
+ /**
462
+ * Dispose of the session manager
463
+ */
464
+ dispose(): void {
465
+ this.stopAutoCleanup();
466
+ this.listeners.clear();
467
+ this.sessions.clear();
468
+ this.activeSessionId = null;
469
+ }
470
+ }