Skip to content

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.

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.

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.

@paramx A numeric expression.

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]
const

The revivableModules option of expose() is a function over the default module list — here withPoint prepends pointModule and keeps the defaults intact.

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() === 10

Passing the module list type as the second type parameter (ReturnType<typeof withPoint>) teaches the Capable check that Point is now a legal value.

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.

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.