Integrate Canvas V2 subgraphs, editable edges and AI runs
This commit is contained in:
+16
-1
@@ -1,3 +1,4 @@
|
||||
import { CANVAS_ACCEPT } from "./canvas-v2.ts";
|
||||
import { decodeCanvas, type CanvasDocument } from "./canvas.ts";
|
||||
|
||||
export type CanvasSnapshot = { version: number; document: CanvasDocument };
|
||||
@@ -23,6 +24,7 @@ export class CanvasSync {
|
||||
version: number;
|
||||
saved: string;
|
||||
error = "";
|
||||
document: CanvasDocument;
|
||||
private pending: CanvasDocument | null = null;
|
||||
private running: Promise<void> | null = null;
|
||||
private request: CanvasRequest;
|
||||
@@ -31,6 +33,7 @@ export class CanvasSync {
|
||||
constructor(request: CanvasRequest, path: string, initial: CanvasSnapshot, notify: (state: SyncState) => void) {
|
||||
this.request = request; this.path = path; this.version = initial.version;
|
||||
this.saved = canvasFingerprint(initial.document); this.notify = notify;
|
||||
this.document = initial.document;
|
||||
}
|
||||
enqueue(document: CanvasDocument): Promise<void> {
|
||||
this.pending = document;
|
||||
@@ -39,6 +42,12 @@ export class CanvasSync {
|
||||
return this.running;
|
||||
}
|
||||
retry() { this.error = ""; return this.pending ? this.enqueue(this.pending) : Promise.resolve(); }
|
||||
async settled() { await this.running; }
|
||||
adopt(snapshot: CanvasSnapshot) {
|
||||
if (this.running) throw new Error("canvas_busy");
|
||||
this.pending = null; this.error = ""; this.version = snapshot.version; this.document = snapshot.document;
|
||||
this.saved = canvasFingerprint(snapshot.document); this.notify({ status: "saved", version: this.version });
|
||||
}
|
||||
private async drain() {
|
||||
while (this.pending) {
|
||||
const document = this.pending; this.pending = null;
|
||||
@@ -48,9 +57,10 @@ export class CanvasSync {
|
||||
if (fingerprint === this.saved) { this.notify({ status: "saved", version: this.version }); continue; }
|
||||
const body = JSON.stringify({ document: canvasPayload(document) });
|
||||
if (new TextEncoder().encode(body).length > 5 * 1024 * 1024) throw new Error("canvas_too_large");
|
||||
const result = await this.request<{ version: number }>(this.path, { method: "PATCH", headers: { "If-Match": `"${this.version}"` }, body });
|
||||
const result = await this.request<{ version: number }>(this.path, { method: "PATCH", headers: { Accept: CANVAS_ACCEPT, "If-Match": `"${this.version}"` }, body });
|
||||
if (!Number.isSafeInteger(result?.version) || result.version !== this.version + 1) throw new Error("invalid_canvas_snapshot");
|
||||
this.version = result.version; this.saved = fingerprint;
|
||||
this.document = document;
|
||||
this.notify({ status: "saved", version: this.version });
|
||||
} catch (error) {
|
||||
this.pending ||= document;
|
||||
@@ -75,3 +85,8 @@ export function readLocalRecovery(storage: Pick<Storage, "getItem">, key: string
|
||||
const doc = decodeCanvas(legacy);
|
||||
return !snapshot || canvasFingerprint(doc) !== canvasFingerprint(snapshot.document) ? doc : null;
|
||||
}
|
||||
export function canRestoreCanvasDraft(draft: CanvasDocument, remote: CanvasDocument) {
|
||||
if (draft.schema_version < remote.schema_version) return false;
|
||||
const relations = (document: CanvasDocument) => document.nodes.filter((node) => node.data.subgraph).map((node) => ({ id: node.id, subgraph: node.data.subgraph, ports: node.data.ports || [] })).sort((a, b) => a.id.localeCompare(b.id));
|
||||
return JSON.stringify(relations(draft)) === JSON.stringify(relations(remote));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user