data fixes; indicator=>workspace sync
This commit is contained in:
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user