Macros
wire
The transportable mark, with which a class declares how its instances cross an island boundary through static wire = import.meta.og.wire({ encode, decode }). It has one strict contract, and the macro creates the codec key, so there is nothing to import or leak.
What crosses between islands is a serialized copy of a prop, not a reference to it. A plain object survives that copy intact, but a live class instance such as a store or an orchestrator does not. A class opts in by declaring how it travels:
export class Cart {
items = $state<string[]>([]);
add(item: string) { this.items.push(item); }
static wire = import.meta.og.wire({
encode: (c: Cart) => $state.snapshot(c.items),
decode: (items: string[]) => Object.assign(new Cart(), { items })
});
}encode says what leaves and decode says how to rebuild it. You write both, so nothing happens behind your back. A Cart instance can now be passed to islands as a prop, and every island that holds it shares one live object whose $state is reactive across all of them. Sharing state explains how the object stays live and why the server never shares it between visitors.
Why a call, not a key
You might expect to write static [import.meta.og.wire] as a computed key, but TypeScript’s grammar forbids it: a computed class key must be a plain dotted name, and import.meta.og.wire is meta-syntax, so it could never typecheck. The call form is the strict, typechecked equivalent. The macro consumes the whole member and writes the real symbol key into the compiled output:
static [Symbol.for('ogygia.wire')] = { encode, decode };The key never appears in your source, so there is nothing to import, alias or leak. Naming the static wire is a convention; the call is what marks the class.
One contract, strictly enforced
wire is valid in exactly one position: a static member named wire, initialized with the call, inside a class body, with exactly one argument (the codec). Every other use is a build error that names the file, the line and the fix:
- a bare value (
const k = import.meta.og.wire) - a call outside a static member (an object property, a spread, an argument)
- a member with the wrong name, or a non-static member
- a call with no argument, or with more than one argument
Nothing is inferred from the shape of the class. You always state what leaves and how it comes back, so nothing breaks silently.
Continuity across navigation
The codec accepts two optional fields for state that should last for the whole session:
static wire = import.meta.og.wire({
encode, decode,
id: 'session-cart', // promote to a tab-scoped singleton
merge: (live, fresh) => { // reconcile on navigation
live.serverStamp = fresh.serverStamp; // pull server truth in; user edits win
}
});Giving the codec a name (id) makes the instance survive SPA navigations, so the same live object follows the visitor across pages within the tab. merge reconciles the two when a new page’s server snapshot arrives, applying whatever should carry over into the live instance. By default the live instance wins, because a cart in the middle of an edit should beat a fresh read. Continuity covers the behaviour in full.
State & context is the full guide to sharing one instance across islands and providing it to a subtree with <Provide> or a drop-in setContext.