@stackmemoryai/stackmemory 0.3.15 → 0.3.16

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.
@@ -0,0 +1,644 @@
1
+ import * as fs from "fs/promises";
2
+ import * as path from "path";
3
+ import { logger } from "../monitoring/logger.js";
4
+ class EnhancedRehydrationManager {
5
+ frameManager;
6
+ compactionHandler;
7
+ snapshotThreshold = 10;
8
+ // Take snapshot every N significant events
9
+ eventCount = 0;
10
+ rehydrationStorage = /* @__PURE__ */ new Map();
11
+ constructor(frameManager, compactionHandler) {
12
+ this.frameManager = frameManager;
13
+ this.compactionHandler = compactionHandler;
14
+ this.setupCompactDetection();
15
+ this.initializeStackTraceStorage();
16
+ }
17
+ /**
18
+ * Initialize dedicated stack trace storage in database
19
+ */
20
+ initializeStackTraceStorage() {
21
+ try {
22
+ const db = this.frameManager.db;
23
+ db.exec(`
24
+ CREATE TABLE IF NOT EXISTS stack_traces (
25
+ trace_id TEXT PRIMARY KEY,
26
+ frame_id TEXT,
27
+ project_id TEXT NOT NULL,
28
+ error_message TEXT NOT NULL,
29
+ stack_frames TEXT NOT NULL,
30
+ file_path TEXT,
31
+ line_number INTEGER,
32
+ function_name TEXT,
33
+ context TEXT,
34
+ resolution_attempted TEXT,
35
+ resolution_status TEXT NOT NULL DEFAULT 'pending',
36
+ error_type TEXT,
37
+ error_severity TEXT DEFAULT 'medium',
38
+ created_at INTEGER DEFAULT (unixepoch()),
39
+ updated_at INTEGER DEFAULT (unixepoch()),
40
+ FOREIGN KEY(frame_id) REFERENCES frames(frame_id)
41
+ );
42
+
43
+ CREATE INDEX IF NOT EXISTS idx_stack_traces_frame ON stack_traces(frame_id);
44
+ CREATE INDEX IF NOT EXISTS idx_stack_traces_status ON stack_traces(resolution_status);
45
+ CREATE INDEX IF NOT EXISTS idx_stack_traces_type ON stack_traces(error_type);
46
+ CREATE INDEX IF NOT EXISTS idx_stack_traces_severity ON stack_traces(error_severity);
47
+ CREATE INDEX IF NOT EXISTS idx_stack_traces_created ON stack_traces(created_at);
48
+ `);
49
+ logger.info("Stack trace storage initialized");
50
+ } catch (error) {
51
+ logger.error("Failed to initialize stack trace storage:", error);
52
+ }
53
+ }
54
+ /**
55
+ * Set up automatic compact detection and recovery
56
+ */
57
+ setupCompactDetection() {
58
+ setInterval(() => this.checkForCompactionEvent(), 3e4);
59
+ }
60
+ /**
61
+ * Enhanced file content snapshot with context
62
+ */
63
+ async captureFileSnapshot(filePath, contextTags = []) {
64
+ try {
65
+ const stats = await fs.stat(filePath);
66
+ const content = await fs.readFile(filePath, "utf8");
67
+ const hash = this.simpleHash(content);
68
+ return {
69
+ path: filePath,
70
+ content,
71
+ size: stats.size,
72
+ lastModified: stats.mtimeMs,
73
+ hash,
74
+ contextTags
75
+ };
76
+ } catch (error) {
77
+ logger.warn(`Failed to capture snapshot for ${filePath}:`, error);
78
+ return null;
79
+ }
80
+ }
81
+ /**
82
+ * Capture conversation reasoning and decisions including stack traces
83
+ */
84
+ captureConversationContext(reasoning, decisions, nextSteps = [], userPrefs = {}, painPoints = [], stackTraces = [], errorPatterns = []) {
85
+ return {
86
+ timestamp: Date.now(),
87
+ reasoning,
88
+ decisions_made: decisions,
89
+ next_steps: nextSteps,
90
+ user_preferences: userPrefs,
91
+ pain_points: painPoints,
92
+ stack_traces: stackTraces,
93
+ error_patterns: errorPatterns
94
+ };
95
+ }
96
+ /**
97
+ * Capture stack trace from error with context and store in database
98
+ */
99
+ captureStackTrace(error, context, filePath, resolutionAttempts = [], frameId) {
100
+ const errorMessage = typeof error === "string" ? error : error.message;
101
+ const stackFrames = typeof error === "string" ? [] : error.stack?.split("\n") || [];
102
+ let extractedFilePath = filePath;
103
+ let lineNumber;
104
+ let functionName;
105
+ if (stackFrames.length > 0) {
106
+ const firstFrame = stackFrames.find((frame) => frame.includes("at "));
107
+ if (firstFrame) {
108
+ const match = firstFrame.match(/at (.+?) \((.+):(\d+):(\d+)\)/);
109
+ if (match) {
110
+ functionName = match[1];
111
+ extractedFilePath = extractedFilePath || match[2];
112
+ lineNumber = parseInt(match[3]);
113
+ }
114
+ }
115
+ }
116
+ const stackTrace = {
117
+ error_message: errorMessage,
118
+ stack_frames: stackFrames,
119
+ file_path: extractedFilePath,
120
+ line_number: lineNumber,
121
+ function_name: functionName,
122
+ timestamp: Date.now(),
123
+ context,
124
+ resolution_attempted: resolutionAttempts,
125
+ resolution_status: "pending"
126
+ };
127
+ this.storeStackTrace(stackTrace, frameId);
128
+ return stackTrace;
129
+ }
130
+ /**
131
+ * Store stack trace in database
132
+ */
133
+ storeStackTrace(stackTrace, frameId) {
134
+ try {
135
+ const db = this.frameManager.db;
136
+ const traceId = this.generateTraceId();
137
+ const currentFrameId = frameId || this.frameManager.getCurrentFrameId();
138
+ const errorType = this.extractErrorType(stackTrace.error_message);
139
+ const severity = this.determineErrorSeverity(stackTrace);
140
+ const stmt = db.prepare(`
141
+ INSERT INTO stack_traces (
142
+ trace_id, frame_id, project_id, error_message, stack_frames,
143
+ file_path, line_number, function_name, context, resolution_attempted,
144
+ resolution_status, error_type, error_severity
145
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
146
+ `);
147
+ stmt.run(
148
+ traceId,
149
+ currentFrameId,
150
+ this.frameManager.projectId,
151
+ stackTrace.error_message,
152
+ JSON.stringify(stackTrace.stack_frames),
153
+ stackTrace.file_path,
154
+ stackTrace.line_number,
155
+ stackTrace.function_name,
156
+ stackTrace.context,
157
+ JSON.stringify(stackTrace.resolution_attempted),
158
+ stackTrace.resolution_status,
159
+ errorType,
160
+ severity
161
+ );
162
+ logger.info(`Stored stack trace ${traceId} for frame ${currentFrameId}`);
163
+ return traceId;
164
+ } catch (error) {
165
+ logger.error("Failed to store stack trace:", error);
166
+ return "";
167
+ }
168
+ }
169
+ /**
170
+ * Retrieve stack traces from database
171
+ */
172
+ getStackTraces(frameId, limit = 50) {
173
+ try {
174
+ const db = this.frameManager.db;
175
+ const traces = [];
176
+ let query;
177
+ let params;
178
+ if (frameId) {
179
+ query = `
180
+ SELECT * FROM stack_traces
181
+ WHERE frame_id = ?
182
+ ORDER BY created_at DESC
183
+ LIMIT ?
184
+ `;
185
+ params = [frameId, limit];
186
+ } else {
187
+ query = `
188
+ SELECT * FROM stack_traces
189
+ WHERE project_id = ?
190
+ ORDER BY created_at DESC
191
+ LIMIT ?
192
+ `;
193
+ params = [this.frameManager.projectId, limit];
194
+ }
195
+ const rows = db.prepare(query).all(...params);
196
+ for (const row of rows) {
197
+ traces.push({
198
+ error_message: row.error_message,
199
+ stack_frames: JSON.parse(row.stack_frames || "[]"),
200
+ file_path: row.file_path,
201
+ line_number: row.line_number,
202
+ function_name: row.function_name,
203
+ timestamp: row.created_at * 1e3,
204
+ // Convert from unix to JS timestamp
205
+ context: row.context,
206
+ resolution_attempted: JSON.parse(row.resolution_attempted || "[]"),
207
+ resolution_status: row.resolution_status
208
+ });
209
+ }
210
+ return traces;
211
+ } catch (error) {
212
+ logger.error("Failed to retrieve stack traces:", error);
213
+ return [];
214
+ }
215
+ }
216
+ /**
217
+ * Update stack trace resolution status
218
+ */
219
+ updateStackTraceStatus(traceId, status, resolutionAttempts) {
220
+ try {
221
+ const db = this.frameManager.db;
222
+ const stmt = db.prepare(`
223
+ UPDATE stack_traces
224
+ SET resolution_status = ?, resolution_attempted = ?, updated_at = unixepoch()
225
+ WHERE trace_id = ?
226
+ `);
227
+ const result = stmt.run(
228
+ status,
229
+ resolutionAttempts ? JSON.stringify(resolutionAttempts) : void 0,
230
+ traceId
231
+ );
232
+ return result.changes > 0;
233
+ } catch (error) {
234
+ logger.error("Failed to update stack trace status:", error);
235
+ return false;
236
+ }
237
+ }
238
+ /**
239
+ * Helper methods for stack trace processing
240
+ */
241
+ generateTraceId() {
242
+ return `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
243
+ }
244
+ extractErrorType(errorMessage) {
245
+ const typeMatch = errorMessage.match(/^(\w+Error?):/);
246
+ return typeMatch ? typeMatch[1] : "Unknown";
247
+ }
248
+ determineErrorSeverity(stackTrace) {
249
+ const message = stackTrace.error_message.toLowerCase();
250
+ if (message.includes("critical") || message.includes("fatal") || message.includes("cannot read properties")) {
251
+ return "high";
252
+ } else if (message.includes("warning") || message.includes("deprecated")) {
253
+ return "low";
254
+ } else {
255
+ return "medium";
256
+ }
257
+ }
258
+ /**
259
+ * Auto-detect project structure and relationships
260
+ */
261
+ async analyzeProjectMapping(workingDir) {
262
+ const mapping = {
263
+ file_relationships: {},
264
+ workflow_sequences: [],
265
+ key_directories: [],
266
+ entry_points: [],
267
+ configuration_files: []
268
+ };
269
+ try {
270
+ const configPatterns = [
271
+ "package.json",
272
+ "tsconfig.json",
273
+ ".env",
274
+ "docker-compose.yml",
275
+ "*.config.js",
276
+ "*.config.ts",
277
+ "Dockerfile",
278
+ "README.md"
279
+ ];
280
+ const files = await this.getDirectoryFiles(workingDir);
281
+ for (const file of files) {
282
+ const ext = path.extname(file);
283
+ const basename = path.basename(file);
284
+ if (configPatterns.some(
285
+ (pattern) => pattern.includes("*") ? basename.includes(pattern.replace("*", "")) : basename === pattern
286
+ )) {
287
+ mapping.configuration_files.push(file);
288
+ }
289
+ if (basename === "index.js" || basename === "index.ts" || basename === "main.js") {
290
+ mapping.entry_points.push(file);
291
+ }
292
+ const filePrefix = basename.split(".")[0];
293
+ const relatedFiles = files.filter(
294
+ (f) => f !== file && path.basename(f).startsWith(filePrefix)
295
+ );
296
+ if (relatedFiles.length > 0) {
297
+ mapping.file_relationships[file] = relatedFiles;
298
+ }
299
+ }
300
+ const dirs = files.map((f) => path.dirname(f)).filter((v, i, a) => a.indexOf(v) === i);
301
+ mapping.key_directories = dirs.filter(
302
+ (dir) => ["src", "lib", "components", "pages", "api", "utils", "types"].some((key) => dir.includes(key))
303
+ );
304
+ } catch (error) {
305
+ logger.warn("Failed to analyze project mapping:", error);
306
+ }
307
+ return mapping;
308
+ }
309
+ /**
310
+ * Create comprehensive rehydration context before compaction
311
+ */
312
+ async createRehydrationCheckpoint() {
313
+ const sessionId = this.frameManager.getSessionId() || "unknown";
314
+ const checkpointId = `${sessionId}_${Date.now()}`;
315
+ try {
316
+ const workingDir = process.cwd();
317
+ const fileSnapshots = [];
318
+ const recentFiles = await this.getRecentlyModifiedFiles(workingDir);
319
+ for (const file of recentFiles.slice(0, 20)) {
320
+ const snapshot = await this.captureFileSnapshot(file, this.inferContextTags(file));
321
+ if (snapshot) {
322
+ fileSnapshots.push(snapshot);
323
+ }
324
+ }
325
+ const projectMapping = await this.analyzeProjectMapping(workingDir);
326
+ const conversationContext = this.extractConversationContext();
327
+ const rehydrationContext = {
328
+ session_id: sessionId,
329
+ compact_detected_at: Date.now(),
330
+ pre_compact_state: {
331
+ file_snapshots: fileSnapshots,
332
+ conversation_context: conversationContext,
333
+ project_mapping: projectMapping,
334
+ active_workflows: this.detectActiveWorkflows(fileSnapshots),
335
+ current_focus: this.inferCurrentFocus(fileSnapshots, conversationContext)
336
+ },
337
+ recovery_anchors: this.createRecoveryAnchors(fileSnapshots, conversationContext)
338
+ };
339
+ this.rehydrationStorage.set(checkpointId, rehydrationContext);
340
+ await this.persistRehydrationContext(checkpointId, rehydrationContext);
341
+ logger.info(`Created rehydration checkpoint ${checkpointId} with ${fileSnapshots.length} file snapshots`);
342
+ return checkpointId;
343
+ } catch (error) {
344
+ logger.error("Failed to create rehydration checkpoint:", error);
345
+ throw error;
346
+ }
347
+ }
348
+ /**
349
+ * Inject rich context after compaction detection
350
+ */
351
+ async rehydrateContext(checkpointId) {
352
+ try {
353
+ let context;
354
+ if (checkpointId) {
355
+ context = this.rehydrationStorage.get(checkpointId);
356
+ if (!context) {
357
+ context = await this.loadPersistedContext(checkpointId);
358
+ }
359
+ } else {
360
+ context = await this.findMostRecentContext();
361
+ }
362
+ if (!context) {
363
+ logger.warn("No rehydration context available");
364
+ return false;
365
+ }
366
+ await this.injectRichContext(context);
367
+ return true;
368
+ } catch (error) {
369
+ logger.error("Failed to rehydrate context:", error);
370
+ return false;
371
+ }
372
+ }
373
+ /**
374
+ * Inject rich context into current session
375
+ */
376
+ async injectRichContext(context) {
377
+ const frameId = this.frameManager.getCurrentFrameId();
378
+ if (!frameId) {
379
+ logger.warn("No active frame for context injection");
380
+ return;
381
+ }
382
+ for (const snapshot of context.pre_compact_state.file_snapshots.slice(0, 5)) {
383
+ this.frameManager.addAnchor(
384
+ "FACT",
385
+ `File: ${snapshot.path} (${snapshot.contextTags.join(", ")})
386
+ Last modified: ${new Date(snapshot.lastModified).toISOString()}
387
+ Size: ${snapshot.size} bytes
388
+ Content preview: ${this.getContentPreview(snapshot.content)}`,
389
+ 9,
390
+ {
391
+ rehydration: true,
392
+ file_path: snapshot.path,
393
+ context_tags: snapshot.contextTags
394
+ },
395
+ frameId
396
+ );
397
+ }
398
+ const conv = context.pre_compact_state.conversation_context;
399
+ if (conv.decisions_made.length > 0) {
400
+ this.frameManager.addAnchor(
401
+ "DECISION",
402
+ `Previous decisions: ${conv.decisions_made.join("; ")}`,
403
+ 8,
404
+ { rehydration: true },
405
+ frameId
406
+ );
407
+ }
408
+ if (conv.next_steps.length > 0) {
409
+ this.frameManager.addAnchor(
410
+ "FACT",
411
+ `Next steps identified: ${conv.next_steps.join("; ")}`,
412
+ 7,
413
+ { rehydration: true },
414
+ frameId
415
+ );
416
+ }
417
+ if (conv.stack_traces.length > 0) {
418
+ for (const trace of conv.stack_traces.slice(0, 3)) {
419
+ this.frameManager.addAnchor(
420
+ "ERROR",
421
+ `Error context: ${trace.error_message}
422
+ Context: ${trace.context}
423
+ File: ${trace.file_path || "unknown"}${trace.line_number ? `:${trace.line_number}` : ""}
424
+ Function: ${trace.function_name || "unknown"}
425
+ Status: ${trace.resolution_status}
426
+ Stack preview: ${trace.stack_frames.slice(0, 3).join("\n")}`,
427
+ 9,
428
+ {
429
+ rehydration: true,
430
+ error_type: trace.error_message.split(":")[0],
431
+ resolution_status: trace.resolution_status,
432
+ file_path: trace.file_path
433
+ },
434
+ frameId
435
+ );
436
+ }
437
+ }
438
+ if (conv.error_patterns.length > 0) {
439
+ this.frameManager.addAnchor(
440
+ "PATTERN",
441
+ `Recurring error patterns detected: ${conv.error_patterns.join(", ")}`,
442
+ 7,
443
+ { rehydration: true },
444
+ frameId
445
+ );
446
+ }
447
+ const mapping = context.pre_compact_state.project_mapping;
448
+ if (mapping.entry_points.length > 0) {
449
+ this.frameManager.addAnchor(
450
+ "FACT",
451
+ `Project entry points: ${mapping.entry_points.join(", ")}`,
452
+ 6,
453
+ { rehydration: true },
454
+ frameId
455
+ );
456
+ }
457
+ if (context.pre_compact_state.current_focus) {
458
+ this.frameManager.addAnchor(
459
+ "CONSTRAINT",
460
+ `Previous focus: ${context.pre_compact_state.current_focus}`,
461
+ 8,
462
+ { rehydration: true },
463
+ frameId
464
+ );
465
+ }
466
+ logger.info("Rich context injected successfully");
467
+ }
468
+ // Helper methods
469
+ async getDirectoryFiles(dir) {
470
+ return [];
471
+ }
472
+ async getRecentlyModifiedFiles(dir) {
473
+ return [];
474
+ }
475
+ inferContextTags(filePath) {
476
+ const tags = [];
477
+ const content = filePath.toLowerCase();
478
+ if (content.includes("pipeline") || content.includes("migrate")) tags.push("migration");
479
+ if (content.includes("hubspot")) tags.push("hubspot");
480
+ if (content.includes("pipedream")) tags.push("pipedream");
481
+ if (content.includes("test")) tags.push("test");
482
+ if (content.includes("config")) tags.push("configuration");
483
+ return tags;
484
+ }
485
+ extractConversationContext() {
486
+ const recentErrors = this.extractRecentStackTraces();
487
+ const errorPatterns = this.detectErrorPatterns(recentErrors);
488
+ return {
489
+ timestamp: Date.now(),
490
+ reasoning: [],
491
+ decisions_made: [],
492
+ next_steps: [],
493
+ user_preferences: {},
494
+ pain_points: [],
495
+ stack_traces: recentErrors,
496
+ error_patterns: errorPatterns
497
+ };
498
+ }
499
+ /**
500
+ * Extract recent stack traces from database and frame events
501
+ */
502
+ extractRecentStackTraces() {
503
+ try {
504
+ const dbTraces = this.getStackTraces(void 0, 10);
505
+ const eventTraces = this.extractStackTracesFromFrameEvents();
506
+ const allTraces = [...dbTraces, ...eventTraces];
507
+ const uniqueTraces = allTraces.filter(
508
+ (trace, index, array) => array.findIndex(
509
+ (t) => t.error_message === trace.error_message && t.file_path === trace.file_path
510
+ ) === index
511
+ );
512
+ return uniqueTraces.sort((a, b) => b.timestamp - a.timestamp).slice(0, 5);
513
+ } catch (error) {
514
+ logger.warn("Failed to extract stack traces:", error);
515
+ return [];
516
+ }
517
+ }
518
+ /**
519
+ * Extract stack traces from frame events (fallback method)
520
+ */
521
+ extractStackTracesFromFrameEvents() {
522
+ const traces = [];
523
+ try {
524
+ const frames = this.frameManager.getActiveFramePath();
525
+ for (const frame of frames.slice(-3)) {
526
+ const frameData = this.frameManager.getFrame(frame.frame_id);
527
+ if (frameData?.events) {
528
+ for (const event of frameData.events) {
529
+ if (event.type === "error" || event.type === "exception") {
530
+ const trace = this.parseStackTraceFromEvent(event);
531
+ if (trace) {
532
+ traces.push(trace);
533
+ }
534
+ }
535
+ }
536
+ }
537
+ }
538
+ } catch (error) {
539
+ logger.warn("Failed to extract frame event traces:", error);
540
+ }
541
+ return traces;
542
+ }
543
+ /**
544
+ * Parse stack trace from frame event
545
+ */
546
+ parseStackTraceFromEvent(event) {
547
+ try {
548
+ const data = typeof event.data === "string" ? JSON.parse(event.data) : event.data;
549
+ return {
550
+ error_message: data.error || data.message || "Unknown error",
551
+ stack_frames: data.stack ? data.stack.split("\n") : [],
552
+ file_path: data.file || data.fileName,
553
+ line_number: data.line || data.lineNumber,
554
+ function_name: data.function || data.functionName,
555
+ timestamp: event.timestamp || Date.now(),
556
+ context: data.context || "Error occurred during frame processing",
557
+ resolution_attempted: data.resolutionAttempts || [],
558
+ resolution_status: data.resolved ? "resolved" : "pending"
559
+ };
560
+ } catch (error) {
561
+ return null;
562
+ }
563
+ }
564
+ /**
565
+ * Detect recurring error patterns
566
+ */
567
+ detectErrorPatterns(traces) {
568
+ const patterns = /* @__PURE__ */ new Map();
569
+ for (const trace of traces) {
570
+ const errorType = trace.error_message.split(":")[0].trim();
571
+ patterns.set(errorType, (patterns.get(errorType) || 0) + 1);
572
+ }
573
+ return Array.from(patterns.entries()).filter(([, count]) => count > 1).map(([pattern]) => pattern);
574
+ }
575
+ detectActiveWorkflows(snapshots) {
576
+ const workflows = [];
577
+ for (const snapshot of snapshots) {
578
+ if (snapshot.contextTags.includes("migration")) {
579
+ workflows.push("data_migration");
580
+ }
581
+ if (snapshot.path.includes("test")) {
582
+ workflows.push("testing");
583
+ }
584
+ }
585
+ return [...new Set(workflows)];
586
+ }
587
+ inferCurrentFocus(snapshots, context) {
588
+ if (snapshots.some((s) => s.contextTags.includes("migration"))) {
589
+ return "Data migration and transformation";
590
+ }
591
+ if (snapshots.some((s) => s.path.includes("test"))) {
592
+ return "Testing and validation";
593
+ }
594
+ return "Development";
595
+ }
596
+ createRecoveryAnchors(snapshots, context) {
597
+ const anchors = [];
598
+ for (const snapshot of snapshots.slice(0, 3)) {
599
+ anchors.push(`File context: ${snapshot.path} with ${snapshot.contextTags.join(", ")}`);
600
+ }
601
+ return anchors;
602
+ }
603
+ async persistRehydrationContext(id, context) {
604
+ const contextDir = path.join(process.cwd(), ".stackmemory", "rehydration");
605
+ await fs.mkdir(contextDir, { recursive: true });
606
+ await fs.writeFile(
607
+ path.join(contextDir, `${id}.json`),
608
+ JSON.stringify(context, null, 2)
609
+ );
610
+ }
611
+ async loadPersistedContext(id) {
612
+ try {
613
+ const contextPath = path.join(process.cwd(), ".stackmemory", "rehydration", `${id}.json`);
614
+ const content = await fs.readFile(contextPath, "utf8");
615
+ return JSON.parse(content);
616
+ } catch {
617
+ return void 0;
618
+ }
619
+ }
620
+ async findMostRecentContext() {
621
+ return void 0;
622
+ }
623
+ checkForCompactionEvent() {
624
+ if (this.compactionHandler.detectCompactionEvent("")) {
625
+ this.rehydrateContext();
626
+ }
627
+ }
628
+ simpleHash(content) {
629
+ let hash = 0;
630
+ for (let i = 0; i < content.length; i++) {
631
+ const char = content.charCodeAt(i);
632
+ hash = (hash << 5) - hash + char;
633
+ hash = hash & hash;
634
+ }
635
+ return hash.toString(16);
636
+ }
637
+ getContentPreview(content, maxLength = 200) {
638
+ return content.length > maxLength ? content.substring(0, maxLength) + "..." : content;
639
+ }
640
+ }
641
+ export {
642
+ EnhancedRehydrationManager
643
+ };
644
+ //# sourceMappingURL=enhanced-rehydration.js.map