@atolljs/islands 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ops.d.ts ADDED
@@ -0,0 +1,339 @@
1
+ /**
2
+ * The whole protocol between the two threads.
3
+ *
4
+ * The worker runs a real React reconciler whose "host environment" is a set
5
+ * of records in a Map — every render-phase and mutation-phase host-config
6
+ * call appends one of these ops to a queue. Task methods (`mount`,
7
+ * `updateProps`, `dispatch`, `flush`) return the flushed batch, and the main
8
+ * thread's only job is to replay them against the DOM. Ops ride postMessage —
9
+ * no shared memory is involved.
10
+ *
11
+ * ISLANDS: every island owns an independent op stream — instance ids are only
12
+ * unique within one worker, so each island's driver keeps its own node map
13
+ * (see src/island.ts). `emit` is the one op that is NOT a DOM mutation: it is
14
+ * the island→shell event channel — the driver hands `{name, payload}` to the
15
+ * shell-registered `onEvent` callback instead of touching the DOM.
16
+ */
17
+ export type Op =
18
+ /**
19
+ * createElement-equivalent — `props` is already wire-serialized. `ns` is
20
+ * the element's namespace URI when it isn't HTML ('http://www.w3.org/2000/svg',
21
+ * 'http://www.w3.org/1998/Math/MathML') — the driver calls
22
+ * `createElementNS(ns, type)`. The reconciler's host context tracks it
23
+ * (svg/math enter, foreignObject/desc/title return to HTML), so SVG trees
24
+ * like recharts' arrive correctly namespaced.
25
+ */
26
+ {
27
+ t: 'create';
28
+ id: number;
29
+ type: string;
30
+ props: WireProps;
31
+ ns?: string;
32
+ }
33
+ /** createTextNode-equivalent. */
34
+ | {
35
+ t: 'text';
36
+ id: number;
37
+ text: string;
38
+ }
39
+ /** `parent.insertBefore(child, before ?? null)` — parent 0 is the root container. */
40
+ | {
41
+ t: 'append';
42
+ parent: number;
43
+ child: number;
44
+ before?: number;
45
+ }
46
+ /** Detach `child` from wherever it is mounted. */
47
+ | {
48
+ t: 'remove';
49
+ child: number;
50
+ }
51
+ /** Re-serialized full prop set for `id` (main diffs against its last seen set). */
52
+ | {
53
+ t: 'update';
54
+ id: number;
55
+ props: WireProps;
56
+ }
57
+ /** New text content for a text instance. Also how the proxy DOM writes
58
+ * `element.textContent` — the driver assigns `node.textContent`, which
59
+ * replaces the element's children with a single text node. */
60
+ | {
61
+ t: 'utext';
62
+ id: number;
63
+ text: string;
64
+ }
65
+ /** Clear the root container (React's clearContainer, or an imperative
66
+ * instance's rebuild). */
67
+ | {
68
+ t: 'clear';
69
+ }
70
+ /**
71
+ * Set/remove a single attribute — emitted by the worker-side proxy DOM
72
+ * (`setAttribute`, `id`/`className` setters, `classList`, `dataset`).
73
+ * `value: null` removes the attribute.
74
+ */
75
+ | {
76
+ t: 'attr';
77
+ id: number;
78
+ name: string;
79
+ value: string | null;
80
+ }
81
+ /**
82
+ * Merge inline-style changes — the proxy DOM's `style` proxy re-sends only
83
+ * the changed keys; `''` clears a key (deleteProperty). Two conventions:
84
+ * keys starting `--` are CSS custom properties (driver routes them to
85
+ * `style.setProperty`), and a value ending in `!important` applies with
86
+ * priority (the proxy's `style.setProperty(k, v, 'important')` encodes it
87
+ * this way — there is no separate priority field).
88
+ */
89
+ | {
90
+ t: 'style';
91
+ id: number;
92
+ props: Record<string, string>;
93
+ }
94
+ /**
95
+ * Attach a DOM listener for a proxy-DOM `addEventListener` — `handler` is
96
+ * a worker handler-table id (same currency as `__evt` refs); the driver
97
+ * wires it to `client.dispatch(handler, payload)`. `id: 0` targets the
98
+ * island's root container — that's where `document`/`window` listeners
99
+ * land, which makes delegated handlers work. `opts` carries the
100
+ * once/passive/capture flags through to the real addEventListener.
101
+ */
102
+ | {
103
+ t: 'listen';
104
+ id: number;
105
+ type: string;
106
+ handler: number;
107
+ opts?: {
108
+ once?: boolean;
109
+ passive?: boolean;
110
+ capture?: boolean;
111
+ };
112
+ }
113
+ /** Detach the listener a `listen` op attached (type + handler identify it).
114
+ * `opts.capture` must match the listen's flag for the detach to apply. */
115
+ | {
116
+ t: 'unlisten';
117
+ id: number;
118
+ type: string;
119
+ handler: number;
120
+ opts?: {
121
+ once?: boolean;
122
+ passive?: boolean;
123
+ capture?: boolean;
124
+ };
125
+ }
126
+ /**
127
+ * Island → shell event — worker code calls `emit(name, payload)` inside a
128
+ * handler (or a commit-phase effect); the driver invokes the island's
129
+ * `onEvent(name, payload)` instead of mutating the DOM. Payloads must be
130
+ * structured-cloneable — same rule as props.
131
+ */
132
+ | {
133
+ t: 'emit';
134
+ name: string;
135
+ payload?: unknown;
136
+ }
137
+ /**
138
+ * Request real geometry for `id` — the worker-side proxy DOM emits this
139
+ * when `doc.observeSize(el, cb)` registers (and once, lazily, when an
140
+ * unmarked element's geometry is read). The driver measures the real
141
+ * node, observes it with a ResizeObserver, and pushes rects back through
142
+ * the `setRects` task. Fire-and-forget: reads keep returning the honest 0
143
+ * until the first push lands. Older drivers ignore the op; the reads stay
144
+ * 0 — same degradation as an element that never gets measured.
145
+ */
146
+ | {
147
+ t: 'measure';
148
+ id: number;
149
+ }
150
+ /**
151
+ * Cancel a `measure` request — emitted when the last `observeSize`
152
+ * observer for `id` detaches. Elements auto-requested by a geometry read
153
+ * stay observed for the element's lifetime (the driver also unobserves
154
+ * on `remove`/`clear`).
155
+ */
156
+ | {
157
+ t: 'unmeasure';
158
+ id: number;
159
+ };
160
+ /**
161
+ * One measured box, pushed main→worker by the `setRects` task. `x`/`y` are
162
+ * the node's viewport-relative position (getBoundingClientRect), `w`/`h`
163
+ * its size — worker-side reads derive top/left/right/bottom from these.
164
+ * Updates fire on element RESIZE (the driver's ResizeObserver); position-only
165
+ * moves don't retrigger, matching what chart/map libraries actually consume.
166
+ */
167
+ export interface MeasuredRect {
168
+ id: number;
169
+ x: number;
170
+ y: number;
171
+ w: number;
172
+ h: number;
173
+ }
174
+ /**
175
+ * Wire-serialized props. Functions named `on*` (onClick, onInput, …) cross as
176
+ * `{ __evt: handlerId }`; the main thread turns that into a DOM listener that
177
+ * dispatches back into the worker. `children` never crosses — tree structure
178
+ * is expressed entirely by append/remove ops.
179
+ *
180
+ * One prop is NOT a normal attribute: `data-atoll-slot` marks a transclusion
181
+ * slot — a leaf element whose box is worker-owned but whose contents the
182
+ * shell mounts main-thread DOM into (the island's `slots` registry). The
183
+ * driver routes it to the slot machinery instead of treating it like any
184
+ * other data attribute.
185
+ */
186
+ export type WireProps = Record<string, unknown>;
187
+ /** Marker for a prop that was a function — a handle into the worker's handler table. */
188
+ export interface EventRef {
189
+ __evt: number;
190
+ }
191
+ export declare const isEventRef: (v: unknown) => v is EventRef;
192
+ /**
193
+ * What the main thread sends back when a DOM event fires. Deliberately NOT a
194
+ * SyntheticEvent — just a best-effort handful of fields read off the DOM
195
+ * event and its target. Mouse fields are `undefined` for non-mouse events;
196
+ * `targetId` is only set when the event target is an op-created node (shell-
197
+ * owned slot content has no instance id).
198
+ */
199
+ export interface EventPayload {
200
+ type: string;
201
+ value?: string;
202
+ checked?: boolean;
203
+ key?: string;
204
+ clientX?: number;
205
+ clientY?: number;
206
+ screenX?: number;
207
+ screenY?: number;
208
+ /**
209
+ * Pointer coords in the LISTENER element's local space:
210
+ * `clientX/Y − event.currentTarget.getBoundingClientRect()` computed
211
+ * main-thread where real rects exist. The proxy DOM has no measurement
212
+ * channel, so worker-side hit-testing libraries cannot derive local
213
+ * coords themselves: zrender's four-marker getBoundingClientRect
214
+ * transform degenerates, and e.offsetX is absent anyway. zrender's
215
+ * normalizeEvent early-returns on `e.zrX != null`, which is why the
216
+ * fields carry its names — charts (echarts SVG renderer) get working
217
+ * axis pointers/tooltips, any library can read them the same way.
218
+ */
219
+ zrX?: number;
220
+ zrY?: number;
221
+ button?: number;
222
+ /**
223
+ * UIEvent.which — non-standard but set by every browser (1 = left button,
224
+ * 2 = middle, 3 = right). Libraries like Leaflet's Draggable gate on it:
225
+ * `e.which !== 1 && e.button !== 1` must not both hold for a left press.
226
+ */
227
+ which?: number;
228
+ /** Modifier states for the event (KeyboardEvent/MouseEvent share them). */
229
+ shiftKey?: boolean;
230
+ ctrlKey?: boolean;
231
+ altKey?: boolean;
232
+ metaKey?: boolean;
233
+ /** WheelEvent deltas — only set for `wheel` events (deltaMode is always
234
+ * 0/pixels in practice; reported raw). */
235
+ deltaX?: number;
236
+ deltaY?: number;
237
+ /**
238
+ * WheelEvent.deltaMode (0 = pixels, 1 = lines, 2 = pages). Libraries like
239
+ * Leaflet gate on `e.deltaMode === 0` inside getWheelDelta, so the driver
240
+ * always sends a concrete number — defaulting to 0 when the environment
241
+ * doesn't expose it but deltas exist.
242
+ */
243
+ deltaMode?: number;
244
+ /** PointerEvent.pointerType ('mouse' | 'touch' | 'pen' | ''). */
245
+ pointerType?: string;
246
+ /** `target.scrollTop` when the target is an element. */
247
+ scrollTop?: number;
248
+ /** The worker instance id of `event.target`, when it maps to one. */
249
+ targetId?: number;
250
+ /**
251
+ * The worker instance id of `event.relatedTarget` (MouseEvent/FocusEvent),
252
+ * when it maps to an op-created node. Lets over/out/leave/enter handlers
253
+ * distinguish "moved to a child" from "actually left the element" —
254
+ * zrender's mouseout gate (`toElement || relatedTarget` → isLocalEl walk)
255
+ * is the canonical consumer; without it every child transition looks like
256
+ * a real leave and charts flicker as tooltips hide/show.
257
+ */
258
+ relatedTargetId?: number;
259
+ /**
260
+ * The proxy-DOM node for `targetId` — SYNTHESIZED worker-side by the proxy
261
+ * DOM when it wraps a listener (it never crosses the wire). Lets delegated
262
+ * handlers written for the real DOM — `e.target.closest('.row')`,
263
+ * `e.target.dataset` — run unmodified. Only set when the target maps to a
264
+ * known worker instance; DOM-fidelity only for nodes built through the
265
+ * same proxy document.
266
+ */
267
+ target?: unknown;
268
+ /**
269
+ * The proxy-DOM node for `relatedTargetId` — SYNTHESIZED worker-side like
270
+ * `target` (never crosses the wire). Handler code can walk
271
+ * `relatedTarget.parentNode` — e.g. zrender's isLocalEl — to test
272
+ * whether the pointer moved within the listener's subtree.
273
+ */
274
+ relatedTarget?: unknown;
275
+ /**
276
+ * Synthesized NO-OPS — added worker-side by the proxy DOM when it wraps a
277
+ * listener's payload (they never cross the wire). They exist so library
278
+ * code written for real DOM events — `e.preventDefault()`,
279
+ * `e.stopPropagation()` — doesn't crash. They CANNOT cancel anything: the
280
+ * real event already dispatched on the main thread before this payload
281
+ * ever reached the worker. Practical consequence: a `wheel` listener on a
282
+ * scrolling island can't preventDefault — the page scrolls anyway.
283
+ */
284
+ preventDefault?(): void;
285
+ stopPropagation?(): void;
286
+ stopImmediatePropagation?(): void;
287
+ }
288
+ /**
289
+ * The task methods every island worker exposes — what `definePolyWorker`
290
+ * registers on the worker side and what `connectIslandWorker`'s typed client
291
+ * calls on the main thread. Every signature leads with the instance key
292
+ * (`'app'` or `'app@instance'`) — `mountIsland` binds it per island.
293
+ */
294
+ export type IslandWorkerMethods = {
295
+ /** Mount the instance's registry app; returns the initial op batch. Async
296
+ * because a React app lazily imports the reconciler on first mount. */
297
+ mount(instance: string, props?: Record<string, unknown>): Op[] | Promise<Op[]>;
298
+ /** Re-render the instance's root with new serializable props. */
299
+ updateProps(instance: string, props: Record<string, unknown>): Op[];
300
+ /** Invoke the worker handler a `__evt` ref or `listen` op points at. */
301
+ dispatch(handlerId: number, payload: EventPayload): Op[];
302
+ /**
303
+ * Push the island container's measured box into the instance — the
304
+ * always-on geometry channel (per-element rects additionally flow through
305
+ * `setRects` for elements that requested measurement). The driver observes
306
+ * the island's `el` with a ResizeObserver and calls this once at mount and
307
+ * on resizes (throttled ~100ms). The instance stores {w,h}; proxy-DOM
308
+ * geometry reads on `doc.body`/`documentElement` and on elements marked via
309
+ * `doc.markContainer(el)` then return it. Handlers registered via
310
+ * `doc.onResize` re-run on each push; their ops ride back in this method's
311
+ * return batch.
312
+ */
313
+ setSize(instance: string, width: number, height: number): Op[];
314
+ /**
315
+ * Push freshly measured element rects — the answer to `measure` ops and
316
+ * the driver's per-element ResizeObserver ticks. Each rect updates the
317
+ * instance's per-element store and fires the element's `observeSize`
318
+ * callbacks inside the instance's scope, so their ops ride back in this
319
+ * method's return batch like `setSize`'s onResize handlers.
320
+ */
321
+ setRects(instance: string, rects: MeasuredRect[]): Op[];
322
+ /** Drain ops committed outside a task (passive effects, timers). */
323
+ flush(instance: string): Op[];
324
+ /**
325
+ * Tear down one instance — unmounts its tree / disposes its proxy document
326
+ * and drops the instance, leaving the worker alive for its other mounts.
327
+ * Returned ops are the detach batch (the driver may skip applying them if
328
+ * the island element is already gone).
329
+ */
330
+ unmount(instance: string): Op[];
331
+ /** The mounted instance's random id — the island's "worker pid" badge. */
332
+ whoami(instance: string): string;
333
+ /**
334
+ * The instance's renderer tag ('react', 'vue', …) from its adapter; null
335
+ * for imperative apps. Read by the driver only for devtools, and only
336
+ * when the mount declared no `framework`.
337
+ */
338
+ renderer(instance: string): string | null;
339
+ };
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Two entry points, two topologies:
3
+ *
4
+ * // render.worker.ts — a REGISTRY worker: one script, many apps
5
+ * import { definePolyWorker } from '@atolljs/islands/worker';
6
+ * export const renderWorker = definePolyWorker({
7
+ * apps: { controls: ControlsApp, table: TableApp },
8
+ * });
9
+ *
10
+ * // charts.worker.ts — a MONO worker: one script, one app (1:1)
11
+ * export const chartsWorker = defineMonoWorker(ChartsApp);
12
+ *
13
+ * `definePolyWorker` serves a registry — every island's worker runs the
14
+ * same script and `mount(instance, props)` picks a component out of `apps`.
15
+ * `defineMonoWorker` serves exactly one app: the shell mounts it without
16
+ * naming a registry key (or with any name — a single-registered-app worker
17
+ * resolves its sole app regardless), and the worker's bundle carries only
18
+ * that app's dependencies.
19
+ *
20
+ * APP REGISTRY is module-level: in a real worker each entry file loads its
21
+ * own module graph (a instance worker sees exactly its app); under the
22
+ * in-process test harness several worker entries share one graph, so
23
+ * registration is a UNION and mounts still resolve — same semantics both
24
+ * worlds.
25
+ *
26
+ * INSTANCE KEYS are `app` or `app@instance` — the part before the last '@'
27
+ * names the registry app (or 'main' for unnamed instance-worker mounts), the
28
+ * rest distinguishes instances so the same app can mount more than once —
29
+ * INCLUDING into one shared worker when two islands share a client
30
+ * (multi-island-per-worker: one worker, two mounts, two reconcilers).
31
+ * MountIsland mints a fresh key per island. Wire signatures carry the instance key (`updateProps(instance,
32
+ * props)`, `flush(instance)`, `whoami(instance)`) so a call routes to its instance —
33
+ * the main-thread mountIsland helper binds it, so shell code just calls
34
+ * `island.updateProps(props)`.
35
+ *
36
+ * IMPERATIVE INSTANCES: a registry entry can be `{ imperative: (doc, props) }`
37
+ * instead of a component. Those mounts hold no reconciler at all: mount
38
+ * hands the app a instance-scoped proxy DOM and its mutations emit ops
39
+ * directly (updateProps = clear + rebuild, dispatch = runInInstance + drain,
40
+ * flush = drain only). See worker/proxyDom.ts.
41
+ *
42
+ * RENDERED INSTANCES: `{ mount: (ctx) => handle | void }` is the same shape
43
+ * for EVERY framework renderer, React included — mount(ctx) renders into the
44
+ * instance's proxy document and returns a handle whose `update(props)` takes
45
+ * over updateProps (fine-grained patching instead of rebuild), whose
46
+ * `sync(fn)`/`flush()` let task-ordered renderers (react-reconciler) pin
47
+ * commits into the task batch, and whose `dispose()` runs before the
48
+ * document dies. This package ships no renderer: React's lives in
49
+ * `@atolljs/react-island/worker` — framework-neutral by construction.
50
+ *
51
+ * Ops still ride back through the pool's ordinary postMessage channel — the
52
+ * sharedMemory contract here is only the doorbell (see memory.ts): a commit
53
+ * counter the main thread observe()s to trigger flush() as a push. Drop it
54
+ * entirely and the islands still work on the 50ms poll — that's the
55
+ * message-only mode.
56
+ */
57
+ import type { SharedMemory, WorkerDefinition } from '@atolljs/core';
58
+ import { type IslandContract } from '../contract.js';
59
+ import { type DoorbellSpec } from '../memory.js';
60
+ import { type ProxyDocument } from './proxyDom.js';
61
+ import type { IslandWorkerMethods } from '../ops.js';
62
+ /**
63
+ * Registry value shapes:
64
+ * - `{ imperative: (doc, props) => void }` — no framework at all. mount()
65
+ * hands it a proxy DOM scoped to the instance; its mutations emit ops
66
+ * directly.
67
+ * - `{ mount: (ctx) => handle | void }` — a framework adapter's output
68
+ * (`reactIslandApp`, `vueIslandApp`, …). Bare component functions are NOT
69
+ * registry values — wrap them first (defineReactPolyWorker does it).
70
+ */
71
+ export interface ImperativeIslandApp {
72
+ imperative: (doc: ProxyDocument, props: Record<string, unknown>) => void;
73
+ /**
74
+ * Optional teardown — runs inside the instance's scope BEFORE its proxy
75
+ * document is disposed (unmount, remount, updateProps rebuild). Cancel
76
+ * library timers/animation loops/listeners here (e.g. `map.remove()`) so
77
+ * deferred work can't mutate a dead instance or a foreign ambient document
78
+ * after teardown.
79
+ */
80
+ dispose?: (doc: ProxyDocument) => void;
81
+ }
82
+ /**
83
+ * What a non-React framework renderer receives at mount: the instance's wire
84
+ * key (scopes emit() and instance-less ops), a fresh proxy document whose
85
+ * mutations emit ops, and the serialized props the shell mounted with.
86
+ */
87
+ export interface RenderContext {
88
+ instance: string;
89
+ doc: ProxyDocument;
90
+ props: Record<string, unknown>;
91
+ }
92
+ export interface RenderedHandle {
93
+ /**
94
+ * Fine-grained prop update — the renderer patches its live tree. When
95
+ * omitted, updateProps falls back to imperative semantics: dispose +
96
+ * clear + re-mount on a fresh document.
97
+ */
98
+ update?(props: Record<string, unknown>): void;
99
+ /**
100
+ * Run `fn` inside the renderer's synchronous commit lane (React:
101
+ * flushSyncFromReconciler + flushSyncWork) so its mutations emit ops
102
+ * inside the current task batch. Dispatch wraps event-handler invocation
103
+ * in this when the handle provides one; renderers without a sync lane
104
+ * simply omit it.
105
+ */
106
+ sync?(fn: () => void): void;
107
+ /**
108
+ * Drain renderer work scheduled outside tasks — passive effects, async
109
+ * state updates (React: flushPassiveEffects + flushSyncWork). The worker's
110
+ * flush() method calls it inside the instance's scope, then drains the
111
+ * op queue.
112
+ */
113
+ flush?(): void;
114
+ /**
115
+ * App teardown — runs inside the instance's scope BEFORE its proxy document
116
+ * is disposed (unmount, remount, updateProps rebuild). Same rules as
117
+ * ImperativeIslandApp.dispose: cancel framework roots/effects here.
118
+ */
119
+ dispose?(): void;
120
+ }
121
+ /**
122
+ * A renderer-backed app: a framework renderer (React, Vue, Svelte,
123
+ * Solid, Angular) whose `mount` renders component output into the instance's
124
+ * proxy document — every proxy mutation already serializes to ops. Package
125
+ * adapters (e.g. @atolljs/react-island/worker, @atolljs/vue-island/worker)
126
+ * wrap a component into this shape so it can sit beside imperative apps in
127
+ * the same `apps` registry.
128
+ */
129
+ export interface RenderedIslandApp {
130
+ mount(ctx: RenderContext): RenderedHandle | void;
131
+ /**
132
+ * Renderer tag ('react', 'vue', 'svelte', 'solid', 'angular'): set by the
133
+ * package adapters, reported by the `renderer` task so devtools can mark
134
+ * an island whose mount declared no `framework`. Hand-written adapters
135
+ * may set it; imperative apps have none.
136
+ */
137
+ renderer?: string;
138
+ }
139
+ /** The apps an island can mount — keyed by the name the shell passes to mount(). */
140
+ export type IslandApp = ImperativeIslandApp | RenderedIslandApp;
141
+ export interface PolyWorkerRegistry {
142
+ /** Name → island app registry. The shell's `mountIsland({ app: name })`
143
+ * picks one per island. */
144
+ apps: Record<string, IslandApp>;
145
+ /**
146
+ * The doorbell contract the worker exposes — defaults to the package's
147
+ * `renderMemory`. Rarely needed: pass a different doorbell-SPEC instance
148
+ * if your worker module already defines one (the main side always uses
149
+ * `makeDoorbell()`, so the spec must match: `opsVersion` number field).
150
+ */
151
+ sharedMemory?: SharedMemory<DoorbellSpec>;
152
+ }
153
+ interface InstanceBase {
154
+ /** The wire key — 'app' or 'app@instance'; op queues route by this. */
155
+ key: string;
156
+ /** The registry name — which component this instance renders. */
157
+ app: string;
158
+ /** Per-instance random id — shown in the island's badge as proof it's a
159
+ * distinct render instance (in production: a distinct worker). Stable across
160
+ * remounts of the same instance key. */
161
+ pid: string;
162
+ /** The `withContract`-stamped contract — drives props validation at
163
+ * mount/updateProps; mirrored into instanceContracts for `emit`. */
164
+ contract?: IslandContract;
165
+ }
166
+ interface ImperativeInstance extends InstanceBase {
167
+ rendered?: undefined;
168
+ imperative: {
169
+ build: ImperativeIslandApp['imperative'];
170
+ /** Optional app teardown — run before the instance's doc is disposed. */
171
+ dispose: ImperativeIslandApp['dispose'];
172
+ /** The instance's proxy document — replaced on each rebuild. */
173
+ doc: ProxyDocument;
174
+ props: Record<string, unknown>;
175
+ };
176
+ }
177
+ interface RenderedInstance extends InstanceBase {
178
+ imperative?: undefined;
179
+ rendered: {
180
+ app: RenderedIslandApp;
181
+ /** The mount-returned handle — drives fine-grained updates/teardown. */
182
+ handle: RenderedHandle | void;
183
+ props: Record<string, unknown>;
184
+ };
185
+ }
186
+ /** A mounted instance — one per island worker in production. */
187
+ export type Instance = ImperativeInstance | RenderedInstance;
188
+ /**
189
+ * PolyWorker — one worker script serving a REGISTRY of islands:
190
+ * `mountIsland({ app: name })` picks one per island, and every island in
191
+ * the registry shares the worker's module graph, framework runtimes, and
192
+ * op pump. This is the bundle-optimization host shape — several islands,
193
+ * one worker to load and keep warm.
194
+ */
195
+ export declare function definePolyWorker(registry: PolyWorkerRegistry, options?: {
196
+ sharedMemory?: SharedMemory<DoorbellSpec>;
197
+ }): WorkerDefinition<DoorbellSpec, IslandWorkerMethods>;
198
+ /**
199
+ * MonoWorker — one worker script pinned to ONE island app (1:1). The
200
+ * isolation host shape: own bundle, own failure domain, nothing reachable
201
+ * outside the single app it serves. `mountIsland` can omit `app` — the
202
+ * worker resolves its sole app regardless of the supplied name.
203
+ */
204
+ export declare function defineMonoWorker(app: IslandApp, options?: {
205
+ sharedMemory?: SharedMemory<DoorbellSpec>;
206
+ }): WorkerDefinition<DoorbellSpec, IslandWorkerMethods>;
207
+ export {};
@@ -0,0 +1,24 @@
1
+ import { ProxyElement } from './element.js';
2
+ /** The small subset of DOMTokenList the proxy implements. */
3
+ export interface ProxyClassList {
4
+ add(...tokens: string[]): void;
5
+ remove(...tokens: string[]): void;
6
+ toggle(token: string, force?: boolean): boolean;
7
+ contains(token: string): boolean;
8
+ readonly value: string;
9
+ }
10
+ /** Build (or reuse) a live classList view for the element. */
11
+ export declare function classListFor(el: ProxyElement): ProxyClassList;
12
+ /**
13
+ * Build (or reuse) a live CSSStyleDeclaration proxy. Writes re-send only the
14
+ * changed key; `delete style.x`/`style.removeProperty('x')` send '' (the
15
+ * protocol's "clear this key"). `setProperty`/`getPropertyValue` accept
16
+ * kebab-case and camelize it, like the real CSSStyleDeclaration.
17
+ */
18
+ export declare function styleProxyFor(el: ProxyElement): CSSStyleDeclaration;
19
+ /**
20
+ * Build (or reuse) a live DOMStringMap proxy. camelCase keys map to `data-*`
21
+ * attribute ops: `el.dataset.foo = 'x'` sets `data-foo`, `delete el.dataset.foo`
22
+ * removes it.
23
+ */
24
+ export declare function datasetProxyFor(el: ProxyElement): DOMStringMap;