diff --git a/src/modules/aix/client/debugger/memstore-aix-client-debugger.ts b/src/modules/aix/client/debugger/memstore-aix-client-debugger.ts new file mode 100644 index 000000000..db483dcbb --- /dev/null +++ b/src/modules/aix/client/debugger/memstore-aix-client-debugger.ts @@ -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; + 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) => 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((_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, + }), + +})); diff --git a/src/modules/aix/client/debugger/reassembler-debug.ts b/src/modules/aix/client/debugger/reassembler-debug.ts new file mode 100644 index 000000000..fc6b2b6ad --- /dev/null +++ b/src/modules/aix/client/debugger/reassembler-debug.ts @@ -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, 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); +}