Certify loaded canvas scopes and publish completeness with runtime queries

This commit is contained in:
2026-11-09 03:42:08 +08:00
parent ef08222915
commit cabf2ec667
26 changed files with 209 additions and 48 deletions
+9 -6
View File
@@ -2,7 +2,7 @@ import type { Applied, Availability, Capability, Change, Command, Commit, CoreSn
import { applyChanges, createDocument, invert, targetElement, validateDocument } from './document.ts';
import { clone, equal, fault, freeze, readMap, reject, validJson } from './values.ts';
import {createDocumentQueries,advanceDocumentQueries,type QueryBudget} from './queries.ts';
import {createDocumentQueries,advanceDocumentQueries,withQueryCoverage,type QueryCoverage,type QueryBudget} from './queries.ts';
import { canvasTransform, convertGeometry } from './coordinates.ts';
import { unitScale, withUnitScale } from './units.ts';
import { removalChanges, repairConnectionOwners } from './structure.ts';
@@ -13,6 +13,7 @@ import type { CreationSizeEffect } from './types.ts';
export type EditorOptions<Runtime = undefined> = {
registry: Registry; document: Document; queryBudget?:QueryBudget; createId?: () => Id; historyLimit?: number;
queryCoverage?: (snapshot:Snapshot<Runtime>)=>QueryCoverage;
policy?: (command: Command, document: Document) => Availability;
traversalPolicy?: (entry: HistoryEntry, direction: 'undo' | 'redo') => Availability;
onListenerError?: (error: unknown) => void;
@@ -36,7 +37,8 @@ export function createEditor<Runtime = undefined>(options: EditorOptions<Runtime
const document = createDocument(source.projectId, [...source.canvases.values()], [...source.elements.values()], source.tombstones);
validateDocument(document, options.registry);
let state: Snapshot<Runtime> = Object.freeze({ revision: options.recovery?.revision ?? 0, rootRevision: options.recovery?.rootRevision ?? 0, document, history: options.recovery?.history ?? Object.freeze({ undo: Object.freeze([]), redo: Object.freeze([]), entries: readMap(new Map<Id, HistoryEntry>()) }), operations: options.recovery?.operations ?? readMap(new Map<Id, OperationIntent>()), runtime: options.runtime as Runtime });
let queries=createDocumentQueries(document,options.registry,state.revision,options.queryBudget);
const bindCoverage=(snapshot:Snapshot<Runtime>,queries:ReturnType<typeof createDocumentQueries>)=>options.queryCoverage?withQueryCoverage(queries,options.queryCoverage(snapshot)):queries;
let queries=bindCoverage(state,createDocumentQueries(document,options.registry,state.revision,options.queryBudget));
const queriesByDocument=new WeakMap([[document,queries]]);
let entered = false, disposed = false, sequence = 0;
for (const intent of state.operations.values()) sequence = Math.max(sequence, intent.sequence);
@@ -191,7 +193,7 @@ export function createEditor<Runtime = undefined>(options: EditorOptions<Runtime
const commit = freeze({ revision: next.revision, origin, changes, operationIds });
const runtime = options.reduceRuntime ? options.reduceRuntime(state.runtime, next, commit) : state.runtime;
if (isThenable(runtime)) throw fault('async_transaction');
const nextState=prepareState(Object.freeze({ ...next, rootRevision: state.rootRevision + 1, runtime })),nextQueries=advanceDocumentQueries(queries,document,options.registry,next.revision,changes);
const nextState=prepareState(Object.freeze({ ...next, rootRevision: state.rootRevision + 1, runtime })),nextQueries=bindCoverage(nextState,advanceDocumentQueries(queries,document,options.registry,next.revision,changes));
state=nextState;queries=nextQueries;queriesByDocument.set(document,queries);
if (outbound) sequence = nextSequence;
emit(commit);
@@ -297,7 +299,8 @@ export function createEditor<Runtime = undefined>(options: EditorOptions<Runtime
const runtime = reduce(state.runtime);
if (isThenable(runtime)) throw fault('async_transaction');
if (runtime !== state.runtime) {
state = prepareState(Object.freeze({ ...state, rootRevision: state.rootRevision + 1, runtime }));
const nextState=prepareState(Object.freeze({ ...state, rootRevision: state.rootRevision + 1, runtime })),nextQueries=bindCoverage(nextState,queries);
state=nextState;queries=nextQueries;queriesByDocument.set(state.document,queries);
emit(freeze({ revision: state.revision, origin: 'ack', changes: [], operationIds: [] }));
}
return Object.freeze({ status: 'applied', revision: state.revision, operationIds: Object.freeze([]) });
@@ -322,8 +325,8 @@ export function createEditor<Runtime = undefined>(options: EditorOptions<Runtime
for(const [id,entry]of history.entries)if(entry.id!==id||entry.operationIds.some(operationId=>!operations.has(operationId)))throw fault('invalid_history');
}
const changed = !documentsEqual(next, state.document);
const nextState=prepareState(Object.freeze({ ...state, ...(replacement?{history:replacement.history,operations:replacement.operations}:{}), document: changed ? next : state.document, runtime, revision: state.revision + Number(changed), rootRevision: state.rootRevision + 1 })),nextQueries=changed?advanceDocumentQueries(queries,next,options.registry,state.revision+1):queries;
state=nextState;queries=nextQueries;if(changed)queriesByDocument.set(next,queries);
const nextState=prepareState(Object.freeze({ ...state, ...(replacement?{history:replacement.history,operations:replacement.operations}:{}), document: changed ? next : state.document, runtime, revision: state.revision + Number(changed), rootRevision: state.rootRevision + 1 })),nextQueries=bindCoverage(nextState,changed?advanceDocumentQueries(queries,next,options.registry,state.revision+1):queries);
state=nextState;queries=nextQueries;queriesByDocument.set(state.document,queries);
sequence=nextSequence;
emit(freeze({ revision: state.revision, origin: 'ack', changes: [], operationIds: [] }));
return Object.freeze({ status: 'applied', revision: state.revision, operationIds: Object.freeze([]) });