client-py connected

This commit is contained in:
2026-03-27 16:33:40 -04:00
parent c76887ab92
commit c3a8fae132
55 changed files with 1598 additions and 426 deletions

View File

@@ -66,7 +66,8 @@ export type {
PathTriggerHandler,
PathTriggerContext,
ChartState,
ChartStore,
ShapesStore,
IndicatorsStore,
ChannelState,
ChannelInfo,
WorkspaceStores,

View File

@@ -404,4 +404,28 @@ export class SyncRegistry {
}
return states;
}
/**
* Get patches since a given sequence number for a store.
* Returns empty array if no patches available since that sequence.
*/
getPatchesSince(storeName: string, sinceSeq: number): JsonPatchOp[] | null {
const entry = this.entries.get(storeName);
if (!entry) {
return null;
}
const catchupPatches = entry.getCatchupPatches(sinceSeq);
if (catchupPatches === null) {
return null; // History not available
}
// Flatten all patches into a single array
const allPatches: JsonPatchOp[] = [];
for (const { patch } of catchupPatches) {
allPatches.push(...patch);
}
return allPatches;
}
}

View File

@@ -84,17 +84,19 @@ export const DEFAULT_STORES: StoreConfig[] = [
symbol: 'BINANCE:BTC/USDT',
start_time: null,
end_time: null,
interval: '15',
period: '15',
selected_shapes: [],
}),
},
{
name: 'chartStore',
name: 'shapes',
persistent: true,
initialState: () => ({
drawings: {},
templates: {},
}),
initialState: () => ({}),
},
{
name: 'indicators',
persistent: true,
initialState: () => ({}),
},
{
name: 'channelState',
@@ -198,22 +200,70 @@ export interface PathTrigger {
*/
export interface ChartState {
symbol: string;
start_time: number | null;
end_time: number | null;
interval: string;
selected_shapes: string[];
start_time: number | null; // unix timestamp
end_time: number | null; // unix timestamp
period: string; // OHLC duration (e.g., '15' for 15 minutes)
selected_shapes: string[]; // list of shape ID's
}
/**
* Chart store - persistent, stores drawings and templates.
* Control point for shapes (drawings/annotations).
*/
export interface ChartStore {
drawings: Record<string, unknown>;
templates: Record<string, unknown>;
export interface ControlPoint {
time: number; // unix timestamp
price: number;
channel?: string;
}
/**
* Shape (drawing/annotation) on TradingView chart.
*/
export interface Shape {
id: string;
type: string;
points: ControlPoint[];
color?: string;
line_width?: number;
line_style?: string;
properties?: Record<string, any>;
symbol?: string;
created_at?: number;
modified_at?: number;
original_id?: string;
}
/**
* Shapes store - persistent, stores TradingView drawings and annotations.
*/
export type ShapesStore = Record<string, Shape>;
/**
* Indicator instance on TradingView chart.
*/
export interface IndicatorInstance {
id: string;
talib_name: string;
instance_name: string;
parameters: Record<string, any>;
tv_study_id?: string;
tv_indicator_name?: string;
tv_inputs?: Record<string, any>;
visible: boolean;
pane: string;
symbol?: string;
created_at?: number;
modified_at?: number;
original_id?: string;
}
/**
* Indicators store - persistent, stores TradingView studies/indicators.
*/
export type IndicatorsStore = Record<string, IndicatorInstance>;
/**
* Channel state - transient, tracks connected channels.
* NOTE: This store is gateway-only and should NOT be synced to web clients.
*/
export interface ChannelState {
connected: Record<string, ChannelInfo>;
@@ -233,7 +283,8 @@ export interface ChannelInfo {
*/
export interface WorkspaceStores {
chartState: ChartState;
chartStore: ChartStore;
shapes: ShapesStore;
indicators: IndicatorsStore;
channelState: ChannelState;
[key: string]: unknown;
}

View File

@@ -271,6 +271,53 @@ export class WorkspaceManager {
return this.registry.getStoreNames();
}
/**
* Serialize entire workspace state as JSON.
*/
serializeState(): string {
const state: Record<string, unknown> = {};
for (const storeConfig of this.stores) {
const storeState = this.registry.getState(storeConfig.name);
if (storeState !== undefined) {
state[storeConfig.name] = storeState;
}
}
return JSON.stringify(state, null, 2);
}
/**
* Get the highest sequence number across all stores.
*/
getCurrentSeq(): number {
let maxSeq = 0;
for (const storeName of this.registry.getStoreNames()) {
const seq = this.registry.getSeq(storeName);
if (seq > maxSeq) {
maxSeq = seq;
}
}
return maxSeq;
}
/**
* Get all patches since a given sequence number across all stores.
* Returns patches grouped by store name.
*/
getChangesSince(sinceSeq: number): Record<string, JsonPatchOp[]> {
const changes: Record<string, JsonPatchOp[]> = {};
for (const storeConfig of this.stores) {
const patches = this.registry.getPatchesSince(storeConfig.name, sinceSeq);
if (patches && patches.length > 0) {
changes[storeConfig.name] = patches;
}
}
return changes;
}
// ===========================================================================
// Path Triggers
// ===========================================================================