@stackmemoryai/stackmemory
Version:
Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.
120 lines (119 loc) • 4.01 kB
JavaScript
import { fileURLToPath as __fileURLToPath } from 'url';
import { dirname as __pathDirname } from 'path';
const __filename = __fileURLToPath(import.meta.url);
const __dirname = __pathDirname(__filename);
import { logger } from "../monitoring/logger.js";
class FrameLifecycleHooksRegistry {
closeHooks = [];
createHooks = [];
/**
* Register a hook to be called when a frame is closed
* @param name - Unique name for the hook (for logging/debugging)
* @param handler - Async function to call when frame closes
* @param priority - Higher priority hooks run first (default: 0)
*/
onFrameClosed(name, handler, priority = 0) {
const hook = { name, handler, priority };
this.closeHooks.push(hook);
this.closeHooks.sort((a, b) => b.priority - a.priority);
logger.debug("Registered frame close hook", { name, priority });
return () => {
this.closeHooks = this.closeHooks.filter((h) => h !== hook);
logger.debug("Unregistered frame close hook", { name });
};
}
/**
* Register a hook to be called when a frame is created
* @param name - Unique name for the hook (for logging/debugging)
* @param handler - Async function to call when frame is created
* @param priority - Higher priority hooks run first (default: 0)
*/
onFrameCreated(name, handler, priority = 0) {
const hook = { name, handler, priority };
this.createHooks.push(hook);
this.createHooks.sort((a, b) => b.priority - a.priority);
logger.debug("Registered frame create hook", { name, priority });
return () => {
this.createHooks = this.createHooks.filter((h) => h !== hook);
logger.debug("Unregistered frame create hook", { name });
};
}
/**
* Trigger all close hooks (called by FrameManager)
* Hooks are fire-and-forget - errors don't affect frame closure
*/
async triggerClose(data) {
if (this.closeHooks.length === 0) return;
const results = await Promise.allSettled(
this.closeHooks.map(async (hook) => {
try {
await hook.handler(data);
} catch (error) {
logger.warn(`Frame close hook "${hook.name}" failed`, {
error: error instanceof Error ? error.message : String(error),
frameId: data.frame.frame_id,
frameName: data.frame.name
});
}
})
);
const failed = results.filter((r) => r.status === "rejected").length;
if (failed > 0) {
logger.debug("Some frame close hooks failed", {
total: this.closeHooks.length,
failed,
frameId: data.frame.frame_id
});
}
}
/**
* Trigger all create hooks (called by FrameManager)
* Hooks are fire-and-forget - errors don't affect frame creation
*/
async triggerCreate(frame) {
if (this.createHooks.length === 0) return;
const results = await Promise.allSettled(
this.createHooks.map(async (hook) => {
try {
await hook.handler(frame);
} catch (error) {
logger.warn(`Frame create hook "${hook.name}" failed`, {
error: error instanceof Error ? error.message : String(error),
frameId: frame.frame_id,
frameName: frame.name
});
}
})
);
const failed = results.filter((r) => r.status === "rejected").length;
if (failed > 0) {
logger.debug("Some frame create hooks failed", {
total: this.createHooks.length,
failed,
frameId: frame.frame_id
});
}
}
/**
* Get count of registered hooks (useful for testing)
*/
getHookCounts() {
return {
close: this.closeHooks.length,
create: this.createHooks.length
};
}
/**
* Clear all hooks (useful for testing)
*/
clearAll() {
this.closeHooks = [];
this.createHooks = [];
logger.debug("Cleared all frame lifecycle hooks");
}
}
const frameLifecycleHooks = new FrameLifecycleHooksRegistry();
export {
frameLifecycleHooks
};
//# sourceMappingURL=frame-lifecycle-hooks.js.map