AIX: Debugger: reactive store

This commit is contained in:
Enrico Ros
2025-02-27 20:00:10 -08:00
parent 5ad11a8b75
commit 0aa70f2b80
2 changed files with 180 additions and 0 deletions
@@ -0,0 +1,151 @@
import { create } from 'zustand';
//
// NOTE: this file is supposed to be lightweight and to be kept in memory. Particles are used by reference and
// not cloned or modified. Visualization is a Reactive stringification of the referred objects pretty much.
//
const DEFAULT_FRAMES_COUNT = 10;
/// Types ///
export namespace AixClientDebugger {
export interface Frame {
// frame information
id: AixFrameId;
timestamp: number;
// calling purpose
context: Context;
// upstream request
url: string;
headers: string;
body: string;
isComplete: boolean;
// NOTE: in the future we could debug the raw SSE streams.. not now
// aix response particles
particles: Particle[];
}
export interface Particle {
timestamp: number;
content: Record<string, any>;
isAborted?: boolean;
}
export interface Context {
contextName: string;
contextRef: string;
}
}
export type AixFrameId = number;
let _lastInMemoryFrameId = 1;
function _createAixClientDebuggerFrame(frameContext: AixClientDebugger.Context): AixClientDebugger.Frame {
return {
id: ++_lastInMemoryFrameId,
timestamp: Date.now(),
url: '',
headers: '',
body: '',
particles: [],
isComplete: false,
context: {
contextName: frameContext.contextName || '_contextName',
contextRef: frameContext.contextRef || '_contextRef',
},
};
}
/// Store ///
interface AixClientDebuggerState {
frames: AixClientDebugger.Frame[];
activeFrameId: AixFrameId | null;
maxFrames: number;
}
interface AixClientDebuggerActions {
// frames
createFrame: (initialContext: AixClientDebugger.Context) => AixFrameId;
setRequest: (fId: AixFrameId, updates: Pick<AixClientDebugger.Frame, 'url' | 'headers' | 'body'>) => void;
addParticle: (fId: AixFrameId, particle: AixClientDebugger.Particle, isAborted?: boolean) => void;
completeFrame: (fId: AixFrameId) => void;
// client view
setActiveFrame: (activeFrameId: AixFrameId | null) => void;
setMaxFrames: (count: number) => void;
clearHistory: () => void;
}
type AixClientDebuggerStore = AixClientDebuggerState & AixClientDebuggerActions;
export const useAixClientDebuggerStore = create<AixClientDebuggerStore>((_set) => ({
// initial state
frames: [],
activeFrameId: null,
maxFrames: DEFAULT_FRAMES_COUNT,
// Frame actions
createFrame: (initialContext) => {
const newFrame = _createAixClientDebuggerFrame(initialContext);
_set((state) => ({
frames: [newFrame, ...state.frames].slice(0, state.maxFrames),
activeFrameId: newFrame.id,
}));
return newFrame.id;
},
setRequest: (fId, requestData) =>
_set(state => ({
frames: state.frames.map(frame => frame.id !== fId ? frame : {
...frame,
...requestData,
}),
})),
addParticle: (fId, particle, isAborted = false) =>
_set(state => ({
frames: state.frames.map(frame => frame.id !== fId ? frame : {
...frame,
particles: [...frame.particles, particle],
}),
})),
completeFrame: (fId) =>
_set(state => ({
frames: state.frames.map(frame => frame.id !== fId ? frame : {
...frame,
isComplete: true,
}),
})),
// Client View actions
setActiveFrame: (activeFrameId) => _set({
activeFrameId,
}),
setMaxFrames: (count) => _set(state => ({
maxFrames: count,
frames: state.frames.slice(0, count),
})),
clearHistory: () => _set({
frames: [],
activeFrameId: null,
}),
}));
@@ -0,0 +1,29 @@
import { AixClientDebugger, AixFrameId, useAixClientDebuggerStore } from './memstore-aix-client-debugger';
export function aixClientDebugger_init(contextInfo: AixClientDebugger.Context): AixFrameId {
return useAixClientDebuggerStore.getState().createFrame(contextInfo);
}
export function aixClientDebugger_setRequest(
frameId: AixFrameId,
request: { url: string, headers: string, body: string },
): void {
useAixClientDebuggerStore.getState().setRequest(frameId, {
url: request.url,
headers: request.headers,
body: request.body,
});
}
export function aixClientDebugger_recordParticle(frameId: AixFrameId, particleContent: Record<string, any>, isAborted = false): void {
useAixClientDebuggerStore.getState().addParticle(frameId, {
timestamp: Date.now(),
content: particleContent,
...(isAborted && { isAborted }),
});
}
export function aixClientDebugger_completeFrame(frameId: number): void {
useAixClientDebuggerStore.getState().completeFrame(frameId);
}