Skip to content

Core

Guide: expose()

function expose<T, TModules, TTransport, TValue, TResult>(
value: TValue extends Contextual<U>
? Contextual<CapableCheck<U, TModules, RevivableContextOf<TTransport>>>
: CapableCheck<TValue, TModules, RevivableContextOf<TTransport>>,
options: Omit<StartConnectionsOptions<TModules>, 'connection'> & {
connection?: (connected: Connected<Remote<T>>) => TResult
transport: TTransport
},
): Exposed<TResult>

Defined in: index.ts:97

Expose a value to whoever connects, and get back what they exposed.

Wrap value in context to build it once per connection, which is what lets one server answer each realm differently (scoped resolvers per app) instead of sharing one object across all of them. A bare function stays a plain exposed endpoint, so the wrapper is what disambiguates the two.

The result is both awaitable and async-iterable: awaiting gives the first peer, iterating gives every peer as it connects. Both hand back the same shape.

const remote = await expose(resolvers, { transport }) // the first peer's value
for await (const remote of expose(resolvers, { transport })) {
} // every peer's value

connection decides what that shape is. Omit it and it is the peer’s value, which is what expose has always resolved to. Return whatever a connection should mean instead:

const { value, context } = await expose(resolvers, {
transport,
connection: ({ value, context }) => ({ value, context }),
})
for await (const peer of expose(resolvers, {
transport,
connection: ({ value, context }) => ({ value, context }),
})) {
if (!allowed(peer.context.origin)) peer.context.abort?.()
}

A peer’s identity is whatever the transport can observe merged over whatever the caller declared in context. Only a window message carries a browser-set origin and source; a MessagePort message carries neither, so a port-based server declares what it learned when it received the port. Observed fields win over declared ones, so a declaration can never spoof a real origin.

T = unknown

TModules extends readonly RevivableModule[] = readonly [__module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, __module, { box: (value: number, context: RevivableContext<any>) => number | BoxedNonFiniteNumber; isType: (value: unknown) => value is number; revive: (value: BoxedNonFiniteNumber, _context: RevivableContext<any>) => number; type: "nonFiniteNumber"; }, { box: (value: undefined, context: RevivableContext<any>) => BoxedUndefined | undefined; isType: (value: unknown) => value is undefined; revive: (_value: BoxedUndefined, _context: RevivableContext<any>) => undefined; type: "undefined"; }, { box: (value: Clonable, _context: RevivableContext<any>) => Clonable; capableOnly: true; isType: (value: unknown) => value is Clonable; objectsOnly: true; revive: (value: BoxedClonable, _context: RevivableContext<any>) => Clonable; type: "clonable"; }, { box: (value: Transferable, _context: RevivableContext<any>) => Transferable; capableOnly: true; isType: (value: unknown) => value is Transferable; objectsOnly: true; revive: (value: BoxedTransferable, _context: RevivableContext<any>) => Transferable; type: "transferable"; }, { box: (value: Blob, context: RevivableContext<any>) => Blob; capableOnly: true; isType: (value: unknown) => value is Blob; objectsOnly: true; revive: (value: BoxedBlob, _context: RevivableContext<any>) => Blob; type: "blob"; }, __module, { box: (_value: never, _context: RevivableContext<any>) => BoxedUnclonable; isType: (value: unknown) => value is never; objectsOnly: true; revive: (_value: BoxedUnclonable, _context: RevivableContext<any>) => Record<string, never>; type: "unclonable"; }]

TTransport extends Transport = Transport

TValue = Capable<TModules, RevivableContextOf<TTransport>>

TResult = Remote<T>

TValue extends Contextual<U> ? Contextual<CapableCheck<U, TModules, RevivableContextOf<TTransport>>> : CapableCheck<TValue, TModules, RevivableContextOf<TTransport>>

Omit<StartConnectionsOptions<TModules>, "connection"> & { connection?: (connected: Connected<Remote<T>>) => TResult; transport: TTransport; }

Exposed<TResult>