Import exported documents with durable root and reference remapping
This commit is contained in:
@@ -18,6 +18,7 @@ import { planDuplicateAsync, planDuplicateResumable, prepareDuplicateStream } fr
|
||||
import type { DigestPort } from './digest-pool.ts';
|
||||
import { importSourceDigest } from './import-integrity.ts';
|
||||
import { encodeDuplicateSource, prepareDuplicateImport, assertDuplicateExternal } from './import-duplicate.ts';
|
||||
import {openDocumentImportSource} from './import-document.ts';
|
||||
import {createJsonArrayImportSource} from './import-json-array.ts';
|
||||
import { createImportJob, createImportRunner, createNdjsonImportSource, type ImportJob } from './imports.ts';
|
||||
import { assertNativeImportDependencies, assertNativeImportTarget, prepareNativeImport } from './import-native.ts';
|
||||
@@ -670,9 +671,9 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
if(importRunners.has(id)){void importRunners.get(id)!.start();return Promise.resolve();}
|
||||
deferredImportLoads.delete(id);
|
||||
let expectedFingerprint=getImport(id).sourceFingerprint;
|
||||
const preparationController=new AbortController();if(['duplicate-snapshot','duplicate-plan'].includes(getImport(id).source?.kind??''))preparationControllers.set(id,preparationController);
|
||||
const preparationController=new AbortController();if(['native-document','duplicate-snapshot','duplicate-plan'].includes(getImport(id).source?.kind??''))preparationControllers.set(id,preparationController);
|
||||
const task=(async()=>{
|
||||
let job=getImport(id),source=job.source;if(!source||!['native-json-array','native-ndjson','duplicate-ndjson','duplicate-snapshot','duplicate-plan'].includes(source.kind)||job.parserVersion!==(source.kind==='native-json-array'?3:source.kind==='duplicate-plan'?2:1))throw fault('import_interpreter_unavailable');
|
||||
let job=getImport(id),source=job.source;if(!source||!['native-document','native-json-array','native-ndjson','duplicate-ndjson','duplicate-snapshot','duplicate-plan'].includes(source.kind)||job.parserVersion!==(source.kind==='native-document'?4:source.kind==='native-json-array'?3:source.kind==='duplicate-plan'?2:1))throw fault('import_interpreter_unavailable');
|
||||
let bytes=fileWrites.get(source.fileId)??await options.storage.readFile(leader,source.fileId);
|
||||
if(closing||closed)return;
|
||||
const latest=getImport(id);if(latest.sourceFingerprint!==job.sourceFingerprint||latest.source?.fileId!==source.fileId||['completed','partial','cancelled'].includes(latest.status))return;
|
||||
@@ -688,7 +689,7 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
if(source.kind==='duplicate-snapshot'){if(preparationController.signal.aborted||importHolds.size||latest.status!=='preparing')return;const materialized=await materializeCopy(id,bytes,preparationController);job=materialized.job;source=job.source!;bytes=materialized.bytes;copyDocument=materialized.document;indexed=materialized.indexed;expectedFingerprint=job.sourceFingerprint;}
|
||||
if(source.kind==='duplicate-plan'&&source.snapshotVersion===2){indexed??=await openIndexedCopySnapshot(bytes,options.scope.projectId,options.registry,preparationController.signal);copyDocument=indexed.document;}
|
||||
if(source.kind==='duplicate-plan'&©Document)validateLoadingCopySource(editor.getSnapshot(),job,copyDocument);
|
||||
const importSource=source.kind==='duplicate-plan'?createDuplicatePlanSource(copyDocument??await decodeCopySnapshot(bytes,options.scope.projectId,options.registry,preparationController.signal),options.registry,source.plan!,job.sourceFingerprint,indexed?.readElement):source.kind==='native-json-array'?createJsonArrayImportSource({bytes,fingerprint:job.sourceFingerprint}):createNdjsonImportSource({bytes,fingerprint:job.sourceFingerprint});
|
||||
const importSource=source.kind==='duplicate-plan'?createDuplicatePlanSource(copyDocument??await decodeCopySnapshot(bytes,options.scope.projectId,options.registry,preparationController.signal),options.registry,source.plan!,job.sourceFingerprint,indexed?.readElement):source.kind==='native-document'?await openDocumentImportSource({bytes,fingerprint:job.sourceFingerprint,signal:preparationController.signal}):source.kind==='native-json-array'?createJsonArrayImportSource({bytes,fingerprint:job.sourceFingerprint}):createNdjsonImportSource({bytes,fingerprint:job.sourceFingerprint});
|
||||
if(closing||closed||preparationController.signal.aborted||getImport(id).generation!==job.generation)return;
|
||||
if(importHolds.size){deferredImportLoads.add(id);return;}
|
||||
const runner=createImportRunner({optimisticLocal:true,awaitDurabilityBudget:stage=>pending.size>=32||stage!=='parsed'&&persistenceFailed?persist():undefined,source:importSource,getJob:()=>getImport(id),createId,
|
||||
@@ -705,12 +706,12 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
})().catch(error=>{if(preparationController.signal.aborted)return;if(!closing&&!closed){try{const job=getImport(id);if(job.sourceFingerprint!==expectedFingerprint||['completed','partial','cancelled','paused'].includes(job.status))return;publishImport(freeze({...job,generation:job.generation+1,status:'paused',error:(error as {code?:string}).code??'import_failed'}),job.generation);}catch{/* Recovery/identity fences retain the original job. */}report(error);}}).finally(()=>{preparationControllers.delete(id);importLoads.delete(id);if(!['running','preparing'].includes(getImport(id).status))deferredImportLoads.delete(id);if(!closing&&!closed&&!importHolds.size&&deferredImportLoads.delete(id)&&!importRunners.has(id)&&['running','preparing'].includes(getImport(id).status))queueMicrotask(()=>{if(!closing&&!closed)void loadImport(id);});});
|
||||
importLoads.set(id,task);return task;
|
||||
}
|
||||
function startNativeImport(bytes:Blob,targetCanvasId:Id,format:'ndjson'|'json-array'):Id{
|
||||
if(!['ndjson','json-array'].includes(format))throw fault('import_interpreter_unavailable');
|
||||
function startNativeImport(bytes:Blob,targetCanvasId:Id,format:'ndjson'|'json-array'|'document'):Id{
|
||||
if(!['ndjson','json-array','document'].includes(format))throw fault('import_interpreter_unavailable');
|
||||
assertImportWritable();const loadingAdmission=loadingImportAdmission(editor.getSnapshot(),targetCanvasId);const permission=options.policy?.({type:'changes.apply',changes:[]},editor.getSnapshot().document);if(permission&&!permission.enabled)throw fault(permission.code,permission.targets);const canvas=editor.getSnapshot().document.canvases.get(targetCanvasId);if(!canvas)throw fault('canvas_not_loaded',[targetCanvasId]);if(!(bytes instanceof Blob)||bytes.size>64*1024*1024)throw fault('invalid_import_source');
|
||||
const id=createId(),fileId=createId(),current=editor.getSnapshot().runtime;if(!id||!fileId||id===fileId||current.imports?.has(id)||current.resources.has(fileId)||current.orphanFiles?.has(fileId)||fileWrites.has(fileId)||importFiles(current.imports).has(fileId))throw fault('import_identity_conflict');
|
||||
const entry=canvas.parent?editor.getSnapshot().document.elements.get(canvas.parent.entryElementId)!:undefined;
|
||||
const job=freeze({...createImportJob({id,projectId:options.scope.projectId,sourceFingerprint:`local-file:${fileId}:${bytes.size}`,parserVersion:format==='json-array'?3:1,source:{...(loadingAdmission?{loadingAdmission}:{}),kind:format==='json-array'?'native-json-array':'native-ndjson',name:((bytes as Blob&{name?:string}).name||'画布数据').slice(0,255),fileId,byteLength:bytes.size,targetCanvasId,...(entry?{targetEntry:{id:entry.id,incarnation:entry.incarnation}}:{})}}),status:'running' as const});
|
||||
const job=freeze({...createImportJob({id,projectId:options.scope.projectId,sourceFingerprint:`local-file:${fileId}:${bytes.size}`,parserVersion:format==='document'?4:format==='json-array'?3:1,source:{...(loadingAdmission?{loadingAdmission}:{}),kind:format==='document'?'native-document':format==='json-array'?'native-json-array':'native-ndjson',name:((bytes as Blob&{name?:string}).name||'画布数据').slice(0,255),fileId,byteLength:bytes.size,targetCanvasId,...(entry?{targetEntry:{id:entry.id,incarnation:entry.incarnation}}:{})}}),status:'running' as const});
|
||||
fileWrites.set(fileId,bytes);
|
||||
const result=editor.updateRuntime(previous=>{const jobs=new Map(previous.imports??[]);jobs.set(id,job);return Object.freeze({...previous,imports:readMap(jobs)});});
|
||||
if(result.status==='rejected'){fileWrites.delete(fileId);throw fault(result.code,result.targets);}void loadImport(id);return id;
|
||||
@@ -721,6 +722,7 @@ export async function openProjectEngine(options: EngineOptions) {
|
||||
canDuplicate(selection:readonly Target[],settings:{viewCanvasId?:Id}={}){try{assertImportWritable();if(collectionReservations.size)throw fault('resource_collection_in_progress',[...collectionReservations]);const snapshot=editor.getSnapshot(),permission=options.policy?.({type:'changes.apply',changes:[]},snapshot.document);if(permission&&!permission.enabled)throw fault(permission.code,permission.targets);if(!selection.length)throw fault('empty_copy_selection');for(const selected of selection){const element=snapshot.document.elements.get(selected.id);if(!element||element.incarnation!==selected.incarnation)throw fault('stale_incarnation',[selected.id]);}const target=settings.viewCanvasId??snapshot.document.elements.get(selection[0].id)!.ownerCanvasId;if(!snapshot.document.canvases.has(target))throw fault('canvas_not_loaded',[target]);loadingImportAdmission(snapshot,target,selection);return{enabled:true as const};}catch(error){const result=reject(error);return{enabled:false as const,code:result.code,targets:result.targets};}},
|
||||
openSource:openImportSource,
|
||||
startNdjson:(bytes:Blob,targetCanvasId:Id)=>startNativeImport(bytes,targetCanvasId,'ndjson'),
|
||||
startDocument:(bytes:Blob,targetCanvasId:Id)=>startNativeImport(bytes,targetCanvasId,'document'),
|
||||
startJsonArray:(bytes:Blob,targetCanvasId:Id)=>startNativeImport(bytes,targetCanvasId,'json-array'),
|
||||
async duplicate(selection:readonly Target[],settings:{readonly worldOffset?:Point;readonly signal?:AbortSignal;readonly viewCanvasId?:Id}={}):Promise<Id>{
|
||||
assertImportWritable();if(collectionReservations.size)throw fault('resource_collection_in_progress',[...collectionReservations]);
|
||||
|
||||
Reference in New Issue
Block a user