2026-09-20 00:14:08 -07:00
import type { BindingSchema } from "./index.js" ;
import type {
ValueType ,
ObjectExpectation ,
PackageExport ,
DependencyPort ,
ExecutionContract ,
} from "../capability-model/types.js" ;
export class PortableBindingError extends Error {
constructor (
readonly path : string ,
message : string ,
) {
super ( ` ${ path } : ${ message } ` );
this . name = "PortableBindingError" ;
}
}
const q = JSON . stringify ;
const unit : ValueType = { kind : "builtin" , name : "unit" };
const string : ValueType = { kind : "scalar" , name : "string" };
const bool : ValueType = { kind : "scalar" , name : "bool" };
const isUnit = ( t : ValueType ) => t . kind === "builtin" && t . name === "unit" ;
const expectation = ( e : ObjectExpectation ) => ({
kind : e.kind ,
id : e.kind === "atom" ? e.atomId : e.interfaceRevisionId ,
});
/** The protocol IR and replica wire schema intentionally share structure, not reference identity. */
export function portableValueSchema ( type : ValueType ) : unknown {
switch ( type . kind ) {
2026-09-20 04:04:39 -07:00
case "change" :
return {
kind : "record" ,
fields : {
codec : { kind : "scalar" , name : "string" },
profile : { kind : "scalar" , name : "string" },
generation : { kind : "scalar" , name : "string" },
changes : { kind : "list" , value : { kind : "scalar" , name : "bytes" } },
},
};
2026-09-20 00:14:08 -07:00
case "scalar" :
return type ;
case "builtin" :
if ( type . name === "unit" ) return type ;
break ;
case "object-ref" :
return { kind : type.kind , expectation : expectation ( type . expectation ) };
case "optional" :
case "list" :
return { kind : type.kind , value : portableValueSchema ( type . value ) };
case "record" :
return {
kind : type.kind ,
fields : Object.fromEntries (
Object . entries ( type . fields ). map (([ key , value ]) => [ key , portableValueSchema ( value )]),
),
};
}
throw new PortableBindingError (
type . kind ,
"Unsupported portable value; use a supported record/reference contract (native edits and generic adapters have separate tasks)" ,
);
}
2026-09-20 01:49:56 -07:00
export function tsType ( type : ValueType ) : string {
2026-09-20 00:14:08 -07:00
portableValueSchema ( type );
switch ( type . kind ) {
2026-09-20 04:04:39 -07:00
case "change" :
return `PreparedChange< ${ tsType ( type . value ) } >` ;
2026-09-20 00:14:08 -07:00
case "scalar" :
return type . name === "bool"
? "boolean"
: type . name === "string"
? "string"
: type . name === "bytes"
? "Uint8Array"
: [ "int64" , "uint64" ]. includes ( type . name )
? "bigint"
: "number" ;
case "builtin" :
return "null" ;
case "object-ref" : {
const e = expectation ( type . expectation );
return `ManagedReference< ${ q ( e . kind ) } , ${ q ( e . id ) } >` ;
}
case "optional" :
return `( ${ tsType ( type . value ) } | null)` ;
case "list" :
return `ReadonlyArray< ${ tsType ( type . value ) } >` ;
case "record" :
return `{ ${ Object . entries ( type . fields )
. map (([ key , value ]) => `readonly ${ q ( key ) } : ${ tsType ( value ) } ` )
. join ( "; " ) } }` ;
default :
throw new PortableBindingError ( type . kind , "Unsupported type" );
}
}
interface Method {
name : string ;
portId : string ;
portName : string ;
action : { kind : string ; primitive? : string ; operationId? : string };
input : ValueType ;
output : ValueType ;
keyed : boolean ;
}
2026-09-20 20:20:47 -07:00
function methods (
schema : BindingSchema ,
entry : PackageExport ,
port : DependencyPort ,
target : string ,
remote = false ,
) : Method [] {
2026-09-20 00:14:08 -07:00
const req = port . requirement ;
const path = ` ${ entry . displayName } .ports. ${ port . displayName } ` ;
const method = (
name : string ,
action : Method [ "action" ],
input : ValueType ,
output : ValueType ,
keyed = false ,
) : Method => ({ name , portId : port.id , portName : port.displayName , action , input , output , keyed });
const writable = () => {
2026-09-20 20:20:47 -07:00
if ( remote )
throw new PortableBindingError ( path , "Remote writes and creation require a named transaction capability" );
2026-09-20 00:14:08 -07:00
if ( entry . execution ? . kind !== "transaction" )
throw new PortableBindingError ( path , "Snapshot context cannot expose mutation ports" );
};
switch ( req . kind ) {
case "creation" :
writable ();
return [
method (
"create" ,
{ kind : "creation" },
req . inputType ,
{ kind : "object-ref" , expectation : { kind : "atom" , atomId : req.atomId } },
true ,
),
];
case "state" :
return req . primitives . map (( primitive ) => {
if ( primitive === "read" ) return method ( "get" , { kind : "state" , primitive }, unit , req . valueType );
if ( primitive === "write" ) {
writable ();
return method ( "set" , { kind : "state" , primitive }, req . valueType , unit );
}
2026-09-20 04:04:39 -07:00
if ( primitive === "edit" ) {
writable ();
return method ( "edit" , { kind : "state" , primitive }, { kind : "change" , value : req.valueType }, unit );
}
2026-09-20 00:14:08 -07:00
throw new PortableBindingError ( path , `State ${ primitive } is not supported by this portable adapter yet` );
});
case "edge" : {
const ref : ValueType = { kind : "object-ref" , expectation : req.target };
const item : ValueType = { kind : "record" , fields : { entry : string , target : ref } };
return req . primitives . map (( primitive ) => {
if ( primitive === "resolve" )
return method ( "resolve" , { kind : "edge" , primitive }, unit , { kind : "list" , value : item });
writable ();
if ( primitive === "connect" ) return method ( "connect" , { kind : "edge" , primitive }, ref , string , true );
if ( primitive === "disconnect" ) return method ( "disconnect" , { kind : "edge" , primitive }, string , bool );
throw new PortableBindingError ( path , `Edge ${ primitive } is not supported by this portable adapter yet` );
});
}
case "interface" : {
const iface = schema . interfaces . find (( i ) => i . revisionId === req . interfaceRevisionId );
if ( ! iface ) throw new PortableBindingError ( path , `Missing interface ${ req . interfaceRevisionId } ` );
const operations = iface . members . flatMap (( member ) => member . operations . map (( op ) => ({ member , op })));
if ( req . operations ? . some (( id ) => ! operations . some (({ op }) => op . id === id )))
throw new PortableBindingError ( path , "Unknown selected interface operation" );
return operations
. filter (({ op }) => ! req . operations || req . operations . includes ( op . id ))
. map (({ member , op }) => {
const execution : ExecutionContract = op . execution ?? { kind : "remote" };
if (
2026-09-20 20:20:47 -07:00
( execution . kind === "remote" ? ! remote : ! execution . targets . includes ( target )) ||
2026-09-20 00:14:08 -07:00
op . mode !== "call" ||
op . scope === "class"
)
throw new PortableBindingError (
` ${ path } . ${ member . displayName } . ${ op . displayName } ` ,
`Selected operation is not an object call available in portable target ${ target } ; select a portable subset or move this handler to remote execution` ,
);
2026-09-20 20:20:47 -07:00
if ( execution . kind === "transaction" && ! remote ) writable ();
2026-09-20 22:05:59 -07:00
if ( req . receiver === "input" && ( remote || execution . kind !== "snapshot" ))
throw new PortableBindingError ( path , "Reference-selected dependencies require portable snapshot calls" );
2026-09-20 00:14:08 -07:00
return method (
` ${ member . displayName } . ${ op . displayName } ` ,
{ kind : "interface" , operationId : op.id },
2026-09-20 22:05:59 -07:00
req . receiver === "input"
? {
kind : "record" ,
fields : {
receiver : {
kind : "object-ref" ,
expectation : { kind : "interface" , interfaceRevisionId : req.interfaceRevisionId },
},
input : op.inputType ,
},
}
: op . inputType ,
2026-09-20 00:14:08 -07:00
op . outputType ,
execution . kind === "transaction" ,
);
});
}
default :
throw new PortableBindingError ( path , `Unsupported ${ req . kind } port in portable bindings` );
}
}
export interface PortableBindingOptions {
target : string ;
runtimeModule? : string ;
}
/** Generate one target's handlers. The same source signatures are used in browser/server builds. */
export function generatePortableTypeScriptBindings (
schema : BindingSchema ,
revision : string ,
options : PortableBindingOptions ,
) : string {
2026-09-20 20:20:47 -07:00
return generateBindings ( schema , revision , options , false );
}
export function generateRemoteTypeScriptBindings (
schema : BindingSchema ,
revision : string ,
options : { runtimeModule? : string } = {},
) : string {
return generateBindings ( schema , revision , { ... options , target : "server" }, true );
}
function generateBindings (
schema : BindingSchema ,
revision : string ,
options : PortableBindingOptions ,
remote : boolean ,
) : string {
const mode = remote ? "Remote" : "Portable" ;
2026-09-20 00:14:08 -07:00
const pkg = schema . packages . find (( p ) => p . revisionId === revision );
if ( ! pkg ) throw new PortableBindingError ( revision , "Missing package" );
2026-09-20 20:20:47 -07:00
const entries = pkg . exports . filter (( e ) =>
remote
? ! e . execution || e . execution . kind === "remote"
: e . execution && e . execution . kind !== "remote" && e . execution . targets . includes ( options . target ),
2026-09-20 00:14:08 -07:00
);
const names = new Set < string >();
const contexts : string [] = [],
implementations : string [] = [],
adapters : string [] = [],
descriptors : unknown [] = [];
for ( const entry of entries ) {
2026-09-20 20:20:47 -07:00
const effect = entry . execution ? . kind ?? "remote" ;
2026-09-20 19:42:24 -07:00
// The checked binding schema already contains concrete generic applications.
// Each has its own identity, value codecs and specialized dependency ports.
if ( entry . kind === "constructor" )
throw new PortableBindingError ( entry . id , "Legacy constructors are not supported; declare an ordinary factory" );
2026-09-20 00:14:08 -07:00
if ( names . has ( entry . displayName )) throw new PortableBindingError ( entry . id , "Duplicate handler name" );
names . add ( entry . displayName );
const portNames = new Set < string >();
const all : Method [] = [],
facets : string [] = [];
for ( const port of entry . dependencyPorts ) {
if ( portNames . has ( port . displayName ))
throw new PortableBindingError ( entry . id , `Duplicate port name ${ port . displayName } ` );
portNames . add ( port . displayName );
2026-09-20 20:20:47 -07:00
const selected = methods ( schema , entry , port , options . target , remote );
2026-09-20 00:14:08 -07:00
if ( new Set ( selected . map (( m ) => m . name )). size !== selected . length )
throw new PortableBindingError ( port . id , "Duplicate method name" );
all . push (... selected );
facets . push (
2026-09-20 20:20:47 -07:00
`readonly ${ q ( port . displayName ) } : { ${ selected . map (( m ) => `readonly ${ q ( m . name ) } : ( ${ [...( isUnit ( m . input ) ? [] : [ `input: ${ tsType ( m . input ) } ` ]), ...( m . keyed ? [ remote ? "options: ServerMutationOptions" : "options: MutationKey" ] : [])]. join ( ", " ) } ) => Promise< ${ tsType ( m . output ) } >` ). join ( "; " ) } }` ,
2026-09-20 00:14:08 -07:00
);
}
const receiver =
entry . kind === "function"
? null
: entry . receiverRequirement . kind === "exact-atom"
? { kind : "atom" , id : entry.receiverRequirement.atomId }
: "object" ;
const receiverType =
receiver === null
? "null"
: receiver === "object"
? "ExactReference"
: `ManagedReference<"atom", ${ q ( receiver . id ) } >` ;
contexts . push (
2026-09-20 20:20:47 -07:00
`readonly ${ q ( entry . displayName ) } : { readonly effect: ${ q ( effect ) } ; ${ remote ? "readonly signal: AbortSignal;" : "" } readonly input: ${ tsType ( entry . inputType ) } ; readonly receiver: ${ receiverType } ; readonly ports: { ${ facets . join ( "; " ) } } }` ,
2026-09-20 00:14:08 -07:00
);
const output = tsType ( entry . outputType );
implementations . push (
`readonly ${ q ( entry . displayName ) } : (context: Contexts[ ${ q ( entry . displayName ) } ]) => ${ output } | Promise< ${ output } >` ,
);
adapters . push (
` ${ q ( entry . id ) } : context => installed[ ${ q ( entry . displayName ) } ](context as Contexts[ ${ q ( entry . displayName ) } ])` ,
);
descriptors . push ({
id : entry.id ,
name : entry.displayName ,
2026-09-20 20:20:47 -07:00
effect ,
2026-09-20 00:14:08 -07:00
receiver ,
input : portableValueSchema ( entry . inputType ),
output : portableValueSchema ( entry . outputType ),
methods : all.map (( m ) => ({ ... m , input : portableValueSchema ( m . input ), output : portableValueSchema ( m . output ) })),
});
}
2026-09-21 12:05:50 -07:00
return `// Generated ${ remote ? "remote" : "portable" } bindings. Regenerate from the checked capability schema; do not edit. \ nimport { define ${ mode } Registry, type ${ mode } Export, type PreparedChange, type ManagedReference, type ExactReference, type ${ remote ? "ServerMutationOptions" : "MutationKey" } } from ${ q ( options . runtimeModule ?? "@quixos/camino-replica-core" ) } ; \ nexport const target = ${ q ( options . target ) } ; \ nexport type Contexts = { ${ contexts . join ( ";\n" ) } }; \ nexport type ${ mode } Implementation = { ${ implementations . join ( ";\n" ) } }; \ nexport const declarations = ${ JSON . stringify ( descriptors , null , 2 ) } as const satisfies readonly ${ mode } Export[]; \ nexport function createRegistry(handlers: ${ mode } Implementation) { const installed = { ...handlers }; return define ${ mode } Registry(declarations, { ${ adapters . join ( ",\n" ) } }); } \ n` ;
2026-09-20 00:14:08 -07:00
}