@atolljs/islands 0.1.12 → 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.
@@ -0,0 +1,1187 @@
1
+ // src/memory.ts
2
+ import { defineSharedMemory, field } from "@atolljs/core";
3
+ var doorbellSpec = {
4
+ opsVersion: field.number()
5
+ };
6
+ var renderMemory = defineSharedMemory(doorbellSpec);
7
+ var makeDoorbell = () => defineSharedMemory(doorbellSpec);
8
+
9
+ // src/ops.ts
10
+ var isEventRef = (v) => typeof v === "object" && v !== null && "__evt" in v;
11
+
12
+ // src/callbackProps.ts
13
+ var CALLBACK_EVENT = "__island_cb__";
14
+ var CALLBACK_WIRE = "__cb";
15
+ var markers = /* @__PURE__ */ new WeakMap();
16
+ function callbackProp(fn) {
17
+ const marker = { fn };
18
+ markers.set(marker, marker.fn);
19
+ return marker;
20
+ }
21
+ var isCallbackMarker = (v) => typeof v === "object" && v !== null && markers.has(v);
22
+ var isPlainObj = (v) => {
23
+ if (typeof v !== "object" || v === null) return false;
24
+ const p = Object.getPrototypeOf(v);
25
+ return p === Object.prototype || p === null;
26
+ };
27
+ function marshalCallbackProps(value, register) {
28
+ const fn = markers.get(value);
29
+ if (fn !== void 0) return { [CALLBACK_WIRE]: register(fn) };
30
+ if (Array.isArray(value)) return value.map((v) => marshalCallbackProps(v, register));
31
+ if (!isPlainObj(value)) return value;
32
+ const out = {};
33
+ for (const [k, v] of Object.entries(value)) out[k] = marshalCallbackProps(v, register);
34
+ return out;
35
+ }
36
+ function unmarshalCallbackProps(value, make) {
37
+ if (Array.isArray(value)) return value.map((v) => unmarshalCallbackProps(v, make));
38
+ if (!isPlainObj(value)) return value;
39
+ const id = value[CALLBACK_WIRE];
40
+ if (typeof id === "number" && Object.keys(value).length === 1) return make(id);
41
+ const out = {};
42
+ for (const [k, v] of Object.entries(value)) out[k] = unmarshalCallbackProps(v, make);
43
+ return out;
44
+ }
45
+
46
+ // src/island.ts
47
+ import {
48
+ connectWorker,
49
+ devtoolsEnabled,
50
+ emitDevtools,
51
+ estimateCloneBytes,
52
+ observe,
53
+ previewValue as previewValue3
54
+ } from "@atolljs/core";
55
+
56
+ // src/devtoolsCommands.ts
57
+ import { registerDevtoolsCommand, previewValue as previewValue2 } from "@atolljs/core";
58
+
59
+ // src/devtoolsPreview.ts
60
+ import { previewValue } from "@atolljs/core";
61
+ var SAFE_BUDGET = 5e3;
62
+ var jsonSafe = (value) => {
63
+ let nodes = 0;
64
+ const seen = /* @__PURE__ */ new WeakSet();
65
+ const walk = (v, depth) => {
66
+ if (++nodes > SAFE_BUDGET) return "[\u2026]";
67
+ if (typeof v === "function" || isCallbackMarker(v)) return "[fn]";
68
+ if (typeof v === "bigint") return `${v}n`;
69
+ if (typeof v === "number") return Number.isFinite(v) ? v : String(v);
70
+ if (typeof v !== "object" || v === null) return v;
71
+ if (v instanceof ArrayBuffer) return `[ArrayBuffer ${v.byteLength}B]`;
72
+ if (ArrayBuffer.isView(v)) return `[${v.constructor.name} ${v.byteLength}B]`;
73
+ if (v instanceof Date) return v.toISOString();
74
+ if (seen.has(v)) return "[cycle]";
75
+ if (depth > 12) return "[\u2026]";
76
+ seen.add(v);
77
+ if (v instanceof Map) return { "[Map]": [...v.entries()].slice(0, 50).map((e) => walk(e, depth + 1)) };
78
+ if (v instanceof Set) return { "[Set]": [...v].slice(0, 50).map((x) => walk(x, depth + 1)) };
79
+ if (Array.isArray(v)) return v.map((x) => walk(x, depth + 1));
80
+ const out = {};
81
+ for (const k of Object.keys(v)) {
82
+ const x = v[k];
83
+ if (x !== void 0) out[k] = walk(x, depth + 1);
84
+ }
85
+ return out;
86
+ };
87
+ return walk(value, 0);
88
+ };
89
+ var propsPreview = (props) => previewValue(jsonSafe(props), 4096);
90
+
91
+ // src/devtoolsCommands.ts
92
+ var islands = /* @__PURE__ */ new Map();
93
+ var commandsRegistered = false;
94
+ var trackIslandForDevtools = (entry) => {
95
+ ensureIslandCommands();
96
+ const instance = entry.handle.instance;
97
+ islands.set(instance, entry);
98
+ return () => {
99
+ if (islands.get(instance) === entry) islands.delete(instance);
100
+ if (highlighted?.instance === instance) clearHighlight();
101
+ };
102
+ };
103
+ var entryOf = (args) => {
104
+ const instance = String(args.instance ?? "");
105
+ const entry = islands.get(instance);
106
+ if (entry) return entry;
107
+ throw new Error(
108
+ instance.includes("~") ? `island '${instance}' is nested: its handle lives in the parent worker, so it can't be commanded from the dashboard (inspect the outer island's tree instead)` : `island '${instance}' is not mounted on this page's main thread`
109
+ );
110
+ };
111
+ var TEXT_CAP = 200;
112
+ var ATTR_CAP = 200;
113
+ var visibleChildren = (n) => [...n.childNodes].filter((c) => c.nodeType === 1 || c.nodeType === 3 && (c.nodeValue ?? "").trim() !== "");
114
+ var shadowOf = (n) => n.nodeType === 1 ? n.shadowRoot ?? null : null;
115
+ var cap = (s, n) => s.length > n ? `${s.slice(0, n)}\u2026` : s;
116
+ var serializeTree = (root, maxNodes) => {
117
+ let count = 0;
118
+ let truncated = false;
119
+ const walk = (n, id) => {
120
+ count++;
121
+ if (n.nodeType === 3) {
122
+ return { id, tag: "#text", attrs: [], text: cap(n.nodeValue ?? "", TEXT_CAP), children: [] };
123
+ }
124
+ const isShadow = n.nodeType === 11;
125
+ const el = n;
126
+ const node = {
127
+ id,
128
+ tag: isShadow ? "#shadow-root" : el.localName ?? el.nodeName.toLowerCase(),
129
+ attrs: isShadow ? [] : [...el.attributes].map((a) => [a.name, cap(a.value, ATTR_CAP)]),
130
+ children: []
131
+ };
132
+ const kids = [];
133
+ const shadow = shadowOf(n);
134
+ if (shadow) kids.push([shadow, `${id}.s`]);
135
+ visibleChildren(n).forEach((c, i) => kids.push([c, `${id}.${i}`]));
136
+ for (let i = 0; i < kids.length; i++) {
137
+ if (count >= maxNodes) {
138
+ node.more = kids.length - i;
139
+ truncated = true;
140
+ break;
141
+ }
142
+ node.children.push(walk(kids[i][0], kids[i][1]));
143
+ }
144
+ return node;
145
+ };
146
+ return { root: walk(root, "0"), count, truncated };
147
+ };
148
+ var resolveId = (root, id) => {
149
+ const parts = id.split(".");
150
+ if (parts[0] !== "0") return null;
151
+ let n = root;
152
+ for (const p of parts.slice(1)) {
153
+ if (!n) return null;
154
+ n = p === "s" ? shadowOf(n) : visibleChildren(n)[Number(p)] ?? null;
155
+ }
156
+ return n;
157
+ };
158
+ var overlay = null;
159
+ var overlayLabel = null;
160
+ var highlighted = null;
161
+ var rectOf = (n) => {
162
+ if (n.nodeType === 1) return n.getBoundingClientRect();
163
+ if (n.nodeType === 11) return n.host?.getBoundingClientRect() ?? null;
164
+ if (n.nodeType === 3) {
165
+ const range = n.ownerDocument?.createRange();
166
+ if (!range) return null;
167
+ range.selectNodeContents(n);
168
+ return range.getBoundingClientRect();
169
+ }
170
+ return null;
171
+ };
172
+ var tagOf = (n) => n.nodeType === 3 ? "#text" : n.nodeType === 11 ? "#shadow-root" : n.localName;
173
+ var clearHighlight = () => {
174
+ highlighted = null;
175
+ if (overlay) overlay.style.display = "none";
176
+ };
177
+ var showHighlight = (instance, doc, target) => {
178
+ const r = rectOf(target);
179
+ if (!r) return null;
180
+ if (!overlay || overlay.ownerDocument !== doc || !overlay.isConnected) {
181
+ overlay?.remove();
182
+ overlay = doc.createElement("div");
183
+ overlay.setAttribute("data-atoll-devtools", "highlight");
184
+ overlay.style.cssText = "position:fixed;pointer-events:none;z-index:2147483647;box-sizing:border-box;outline:2px solid #58a6ff;background:rgba(88,166,255,.18);display:none";
185
+ overlayLabel = doc.createElement("span");
186
+ overlayLabel.style.cssText = "position:absolute;left:0;white-space:nowrap;font:11px/16px ui-monospace,monospace;padding:0 5px;border-radius:3px;background:#1f6feb;color:#fff";
187
+ overlay.appendChild(overlayLabel);
188
+ (doc.body ?? doc.documentElement).appendChild(overlay);
189
+ }
190
+ overlay.style.left = `${r.left}px`;
191
+ overlay.style.top = `${r.top}px`;
192
+ overlay.style.width = `${r.width}px`;
193
+ overlay.style.height = `${r.height}px`;
194
+ overlay.style.display = "block";
195
+ if (overlayLabel) {
196
+ overlayLabel.textContent = `${tagOf(target)} ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
197
+ overlayLabel.style.top = r.top >= 18 ? "-18px" : "100%";
198
+ }
199
+ highlighted = { instance };
200
+ return { x: r.left, y: r.top, w: r.width, h: r.height };
201
+ };
202
+ var restoreFns = (next, current) => {
203
+ if (next === "[fn]" && (typeof current === "function" || isCallbackMarker(current))) return current;
204
+ if (Array.isArray(next)) {
205
+ return next.map((v, i) => restoreFns(v, Array.isArray(current) ? current[i] : void 0));
206
+ }
207
+ if (next !== null && typeof next === "object") {
208
+ const cur = current !== null && typeof current === "object" ? current : {};
209
+ const out = {};
210
+ for (const [k, v] of Object.entries(next)) out[k] = restoreFns(v, cur[k]);
211
+ return out;
212
+ }
213
+ return next;
214
+ };
215
+ var ensureIslandCommands = () => {
216
+ if (commandsRegistered) return;
217
+ commandsRegistered = true;
218
+ registerDevtoolsCommand(
219
+ "island.list",
220
+ () => [...islands.values()].map(({ handle }) => ({
221
+ instance: handle.instance,
222
+ app: handle.app,
223
+ pid: handle.pid,
224
+ mode: handle.mode
225
+ }))
226
+ );
227
+ registerDevtoolsCommand("island.tree", (args) => {
228
+ const { el } = entryOf(args);
229
+ const max = Math.min(Math.max(Number(args.maxNodes) || 3e3, 1), 2e4);
230
+ return serializeTree(el, max);
231
+ });
232
+ registerDevtoolsCommand("island.highlight", (args) => {
233
+ const entry = entryOf(args);
234
+ if (args.id === null || args.id === void 0) {
235
+ clearHighlight();
236
+ return { ok: true };
237
+ }
238
+ const target = resolveId(entry.el, String(args.id));
239
+ if (!target) {
240
+ clearHighlight();
241
+ return { ok: false };
242
+ }
243
+ const rect = showHighlight(entry.handle.instance, entry.el.ownerDocument, target);
244
+ return { ok: rect !== null, rect };
245
+ });
246
+ registerDevtoolsCommand("island.props", (args) => {
247
+ const raw = entryOf(args).props();
248
+ return { props: jsonSafe(raw), preview: propsPreview(raw) };
249
+ });
250
+ registerDevtoolsCommand("island.updateProps", async (args) => {
251
+ const entry = entryOf(args);
252
+ const next = args.props;
253
+ if (next === null || typeof next !== "object" || Array.isArray(next)) {
254
+ throw new Error("island.updateProps: `props` must be an object");
255
+ }
256
+ const restored = restoreFns(next, entry.props());
257
+ await entry.handle.updateProps(restored);
258
+ return { ok: true, preview: previewValue2(jsonSafe(restored), 4096) };
259
+ });
260
+ registerDevtoolsCommand("island.setMode", (args) => {
261
+ const { handle } = entryOf(args);
262
+ const mode = args.mode;
263
+ if (mode !== "push" && mode !== "poll") throw new Error("island.setMode: mode must be 'push' or 'poll'");
264
+ handle.setMode(mode);
265
+ return { mode: handle.mode };
266
+ });
267
+ };
268
+
269
+ // src/island.ts
270
+ var keepAliveClients = /* @__PURE__ */ new WeakSet();
271
+ var isKeepAliveClient = (client) => keepAliveClients.has(client);
272
+ var connectIslandWorker = ({
273
+ worker,
274
+ doorbell,
275
+ keepAlive,
276
+ ...options
277
+ }) => {
278
+ const client = connectWorker({
279
+ ...options,
280
+ sharedMemory: doorbell === false ? void 0 : makeDoorbell(),
281
+ worker,
282
+ workers: 1
283
+ });
284
+ if (keepAlive) keepAliveClients.add(client);
285
+ return client;
286
+ };
287
+ var POINTER_FAMILY = /^(mouse|pointer|drag|drop|wheel|mousewheel|DOMMouseScroll|click|dblclick|auxclick|contextmenu|selectstart|gotpointercapture|lostpointercapture)/;
288
+ var WHEEL_FAMILY = /^(wheel|mousewheel|DOMMouseScroll)$/;
289
+ function buildEventPayload(e, target, targetId, relatedTargetId) {
290
+ const input = target;
291
+ const mouse = e;
292
+ const wheel = e;
293
+ const pointer = e;
294
+ const pointerFam = POINTER_FAMILY.test(e.type) || typeof MouseEvent !== "undefined" && e instanceof MouseEvent;
295
+ const wheelFam = WHEEL_FAMILY.test(e.type) || typeof WheelEvent !== "undefined" && e instanceof WheelEvent;
296
+ const num = (v) => typeof v === "number" ? v : void 0;
297
+ const coord = (v) => num(v) ?? (pointerFam ? 0 : void 0);
298
+ const button = num(mouse.button) ?? (pointerFam ? 0 : void 0);
299
+ const ct = e.currentTarget;
300
+ const localRect = ct && typeof ct.getBoundingClientRect === "function" ? ct.getBoundingClientRect() : null;
301
+ const local = (v, origin) => localRect && typeof v === "number" ? v - origin : void 0;
302
+ return {
303
+ type: e.type,
304
+ value: input && "value" in input ? input.value : void 0,
305
+ checked: input && "checked" in input ? input.checked : void 0,
306
+ key: e.key,
307
+ clientX: coord(mouse.clientX),
308
+ clientY: coord(mouse.clientY),
309
+ screenX: coord(mouse.screenX),
310
+ screenY: coord(mouse.screenY),
311
+ zrX: local(mouse.clientX, localRect?.left ?? 0),
312
+ zrY: local(mouse.clientY, localRect?.top ?? 0),
313
+ button,
314
+ which: typeof mouse.which === "number" && mouse.which !== 0 ? mouse.which : button !== void 0 ? button + 1 : void 0,
315
+ shiftKey: typeof mouse.shiftKey === "boolean" ? mouse.shiftKey : void 0,
316
+ ctrlKey: typeof mouse.ctrlKey === "boolean" ? mouse.ctrlKey : void 0,
317
+ altKey: typeof mouse.altKey === "boolean" ? mouse.altKey : void 0,
318
+ metaKey: typeof mouse.metaKey === "boolean" ? mouse.metaKey : void 0,
319
+ deltaX: num(wheel.deltaX) ?? (wheelFam ? 0 : void 0),
320
+ deltaY: num(wheel.deltaY) ?? (wheelFam ? 0 : void 0),
321
+ deltaMode: num(wheel.deltaMode) ?? (wheelFam || typeof wheel.deltaX === "number" || typeof wheel.deltaY === "number" ? 0 : void 0),
322
+ pointerType: typeof pointer.pointerType === "string" ? pointer.pointerType : void 0,
323
+ scrollTop: input instanceof HTMLElement ? input.scrollTop : void 0,
324
+ targetId,
325
+ relatedTargetId
326
+ };
327
+ }
328
+ var measureReplay = (instance, via, count, start, end) => {
329
+ if (typeof performance?.measure !== "function") return;
330
+ const name = `atoll replay ${instance}`;
331
+ try {
332
+ performance.measure(name, {
333
+ start,
334
+ end,
335
+ detail: {
336
+ devtools: {
337
+ dataType: "track-entry",
338
+ trackGroup: "atoll",
339
+ track: "islands",
340
+ color: "primary",
341
+ tooltipText: `${instance} \xB7 ${via} \xB7 ${count} ops`,
342
+ properties: [["via", via], ["ops", count]]
343
+ }
344
+ }
345
+ });
346
+ performance.clearMeasures?.(name);
347
+ } catch {
348
+ }
349
+ };
350
+ var islandSeq = 0;
351
+ var clientMounts = /* @__PURE__ */ new WeakMap();
352
+ var canClone = (v) => {
353
+ try {
354
+ structuredClone(v);
355
+ return true;
356
+ } catch {
357
+ return false;
358
+ }
359
+ };
360
+ var isPlainObject = (v) => {
361
+ if (typeof v !== "object" || v === null) return false;
362
+ const proto = Object.getPrototypeOf(v);
363
+ return proto === Object.prototype || proto === null;
364
+ };
365
+ var uncloneablePath = (value, path) => {
366
+ const kids = Array.isArray(value) ? value.map((v, i) => [`[${i}]`, v]) : isPlainObject(value) ? Object.entries(value) : [];
367
+ for (const [k, v] of kids) {
368
+ if (!canClone(v)) {
369
+ const next = path === "" ? k : k.startsWith("[") ? `${path}${k}` : `${path}.${k}`;
370
+ return uncloneablePath(v, next);
371
+ }
372
+ }
373
+ return path;
374
+ };
375
+ var assertCloneableProps = (props, context) => {
376
+ if (canClone(props)) return;
377
+ const path = uncloneablePath(props, "");
378
+ throw new Error(
379
+ `[island] ${context}: prop '${path}' is not structured-cloneable \u2014 props cross the worker boundary via postMessage. Pass plain data; behavior crosses through emit/onEvent, callbackProp() for shell functions, slots, or a task method on the worker.`
380
+ );
381
+ };
382
+ var UNITLESS_CSS = /* @__PURE__ */ new Set([
383
+ "animationIterationCount",
384
+ "aspectRatio",
385
+ "borderImageOutset",
386
+ "borderImageSlice",
387
+ "borderImageWidth",
388
+ "boxFlex",
389
+ "boxFlexGroup",
390
+ "boxOrdinalGroup",
391
+ "columnCount",
392
+ "columns",
393
+ "flex",
394
+ "flexGrow",
395
+ "flexPositive",
396
+ "flexShrink",
397
+ "flexNegative",
398
+ "flexOrder",
399
+ "gridArea",
400
+ "gridRow",
401
+ "gridRowEnd",
402
+ "gridRowSpan",
403
+ "gridRowStart",
404
+ "gridColumn",
405
+ "gridColumnEnd",
406
+ "gridColumnSpan",
407
+ "gridColumnStart",
408
+ "fontWeight",
409
+ "lineClamp",
410
+ "lineHeight",
411
+ "opacity",
412
+ "order",
413
+ "orphans",
414
+ "scale",
415
+ "tabSize",
416
+ "widows",
417
+ "zIndex",
418
+ "zoom",
419
+ "fillOpacity",
420
+ "floodOpacity",
421
+ "stopOpacity",
422
+ "strokeDasharray",
423
+ "strokeDashoffset",
424
+ "strokeMiterlimit",
425
+ "strokeOpacity",
426
+ "strokeWidth"
427
+ ]);
428
+ var cssStyleValue = (key, value) => {
429
+ if (typeof value === "number" && value !== 0 && !key.startsWith("--") && !UNITLESS_CSS.has(key)) {
430
+ return `${value}px`;
431
+ }
432
+ return String(value);
433
+ };
434
+ var subMounter = null;
435
+ var registerSubIslandMounter = (fn) => {
436
+ subMounter = fn;
437
+ };
438
+ var isProxyMountTarget = (el) => typeof el?.doc?.instance === "string";
439
+ async function mountIsland(opts) {
440
+ const {
441
+ el: target,
442
+ onEvent,
443
+ onActivity,
444
+ onOps,
445
+ slots,
446
+ mode: initialMode,
447
+ mountTimeout,
448
+ client: givenClient,
449
+ worker,
450
+ app: givenApp,
451
+ props: givenProps,
452
+ framework,
453
+ ...workerConfig
454
+ } = opts;
455
+ if (isProxyMountTarget(target)) {
456
+ if (subMounter === null) {
457
+ throw new Error(
458
+ "mountIsland: a proxy-element mount target means a nested island \u2014 the worker entry (`@atolljs/islands/worker`) must be loaded to register the sub-mounter."
459
+ );
460
+ }
461
+ return subMounter(opts);
462
+ }
463
+ const el = target;
464
+ const cbTable = /* @__PURE__ */ new Map();
465
+ let cbSeq = 0;
466
+ const registerCb = (fn) => {
467
+ cbTable.set(++cbSeq, fn);
468
+ return cbSeq;
469
+ };
470
+ const props = marshalCallbackProps(givenProps ?? {}, registerCb);
471
+ let currentProps = givenProps ?? {};
472
+ if (givenClient === void 0 && worker === void 0) {
473
+ throw new Error(
474
+ "mountIsland: pass `worker` (a `() => new Worker(...)`/URL entry \u2014 this call builds the island-owned client) or `client` (a shared connectIslandWorker \u2014 several islands, one worker)"
475
+ );
476
+ }
477
+ const client = givenClient ?? connectIslandWorker({
478
+ name: givenApp,
479
+ ...workerConfig,
480
+ worker,
481
+ // Poll-mode shorthand islands don't need SharedArrayBuffer at all —
482
+ // skip the doorbell contract so the page needs no COOP/COEP headers.
483
+ doorbell: initialMode !== "poll"
484
+ });
485
+ assertCloneableProps(props, `mountIsland(${givenApp ?? "main"})`);
486
+ const app = givenApp ?? "main";
487
+ const instance = `${app}@${++islandSeq}`;
488
+ clientMounts.set(client, (clientMounts.get(client) ?? 0) + 1);
489
+ const nodes = /* @__PURE__ */ new Map([[0, el]]);
490
+ const nodeIds = new WeakMap([[el, 0]]);
491
+ const prevProps = /* @__PURE__ */ new Map();
492
+ const nodeListeners = /* @__PURE__ */ new Map();
493
+ const slotNodes = /* @__PURE__ */ new Map();
494
+ const measuredIds = /* @__PURE__ */ new Set();
495
+ const pendingRects = /* @__PURE__ */ new Map();
496
+ let rectObserver = null;
497
+ let rectTimer = null;
498
+ let rectLastPush = -Infinity;
499
+ function flushRects() {
500
+ rectTimer = null;
501
+ if (pendingRects.size === 0 || destroyed) return;
502
+ rectLastPush = Date.now();
503
+ const batch = [...pendingRects.values()];
504
+ pendingRects.clear();
505
+ void islandCall("setRects", client.setRects(instance, batch)).then(
506
+ (ops) => applyOps(ops, "setRects")
507
+ );
508
+ }
509
+ function scheduleRectFlush() {
510
+ if (rectTimer !== null) return;
511
+ const elapsed = Date.now() - rectLastPush;
512
+ rectTimer = window.setTimeout(flushRects, elapsed >= 100 ? 0 : 100 - elapsed);
513
+ }
514
+ const queueRect = (id, node) => {
515
+ const r = node.getBoundingClientRect();
516
+ pendingRects.set(id, { id, x: r.x, y: r.y, w: r.width, h: r.height });
517
+ scheduleRectFlush();
518
+ };
519
+ const ensureRectObserver = () => {
520
+ if (rectObserver === null && typeof ResizeObserver === "function") {
521
+ rectObserver = new ResizeObserver((entries) => {
522
+ for (const entry of entries) {
523
+ const id = nodeIds.get(entry.target);
524
+ if (id !== void 0) queueRect(id, entry.target);
525
+ }
526
+ });
527
+ }
528
+ };
529
+ const unmeasure = (id) => {
530
+ if (!measuredIds.delete(id)) return;
531
+ pendingRects.delete(id);
532
+ const node = nodes.get(id);
533
+ if (node !== void 0) rectObserver?.unobserve(node);
534
+ };
535
+ const mountSlot = (node, id, name) => {
536
+ node.setAttribute("data-atoll-slot", name);
537
+ const prev = slotNodes.get(id);
538
+ if (prev !== void 0 && prev !== name) opts.slots?.[prev]?.(null);
539
+ slotNodes.set(id, name);
540
+ slots?.[name]?.(node);
541
+ };
542
+ const unmountSlot = (id) => {
543
+ const name = slotNodes.get(id);
544
+ if (name !== void 0) {
545
+ slots?.[name]?.(null);
546
+ slotNodes.delete(id);
547
+ }
548
+ };
549
+ const isElementNode = (node) => node !== void 0 && node.nodeType === 1;
550
+ const unmountSlotSubtree = (node) => {
551
+ if (!isElementNode(node)) return;
552
+ for (const [id, slotEl] of Array.from(slotNodes.entries()).map(([id2]) => [id2, nodes.get(id2)])) {
553
+ if (slotEl === node || node.contains(slotEl)) unmountSlot(id);
554
+ }
555
+ };
556
+ const DETACHED_CAP = 1024;
557
+ const detached = /* @__PURE__ */ new Map();
558
+ const detachedIds = /* @__PURE__ */ new Map();
559
+ const tombstoneId = (id, node) => {
560
+ unmeasure(id);
561
+ detachedIds.set(node, id);
562
+ detached.set(id, {
563
+ node,
564
+ props: prevProps.get(id),
565
+ listeners: nodeListeners.get(id),
566
+ // The slot name survives unmountSlot's slotNodes.delete only on the
567
+ // element's marker attribute — read it from the DOM, not the map.
568
+ slot: isElementNode(node) ? node.getAttribute("data-atoll-slot") ?? void 0 : void 0
569
+ });
570
+ nodes.delete(id);
571
+ nodeIds.delete(node);
572
+ prevProps.delete(id);
573
+ nodeListeners.delete(id);
574
+ if (detached.size > DETACHED_CAP) {
575
+ const oldest = detached.keys().next().value;
576
+ if (oldest !== void 0) {
577
+ const dead = detached.get(oldest);
578
+ detached.delete(oldest);
579
+ if (dead !== void 0) detachedIds.delete(dead.node);
580
+ }
581
+ }
582
+ };
583
+ const tombstoneSubtree = (rootId, root) => {
584
+ const walk = (node) => {
585
+ const id = node === root ? rootId : nodeIds.get(node);
586
+ if (id !== void 0 && id !== 0) tombstoneId(id, node);
587
+ for (const child of Array.from(node.childNodes)) walk(child);
588
+ };
589
+ walk(root);
590
+ };
591
+ const promoteSubtree = (id) => {
592
+ const tomb = detached.get(id);
593
+ if (tomb === void 0) return nodes.get(id);
594
+ detached.delete(id);
595
+ detachedIds.delete(tomb.node);
596
+ nodes.set(id, tomb.node);
597
+ nodeIds.set(tomb.node, id);
598
+ if (tomb.props !== void 0) prevProps.set(id, tomb.props);
599
+ if (tomb.listeners !== void 0) nodeListeners.set(id, tomb.listeners);
600
+ if (tomb.slot !== void 0) {
601
+ slotNodes.set(id, tomb.slot);
602
+ opts.slots?.[tomb.slot]?.(tomb.node);
603
+ }
604
+ for (const child of Array.from(tomb.node.childNodes)) {
605
+ const cid = detachedIds.get(child);
606
+ if (cid !== void 0) promoteSubtree(cid);
607
+ }
608
+ return tomb.node;
609
+ };
610
+ const nodeById = (id) => nodes.get(id) ?? promoteSubtree(id);
611
+ let opsApplied = 0;
612
+ let flushCalls = 0;
613
+ let mode = initialMode ?? "push";
614
+ let unsubscribe = null;
615
+ let pollTimer = null;
616
+ let destroyed = false;
617
+ const COALESCE_EVENTS = /* @__PURE__ */ new Set(["mousemove", "pointermove", "touchmove", "drag", "dragover"]);
618
+ const MAX_IN_FLIGHT_DISPATCH = 4;
619
+ const UNMOUNT_GRACE_MS = 1500;
620
+ const dispatchQueue = [];
621
+ let inFlightDispatches = 0;
622
+ const pumpDispatches = () => {
623
+ while (!destroyed && inFlightDispatches < MAX_IN_FLIGHT_DISPATCH && dispatchQueue.length > 0) {
624
+ const next = dispatchQueue.shift();
625
+ inFlightDispatches++;
626
+ void islandCall("dispatch", client.dispatch(next.handlerId, next.payload)).then((ops) => applyOps(ops, "dispatch")).catch((err) => console.error(`[island ${instance}] dispatch failed`, err)).finally(() => {
627
+ inFlightDispatches--;
628
+ pumpDispatches();
629
+ });
630
+ }
631
+ };
632
+ function listenerFor(handlerId) {
633
+ return (e) => {
634
+ if (destroyed) return;
635
+ const target2 = e.target;
636
+ const payload = buildEventPayload(
637
+ e,
638
+ target2,
639
+ target2 !== null ? nodeIds.get(target2) : void 0,
640
+ nodeIds.get(e.relatedTarget)
641
+ );
642
+ const key = COALESCE_EVENTS.has(e.type) ? `${handlerId}:${e.type}` : null;
643
+ if (key !== null) {
644
+ const stale = dispatchQueue.findIndex((q) => q.key === key);
645
+ if (stale !== -1) dispatchQueue.splice(stale, 1);
646
+ }
647
+ dispatchQueue.push({ handlerId, key, payload });
648
+ pumpDispatches();
649
+ };
650
+ }
651
+ function setProp(el2, id, name, value) {
652
+ if (name === "data-atoll-slot") {
653
+ mountSlot(el2, id, String(value));
654
+ return;
655
+ }
656
+ if (isEventRef(value)) {
657
+ const eventName = name.slice(2).toLowerCase();
658
+ let table = nodeListeners.get(id);
659
+ if (!table) nodeListeners.set(id, table = /* @__PURE__ */ new Map());
660
+ if (!table.has(eventName)) {
661
+ const listener = listenerFor(value.__evt);
662
+ table.set(eventName, listener);
663
+ el2.addEventListener(eventName, listener);
664
+ }
665
+ return;
666
+ }
667
+ if (name === "style" && typeof value === "object" && value !== null) {
668
+ const decl = el2.style;
669
+ const elStyle = decl;
670
+ const prevStyle = prevProps.get(id)?.style ?? {};
671
+ const nextStyle = value;
672
+ for (const k of Object.keys(prevStyle)) if (!(k in nextStyle)) elStyle[k] = "";
673
+ for (const [k, v] of Object.entries(nextStyle)) {
674
+ if (prevStyle[k] !== v) {
675
+ if (k.startsWith("--") || k.includes("-")) decl.setProperty(k, cssStyleValue(k, v));
676
+ else elStyle[k] = cssStyleValue(k, v);
677
+ }
678
+ }
679
+ return;
680
+ }
681
+ if (name === "className") {
682
+ if (el2 instanceof SVGElement) el2.setAttribute("class", String(value));
683
+ else el2.className = String(value);
684
+ return;
685
+ }
686
+ if (value === true) {
687
+ el2.setAttribute(name, "");
688
+ if (name in el2) el2[name] = true;
689
+ return;
690
+ }
691
+ if (value === false) {
692
+ el2.removeAttribute(name);
693
+ if (name in el2) el2[name] = false;
694
+ return;
695
+ }
696
+ if (name in el2) {
697
+ try {
698
+ el2[name] = value;
699
+ return;
700
+ } catch {
701
+ }
702
+ }
703
+ el2.setAttribute(name, String(value));
704
+ }
705
+ function removeProp(el2, id, name, oldValue) {
706
+ if (name === "data-atoll-slot") {
707
+ unmountSlot(id);
708
+ el2.removeAttribute("data-atoll-slot");
709
+ return;
710
+ }
711
+ if (isEventRef(oldValue)) {
712
+ const eventName = name.slice(2).toLowerCase();
713
+ const listener = nodeListeners.get(id)?.get(eventName);
714
+ if (listener) {
715
+ el2.removeEventListener(eventName, listener);
716
+ nodeListeners.get(id)?.delete(eventName);
717
+ }
718
+ return;
719
+ }
720
+ if (name === "className") {
721
+ if (el2 instanceof SVGElement) el2.removeAttribute("class");
722
+ else el2.className = "";
723
+ return;
724
+ }
725
+ if (name === "style") {
726
+ el2.removeAttribute("style");
727
+ return;
728
+ }
729
+ if (name in el2 && typeof el2[name] === "boolean") {
730
+ el2[name] = false;
731
+ }
732
+ el2.removeAttribute(name);
733
+ }
734
+ function applyProps(el2, id, prev, next) {
735
+ for (const name of Object.keys(prev)) {
736
+ if (!(name in next)) removeProp(el2, id, name, prev[name]);
737
+ }
738
+ for (const [name, value] of Object.entries(next)) {
739
+ if (prev[name] !== value || isEventRef(value)) setProp(el2, id, name, value);
740
+ }
741
+ }
742
+ function setAttr(node, id, name, value) {
743
+ if (name === "data-atoll-slot") {
744
+ if (value === null) {
745
+ unmountSlot(id);
746
+ node.removeAttribute("data-atoll-slot");
747
+ } else {
748
+ mountSlot(node, id, value);
749
+ }
750
+ return;
751
+ }
752
+ if (value === null) {
753
+ if (name === "className") node.className = "";
754
+ else if (name === "style") node.removeAttribute("style");
755
+ else {
756
+ if (name in node && typeof node[name] === "boolean") {
757
+ node[name] = false;
758
+ }
759
+ node.removeAttribute(name);
760
+ }
761
+ return;
762
+ }
763
+ if (name === "class" || name === "className") {
764
+ if (node instanceof SVGElement) node.setAttribute("class", value);
765
+ else node.className = value;
766
+ return;
767
+ }
768
+ if (name === "style") {
769
+ node.setAttribute("style", value);
770
+ return;
771
+ }
772
+ if (name in node) {
773
+ try {
774
+ const cur = node[name];
775
+ node[name] = typeof cur === "boolean" ? true : value;
776
+ return;
777
+ } catch {
778
+ }
779
+ }
780
+ node.setAttribute(name, value);
781
+ }
782
+ function mergeStyle(node, id, changes) {
783
+ const elStyle = node.style;
784
+ const elStyleMap = elStyle;
785
+ const hadStyleAttr = node.hasAttribute("style");
786
+ const prev = prevProps.get(id) ?? {};
787
+ const merged = { ...prev.style ?? {} };
788
+ for (const [k, v] of Object.entries(changes)) {
789
+ const cv = cssStyleValue(k, v);
790
+ if (k.startsWith("--") || k.includes("-")) {
791
+ elStyle.setProperty(k, cv);
792
+ } else if (cv.endsWith("!important")) {
793
+ elStyle.setProperty(k, cv.replace(/\s*!important$/, ""), "important");
794
+ } else {
795
+ elStyleMap[k] = cv;
796
+ }
797
+ merged[k] = v;
798
+ }
799
+ if (hadStyleAttr && !node.hasAttribute("style")) node.setAttribute("style", "");
800
+ prev.style = merged;
801
+ prevProps.set(id, prev);
802
+ }
803
+ const realDocument = el.ownerDocument ?? document;
804
+ function applyOp(op) {
805
+ switch (op.t) {
806
+ case "create": {
807
+ const node = op.ns !== void 0 ? realDocument.createElementNS(op.ns, op.type) : realDocument.createElement(op.type);
808
+ nodes.set(op.id, node);
809
+ nodeIds.set(node, op.id);
810
+ for (const [name, value] of Object.entries(op.props)) setProp(node, op.id, name, value);
811
+ prevProps.set(op.id, op.props);
812
+ break;
813
+ }
814
+ case "text": {
815
+ const node = realDocument.createTextNode(op.text);
816
+ nodes.set(op.id, node);
817
+ nodeIds.set(node, op.id);
818
+ break;
819
+ }
820
+ case "append": {
821
+ let parent = nodeById(op.parent);
822
+ const child = nodeById(op.child);
823
+ if (!parent || !child) {
824
+ console.error(`[island] append skipped: parent=${op.parent}\u2192${String(parent)} child=${op.child}\u2192${String(child)}`);
825
+ break;
826
+ }
827
+ if (isElementNode(parent) && parent.tagName === "TEMPLATE") {
828
+ parent = parent.content;
829
+ }
830
+ const before = op.before !== void 0 ? nodeById(op.before) ?? null : null;
831
+ parent.insertBefore(child, before);
832
+ break;
833
+ }
834
+ case "remove": {
835
+ const child = nodeById(op.child);
836
+ if (child !== void 0) {
837
+ unmountSlotSubtree(child);
838
+ tombstoneSubtree(op.child, child);
839
+ }
840
+ child?.parentNode?.removeChild(child);
841
+ break;
842
+ }
843
+ case "update": {
844
+ const node = nodeById(op.id);
845
+ if (!isElementNode(node)) break;
846
+ const prev = prevProps.get(op.id) ?? {};
847
+ applyProps(node, op.id, prev, op.props);
848
+ prevProps.set(op.id, op.props);
849
+ break;
850
+ }
851
+ case "utext": {
852
+ const node = nodeById(op.id);
853
+ if (node) node.textContent = op.text;
854
+ break;
855
+ }
856
+ case "attr": {
857
+ const node = nodeById(op.id);
858
+ if (!isElementNode(node)) break;
859
+ setAttr(node, op.id, op.name, op.value);
860
+ break;
861
+ }
862
+ case "style": {
863
+ const node = nodeById(op.id);
864
+ if (!isElementNode(node)) break;
865
+ mergeStyle(node, op.id, op.props);
866
+ break;
867
+ }
868
+ case "listen": {
869
+ const node = nodeById(op.id);
870
+ if (!isElementNode(node)) break;
871
+ const key = `${op.type}#${op.handler}`;
872
+ let table = nodeListeners.get(op.id);
873
+ if (!table) nodeListeners.set(op.id, table = /* @__PURE__ */ new Map());
874
+ if (!table.has(key)) {
875
+ const listener = listenerFor(op.handler);
876
+ table.set(key, listener);
877
+ node.addEventListener(op.type, listener, op.opts);
878
+ }
879
+ break;
880
+ }
881
+ case "unlisten": {
882
+ const node = nodeById(op.id);
883
+ if (!isElementNode(node)) break;
884
+ const key = `${op.type}#${op.handler}`;
885
+ const listener = nodeListeners.get(op.id)?.get(key);
886
+ if (listener) {
887
+ node.removeEventListener(op.type, listener, op.opts?.capture);
888
+ nodeListeners.get(op.id)?.delete(key);
889
+ }
890
+ break;
891
+ }
892
+ case "clear": {
893
+ unmountSlotSubtree(el);
894
+ for (const child of Array.from(el.childNodes)) {
895
+ const id = nodeIds.get(child);
896
+ if (id !== void 0) tombstoneSubtree(id, child);
897
+ }
898
+ el.replaceChildren();
899
+ break;
900
+ }
901
+ case "emit": {
902
+ if (op.name === CALLBACK_EVENT) {
903
+ const p = op.payload;
904
+ if (p !== null && typeof p === "object" && typeof p.id === "number") {
905
+ cbTable.get(p.id)?.(...p.args ?? []);
906
+ }
907
+ break;
908
+ }
909
+ if (devtoolsEnabled()) {
910
+ emitDevtools({ type: "island:event", instance, name: op.name, payload: previewValue3(op.payload, 1024) });
911
+ }
912
+ onEvent?.(op.name, op.payload);
913
+ break;
914
+ }
915
+ case "measure": {
916
+ const node = nodeById(op.id);
917
+ if (!isElementNode(node)) break;
918
+ measuredIds.add(op.id);
919
+ ensureRectObserver();
920
+ rectObserver?.observe(node);
921
+ queueRect(op.id, node);
922
+ break;
923
+ }
924
+ case "unmeasure": {
925
+ unmeasure(op.id);
926
+ break;
927
+ }
928
+ }
929
+ }
930
+ function applyOps(ops, via) {
931
+ opsApplied += ops.length;
932
+ onActivity?.();
933
+ const t0 = performance.now();
934
+ for (const op of ops) applyOp(op);
935
+ const t1 = performance.now();
936
+ const replayMs = t1 - t0;
937
+ if (devtoolsEnabled()) {
938
+ emitDevtools({
939
+ type: "island:ops",
940
+ instance,
941
+ via,
942
+ count: ops.length,
943
+ replayMs,
944
+ bytes: estimateCloneBytes(ops)
945
+ });
946
+ measureReplay(instance, via, ops.length, t0, t1);
947
+ }
948
+ onOps?.(ops, replayMs);
949
+ }
950
+ const islandCall = (method, p) => {
951
+ const t0 = performance.now();
952
+ return p.then(
953
+ (r) => {
954
+ emitDevtools({
955
+ type: "island:task",
956
+ instance,
957
+ method,
958
+ ms: performance.now() - t0,
959
+ ops: Array.isArray(r) ? r.length : void 0
960
+ });
961
+ return r;
962
+ },
963
+ (err) => {
964
+ emitDevtools({
965
+ type: "island:task",
966
+ instance,
967
+ method,
968
+ ms: performance.now() - t0,
969
+ error: err instanceof Error ? err.message : String(err)
970
+ });
971
+ throw err;
972
+ }
973
+ );
974
+ };
975
+ const doFlush = () => {
976
+ if (destroyed) return;
977
+ flushCalls++;
978
+ void islandCall("flush", client.flush(instance)).then((ops) => applyOps(ops, "flush"));
979
+ };
980
+ const doorbell = client.sharedMemory ? observe(client.sharedMemory, "opsVersion") : null;
981
+ function setMode(next) {
982
+ mode = next;
983
+ unsubscribe?.();
984
+ unsubscribe = null;
985
+ if (pollTimer !== null) {
986
+ clearInterval(pollTimer);
987
+ pollTimer = null;
988
+ }
989
+ if (mode === "push" && doorbell !== null) unsubscribe = doorbell.subscribe(doFlush);
990
+ else pollTimer = window.setInterval(doFlush, 50);
991
+ onActivity?.();
992
+ }
993
+ const mountMs = mountTimeout ?? 15e3;
994
+ const raced = (p) => {
995
+ if (mountMs <= 0) return p;
996
+ let timer;
997
+ return Promise.race([
998
+ p,
999
+ new Promise((_, reject) => {
1000
+ timer = setTimeout(
1001
+ () => reject(
1002
+ new Error(
1003
+ `mountIsland(${app}): the worker never answered within ${mountMs}ms \u2014 check that the entry module loads (bundler path, uncaught import errors) and calls definePolyWorker/defineMonoWorker. Pass { mountTimeout: 0 } to disable.`
1004
+ )
1005
+ ),
1006
+ mountMs
1007
+ );
1008
+ })
1009
+ ]).finally(() => clearTimeout(timer));
1010
+ };
1011
+ let pid;
1012
+ try {
1013
+ applyOps(await raced(islandCall("mount", client.mount(instance, props))), "mount");
1014
+ pid = await raced(islandCall("whoami", client.whoami(instance)));
1015
+ if (devtoolsEnabled()) {
1016
+ const fw = framework ?? await client.renderer(instance).catch(() => null) ?? void 0;
1017
+ emitDevtools({ type: "island:mount", instance, app, pid, poolId: client.pool?.poolId, framework: fw });
1018
+ emitDevtools({ type: "island:props", instance, props: propsPreview(currentProps) });
1019
+ }
1020
+ } catch (err) {
1021
+ clientMounts.set(client, (clientMounts.get(client) ?? 1) - 1);
1022
+ if (givenClient === void 0) client.terminate();
1023
+ else void islandCall("unmount", client.unmount(instance)).catch(() => {
1024
+ });
1025
+ throw err;
1026
+ }
1027
+ setMode(mode);
1028
+ let sizeTimer = null;
1029
+ let sizeLastPush = -Infinity;
1030
+ let lastW = -1;
1031
+ let lastH = -1;
1032
+ const pushSize = () => {
1033
+ const w = el.clientWidth;
1034
+ const h = el.clientHeight;
1035
+ if (w === lastW && h === lastH) return;
1036
+ lastW = w;
1037
+ lastH = h;
1038
+ void islandCall("setSize", client.setSize(instance, w, h)).then((ops) => applyOps(ops, "setSize"));
1039
+ };
1040
+ const resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(() => {
1041
+ const now = Date.now();
1042
+ const elapsed = now - sizeLastPush;
1043
+ if (elapsed >= 100) {
1044
+ sizeLastPush = now;
1045
+ pushSize();
1046
+ } else if (sizeTimer === null) {
1047
+ sizeTimer = window.setTimeout(() => {
1048
+ sizeTimer = null;
1049
+ sizeLastPush = Date.now();
1050
+ pushSize();
1051
+ }, 100 - elapsed);
1052
+ }
1053
+ }) : null;
1054
+ resizeObserver?.observe(el);
1055
+ sizeLastPush = Date.now();
1056
+ pushSize();
1057
+ const handle = {
1058
+ app,
1059
+ pid,
1060
+ instance,
1061
+ get mode() {
1062
+ return mode;
1063
+ },
1064
+ get opsApplied() {
1065
+ return opsApplied;
1066
+ },
1067
+ get flushCalls() {
1068
+ return flushCalls;
1069
+ },
1070
+ setMode,
1071
+ updateProps: async (next) => {
1072
+ const marshalled = marshalCallbackProps(next, registerCb);
1073
+ assertCloneableProps(marshalled, `updateProps(${app})`);
1074
+ applyOps(await islandCall("updateProps", client.updateProps(instance, marshalled)), "updateProps");
1075
+ currentProps = next;
1076
+ if (devtoolsEnabled()) emitDevtools({ type: "island:props", instance, props: propsPreview(next) });
1077
+ },
1078
+ flush: async () => {
1079
+ applyOps(await islandCall("flush", client.flush(instance)), "flush");
1080
+ },
1081
+ destroy: () => {
1082
+ destroyed = true;
1083
+ untrackDevtools?.();
1084
+ untrackDevtools = null;
1085
+ emitDevtools({ type: "island:unmount", instance });
1086
+ unsubscribe?.();
1087
+ resizeObserver?.disconnect();
1088
+ rectObserver?.disconnect();
1089
+ if (pollTimer !== null) clearInterval(pollTimer);
1090
+ if (sizeTimer !== null) clearTimeout(sizeTimer);
1091
+ if (rectTimer !== null) clearTimeout(rectTimer);
1092
+ pendingRects.clear();
1093
+ measuredIds.clear();
1094
+ const remaining = (clientMounts.get(client) ?? 1) - 1;
1095
+ clientMounts.set(client, remaining);
1096
+ let finished = false;
1097
+ const finish = () => {
1098
+ if (finished) return;
1099
+ finished = true;
1100
+ nodes.clear();
1101
+ detached.clear();
1102
+ detachedIds.clear();
1103
+ prevProps.clear();
1104
+ nodeListeners.clear();
1105
+ cbTable.clear();
1106
+ slotNodes.clear();
1107
+ dispatchQueue.length = 0;
1108
+ const warm = opts.keepAlive === true || isKeepAliveClient(client);
1109
+ if (remaining <= 0 && !warm && (clientMounts.get(client) ?? 0) === 0)
1110
+ client.terminate();
1111
+ };
1112
+ void islandCall("unmount", client.unmount(instance)).then((ops) => applyOps(ops, "unmount")).catch(() => {
1113
+ }).finally(finish);
1114
+ setTimeout(finish, UNMOUNT_GRACE_MS);
1115
+ }
1116
+ };
1117
+ let untrackDevtools = devtoolsEnabled() ? trackIslandForDevtools({ handle, el, props: () => currentProps }) : null;
1118
+ return handle;
1119
+ }
1120
+
1121
+ // src/contract.ts
1122
+ var CONTRACT_MARK = "islandContract";
1123
+ var APP_CONTRACT = "appContract";
1124
+ function defineIslandContract(contract) {
1125
+ return { ...contract, [CONTRACT_MARK]: true };
1126
+ }
1127
+ var isIslandContract = (value) => typeof value === "object" && value !== null && value[CONTRACT_MARK] === true;
1128
+ function withContract(contract, app) {
1129
+ app[APP_CONTRACT] = contract;
1130
+ return app;
1131
+ }
1132
+ var contractOf = (app) => {
1133
+ if (typeof app !== "object" && typeof app !== "function") return void 0;
1134
+ if (app === null) return void 0;
1135
+ const c = app[APP_CONTRACT];
1136
+ return isIslandContract(c) ? c : void 0;
1137
+ };
1138
+ var contractWorkerOf = (app) => isIslandContract(app) ? app.worker : void 0;
1139
+
1140
+ // src/app.ts
1141
+ var ISLAND_APP_NAME = "islandAppName";
1142
+ var islandApp = (name, app) => {
1143
+ app[ISLAND_APP_NAME] = name;
1144
+ return app;
1145
+ };
1146
+ var islandAppNameOf = (app) => {
1147
+ if (typeof app === "string") return app === "" ? void 0 : app;
1148
+ if (app === null || typeof app !== "function" && typeof app !== "object") return void 0;
1149
+ if (isIslandContract(app)) return app.app === "" ? void 0 : app.app;
1150
+ const stamped = app[ISLAND_APP_NAME];
1151
+ if (typeof stamped === "string" && stamped !== "") return stamped;
1152
+ if (typeof app === "function") {
1153
+ const named = app;
1154
+ return named.displayName ?? (named.name !== "" ? named.name : void 0);
1155
+ }
1156
+ const imp = app.imperative;
1157
+ if (typeof imp === "function") {
1158
+ const stampedImp = imp[ISLAND_APP_NAME];
1159
+ if (typeof stampedImp === "string" && stampedImp !== "") return stampedImp;
1160
+ return imp.name !== "" ? imp.name : void 0;
1161
+ }
1162
+ return void 0;
1163
+ };
1164
+
1165
+ export {
1166
+ renderMemory,
1167
+ makeDoorbell,
1168
+ isEventRef,
1169
+ CALLBACK_EVENT,
1170
+ callbackProp,
1171
+ marshalCallbackProps,
1172
+ unmarshalCallbackProps,
1173
+ propsPreview,
1174
+ isKeepAliveClient,
1175
+ connectIslandWorker,
1176
+ assertCloneableProps,
1177
+ cssStyleValue,
1178
+ registerSubIslandMounter,
1179
+ mountIsland,
1180
+ defineIslandContract,
1181
+ isIslandContract,
1182
+ withContract,
1183
+ contractOf,
1184
+ contractWorkerOf,
1185
+ islandApp,
1186
+ islandAppNameOf
1187
+ };