Expose portable scene bindings and an explicit React Flow peer adapter
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdtemp, readFile, realpath, rm, writeFile } from 'node:fs/promises';
|
||||
import { mkdtemp, readFile, realpath, rm, writeFile, mkdir, symlink } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -17,7 +17,7 @@ try {
|
||||
const metadata=JSON.parse(await readFile(join(packageRoot,'package.json'),'utf8'));
|
||||
assert.equal(metadata.private,true);assert.equal(metadata.type,'module');
|
||||
assert.equal(Object.keys(metadata.dependencies??{}).length,0);
|
||||
assert.equal(Object.keys(metadata.peerDependencies??{}).length,0);
|
||||
assert.deepEqual(Object.keys(metadata.peerDependencies??{}).sort(),['@xyflow/react','react','react-dom']);assert.ok(Object.values(metadata.peerDependenciesMeta).every(value=>value.optional===true));
|
||||
// Resolve every import through the packed artifact, never checkout node_modules.
|
||||
await writeFile(join(temporary,'guard-loader.mjs'),`
|
||||
import {fileURLToPath} from 'node:url';
|
||||
@@ -36,11 +36,12 @@ try {
|
||||
for(const name of ['window','document','navigator','indexedDB','Worker'])Object.defineProperty(globalThis,name,{configurable:true,get(){throw Error('Browser global: '+name);}});
|
||||
`);
|
||||
const report=JSON.parse(run(process.execPath,['--import',join(temporary,'guard.mjs'),join(packageRoot,'consumer.mjs')],{cwd:temporary}));
|
||||
assert.equal(report.status,'ok');assert.equal(report.reopened,true);assert.equal(report.elements,2);
|
||||
assert.equal(report.status,'ok');assert.equal(report.portableBindings,true);assert.equal(report.reopened,true);assert.equal(report.elements,2);
|
||||
// No DOM library: browser adapter declarations must not leak into portable ports.
|
||||
await writeFile(join(packageRoot,'consumer-types.mts'),`
|
||||
import {createRegistry,builtinComponents,createDocument} from '@endoleap/headless-canvas/core';
|
||||
import {openProjectEngine,type StoragePort,type StorageScope,type LoadingState} from '@endoleap/headless-canvas/runtime';
|
||||
import {projectCanvas,hitProjectedCanvas,type ProjectionNode,type ProjectionEdge} from '@endoleap/headless-canvas/bindings';
|
||||
import {createFoundryPersistence,type ServiceTransport} from '@endoleap/headless-canvas/service';
|
||||
const registry=createRegistry(builtinComponents());
|
||||
export async function completePartial(engine:Awaited<ReturnType<typeof openProjectEngine>>,document:Parameters<typeof openProjectEngine>[0]['document'],versions:ReadonlyMap<string,number>,state:LoadingState){
|
||||
@@ -60,14 +61,42 @@ try {
|
||||
const complete:boolean=queries.membersResult(engine.getSnapshot().document.projectId).complete;
|
||||
return {id,complete,projectComplete:queries.coverage.projectComplete};
|
||||
}
|
||||
export function project(engine:Awaited<ReturnType<typeof openProjectEngine>>){const snapshot=engine.getSnapshot(),queries=engine.editor.getQueries();const scene=projectCanvas(snapshot.document,snapshot.document.projectId,registry,new Set(),()=>undefined);const nodes:ProjectionNode[]=scene.nodes,edges:ProjectionEdge[]=scene.edges;return {nodes,edges,hit:hitProjectedCanvas(scene,queries,registry,'view',{space:'scene',point:{x:0,y:0},zoom:1})};}
|
||||
export function open(scope:StorageScope,storage:StoragePort,transport:ServiceTransport){
|
||||
return openProjectEngine({scope,storage,registry,document:createDocument(scope.projectId,[{id:scope.projectId,parent:null}],[]),versions:new Map([[scope.projectId,1]]),persistence:createFoundryPersistence({registry,transport,rootId:scope.projectId,workspaceId:scope.workspaceId})});
|
||||
}
|
||||
`);
|
||||
run(process.execPath,[join(root,'node_modules/typescript/bin/tsc'),'--noEmit','--strict','--target','ES2022','--module','NodeNext','--moduleResolution','NodeNext','--lib','ES2022','--types','node','--typeRoots',join(root,'node_modules/@types'),join(packageRoot,'consumer-types.mts')],{cwd:temporary});
|
||||
// A separate consumer opts into framework peers. The portable guard above
|
||||
// remains strict and has already proved /bindings does not resolve any peers.
|
||||
await mkdir(join(packageRoot,'node_modules/@xyflow'),{recursive:true});
|
||||
for(const peer of ['react','react-dom','@xyflow/react'])await symlink(await realpath(join(root,'node_modules',peer)),join(packageRoot,'node_modules',peer),'dir');
|
||||
await mkdir(join(packageRoot,'node_modules/@types'),{recursive:true});
|
||||
for(const peer of ['react','react-dom'])await symlink(await realpath(join(root,'node_modules/@types',peer)),join(packageRoot,'node_modules/@types',peer),'dir');
|
||||
await writeFile(join(packageRoot,'react-consumer.mjs'),`
|
||||
import assert from 'node:assert/strict';
|
||||
import React from 'react';import {renderToStaticMarkup} from 'react-dom/server';
|
||||
import {createReactRenderers,CanvasRendererCommandsContext,useCanvasRendererCommands} from '@endoleap/headless-canvas/react-flow';
|
||||
import {createRegistry,builtinComponents} from '@endoleap/headless-canvas/core';
|
||||
const registry=createRegistry(builtinComponents());let dispatched=0,seen;const commands={dispatch(){dispatched++;return{status:'applied',revision:1,operationIds:[]};},transaction(){throw Error('unused');}};
|
||||
function View(){seen=useCanvasRendererCommands();return React.createElement('span',null,'peer renderer');}
|
||||
const renderers=createReactRenderers([{componentId:'text',dataVersion:1,structure:'node',rendererId:'fixture',render:View}],registry);
|
||||
const html=renderToStaticMarkup(React.createElement(CanvasRendererCommandsContext.Provider,{value:commands},React.createElement(renderers.nodeTypes.fixture)));
|
||||
assert.equal(html,'<span>peer renderer</span>');assert.strictEqual(seen,commands);seen.dispatch({type:'element.data',target:{id:'fixture',incarnation:1},patch:{title:'command'}});assert.equal(dispatched,1);console.log('PASS explicit React peer renderer/context consumer');
|
||||
`);
|
||||
const react=run(process.execPath,[join(packageRoot,'react-consumer.mjs')],{cwd:temporary});process.stdout.write(react);
|
||||
await writeFile(join(packageRoot,'react-consumer-types.mts'),`
|
||||
import {createElement} from 'react';import type {NodeProps,EdgeProps} from '@xyflow/react';
|
||||
import {createReactRenderers,type ReactFlowProjectionNode,type ReactFlowProjectionEdge} from '@endoleap/headless-canvas/react-flow';
|
||||
function Card({data}:NodeProps<ReactFlowProjectionNode>){return createElement('div',null,data.title);}
|
||||
function Link({data}:EdgeProps<ReactFlowProjectionEdge>){return createElement('span',null,data?.__headlessRecord?.id);}
|
||||
export const adapter=createReactRenderers([{componentId:'text',dataVersion:1,structure:'node',rendererId:'card',render:Card},{componentId:'connection',dataVersion:1,structure:'connection',rendererId:'link',render:Link}]);
|
||||
`);
|
||||
run(process.execPath,[join(root,'node_modules/typescript/bin/tsc'),'--noEmit','--strict','--target','ES2022','--module','NodeNext','--moduleResolution','NodeNext','--lib','ES2022,DOM,DOM.Iterable','--types','react,react-dom,node','--typeRoots',join(root,'node_modules/@types'),join(packageRoot,'react-consumer-types.mts')],{cwd:temporary});
|
||||
|
||||
const browser=run(process.execPath,['tests/headless/browser-storage.mjs','--package']);
|
||||
process.stdout.write(browser);
|
||||
console.log(JSON.stringify({compiledBrowserAdapters:true,status:'ok',packedFiles:packed[0].files.length,consumer:report,nodeTypes:true,browserGlobalsBlocked:true,externalRuntimeDependenciesBlocked:true}));
|
||||
console.log(JSON.stringify({compiledBrowserAdapters:true,portableBindings:true,explicitReactPeers:true,status:'ok',packedFiles:packed[0].files.length,consumer:report,nodeTypes:true,browserGlobalsBlocked:true,externalRuntimeDependenciesBlocked:true}));
|
||||
} catch(error){
|
||||
if(error.stdout)process.stderr.write(error.stdout);
|
||||
if(error.stderr)process.stderr.write(error.stderr);
|
||||
|
||||
Reference in New Issue
Block a user