Custom revivables
Every type osra knows how to send is implemented as a small module: a guard that recognizes the value, a box function that flattens it into something the wire can carry, and a revive function that rebuilds it on the other side.
The defaults are an ordered list of those modules, and the revivableModules option of expose() hands you that list so you can extend it, reorder it, or replace parts of it.
This is also the way to get your own classes across.
If you try to send an instance of your own class, osra rejects it at compile time, because it has no way to rebuild an arbitrary prototype on the other side.
And even if it went through, structured clone would only keep the instance’s own data fields, so it would arrive as a plain object stripped of its methods.
A custom module fills exactly that gap: you tell osra how to flatten the instance, and how to build a real one back from those fields.
A module
Section titled “A module”Here is a module for a small Point class:
import type { type RevivableContext<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"), ... 23 more ..., { ...; }]> = { transport: Transport; remoteUuid: Uuid; sendMessage: (message: MessageFields & Record<string, unknown>) => void; revivableModules: TModules; eventTarget: MessageEventTarget<TModules>;}
RevivableContext, type RevivableModule<T extends string = string, T2 = any, T3 extends BoxBase<T> = any, T4 extends MessageFields = MessageFields> = { readonly type: T; readonly isType: (value: unknown) => value is T2; readonly objectsOnly?: boolean; readonly box: ((value: T2, context: RevivableContext<any>) => T3) | ((...args: any[]) => any); readonly revive: (value: T3, context: RevivableContext<any>) => T2; readonly init?: (context: RevivableContext<any>) => void; readonly Messages?: T4;}
RevivableModule } from 'osra'import { type BoxBase<T extends string = string> = { readonly __OSRA_BOX__: "revivable";} & { type: T;}const BoxBase: { readonly __OSRA_BOX__: "revivable";}
BoxBase } from 'osra'
class class Point
Point { constructor(public Point.x: number
x: number, public Point.y: number
y: number) {} Point.distance(): number
distance() { return var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math.Math.hypot(...values: number[]): number
Returns the square root of the sum of squares of its arguments.
hypot(this.Point.x: number
x, this.Point.y: number
y) }}
const const point: { readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}
point = { type: string
type: 'point' as type const = "point"
const, objectsOnly?: boolean | undefined
Declare true when isType never claims a primitive (string, number, boolean, bigint, symbol,
undefined, null). The walker then skips the module for primitive leaves, which is most of what a
large payload is made of. Leaving it off is always safe: the default is to call isType for
every value, exactly as before. objectsOnlyFlagsAreHonest in the suite calls every flagged
module with a set of primitives and fails if one of them says yes.
objectsOnly: true, isType: (value: unknown) => value is any
isType: (value: unknown
value: unknown): value: unknown
value is class Point
Point => value: unknown
value instanceof class Point
Point, box: ((...args: any[]) => any) | ((value: any, context: RevivableContext<any>) => any)
box: (value: Point
value: class Point
Point, _context: RevivableContext
_context: type RevivableContext<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"), ... 23 more ..., { ...; }]> = { transport: Transport; remoteUuid: Uuid; sendMessage: (message: MessageFields & Record<string, unknown>) => void; revivableModules: TModules; eventTarget: MessageEventTarget<TModules>;}
RevivableContext) => ({ ...const BoxBase: { readonly __OSRA_BOX__: "revivable";}
BoxBase, type: "point"
type: 'point' as type const = "point"
const, x: number
x: value: Point
value.Point.x: number
x, y: number
y: value: Point
value.Point.y: number
y }), revive: (value: any, context: RevivableContext<any>) => any
revive: (value: { x: number; y: number;}
value: { x: number
x: number, y: number
y: number }, _context: RevivableContext
_context: type RevivableContext<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"), ... 23 more ..., { ...; }]> = { transport: Transport; remoteUuid: Uuid; sendMessage: (message: MessageFields & Record<string, unknown>) => void; revivableModules: TModules; eventTarget: MessageEventTarget<TModules>;}
RevivableContext) => new constructor Point(x: number, y: number): Point
Point(value: { x: number; y: number;}
value.x: number
x, value: { x: number; y: number;}
value.y: number
y)} as type const = { readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}
const satisfies type RevivableModule<T extends string = string, T2 = any, T3 extends BoxBase<T> = any, T4 extends MessageFields = MessageFields> = { readonly type: T; readonly isType: (value: unknown) => value is T2; readonly objectsOnly?: boolean; readonly box: ((value: T2, context: RevivableContext<any>) => T3) | ((...args: any[]) => any); readonly revive: (value: T3, context: RevivableContext<any>) => T2; readonly init?: (context: RevivableContext<any>) => void; readonly Messages?: T4;}
RevivableModule| Field | |
|---|---|
type |
Names the box on the wire. Keep it unique within the list, reviving picks the first module whose type matches. |
isType |
Decides whether this module handles a value on the way out. |
box |
Flattens the value into something sendable. Spread BoxBase into the result so osra recognizes it as a box, and keep the type field on it. |
revive |
Rebuilds the value on the receiving side, from exactly the fields box produced. |
objectsOnly |
Optional. Set it to true when isType never claims a primitive, so osra skips your module for strings, numbers and the like. Most modules qualify. |
init |
Optional. Runs once when a connection starts, see live values. |
Messages |
Optional. Declares the custom message types your module sends, see live values. |
One thing to note is that both box and revive are synchronous.
Osra runs them inline while it serializes and deserializes messages, so they must return their result directly, never a Promise.
If your type needs to keep talking after it was sent, the way functions and streams do, that is what live values are for.
Also keep in mind that whatever box returns is sent as is, osra does not walk into your box looking for more values to convert.
Plain JSON data like the two numbers above is always safe, but a field holding a Date, a function, or another Point needs to be boxed by you, see nested values.
Using it
Section titled “Using it”revivableModules is a function that receives the default module list and returns the list you actually want.
Putting your module in front of the defaults is the usual shape, and typing the parameter as DefaultRevivableModules is what lets osra infer the final list from the option:
import type { type DefaultRevivableModules = 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
DefaultRevivableModules } from 'osra'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"), typeof import("osra/build/revivables/typed-array"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), ... 16 more ..., { ...;}], const TTransport extends Transport = Transport, const TValue = Capable<...>, TResult = Remote<...>>(value: TValue extends Contextual<infer U> ? Contextual<CapableCheck<...>> : CapableCheck<...>, options: Omit<StartConnectionsOptions<TModules>, "connection"> & { transport: TTransport; connection?: (connected: Connected<Remote<T>>) => TResult;}) => Exposed<TResult>
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 valuefor 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.
expose } from 'osra'import { class Point
Point, const point: { readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}
point } from './point'
const const withPoint: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), ... 24 more ..., { ...;}]
withPoint = (defaults: 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
defaults: type DefaultRevivableModules = 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
DefaultRevivableModules) => [const point: { readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}
point, ...defaults: 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
defaults] as type const = readonly [{ readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), ... 24 more ..., { ...;}]
const
const const payload: { scale: (p: Point) => Point;}
payload = { scale: (p: Point) => Point
scale: (p: Point
p: class Point
Point) => new new Point(x: number, y: number): Point
Point(p: Point
p.Point.x: number
x * 2, p: Point
p.Point.y: number
y * 2) }
expose<unknown, readonly [{ readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), ... 24 more ..., { ...;}], Worker, { ...;}, unknown>(value: { ...;}, options: Omit<...> & { ...;}): Exposed<...>
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 valuefor 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.
expose(const payload: { scale: (p: Point) => Point;}
payload, { transport: Transport & Worker
transport, revivableModules?: ((defaults: DefaultRevivableModules) => readonly [{ readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), ... 24 more ..., { ...;}]) | undefined
Configure the revivable module list. Receives the defaults and
returns the final ordered list - add modules, drop defaults, reorder,
or override per-type as needed.
revivableModules: const withPoint: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), ... 24 more ..., { ...;}]
withPoint })
const const remote: { scale: (p: Point) => Promise<{ x: number; y: number; distance: () => Promise<number>; }>;}
remote = await expose<{ scale: (p: Point) => Point;}, readonly [{ readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), ... 24 more ..., { ...;}], Transport, Capable<...>, { ...;}>(value: Capable<...>, options: Omit<...> & { ...;}): Exposed<...>
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 valuefor 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.
expose<typeof const payload: { scale: (p: Point) => Point;}
payload, type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function type
ReturnType<typeof const withPoint: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), ... 24 more ..., { ...;}]
withPoint>>( {}, { transport: Transport
transport, revivableModules?: ((defaults: DefaultRevivableModules) => readonly [{ readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), ... 24 more ..., { ...;}]) | undefined
Configure the revivable module list. Receives the defaults and
returns the final ordered list - add modules, drop defaults, reorder,
or override per-type as needed.
revivableModules: const withPoint: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "point"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { type: "point"; x: number; y: number; __OSRA_BOX__: "revivable"; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), typeof import("osra/build/revivables/array-buffer"), ... 24 more ..., { ...;}]
withPoint })
const const doubled: { x: number; y: number; distance: () => Promise<number>;}
doubled = await const remote: { scale: (p: Point) => Promise<{ x: number; y: number; distance: () => Promise<number>; }>;}
remote.scale: (p: Point) => Promise<{ x: number; y: number; distance: () => Promise<number>;}>
scale(new new Point(x: number, y: number): Point
Point(3, 4))const doubled: { x: number; y: number; distance: () => Promise<number>;}
doubled.function distance(): Promise<number>
distance() // 10, a real Point with its methodsBoth sides need the same list.
The peer needs your revive to rebuild the value, and the same type string to find the right module.
A side that does not know the type hands your code the raw box, a plain object carrying the wire fields, instead of a Point.
On the side that names the peer’s type, pass the module list’s type as the second type argument too, ReturnType<typeof withPoint> above.
That second type argument is what lets the compile time Capable check know about your type.
TypeScript has no partial inference, so naming typeof payload alone resets the module list back to the defaults, and the error lands on revivableModules: withPoint, since the option then expects a function returning the default list.
The bare expose(payload, ...) call needs no type arguments at all, everything is inferred from the options.
One thing to note is that Remote<T> does not know about your module either, so on the peer’s type your class’s methods show up as async.
At runtime they are the real methods of a real instance, doubled.distance() above returns 10 synchronously.
Note: none of this is tied to structured transports.
A custom module works on JSON transports too, as long as what its box produces is JSON safe or boxed the rest of the way.
Ordering
Section titled “Ordering”Boxing walks the list front to back and the first isType that matches wins.
Reviving looks the module up by its type string instead, so on that side the order does not matter.
This means that putting your module first lets it win over the defaults.
That is what you want when your class extends something a default already handles, an Error subclass for example: your module sees the value before the generic error one does, so your extra fields survive.
Dropping or replacing a default works the same way, since you receive the whole list and return whatever you like:
const const modules: (defaults: DefaultRevivableModules) => readonly [RevivableModule, ...(typeof import("osra/build/revivables/transfer") | typeof import("osra/build/revivables/identity") | typeof import("osra/build/revivables/array-buffer") | typeof import("osra/build/revivables/headers") | typeof import("osra/build/revivables/error") | typeof import("osra/build/revivables/typed-array") | typeof import("osra/build/revivables/promise") | typeof import("osra/build/revivables/function") | typeof import("osra/build/revivables/message-port") | typeof import("osra/build/revivables/readable-stream") | ... 16 more ... | { ...;})[]]
modules = (defaults: 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
defaults: type DefaultRevivableModules = 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
DefaultRevivableModules) => [const myDate: RevivableModule
myDate, ...defaults: 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
defaults.ReadonlyArray<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") | ... 23 more ... | { ...; }>.filter<typeof import("osra/build/revivables/transfer") | typeof import("osra/build/revivables/identity") | typeof import("osra/build/revivables/array-buffer") | typeof import("osra/build/revivables/headers") | typeof import("osra/build/revivables/error") | typeof import("osra/build/revivables/typed-array") | typeof import("osra/build/revivables/promise") | typeof import("osra/build/revivables/function") | typeof import("osra/build/revivables/message-port") | typeof import("osra/build/revivables/readable-stream") | typeof import("osra/build/revivables/writable-stream") | typeof import("osra/build/revivables/abort-signal") | ... 14 more ... | { ...;}>(predicate: (value: typeof import("osra/build/revivables/transfer") | ... 26 more ... | { ...;}, index: number, array: readonly (typeof import("osra/build/revivables/transfer") | ... 26 more ... | { ...;})[]) => value is typeof import("osra/build/revivables/transfer") | ... 25 more ... | { ...;}, thisArg?: any): (typeof import("osra/build/revivables/transfer") | ... 25 more ... | { ...;})[] (+1 overload)
Returns the elements of an array that meet the condition specified in a callback function.
filter(m: 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") | typeof import("osra/build/revivables/promise") | typeof import("osra/build/revivables/function") | typeof import("osra/build/revivables/message-port") | typeof import("osra/build/revivables/readable-stream") | typeof import("osra/build/revivables/writable-stream") | ... 15 more ... | { ...;}
m => m: 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") | typeof import("osra/build/revivables/promise") | typeof import("osra/build/revivables/function") | typeof import("osra/build/revivables/message-port") | typeof import("osra/build/revivables/readable-stream") | typeof import("osra/build/revivables/writable-stream") | ... 15 more ... | { ...;}
m.type: "bigint" | "symbol" | "undefined" | "function" | "transfer" | "identity" | "arrayBuffer" | "date" | "headers" | "error" | "typedArray" | "promise" | "messagePort" | "readableStream" | "writableStream" | "abortSignal" | "response" | "request" | "map" | "set" | "event" | "asyncIterator" | "nonFiniteNumber" | "clonable" | "transferable" | "blob" | "eventTarget" | "unclonable"
type !== 'date')] as type const = readonly [RevivableModule, ...(typeof import("osra/build/revivables/transfer") | typeof import("osra/build/revivables/identity") | typeof import("osra/build/revivables/array-buffer") | typeof import("osra/build/revivables/headers") | typeof import("osra/build/revivables/error") | typeof import("osra/build/revivables/typed-array") | typeof import("osra/build/revivables/promise") | typeof import("osra/build/revivables/function") | typeof import("osra/build/revivables/message-port") | typeof import("osra/build/revivables/readable-stream") | typeof import("osra/build/revivables/writable-stream") | ... 15 more ... | { ...;})[]]
constBe careful when reordering the defaults though, a few of them depend on their position:
- The async iterator module sits before the fallbacks, otherwise generators would fall through and coerce to
{}. clonableandtransferablesit beforeeventTarget, becauseOffscreenCanvasand friends also extendEventTarget.blobsits afterclonable, so aFile, which extendsBlob, keeps riding the more specificclonablepath.eventTargetsits last among the modules matchingEventTargetsubclasses, sinceMessagePortandAbortSignalneed first pick.unclonablesits at the very end: it is the catch-all that probes values withstructuredClone()and turns whatever fails into{}.
Nested values
Section titled “Nested values”When your module claims a value, osra stops walking: your box runs, and its result ships untouched.
This means that any field of your box that itself needs osra’s treatment, a function, a stream, another custom class, has to go through the walker explicitly.
Call recursiveBox on it with the context you were handed, and mirror it with recursiveRevive on the other side.
This is exactly what the built-in Map, Error and CustomEvent modules do for their own fields:
const const result: { type: "result"; objectsOnly: boolean; isType: (value: unknown) => value is Result; box: (value: Result, context: RevivableContext) => { type: "result"; value: never; at: never; __OSRA_BOX__: "revivable"; }; revive: (value: { value: unknown; at: unknown; }, context: RevivableContext) => Result;}
result = { type: "result"
type: 'result' as type const = "result"
const, objectsOnly: boolean
objectsOnly: true, isType: (value: unknown) => value is Result
isType: (value: unknown
value: unknown): value: unknown
value is class Result
Result => value: unknown
value instanceof class Result
Result, box: (value: Result, context: RevivableContext) => { type: "result"; value: never; at: never; __OSRA_BOX__: "revivable";}
box: (value: Result
value: class Result
Result, context: RevivableContext
context: type RevivableContext<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"), ... 23 more ..., { ...; }]> = { transport: Transport; remoteUuid: Uuid; sendMessage: (message: MessageFields & Record<string, unknown>) => void; revivableModules: TModules; eventTarget: MessageEventTarget<TModules>;}
RevivableContext) => ({ ...const BoxBase: { readonly __OSRA_BOX__: "revivable";}
BoxBase, type: "result"
type: 'result' as type const = "result"
const, value: never
value: recursiveBox<never, 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]>(value: never, context: RevivableContext<...>): never
recursiveBox(value: Result
value.Result.value: unknown
value as never, context: RevivableContext
context), at: never
at: recursiveBox<never, 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]>(value: never, context: RevivableContext<...>): never
recursiveBox(value: Result
value.Result.at: Date
at as never, context: RevivableContext
context) }), revive: (value: { value: unknown; at: unknown;}, context: RevivableContext) => Result
revive: (value: { value: unknown; at: unknown;}
value: { value: unknown
value: unknown, at: unknown
at: unknown }, context: RevivableContext
context: type RevivableContext<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"), ... 23 more ..., { ...; }]> = { transport: Transport; remoteUuid: Uuid; sendMessage: (message: MessageFields & Record<string, unknown>) => void; revivableModules: TModules; eventTarget: MessageEventTarget<TModules>;}
RevivableContext) => new constructor Result(value: unknown, at: Date): Result
Result( recursiveRevive<never, 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]>(value: never, context: RevivableContext<...>): never
recursiveRevive(value: { value: unknown; at: unknown;}
value.value: unknown
value as never, context: RevivableContext
context), recursiveRevive<never, 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]>(value: never, context: RevivableContext<...>): never
recursiveRevive(value: { value: unknown; at: unknown;}
value.at: unknown
at as never, context: RevivableContext
context) as unknown as interface Date
Enables basic storage and retrieval of dates and times.
Date )}Fields holding plain JSON data (strings, numbers, booleans, and arrays or plain objects of those) can stay raw, they survive any transport as they are.
On a structured transport other clonables happen to survive raw too, but on a JSON transport a raw Date field would silently turn into a string, so going through recursiveBox is what keeps a module correct on both modes.
One thing to note is that a module claiming a value in place, rather than a wrapper around one, needs boxClaimedValue(value, context, yourType) for its payload instead.
It walks the value through every other module while skipping yours, which recursiveBox on that same value would hand straight back to you. identity() is the built-in example, since it marks a reference rather than wrapping it.
Live values
Section titled “Live values”A wrapper like Point is done the moment it arrives.
A live value keeps talking after it was sent, the way a function keeps receiving calls, or a stream keeps receiving chunks.
Both box and revive receive the connection’s context as their second argument, and it has everything a live value needs:
| Field | |
|---|---|
sendMessage |
Sends a message of your own to the peer. It must carry a type of your choosing and the remoteUuid of the connection. |
eventTarget |
Where this connection’s incoming messages are dispatched, as CustomEvents with the message on event.detail. |
remoteUuid |
The peer this connection belongs to. |
transport |
The normalized transport. Useful as isJsonOnlyTransport(context.transport) when your wire format differs per mode. |
revivableModules |
The resolved module list of this connection. |
The pattern every built-in live value follows is that box registers some local state and returns a plain descriptor, updates flow through sendMessage, and a listener on eventTarget picks them up on the other side.
Here it is for a Cell, a value you can subscribe to.
The peer gets a cell of its own that follows the original:
import type { type Capable<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"), ... 23 more ..., { ...; }], Ctx extends RevivableContext = RevivableContext> = CapableBase<Ctx> | InferRevivables<TModules, Ctx> | { [key: string]: Capable<TModules, Ctx>;} | readonly Capable<TModules, Ctx>[] | Map<Capable<TModules, Ctx>, Capable<TModules, Ctx>> | Set<Capable<TModules, Ctx>>
Capable, type DefaultRevivableModules = 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
DefaultRevivableModules, type RevivableContext<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"), ... 23 more ..., { ...; }]> = { transport: Transport; remoteUuid: Uuid; sendMessage: (message: MessageFields & Record<string, unknown>) => void; revivableModules: TModules; eventTarget: MessageEventTarget<TModules>;}
RevivableContext, type RevivableModule<T extends string = string, T2 = any, T3 extends BoxBase<T> = any, T4 extends MessageFields = MessageFields> = { readonly type: T; readonly isType: (value: unknown) => value is T2; readonly objectsOnly?: boolean; readonly box: ((value: T2, context: RevivableContext<any>) => T3) | ((...args: any[]) => any); readonly revive: (value: T3, context: RevivableContext<any>) => T2; readonly init?: (context: RevivableContext<any>) => void; readonly Messages?: T4;}
RevivableModule } from 'osra'import { type BoxBase<T extends string = string> = { readonly __OSRA_BOX__: "revivable";} & { type: T;}const BoxBase: { readonly __OSRA_BOX__: "revivable";}
BoxBase, const onTeardown: (scope: WeakKey, fn: () => void) => (() => void)
onTeardown, const recursiveBox: <T extends Capable, TModules extends readonly RevivableModule[]>(value: T, context: RevivableContext<TModules>) => DeepReplaceWithBox<T, TModules[number]>
recursiveBox, const recursiveRevive: <T extends Capable, TModules extends readonly RevivableModule[]>(value: T, context: RevivableContext<TModules>) => DeepReplaceWithRevive<T, TModules[number]>
recursiveRevive } from 'osra'
export class class Cell<T>
Cell<function (type parameter) T in Cell<T>
T> { #value: function (type parameter) T in Cell<T>
T #listeners = new var Set: SetConstructornew <(value: T) => void>(iterable?: Iterable<(value: T) => void> | null | undefined) => Set<(value: T) => void> (+1 overload)
Set<(value: T
value: function (type parameter) T in Cell<T>
T) => void>() constructor(value: T
value: function (type parameter) T in Cell<T>
T) { this.#value = value: T
value } get Cell<T>.value: T
value() { return this.#value } Cell<T>.set(value: T): void
set(value: T
value: function (type parameter) T in Cell<T>
T) { this.#value = value: T
value for (const const listener: (value: T) => void
listener of this.#listeners) const listener: (value: T) => void
listener(value: T
value) } Cell<T>.subscribe(listener: (value: T) => void): () => void
subscribe(listener: (value: T) => void
listener: (value: T
value: function (type parameter) T in Cell<T>
T) => void) { this.#listeners.Set<(value: T) => void>.add(value: (value: T) => void): Set<(value: T) => void>
Appends a new element with a specified value to the end of the Set.
add(listener: (value: T) => void
listener) return () => { this.#listeners.Set<(value: T) => void>.delete(value: (value: T) => void): boolean
Removes a specified value from the Set.
delete(listener: (value: T) => void
listener) } }}
type type CellSet = { type: "cell-set"; cellId: string; value: Capable;}
CellSet = { type: "cell-set"
type: 'cell-set', cellId: string
cellId: string, value: Capable
value: type Capable<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"), ... 23 more ..., { ...; }], Ctx extends RevivableContext = RevivableContext> = CapableBase<Ctx> | InferRevivables<TModules, Ctx> | { [key: string]: Capable<TModules, Ctx>;} | readonly Capable<TModules, Ctx>[] | Map<Capable<TModules, Ctx>, Capable<TModules, Ctx>> | Set<Capable<TModules, Ctx>>
Capable }const const isCellSet: (message: { type: string;}) => message is CellSet
isCellSet = (message: { type: string;}
message: { type: string
type: string }): message: { type: string;}
message is type CellSet = { type: "cell-set"; cellId: string; value: Capable;}
CellSet => message: { type: string;}
message.type: string
type === 'cell-set'
export const const cell: { readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ base64Buffer: string; } | { arrayBuffer: ArrayBuffer; })) | { __OSRA_BOX__: "revivable"; type: "date"; ISOString: string; } | ... 20 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}
cell = { type: string
type: 'cell' as type const = "cell"
const, objectsOnly?: boolean | undefined
Declare true when isType never claims a primitive (string, number, boolean, bigint, symbol,
undefined, null). The walker then skips the module for primitive leaves, which is most of what a
large payload is made of. Leaving it off is always safe: the default is to call isType for
every value, exactly as before. objectsOnlyFlagsAreHonest in the suite calls every flagged
module with a set of primitives and fails if one of them says yes.
objectsOnly: true, isType: (value: unknown) => value is any
isType: (value: unknown
value: unknown): value: unknown
value is class Cell<T>
Cell<any> => value: unknown
value instanceof class Cell<T>
Cell, box: ((...args: any[]) => any) | ((value: any, context: RevivableContext<any>) => any)
box: (value: Cell<any>
value: class Cell<T>
Cell<any>, context: RevivableContext
context: type RevivableContext<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"), ... 23 more ..., { ...; }]> = { transport: Transport; remoteUuid: Uuid; sendMessage: (message: MessageFields & Record<string, unknown>) => void; revivableModules: TModules; eventTarget: MessageEventTarget<TModules>;}
RevivableContext) => { const const cellId: `${string}-${string}-${string}-${string}-${string}`
cellId = var crypto: Crypto
crypto.Crypto.randomUUID(): `${string}-${string}-${string}-${string}-${string}`
The randomUUID() method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator.
Available only in secure contexts.
randomUUID() const const unsubscribe: () => void
unsubscribe = value: Cell<any>
value.Cell<any>.subscribe(listener: (value: any) => void): () => void
subscribe(next: any
next => { context: RevivableContext
context.sendMessage: (message: MessageFields & Record<string, unknown>) => void
Typed as a broad dispatcher so revivables can post their own message
variants without triggering contravariant function-parameter mismatches
across modules. The shape is enforced structurally via MessageFields.
sendMessage({ type: string
type: 'cell-set', remoteUuid: `${string}-${string}-${string}-${string}-${string}`
remoteUuid: context: RevivableContext
context.remoteUuid: `${string}-${string}-${string}-${string}-${string}`
remoteUuid, cellId: `${string}-${string}-${string}-${string}-${string}`
cellId, value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer";} & ({ base64Buffer: string;} | { arrayBuffer: ArrayBuffer;})) | { __OSRA_BOX__: "revivable"; type: "date"; ISOString: string;} | { __OSRA_BOX__: "revivable"; type: "headers"; entries: [string, string][];} | BoxedError | BoxedTypedArray<TypedArray, RevivableContext> | ... 17 more ... | undefined
value: recursiveBox<any, 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]>(value: any, context: RevivableContext<...>): number | ... 27 more ... | undefined
recursiveBox(next: any
next, context: RevivableContext
context) }) }) function onTeardown(scope: WeakKey, fn: () => void): (() => void)
onTeardown(context: RevivableContext
context, const unsubscribe: () => void
unsubscribe) return { ...const BoxBase: { readonly __OSRA_BOX__: "revivable";}
BoxBase, type: "cell"
type: 'cell' as type const = "cell"
const, cellId: `${string}-${string}-${string}-${string}-${string}`
cellId, value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer";} & ({ base64Buffer: string;} | { arrayBuffer: ArrayBuffer;})) | { __OSRA_BOX__: "revivable"; type: "date"; ISOString: string;} | { __OSRA_BOX__: "revivable"; type: "headers"; entries: [string, string][];} | BoxedError | BoxedTypedArray<TypedArray, RevivableContext> | ... 17 more ... | undefined
value: recursiveBox<any, 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]>(value: any, context: RevivableContext<...>): number | ... 27 more ... | undefined
recursiveBox(value: Cell<any>
value.Cell<any>.value: any
value, context: RevivableContext
context) } }, revive: (value: any, context: RevivableContext<any>) => any
revive: (boxed: { cellId: string; value: Capable;}
boxed: { cellId: string
cellId: string, value: Capable
value: type Capable<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"), ... 23 more ..., { ...; }], Ctx extends RevivableContext = RevivableContext> = CapableBase<Ctx> | InferRevivables<TModules, Ctx> | { [key: string]: Capable<TModules, Ctx>;} | readonly Capable<TModules, Ctx>[] | Map<Capable<TModules, Ctx>, Capable<TModules, Ctx>> | Set<Capable<TModules, Ctx>>
Capable }, context: RevivableContext
context: type RevivableContext<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"), ... 23 more ..., { ...; }]> = { transport: Transport; remoteUuid: Uuid; sendMessage: (message: MessageFields & Record<string, unknown>) => void; revivableModules: TModules; eventTarget: MessageEventTarget<TModules>;}
RevivableContext) => { const const revived: Cell<number | Blob | Clonable | Transferable | undefined>
revived = new constructor Cell<number | Blob | Clonable | Transferable | undefined>(value: number | Blob | Clonable | Transferable | undefined): Cell<number | Blob | Clonable | Transferable | undefined>
Cell(recursiveRevive<Capable, 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]>(value: Capable, context: RevivableContext<...>): number | ... 3 more ... | undefined
recursiveRevive(boxed: { cellId: string; value: Capable;}
boxed.value: Capable
value, context: RevivableContext
context)) context: RevivableContext
context.eventTarget: MessageEventTarget<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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]>
eventTarget.TypedEventTarget<MessageEventMap<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"), ... 23 more ..., { ...; }]>>.addEventListener<"message">(type: "message", listener: ((event: CustomEvent<Message<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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), ... 16 more ..., { ...;}]>>) => void) | null, options?: boolean | AddEventListenerOptions): void (+1 overload)
The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
addEventListener('message', (event: CustomEvent<{ type: string;}>
event: interface CustomEvent<T = any>
The CustomEvent interface can be used to attach custom data to an event generated by an application.
CustomEvent<{ type: string
type: string }>) => { if (!const isCellSet: (message: { type: string;}) => message is CellSet
isCellSet(event: CustomEvent<{ type: string;}>
event.CustomEvent<{ type: string; }>.detail: { type: string;}
The read-only detail property of the CustomEvent interface returns any data passed when initializing the event.
detail) || event: CustomEvent<{ type: string;}>
event.CustomEvent<{ type: string; }>.detail: CellSet
The read-only detail property of the CustomEvent interface returns any data passed when initializing the event.
detail.cellId: string
cellId !== boxed: { cellId: string; value: Capable;}
boxed.cellId: string
cellId) return const revived: Cell<number | Blob | Clonable | Transferable | undefined>
revived.Cell<number | Blob | Clonable | Transferable | undefined>.set(value: number | Blob | Clonable | Transferable | undefined): void
set(recursiveRevive<Capable, 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]>(value: Capable, context: RevivableContext<...>): number | ... 3 more ... | undefined
recursiveRevive(event: CustomEvent<{ type: string;}>
event.CustomEvent<{ type: string; }>.detail: CellSet
The read-only detail property of the CustomEvent interface returns any data passed when initializing the event.
detail.value: Capable
value, context: RevivableContext
context)) }) return const revived: Cell<number | Blob | Clonable | Transferable | undefined>
revived }} as type const = { readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ base64Buffer: string; } | { arrayBuffer: ArrayBuffer; })) | { __OSRA_BOX__: "revivable"; type: "date"; ISOString: string; } | ... 20 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}
const satisfies type RevivableModule<T extends string = string, T2 = any, T3 extends BoxBase<T> = any, T4 extends MessageFields = MessageFields> = { readonly type: T; readonly isType: (value: unknown) => value is T2; readonly objectsOnly?: boolean; readonly box: ((value: T2, context: RevivableContext<any>) => T3) | ((...args: any[]) => any); readonly revive: (value: T3, context: RevivableContext<any>) => T2; readonly init?: (context: RevivableContext<any>) => void; readonly Messages?: T4;}
RevivableModule
export const const withCell: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ base64Buffer: string; } | { arrayBuffer: ArrayBuffer; })) | ... 21 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}]
withCell = (defaults: 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
defaults: type DefaultRevivableModules = 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
DefaultRevivableModules) => [const cell: { readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ base64Buffer: string; } | { arrayBuffer: ArrayBuffer; })) | { __OSRA_BOX__: "revivable"; type: "date"; ISOString: string; } | ... 20 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}
cell, ...defaults: 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"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), typeof import("osra/build/revivables/writable-stream"), ... 15 more ..., { ...;}]
defaults] as type const = readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ base64Buffer: string; } | { arrayBuffer: ArrayBuffer; })) | { __OSRA_BOX__: "revivable"; type: "date"; ISOString: string; } | ... 20 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}]
constA few things to note about it:
boxgives each cell an id and puts it in the box, sorevivecan tell which messages are for it. Every message on the connection reaches every listener, so filtering bytypeand by that id is what keeps modules from hearing each other’s traffic.sendMessagewraps your message in osra’s envelope and routes it to this connection’s peer, which is why it needscontext.remoteUuid.- The listener types its event as
CustomEvent<{ type: string }>, since the messages of every module flow through the same target, and narrows down to its own message with a type guard. onTeardown(context, fn)runsfnwhen the connection closes, so the subscription does not outlive it.
Using it is the same as before, both sides register the module and the reading side names it in the second type argument:
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"), typeof import("osra/build/revivables/typed-array"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), ... 16 more ..., { ...;}], const TTransport extends Transport = Transport, const TValue = Capable<...>, TResult = Remote<...>>(value: TValue extends Contextual<infer U> ? Contextual<CapableCheck<...>> : CapableCheck<...>, options: Omit<StartConnectionsOptions<TModules>, "connection"> & { transport: TTransport; connection?: (connected: Connected<Remote<T>>) => TResult;}) => Exposed<TResult>
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 valuefor 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.
expose } from 'osra'import { class Cell<T>
Cell, const withCell: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ ...; } | { ...; })) | ... 21 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}]
withCell } from './cell'
const const temperature: Cell<number>
temperature = new new Cell<number>(value: number): Cell<number>
Cell(20)const const payload: { temperature: Cell<number>; bump: (by: number) => void;}
payload = { temperature: Cell<number>
temperature, bump: (by: number) => void
bump: (by: number
by: number) => const temperature: Cell<number>
temperature.Cell<number>.set(value: number): void
set(const temperature: Cell<number>
temperature.Cell<number>.value: number
value + by: number
by)}export type type Payload = { temperature: Cell<number>; bump: (by: number) => void;}
Payload = typeof const payload: { temperature: Cell<number>; bump: (by: number) => void;}
payload
expose<unknown, readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ base64Buffer: string; } | { arrayBuffer: ArrayBuffer; })) | ... 21 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}], typeof globalThis, { ...;}, unknown>(value: { ...;}, options: Omit<...> & { ...;}): Exposed<...>
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 valuefor 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.
expose(const payload: { temperature: Cell<number>; bump: (by: number) => void;}
payload, { transport: Transport & typeof globalThis
transport: module globalThis
globalThis, revivableModules?: ((defaults: DefaultRevivableModules) => readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ ...; } | { ...; })) | ... 21 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}]) | undefined
Configure the revivable module list. Receives the defaults and
returns the final ordered list - add modules, drop defaults, reorder,
or override per-type as needed.
revivableModules: const withCell: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ ...; } | { ...; })) | ... 21 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}]
withCell })import type { type Payload = { temperature: Cell<number>; bump: (by: number) => void;}
Payload } from './worker'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"), typeof import("osra/build/revivables/typed-array"), typeof import("osra/build/revivables/promise"), typeof import("osra/build/revivables/function"), typeof import("osra/build/revivables/message-port"), typeof import("osra/build/revivables/readable-stream"), ... 16 more ..., { ...;}], const TTransport extends Transport = Transport, const TValue = Capable<...>, TResult = Remote<...>>(value: TValue extends Contextual<infer U> ? Contextual<CapableCheck<...>> : CapableCheck<...>, options: Omit<StartConnectionsOptions<TModules>, "connection"> & { transport: TTransport; connection?: (connected: Connected<Remote<T>>) => TResult;}) => Exposed<TResult>
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 valuefor 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.
expose } from 'osra'import { const withCell: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ... 22 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}]
withCell } from './cell'
const { const temperature: { readonly value: number; set: (value: number) => Promise<void>; subscribe: (listener: (value: number) => void) => Promise<() => Promise<void>>;}
temperature, const bump: (by: number) => Promise<void>
bump } = await expose<{ temperature: Cell<number>; bump: (by: number) => void;}, readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ({ __OSRA_BOX__: "revivable"; type: "arrayBuffer"; } & ({ ...; } | { ...; })) | ... 21 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}], Transport, Capable<...>, { ...;}>(value: Capable<...>, options: Omit<...> & { ...;}): Exposed<...>
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 valuefor 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.
expose<type Payload = { temperature: Cell<number>; bump: (by: number) => void;}
Payload, type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
Obtain the return type of a function type
ReturnType<typeof const withCell: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ... 22 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}]
withCell>>( {}, { transport: Transport
transport: const worker: Worker
worker, revivableModules?: ((defaults: DefaultRevivableModules) => readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ... 22 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}]) | undefined
Configure the revivable module list. Receives the defaults and
returns the final ordered list - add modules, drop defaults, reorder,
or override per-type as needed.
revivableModules: const withCell: (defaults: DefaultRevivableModules) => readonly [{ readonly type: "cell"; readonly objectsOnly: true; readonly isType: (value: unknown) => value is Cell<any>; readonly box: (value: Cell<any>, context: RevivableContext) => { type: "cell"; cellId: `${string}-${string}-${string}-${string}-${string}`; value: number | Blob | Clonable | Transferable | BoxedTransfer<Capable> | BoxedIdentity<Capable> | ... 22 more ... | undefined; __OSRA_BOX__: "revivable"; }; readonly revive: (boxed: { cellId: string; value: Capable; }, context: RevivableContext) => Cell<...>;}, ... 27 more ..., { ...;}]
withCell })
const temperature: { readonly value: number; set: (value: number) => Promise<void>; subscribe: (listener: (value: number) => void) => Promise<() => Promise<void>>;}
temperature.value: number
value // 20const temperature: { readonly value: number; set: (value: number) => Promise<void>; subscribe: (listener: (value: number) => void) => Promise<() => Promise<void>>;}
temperature.function subscribe(listener: (value: number) => void): Promise<() => Promise<void>>
subscribe(value: number
value => var console: Console
console.Console.log(...data: any[]): void
The console.log() static method outputs a message to the console.
log(value: number
value))
await const bump: (by: number) => Promise<void>
bump(5) // logs 25, and temperature.value is now 25Two optional module fields exist to support this pattern:
init(context)runs once per connection when it starts, before the first value is boxed, in the order of the module list. It is the place to set up per connection state and attach a singleeventTargetlistener, instead of one per revived value like above.Messagesdeclares, at the type level, the message variants your module sends, so thatMessage<YourModules>includes them. Every custom message must carry a literaltypestring and theremoteUuidof the connection.
If you want to go further, the built-in modules in src/revivables/ are the reference implementations, with message-port.ts being the canonical example of the full pattern.