Custom revivables
Classes and prototypes are not preserved by default — values cross as plain data. A revivable module fixes that for a type you own: it tells osra how to recognize the value, flatten it into a serializable box, and reconstruct it on the other side.
Anatomy of a RevivableModule
Section titled “Anatomy of a RevivableModule”A revivable module owns one type: type (unique string, identical on both sides), isType (runtime guard used for boxing), box (value → JSON/clone-safe box), revive (box → value), and optionally init (per-connection setup):
type RevivableModule = { readonly type: string readonly isType: (value: unknown) => value is T readonly box: (value: T, context: RevivableContext) => BoxBase & { type: string } readonly revive: (boxed, context: RevivableContext) => T readonly init?: (context: RevivableContext) => void}box turns a matched value into a plain serializable box (spread BoxBase in to tag it); revive reconstructs it on the other side. context gives access to sendMessage/eventTarget for modules that need their own wire traffic, and recursiveBox/recursiveRevive (exported) handle nested values.
Example: preserving a class instance
Section titled “Example: preserving a class instance”import type { type RevivableContext<TModules extends readonly RevivableModule[] = readonly [typeof import("osra/build/revivables/transfer"), typeof import("osra/build/revivables/identity"), ... 25 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 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 { 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"), ... 20 more ..., { ...;}], const TTransport extends Transport = Transport, const TValue = Capable<...>>(value: CapableCheck<...>, options: StartConnectionsOptions<TModules> & { transport: TTransport;}) => Promise<Remote<T>>
expose, 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.sqrt(x: number): number
Returns the square root of a number.
sqrt(this.Point.x: number
x ** 2 + this.Point.y: number
y ** 2) }}
const const pointModule: { readonly type: "point"; 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;}
pointModule = { type: string
type: 'point' as type const = "point"
const, 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"), ... 25 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"), ... 25 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 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 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
const const withPoint: <TDefaults extends readonly RevivableModule[]>(defaults: TDefaults) => readonly [{ readonly type: "point"; 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;}, ...TDefaults]
withPoint = <function (type parameter) TDefaults in <TDefaults extends readonly RevivableModule[]>(defaults: TDefaults): readonly [{ readonly type: "point"; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { ...; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, ...TDefaults]
TDefaults extends readonly 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 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[]>(defaults: TDefaults extends readonly RevivableModule[]
defaults: function (type parameter) TDefaults in <TDefaults extends readonly RevivableModule[]>(defaults: TDefaults): readonly [{ readonly type: "point"; readonly isType: (value: unknown) => value is Point; readonly box: (value: Point, _context: RevivableContext) => { ...; }; readonly revive: (value: { x: number; y: number; }, _context: RevivableContext) => Point;}, ...TDefaults]
TDefaults) => [const pointModule: { readonly type: "point"; 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;}
pointModule, ...defaults: TDefaults extends readonly RevivableModule[]
defaults] as type const = readonly [{ readonly type: "point"; 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;}, ...TDefaults]
constThe revivableModules option of expose() is a function over the default module list — here withPoint prepends pointModule and keeps the defaults intact.
Register on both sides
Section titled “Register on both sides”Both sides must register the same modules; the second type parameter of expose carries the extended module list into the Capable check:
const const value: (p: Point) => Promise<Point>
value = async (p: Point
p: class Point
Point) => new constructor 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 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, (p: Point) => Promise<...>>(value: (p: Point) => Promise<...>, options: StartConnectionsOptions<...> & { ...;}): Promise<...>
expose(const value: (p: Point) => Promise<Point>
value, { transport: Transport & Worker
transport, revivableModules?: ((defaults: DefaultRevivableModules) => readonly [{ readonly type: "point"; 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"), ... 25 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: <TDefaults extends readonly RevivableModule[]>(defaults: TDefaults) => readonly [{ readonly type: "point"; 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;}, ...TDefaults]
withPoint })
const const remote: (p: Point) => Promise<{ x: number; y: number; distance: () => Promise<number>;}>
remote = await expose<(p: Point) => Promise<Point>, readonly [{ readonly type: "point"; 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;}, ...RevivableModule[]], Transport, any>(value: any, options: StartConnectionsOptions<readonly [{ readonly type: "point"; 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;}, ...RevivableModule[]]> & { ...;}): Promise<...>
expose<typeof const value: (p: Point) => Promise<Point>
value, 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: <TDefaults extends readonly RevivableModule[]>(defaults: TDefaults) => readonly [{ readonly type: "point"; 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;}, ...TDefaults]
withPoint>>( {}, { transport: Transport
transport, revivableModules?: ((defaults: DefaultRevivableModules) => readonly [{ readonly type: "point"; 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;}, ...RevivableModule[]]) | 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: <TDefaults extends readonly RevivableModule[]>(defaults: TDefaults) => readonly [{ readonly type: "point"; 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;}, ...TDefaults]
withPoint },)const const doubled: { x: number; y: number; distance: () => Promise<number>;}
doubled = await const remote: (p: Point) => Promise<{ x: number; y: number; distance: () => Promise<number>;}>
remote(new constructor Point(x: number, y: number): Point
Point(3, 4)) // instanceof Point, distance() === 10Passing the module list type as the second type parameter (ReturnType<typeof withPoint>) teaches the Capable check that Point is now a legal value.
Ordering matters
Section titled “Ordering matters”Boxing picks the first module whose isType matches, so prepend your module ahead of the defaults; otherwise a fallback (clonable, eventTarget, the unclonable catch-all) may claim your instances first. The default list itself is order-sensitive for the same reason.
The revivableModules function receives the defaults and returns the final ordered list, so you can also drop, reorder, or replace built-ins, not just prepend.
Box contents and nested values
Section titled “Box contents and nested values”A box must spread BoxBase ({ __OSRA_BOX__: 'revivable' }) and carry only JSON/clone-safe fields — see the wire protocol for how boxes travel inside envelopes.
Nested capable values are not walked for you; call recursiveBox/recursiveRevive with the provided context. When your type needs a live channel, box a function or MessagePort through recursiveBox and embed the resulting box.