Data & state
The Boundary
Every island is a serialization boundary. import.meta.og.$ is the one mark that carries anything across it, functions included, into context or props. Stores cross automatically. Refusals fail at your line, not in a serializer.
On this page
Each island is its own hydration root. Thus each value that travels from a server-rendered host into an island — a prop, a context value — crosses a serialization boundary. Plain data crosses with no work. All other values need a way to travel. The rule of ogygia for this is one sentence:
Mark the boundary with
import.meta.og.$(). Everything that crosses is either data, or something that knows how to rebuild itself on the other side.
import.meta.og.$ — the one mark
og.$ wraps a value at the exact place where it goes to a boundary. What it does depends on
what you give it:
A function — it crosses alive
<script>
import { setContext } from 'ogygia';
const tax = 0.19; // a local — captured by VALUE
setContext('fmt', import.meta.og.$((n) => `€${(n * (1 + tax)).toFixed(2)}`));
</script><!-- inside any island, plain svelte: -->
<script>
import { getContext } from 'svelte';
const fmt = getContext('fmt'); // a REAL, callable function
</script>
<p>{fmt(100)}</p> <!-- €119.00 — synchronous, no eval in production -->The compiler hoists the function. It changes each captured local into a bound value that crosses as data. The island binds it again. The function itself is never serialized. Its code ships as a build artifact. Its captures ship like any prop. Captures follow prop law: they must serialize. A captured store crosses live and stays shared — see below.
Two things are build errors, at your file and line:
- a capture from a server-only module (
$env/static/private,$app/server,*.server.ts) — the value would ship into client HTML; - a mark on something that can never cross (a DOM node, a bare function inside an object) — the error fires at the mark, not later inside a serializer.
Anything else — an explicit, checked crossing
<Cart
items={import.meta.og.$(cartStore)}
onCheckout={import.meta.og.$((id) => `/checkout/${id}`)}
/>For a non-function value, og.$ is a boundary assertion. The value is classified at that
line. A store passes, and will auto-wire. Plain data passes. A live DOM node or a class
instance without a codec throws at that line, with the path inside the value that caused
it. The value itself returns untouched. There is no wrapper to unlearn.
You never need og.$ for values that cross on their own (see the table below). But the mark
is the recommended style. The next reader sees exactly where the page splits, and mistakes
surface at the mark, not at serialize time.
What crosses, and how
| You pass | How it travels | Needs a mark? |
|---|---|---|
plain data, Map/Set/Date | serialized (devalue) | no |
a store (subscribe-shaped) | value crosses; islands join to ONE live instance — set in island A repaints island B | no — automatic |
| a factory store (custom methods) | value crosses; methods rebuilt from the factory on the other side | usually no — provable factories auto-brand; assert with og.store (below) in other cases |
| a class instance | through its own codec: import.meta.og.wire | one time, on the class |
| a function | hoisted code plus bound captures (og.$) | yes — og.$ |
| a snippet | freezes to HTML, or crosses live ({#snippet}) | no |
| a component / UI | it is a region — a different noun by intent | region() / <Region> |
| a DOM node, a bare callback in an object, a secret | refused, with the path and a fix | — |
Stores: automatic, shared, live
<script>
import { setContext } from 'ogygia';
import { writable } from 'svelte/store';
setContext('theme', writable('dark'));
</script>Each island that reads getContext('theme') gets the same live store, not a copy. The
current value crosses at render time. Identity is joined again client-side. Thus writes move
between islands through plain Svelte reactivity. The same store provided under two keys stays
one instance.
Factory stores
A store with custom methods needs its factory on the other side. Methods are code. They never serialize. Factories that the compiler can prove are branded automatically:
// auto-branded: the body provably returns a store
export const createCart = (seed = []) => {
const { subscribe, set, update } = writable(seed);
return { subscribe, set, update, add: (item) => update((x) => [...x, item]) };
};For a factory it cannot prove (it returns the result of a helper), assert it:
export const createCart = import.meta.og.store((seed = []) => makeStore(seed));The first parameter of the factory is the seed — the value that crosses. Sometimes a branded store reaches an island whose bundle never loaded the factory. It then degrades to a plain writable: the value stays, the methods are absent. A console warning names the one-import fix. It never crashes.
Two store shapes need care. A derived crosses as a frozen seed. The derivation cannot
travel. Derive again client-side, or share the sources. Store values follow prop law: a
callback inside the value of a store is still a bare function.
Keeping things home: { islands: false }
Not everything must cross. Some context is read only by the server-rendered host: a bag of element refs, a host-only callback, a very large config object. Keep it native with one option:
setContext('scrollRefs', refs, { islands: false });Same-tree getContext operates as always. Nothing is serialized. In dev, ogygia warns when
the island context payload grows past 32kB, and names the heaviest keys. This option is
usually the fix.
Policy and errors
The boundary refuses keys that look like secrets (token, apiKey, …). Thus credentials do
not ride into client HTML. Adjust it one time, server-side:
// hooks.server.ts
import { configure_boundary } from 'ogygia/internal';
configure_boundary({
allow: ['tokenColor'], // a false positive the sniff should let through
deny: ['sessionBlob'] // a semantic secret the sniff can't see
});Everything that the boundary declines is explained. A dropped context key logs the key, the dot-path to the leaf that caused it, and what to do instead. The design goal: nothing crosses, or fails to cross, silently.