Certify loaded canvas scopes and publish completeness with runtime queries
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import {validateCanvasElementCounts,type CanvasElementCounts} from '../canvas-runtime/scope-counts.ts';
|
||||
import type { CanvasRecord, ElementRecord, Id, Registry } from '../canvas-core/types.ts';
|
||||
import { createDocument, validateDocument } from '../canvas-core/document.ts';
|
||||
import { fault, freeze, readMap, validJson } from '../canvas-core/values.ts';
|
||||
@@ -29,6 +30,7 @@ export async function* decodeModelStream(source:AsyncIterable<Uint8Array>,canvas
|
||||
let sequence=0,token='',projectId='',expected=0,ended=false;
|
||||
let terminal: {sequence:number;snapshotToken:string;projectId:string;versions:ReadonlyMap<Id,number>;complete:boolean;byteLength:number;coverage:string[];value:CanonicalReadUpdate}|undefined;
|
||||
let canvases:CanvasRecord[]=[],versions:ReadonlyMap<Id,number>=new Map(),tombstones:ReadonlyMap<Id,number>=new Map();
|
||||
let canvasElementCounts:CanvasElementCounts|undefined;const ownerCounts=new Map<Id,number>();
|
||||
const elements:ElementRecord[]=[],ids=new Set<Id>(),canvasIds=new Set<Id>();
|
||||
for await(const line of lines(source,signal)){
|
||||
const raw=object(line.raw);
|
||||
@@ -44,13 +46,14 @@ export async function* decodeModelStream(source:AsyncIterable<Uint8Array>,canvas
|
||||
if(!Array.isArray(raw.removedCanvasIds)||new Set(raw.removedCanvasIds).size!==raw.removedCanvasIds.length||raw.removedCanvasIds.some(id=>typeof id!=='string'||canvasIds.has(id)||!versionMap.has(id))||versionMap.size!==canvasIds.size+raw.removedCanvasIds.length)throw fault('invalid_model_stream');
|
||||
for(const [id,value]of Object.entries(object(raw.tombstones))){if(!id||!Number.isSafeInteger(value)||(value as number)<1)throw fault('invalid_model_stream');dead.set(id,value as number);}
|
||||
versions=readMap(versionMap);tombstones=readMap(dead);
|
||||
const value:CanonicalReadUpdate=freeze({status:'receiving',projectId,canvases,elements:[],tombstones,versions,receivedElements:0,totalElements:expected});
|
||||
if(Object.hasOwn(raw,'canvasElementCounts')){validateCanvasElementCounts(raw.canvasElementCounts,[...canvasIds],expected);canvasElementCounts=freeze({...raw.canvasElementCounts});}
|
||||
const value:CanonicalReadUpdate=freeze({status:'receiving',...(canvasElementCounts?{canvasElementCounts}:{}),projectId,canvases,elements:[],tombstones,versions,receivedElements:0,totalElements:expected});
|
||||
yield{sequence:0,snapshotToken:token,projectId,versions,complete:false,byteLength:line.byteLength,coverage:['model-header'],value};continue;
|
||||
}
|
||||
if(raw.snapshotToken!==token)throw fault('load_snapshot_changed');
|
||||
if(raw.kind==='elements'){
|
||||
if(!Array.isArray(raw.elements)||!raw.elements.length||raw.elements.length>64||elements.length+raw.elements.length>expected)throw fault('invalid_model_stream');
|
||||
for(const item of raw.elements){const element=object(item) as unknown as ElementRecord;if(typeof element.id!=='string'||!element.id||ids.has(element.id)||!canvasIds.has(element.ownerCanvasId))throw fault('invalid_model_stream');ids.add(element.id);elements.push(element);}
|
||||
for(const item of raw.elements){const element=object(item) as unknown as ElementRecord;if(typeof element.id!=='string'||!element.id||ids.has(element.id)||!canvasIds.has(element.ownerCanvasId))throw fault('invalid_model_stream');const count=(ownerCounts.get(element.ownerCanvasId)??0)+1;if(canvasElementCounts&&count>canvasElementCounts[element.ownerCanvasId])throw fault('invalid_model_coverage');ownerCounts.set(element.ownerCanvasId,count);ids.add(element.id);elements.push(element);}
|
||||
const value:CanonicalReadUpdate=freeze({status:'receiving',projectId,canvases:[],elements:raw.elements as ElementRecord[],tombstones:readMap(new Map()),versions,receivedElements:elements.length,totalElements:expected});
|
||||
yield{sequence:sequence-1,snapshotToken:token,projectId,versions,complete:false,byteLength:line.byteLength,coverage:raw.elements.map(item=>`element:${(item as ElementRecord).id}`),value};
|
||||
}else if(raw.kind==='complete'){
|
||||
|
||||
Reference in New Issue
Block a user