Skip to content

expose()

expose() is osra’s single entry point. It exposes value to the peer on the other side of a transport and resolves with the peer’s exposed value.

const expose: <T = unknown>(
value: Capable,
options: StartConnectionsOptions & { transport: Transport },
) => Promise<Remote<T>>

The value you pass is validated at compile time against Capable, the union of everything serializable for the inferred transport — see Remote<T> and TypeScript.

There is no separate client/server entry point. A side that only consumes passes {}:

worker.ts
import {
const expose: <T = unknown, const TModules extends readonly RevivableModule[] = readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), ... 21 more ..., {
...;
}], const TTransport extends Transport = Transport, const TValue = Capable<...>>(value: CapableCheck<...>, options: StartConnectionsOptions<TModules> & {
transport: TTransport;
}) => Promise<Remote<T>>
expose
} from 'osra'
expose<unknown, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], typeof globalThis, {
...;
}>(value: {
...;
}, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
({
ping: (n: number) => Promise<number>
ping
: async (
n: number
n
: number) =>
n: number
n
+ 1 }, {
transport: Transport & typeof globalThis
transport
:
module globalThis
globalThis
})
main.ts
import {
const expose: <T = unknown, const TModules extends readonly RevivableModule[] = readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), ... 21 more ..., {
...;
}], const TTransport extends Transport = Transport, const TValue = Capable<...>>(value: CapableCheck<...>, options: StartConnectionsOptions<TModules> & {
transport: TTransport;
}) => Promise<Remote<T>>
expose
} from 'osra'
type
type Api = {
ping: (n: number) => Promise<number>;
}
Api
= {
ping: (n: number) => Promise<number>
ping
: (
n: number
n
: number) =>
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<number> }
const
const worker: Worker
worker
= new
var Worker: new (scriptURL: string | URL, options?: WorkerOptions) => Worker

The Worker interface of the Web Workers API represents a background task that can be created via script, which can send messages back to its creator.

MDN Reference

Worker
('./worker.js', {
WorkerOptions.type?: WorkerType | undefined
type
: 'module' })
const
const api: {
ping: (n: number) => Promise<number>;
}
api
= await
expose<Api, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], Transport, Capable<...>>(value: Capable<...>, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
<
type Api = {
ping: (n: number) => Promise<number>;
}
Api
>({}, {
transport: Transport
transport
:
const worker: Worker
worker
})
await
const api: {
ping: (n: number) => Promise<number>;
}
api
.
ping: (n: number) => Promise<number>
ping
(41) // 42

A side that only serves can ignore the returned promise.

The handshake is announce → announce-reply → init: each side broadcasts announce, peers reply with an addressed announce, then each side sends init carrying its boxed value. The returned promise resolves once the peer’s init arrives and revives. The full dance, including its loss tolerance, is described in handshake internals.

With multiple peers on one transport, the promise resolves with the first peer’s value (first wins); later peers still connect and can call into your value, but there is no public accessor for their values. See multi-peer for patterns that give you a value per peer.

Option Type Default Semantics
transport Transport required The channel to the peer. See transports.
name string - Stamped on every outgoing envelope as name.
remoteName string - Inbound filter: envelopes whose name differs are dropped.
key string OSRA_DEFAULT_KEY ('__OSRA_DEFAULT_KEY__') Namespacing, not authentication. Envelopes carry it under __OSRA_KEY__; inbound messages with a different key are ignored, so multiple independent osra connections can share one channel.
origin string '*' Outbound: the targetOrigin for window.postMessage (windows only). Inbound: on window receive transports, events whose non-empty event.origin differs are dropped; non-window transports are not origin-filtered. The one exception (the announce beacon broadcasts with '*') and the full rationale live in security.
unregisterSignal AbortSignal - Teardown handle, see below.
revivableModules (defaults: DefaultRevivableModules) => TModules defaults as-is Configure the revivable module list. See custom revivables.
uuid Uuid crypto.randomUUID() This side’s identity, stamped on every envelope. Own messages looped back on the channel are ignored by uuid match.
remoteUuid Uuid - Preset the peer’s uuid to skip the announce handshake, see below.

Aborting the signal tears the connection down on both sides: the message listener stops, every tracked peer receives a protocol close, per-connection state is disposed, the pending expose() promise rejects with the abort reason, and in-flight RPC calls reject with Error('osra: connection closed') — on the peer too. The full teardown behavior (the already-aborted case, stream cancellation, what survives connection death) is documented in lifecycle.

When remoteUuid is set, that side skips announce entirely and immediately sends init addressed at the preset uuid. Both sides must preset: each side’s uuid fixed and remoteUuid pointing at the other:

expose<unknown, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], MessagePort, {
...;
}>(value: {
...;
}, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
(
const value: {
hello: () => Promise<string>;
}
value
, {
transport: Transport & MessagePort
transport
:
const port1: MessagePort

The port1 read-only property of the the port attached to the context that originated the channel.

MDN Reference

port1
,
uuid?: `${string}-${string}-${string}-${string}-${string}` | undefined
uuid
:
const uuidA: `${string}-${string}-${string}-${string}-${string}`
uuidA
,
remoteUuid?: `${string}-${string}-${string}-${string}-${string}` | undefined
remoteUuid
:
const uuidB: `${string}-${string}-${string}-${string}-${string}`
uuidB
})
const
const remote: unknown
remote
= await
expose<unknown, readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), typeof import("osra/build/revivables/date"), typeof import("osra/build/revivables/headers"), typeof import("osra/build/revivables/error"), typeof import("osra/build/revivables/typed-array"), ... 20 more ..., {
...;
}], MessagePort, {}>(value: {}, options: StartConnectionsOptions<...> & {
...;
}): Promise<...>
expose
({}, {
transport: Transport & MessagePort
transport
:
const port2: MessagePort

The port2 read-only property of the the port attached to the context at the other end of the channel, which the message is initially sent to.

MDN Reference

port2
,
uuid?: `${string}-${string}-${string}-${string}-${string}` | undefined
uuid
:
const uuidB: `${string}-${string}-${string}-${string}-${string}`
uuidB
,
remoteUuid?: `${string}-${string}-${string}-${string}-${string}` | undefined
remoteUuid
:
const uuidA: `${string}-${string}-${string}-${string}-${string}`
uuidA
})

No announce envelope is ever emitted; init flows directly.

  • expose() rejects immediately if the (normalized) transport cannot both emit and receive, e.g. a bare ServiceWorker or a custom { emit } without receive.
  • Boxing a value that cannot be serialized (e.g. a circular structure) rejects the returned promise with a TypeError; so does reviving a malformed/cyclic init payload from a peer.
  • A peer’s protocol close arriving before init rejects the pending promise with Error('osra: peer closed the connection').