data fixes; indicator=>workspace sync

This commit is contained in:
2026-03-31 20:29:12 -04:00
parent 998f69fa1a
commit cd28e18e52
45 changed files with 1324 additions and 1239 deletions

View File

@@ -110,11 +110,33 @@ class SyncEntry {
return this.history.filter((entry) => entry.seq > sinceSeq);
}
/**
* Ensure intermediate objects exist for all patch add/replace operations.
* Called before applying patches to gracefully handle paths into previously-absent sub-objects.
*/
private ensureIntermediatePaths(doc: any, patch: JsonPatchOp[]): void {
for (const op of patch) {
if (op.op !== 'add' && op.op !== 'replace') continue;
const parts = op.path.split('/').filter(Boolean);
if (parts.length <= 1) continue;
let current = doc;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (current[part] === undefined || current[part] === null) {
current[part] = {};
}
current = current[part];
}
}
}
/**
* Apply a patch to state (used when applying local changes).
*/
applyPatch(patch: JsonPatchOp[]): void {
const result = applyPatch(deepClone(this.state), patch, false, false);
const doc = deepClone(this.state) as any;
this.ensureIntermediatePaths(doc, patch);
const result = applyPatch(doc, patch, false, false);
this.state = result.newDocument;
}
@@ -130,7 +152,8 @@ class SyncEntry {
try {
if (clientBaseSeq === this.seq) {
// No conflict - apply directly
const currentState = deepClone(this.state);
const currentState = deepClone(this.state) as any;
this.ensureIntermediatePaths(currentState, patch);
const result = applyPatch(currentState, patch, false, false);
this.state = result.newDocument;
this.commitPatch(patch);
@@ -160,14 +183,15 @@ class SyncEntry {
const frontendPaths = new Set(patch.map((op) => op.path));
// Apply frontend patch first
const currentState = deepClone(this.state);
const currentState = deepClone(this.state) as any;
this.ensureIntermediatePaths(currentState, patch);
let newState: unknown;
try {
const result = applyPatch(currentState, patch, false, false);
newState = result.newDocument;
} catch (e) {
logger?.warn(
{ store: this.storeName, error: e },
{ store: this.storeName, err: e },
'Failed to apply client patch during conflict resolution'
);
return { needsSnapshot: true, resolvedState: this.state };
@@ -182,7 +206,7 @@ class SyncEntry {
newState = result.newDocument;
} catch (e) {
logger?.debug(
{ store: this.storeName, error: e },
{ store: this.storeName, err: e },
'Skipping backend patch during conflict resolution'
);
}
@@ -209,7 +233,7 @@ class SyncEntry {
return { needsSnapshot: true, resolvedState: this.state };
} catch (e) {
logger?.error(
{ store: this.storeName, error: e },
{ store: this.storeName, err: e },
'Unexpected error applying client patch'
);
return { needsSnapshot: true, resolvedState: this.state };

View File

@@ -237,7 +237,7 @@ export interface ChartState {
symbol: string;
start_time: number | null; // unix timestamp
end_time: number | null; // unix timestamp
period: string; // OHLC duration (e.g., '15' for 15 minutes)
period: number; // OHLC period in seconds (e.g., 900 for 15 minutes)
selected_shapes: string[]; // list of shape ID's
}