ilha 0.14.6 → 0.14.8

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/README.md CHANGED
@@ -51,7 +51,7 @@ count.update((n) => n + 1); // patch
51
51
  In JSX, `{count}` subscribes the render. Derived values use Effect's `Atom.map` or `Atom.transform`:
52
52
 
53
53
  ```tsx
54
- import * as Atom from "effect/unstable/reactivity/Atom";
54
+ import * as Atom from "effect/reactivity/Atom";
55
55
  import { atom } from "ilha";
56
56
 
57
57
  const items = atom([{ n: 1 }, { n: 2 }]);
@@ -74,7 +74,7 @@ Paint [Effect `Stream`](https://www.effect.website/docs/v4/api/effect/Stream) va
74
74
 
75
75
  ```tsx
76
76
  import * as Stream from "effect/Stream";
77
- import * as Atom from "effect/unstable/reactivity/Atom";
77
+ import * as Atom from "effect/reactivity/Atom";
78
78
  import { atom } from "ilha";
79
79
 
80
80
  function* List() {
package/dist/define.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as Component } from "./types-CyoYWhJf.js";
1
+ import { i as Component } from "./types-CJF2qMK6.js";
2
2
  //#region src/define.d.ts
3
3
  declare const define: (name: string, component: Component) => void;
4
4
  //#endregion
package/dist/define.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as mount } from "./mount-DiIEX8s7.js";
1
+ import { t as mount } from "./mount-BQ-zcg2x.js";
2
2
 
3
3
  //#region src/define.ts
4
4
  const define = (name, component) => {
package/dist/index.d.ts CHANGED
@@ -1,34 +1,11 @@
1
- import { a as Instruction, c as UnsafeHtml, d as Yielded, i as Fragment, n as Component, t as AtomHandle, u as View } from "./types-CyoYWhJf.js";
2
- import { t as h } from "./vnode-C_BdUnIA.js";
3
- import * as Effect from "effect/Effect";
4
- import * as Stream from "effect/Stream";
5
- import * as Atom from "effect/unstable/reactivity/Atom";
1
+ import { S as Yielded, b as WatchContext, c as EventSourceStatus, g as UnsafeHtml, h as ResourceOptions, i as Component, l as Fragment, m as ResourceFetcher, n as AtomHandle, o as EventSourceFeed, p as Resource, r as AtomOptions, s as EventSourceOptions, t as AtomEquality, u as Instruction, v as View, x as WatchOnceCleanup, y as WatchCallback } from "./types-CJF2qMK6.js";
2
+ import { t as h } from "./vnode-CUTNGblA.js";
3
+ import * as Atom from "effect/reactivity/Atom";
4
+ import { Equal } from "effect";
5
+ import * as Effect$1 from "effect/Effect";
6
+ import * as Stream$1 from "effect/Stream";
7
+ import { AtomRegistry } from "effect/reactivity/AtomRegistry";
6
8
  import "effect/Scope";
7
- import { AtomRegistry } from "effect/unstable/reactivity/AtomRegistry";
8
- //#region src/watch.d.ts
9
- /** Values delivered to watch callbacks. */
10
- type WatchValue = string | number | boolean | bigint | null | undefined | readonly WatchValue[] | WatchRecord;
11
- interface WatchRecord {
12
- readonly [key: string]: WatchValue | undefined;
13
- }
14
- /** Optional disposer returned from `watch.once`. */
15
- type WatchOnceCleanup = () => void;
16
- interface WatchFn {
17
- <A extends WatchValue>(source: AtomHandle<A> | Atom.Atom<A> | Stream.Stream<A, unknown, unknown>, fn: (value: A) => void): Instruction<undefined>;
18
- once: (fn: () => WatchOnceCleanup | undefined) => Instruction<undefined>;
19
- }
20
- declare const watch: WatchFn;
21
- //#endregion
22
- //#region src/atom.d.ts
23
- /** Read atoms inside `fn` without subscribing the active render. Sync only. */
24
- declare const untrack: <A>(fn: () => A) => A;
25
- interface AtomFn {
26
- <A>(init: A | Atom.Atom<A> | Effect.Effect<A, unknown, AtomRegistry> | Stream.Stream<A, unknown, AtomRegistry>): AtomHandle<A>;
27
- lazy: <A>(init: () => A) => AtomHandle<A>;
28
- }
29
- declare const atom: AtomFn;
30
- declare const batch: (f: () => void) => void;
31
- //#endregion
32
9
  //#region src/context.d.ts
33
10
  interface ContextProviderProps<A> {
34
11
  value: A;
@@ -57,8 +34,52 @@ interface ErrorBoundaryProps {
57
34
  /** Catch subtree failures and paint a fallback. Supports reset(). */
58
35
  declare const ErrorBoundary: (props: ErrorBoundaryProps) => View;
59
36
  //#endregion
37
+ //#region src/watch.d.ts
38
+ /** Values delivered to watch callbacks. */
39
+ type WatchValue = string | number | boolean | bigint | null | undefined | readonly WatchValue[] | WatchRecord;
40
+ interface WatchRecord {
41
+ readonly [key: string]: WatchValue | undefined;
42
+ }
43
+ interface WatchFn {
44
+ <A extends WatchValue>(source: AtomHandle<A> | Atom.Atom<A> | Stream$1.Stream<A, unknown, unknown>, fn: WatchCallback<A>): Instruction<undefined>;
45
+ once: (fn: (ctx: WatchContext) => WatchOnceCleanup | undefined | Promise<WatchOnceCleanup | undefined>) => Instruction<undefined>;
46
+ }
47
+ declare const watch: WatchFn;
48
+ //#endregion
49
+ //#region src/atom.d.ts
50
+ /** Key-order-insensitive deep equality for JSON-shaped values. */
51
+ declare const structuralEqual: typeof Equal.equals;
52
+ /** Read atoms inside `fn` without subscribing the active render. Sync only. */
53
+ declare const untrack: <A>(fn: () => A) => A;
54
+ interface AtomFn {
55
+ <A>(init: A | Atom.Atom<A> | Effect$1.Effect<A, unknown, AtomRegistry> | Stream$1.Stream<A, unknown, AtomRegistry>, options?: AtomOptions<A>): AtomHandle<A>;
56
+ lazy: <A>(init: () => A) => AtomHandle<A>;
57
+ }
58
+ declare const atom: AtomFn;
59
+ declare const batch: (f: () => void) => void;
60
+ //#endregion
61
+ //#region src/resource.d.ts
62
+ /** Drop the cached value for `key` and refetch it in live components. */
63
+ declare const invalidate: (key: string) => void;
64
+ /**
65
+ * Fetch-once, share-everywhere async state for `key`. Concurrent mounts
66
+ * dedupe on one in-flight request; remounts seed from the shared cache
67
+ * (and revalidate when `staleWhileRevalidate` holds). Call unconditionally
68
+ * at the top level of a sync component, before any `await`.
69
+ */
70
+ declare const resource: <T>(key: string, fetcher: ResourceFetcher<T>, opts?: ResourceOptions) => Resource<T>;
71
+ //#endregion
72
+ //#region src/sse.d.ts
73
+ /**
74
+ * Subscribe to server-sent events. Opens one connection per `(url, event)`
75
+ * per component, reconnects with backoff, and closes on unmount. SSR renders
76
+ * an empty stream with status `"closed"`. Call unconditionally at the top
77
+ * level of a component.
78
+ */
79
+ declare const fromEventSource: <T = string>(url: string, opts?: EventSourceOptions<T>) => EventSourceFeed<T>;
80
+ //#endregion
60
81
  //#region src/when.d.ts
61
- declare const when: <A, E = never, R = never>(stream: Stream.Stream<A, E, R>, body: (value: A) => Generator<Yielded, View | undefined, View>) => Instruction<undefined, E>;
82
+ declare const when: <A, E = never, R = never>(stream: Stream$1.Stream<A, E, R>, body: (value: A) => Generator<Yielded, View | undefined, View>) => Instruction<undefined, E>;
62
83
  //#endregion
63
84
  //#region src/mount.d.ts
64
85
  interface RenderToStringOptions {
@@ -85,4 +106,4 @@ declare const renderToString: (fn: Component, opts?: RenderToStringOptions) => P
85
106
  */
86
107
  declare const unsafe: (html: string) => UnsafeHtml;
87
108
  //#endregion
88
- export { type AtomHandle, type Component, type ContextProviderProps, ErrorBoundary, type ErrorBoundaryProps, type ErrorFallback, type ErrorFallbackProps, Fragment, type IlhaContext, type MountOptions, type RenderToStringOptions, type UnsafeHtml, type View, type WatchOnceCleanup, atom, batch, context, createContext, h, mount, renderToString, unsafe, untrack, watch, when };
109
+ export { type AtomEquality, type AtomHandle, type AtomOptions, type Component, type ContextProviderProps, ErrorBoundary, type ErrorBoundaryProps, type ErrorFallback, type ErrorFallbackProps, type EventSourceFeed, type EventSourceOptions, type EventSourceStatus, Fragment, type IlhaContext, type MountOptions, type RenderToStringOptions, type Resource, type ResourceFetcher, type ResourceOptions, type UnsafeHtml, type View, type WatchCallback, type WatchContext, type WatchOnceCleanup, atom, batch, context, createContext, fromEventSource, h, invalidate, mount, renderToString, resource, structuralEqual, unsafe, untrack, watch, when };
package/dist/index.js CHANGED
@@ -1,12 +1,13 @@
1
- import { a as interpret, c as handleOwner, d as untrack, f as wrapHandle, g as makeFiber, h as getFiber, i as runSetup, l as instr, m as closeFiber, n as renderToString, o as atom, p as clearFiberView, r as unsafe, s as batch, t as mount, u as isAtomHandle } from "./mount-DiIEX8s7.js";
2
- import { d as errorView, i as Fragment, p as isFunction, t as h } from "./vnode-DEMDQ0eA.js";
1
+ import { _ as makeFiber, a as interpret, c as handleOwner, d as structuralEqual, f as untrack, g as getFiber, h as closeFiber, i as runSetup, l as instr, m as clearFiberView, n as renderToString, o as atom, p as wrapHandle, r as unsafe, s as batch, t as mount, u as isAtomHandle } from "./mount-BQ-zcg2x.js";
2
+ import { d as errorView, g as isString, i as Fragment, p as isFunction, t as h } from "./vnode-M-oJMKcv.js";
3
+ import * as Atom from "effect/reactivity/Atom";
3
4
  import * as Effect from "effect/Effect";
4
5
  import * as Stream from "effect/Stream";
5
- import * as Atom from "effect/unstable/reactivity/Atom";
6
6
  import * as Exit from "effect/Exit";
7
7
  import * as Fiber from "effect/Fiber";
8
+ import * as Registry from "effect/reactivity/AtomRegistry";
8
9
  import * as Scope from "effect/Scope";
9
- import * as Registry from "effect/unstable/reactivity/AtomRegistry";
10
+ import * as Queue from "effect/Queue";
10
11
 
11
12
  //#region src/context.ts
12
13
  const defaults = /* @__PURE__ */ new WeakMap();
@@ -77,6 +78,56 @@ const ErrorBoundary = (props) => {
77
78
  //#endregion
78
79
  //#region src/watch.ts
79
80
  const WATCH_ONCE = Symbol("ilha.watch.once");
81
+ const newRunCell = () => ({
82
+ cleanup: void 0,
83
+ ctrl: null,
84
+ extra: [],
85
+ gen: 0
86
+ });
87
+ const cancelRun = (cell) => {
88
+ cell.gen += 1;
89
+ cell.ctrl?.abort();
90
+ cell.ctrl = null;
91
+ const run = cell.cleanup;
92
+ cell.cleanup = void 0;
93
+ run?.();
94
+ for (const extra of cell.extra.splice(0)) extra();
95
+ };
96
+ const asCleanup = (value) => isFunction(value) ? value : void 0;
97
+ const settleAsync = async (cell, gen, out) => {
98
+ try {
99
+ const c = await out;
100
+ if (cell.gen === gen) cell.cleanup = asCleanup(c);
101
+ else asCleanup(c)?.();
102
+ } catch (error) {
103
+ console.error(error);
104
+ }
105
+ };
106
+ const settleRun = (cell, gen, out) => {
107
+ if (out instanceof Promise) {
108
+ settleAsync(cell, gen, out);
109
+ return;
110
+ }
111
+ if (cell.gen === gen) cell.cleanup = asCleanup(out);
112
+ else asCleanup(out)?.();
113
+ };
114
+ const runWithSignal = (cell, fn) => {
115
+ cancelRun(cell);
116
+ const { gen } = cell;
117
+ const ctrl = new AbortController();
118
+ cell.ctrl = ctrl;
119
+ const ctx = {
120
+ onCleanup: (extra) => {
121
+ if (ctrl.signal.aborted || cell.gen !== gen) {
122
+ extra();
123
+ return;
124
+ }
125
+ cell.extra.push(extra);
126
+ },
127
+ signal: ctrl.signal
128
+ };
129
+ settleRun(cell, gen, fn(ctx));
130
+ };
80
131
  const runStreamWatch = (fiber, effect) => {
81
132
  const scope = Scope.forkUnsafe(fiber.scope);
82
133
  const provided = effect.pipe(Effect.provideService(Registry.AtomRegistry, fiber.registry));
@@ -110,7 +161,28 @@ const useWatchSlot = (fiber, key, mount, fn) => {
110
161
  key
111
162
  };
112
163
  };
164
+ const sourceCells = /* @__PURE__ */ new WeakMap();
113
165
  const registerWatch = (fiber, source, fn) => {
166
+ const slot = fiber.watchI ?? 0;
167
+ let cells = sourceCells.get(fiber);
168
+ if (!cells) {
169
+ cells = [];
170
+ sourceCells.set(fiber, cells);
171
+ }
172
+ const entry = cells[slot] ?? {
173
+ cell: newRunCell(),
174
+ fn: void 0
175
+ };
176
+ cells[slot] = entry;
177
+ entry.fn = fn;
178
+ const { cell } = entry;
179
+ const wrapped = (value) => {
180
+ const latest = entry.fn ?? fn;
181
+ runWithSignal(cell, (ctx) => latest(value, ctx));
182
+ };
183
+ const disposeCell = () => {
184
+ cancelRun(cell);
185
+ };
114
186
  if (Stream.isStream(source)) {
115
187
  useWatchSlot(fiber, source, (run) => {
116
188
  let active = true;
@@ -119,15 +191,22 @@ const registerWatch = (fiber, source, fn) => {
119
191
  })));
120
192
  return () => {
121
193
  active = false;
194
+ disposeCell();
122
195
  stop();
123
196
  };
124
- }, fn);
197
+ }, wrapped);
125
198
  return;
126
199
  }
127
200
  const atom = isAtomHandle(source) ? source.atom : source;
128
- useWatchSlot(fiber, atom, (run) => fiber.registry.subscribe(atom, run, { immediate: true }), fn);
201
+ useWatchSlot(fiber, atom, (run) => {
202
+ const unsub = fiber.registry.subscribe(atom, run, { immediate: true });
203
+ return () => {
204
+ disposeCell();
205
+ unsub();
206
+ };
207
+ }, wrapped);
129
208
  };
130
- /** Run `fn` once on mount. Return a function to clean up on unmount. */
209
+ /** Run `fn` once on mount. Return a function (or a promise of one) to clean up on unmount. */
131
210
  const watchOnce = (fn) => {
132
211
  const fiber = getFiber();
133
212
  const i = fiber.watchI ?? 0;
@@ -139,12 +218,11 @@ const watchOnce = (fn) => {
139
218
  existing.dispose();
140
219
  fiber.watchSlots[i] = void 0;
141
220
  }
142
- let cleanup = fn();
221
+ const cell = newRunCell();
222
+ runWithSignal(cell, fn);
143
223
  fiber.watchSlots[i] = {
144
224
  dispose: () => {
145
- const run = cleanup;
146
- cleanup = void 0;
147
- run?.();
225
+ cancelRun(cell);
148
226
  },
149
227
  key: WATCH_ONCE
150
228
  };
@@ -158,6 +236,381 @@ const watchSource = (source, fn) => {
158
236
  };
159
237
  const watch = Object.assign(watchSource, { once: watchOnce });
160
238
 
239
+ //#endregion
240
+ //#region src/resource.ts
241
+ const sharedCache = /* @__PURE__ */ new Map();
242
+ const liveRefetchers = /* @__PURE__ */ new Map();
243
+ const cells$1 = /* @__PURE__ */ new WeakMap();
244
+ const entryFor = (key) => sharedCache.get(key);
245
+ /** Drop the cached value for `key` and refetch it in live components. */
246
+ const invalidate = (key) => {
247
+ sharedCache.delete(key);
248
+ const live = liveRefetchers.get(key);
249
+ if (!live) return;
250
+ for (const refetch of live) refetch();
251
+ };
252
+ /**
253
+ * Fetch-once, share-everywhere async state for `key`. Concurrent mounts
254
+ * dedupe on one in-flight request; remounts seed from the shared cache
255
+ * (and revalidate when `staleWhileRevalidate` holds). Call unconditionally
256
+ * at the top level of a sync component, before any `await`.
257
+ */
258
+ const resource = (key, fetcher, opts) => {
259
+ const fiber = getFiber();
260
+ const data = atom(void 0);
261
+ const error = atom(void 0);
262
+ const loading = atom(true);
263
+ let byKey = cells$1.get(fiber);
264
+ if (!byKey) {
265
+ byKey = /* @__PURE__ */ new Map();
266
+ cells$1.set(fiber, byKey);
267
+ }
268
+ if (fiber.runtime.ssr) {
269
+ let ssrCell = byKey.get(key);
270
+ if (!ssrCell) {
271
+ const { runtime } = fiber;
272
+ const sc = {
273
+ ctrl: null,
274
+ refetch: async () => {
275
+ sc.ctrl?.abort();
276
+ const ctrl = new AbortController();
277
+ sc.ctrl = ctrl;
278
+ sc.sink.loading.set(true);
279
+ runtime.begin();
280
+ try {
281
+ const v = await fetcher(key, { signal: ctrl.signal });
282
+ if (!ctrl.signal.aborted) {
283
+ sc.sink.data.set(v);
284
+ sc.sink.error.set(void 0);
285
+ sc.sink.loading.set(false);
286
+ }
287
+ return v;
288
+ } catch (fetchError) {
289
+ if (!ctrl.signal.aborted) {
290
+ sc.sink.error.set(fetchError);
291
+ sc.sink.loading.set(false);
292
+ }
293
+ throw fetchError;
294
+ } finally {
295
+ runtime.end();
296
+ }
297
+ },
298
+ sink: {
299
+ data,
300
+ error,
301
+ loading
302
+ }
303
+ };
304
+ Effect.runSync(Scope.addFinalizer(fiber.scope, Effect.sync(() => sc.ctrl?.abort())));
305
+ byKey.set(key, sc);
306
+ ssrCell = sc;
307
+ const settleSsr = async () => {
308
+ try {
309
+ await sc.refetch();
310
+ } catch {}
311
+ };
312
+ settleSsr();
313
+ }
314
+ ssrCell.sink = {
315
+ data,
316
+ error,
317
+ loading
318
+ };
319
+ return {
320
+ data,
321
+ error,
322
+ loading,
323
+ refetch: ssrCell.refetch
324
+ };
325
+ }
326
+ let cell = byKey.get(key);
327
+ if (!cell) {
328
+ cell = {
329
+ alive: true,
330
+ ctrl: null,
331
+ held: false,
332
+ sink: {
333
+ data,
334
+ error,
335
+ loading
336
+ },
337
+ started: false
338
+ };
339
+ byKey.set(key, cell);
340
+ }
341
+ cell.sink = {
342
+ data,
343
+ error,
344
+ loading
345
+ };
346
+ const c = cell;
347
+ const fiberCells = byKey;
348
+ const adoptShared = async (shared) => {
349
+ try {
350
+ const v = await shared;
351
+ if (c.alive) {
352
+ c.sink.data.set(v);
353
+ c.sink.error.set(void 0);
354
+ c.sink.loading.set(false);
355
+ }
356
+ } catch (fetchError) {
357
+ if (c.alive) {
358
+ c.sink.error.set(fetchError);
359
+ c.sink.loading.set(false);
360
+ }
361
+ }
362
+ };
363
+ const start = () => {
364
+ if (!c.alive) return Promise.resolve(c.sink.data());
365
+ const hit = entryFor(key);
366
+ if (hit?.promise) {
367
+ c.sink.loading.set(true);
368
+ adoptShared(hit.promise);
369
+ return hit.promise;
370
+ }
371
+ c.ctrl?.abort();
372
+ const ctrl = new AbortController();
373
+ c.ctrl = ctrl;
374
+ c.sink.loading.set(true);
375
+ const flight = {};
376
+ const run = async () => {
377
+ try {
378
+ const v = await fetcher(key, { signal: ctrl.signal });
379
+ if (entryFor(key)?.promise === flight.promise) sharedCache.set(key, {
380
+ data: v,
381
+ has: true
382
+ });
383
+ if (c.alive && !ctrl.signal.aborted) {
384
+ c.sink.data.set(v);
385
+ c.sink.error.set(void 0);
386
+ c.sink.loading.set(false);
387
+ }
388
+ return v;
389
+ } catch (fetchError) {
390
+ const cur = entryFor(key);
391
+ if (cur?.promise === flight.promise) cur.promise = void 0;
392
+ if (c.alive && !ctrl.signal.aborted) {
393
+ c.sink.error.set(fetchError);
394
+ c.sink.loading.set(false);
395
+ }
396
+ throw fetchError;
397
+ }
398
+ };
399
+ const p = run();
400
+ flight.promise = p;
401
+ const prev = entryFor(key);
402
+ sharedCache.set(key, {
403
+ data: prev?.data,
404
+ has: prev?.has ?? false,
405
+ promise: p
406
+ });
407
+ return p;
408
+ };
409
+ const settleStart = async () => {
410
+ try {
411
+ await start();
412
+ } catch {}
413
+ };
414
+ if (!c.started) {
415
+ c.started = true;
416
+ const swr = opts?.staleWhileRevalidate ?? true;
417
+ const hit = entryFor(key);
418
+ if (hit?.promise) {
419
+ if (hit.has) {
420
+ c.sink.data.set(hit.data);
421
+ c.sink.loading.set(false);
422
+ }
423
+ adoptShared(hit.promise);
424
+ } else if (hit?.has) {
425
+ c.sink.data.set(hit.data);
426
+ c.sink.error.set(void 0);
427
+ c.sink.loading.set(false);
428
+ if (swr) settleStart();
429
+ } else {
430
+ const seed = c.sink.data();
431
+ if (seed === void 0) settleStart();
432
+ else {
433
+ sharedCache.set(key, {
434
+ data: seed,
435
+ has: true
436
+ });
437
+ if (swr) settleStart();
438
+ else c.sink.loading.set(false);
439
+ }
440
+ }
441
+ }
442
+ if (!c.held) {
443
+ c.held = true;
444
+ const onInvalidate = () => {
445
+ settleStart();
446
+ };
447
+ watch.once(() => {
448
+ let live = liveRefetchers.get(key);
449
+ if (!live) {
450
+ live = /* @__PURE__ */ new Set();
451
+ liveRefetchers.set(key, live);
452
+ }
453
+ live.add(onInvalidate);
454
+ return () => {
455
+ c.alive = false;
456
+ c.ctrl?.abort();
457
+ const s = liveRefetchers.get(key);
458
+ s?.delete(onInvalidate);
459
+ if (s && s.size === 0) liveRefetchers.delete(key);
460
+ if (fiberCells.get(key) === c) fiberCells.delete(key);
461
+ };
462
+ });
463
+ }
464
+ return {
465
+ data,
466
+ error,
467
+ loading,
468
+ refetch: () => start()
469
+ };
470
+ };
471
+
472
+ //#endregion
473
+ //#region src/sse.ts
474
+ const cells = /* @__PURE__ */ new WeakMap();
475
+ const MAX_BACKOFF_MS = 3e4;
476
+ const setStatus = (c, next) => {
477
+ c.state = next;
478
+ for (const sink of c.sinks.values()) sink.status.set(next);
479
+ };
480
+ const connect = ({ cell: c, decode, event, retryBase, url }) => {
481
+ let attempt = 0;
482
+ let timer;
483
+ let source;
484
+ let detach;
485
+ let disposed = false;
486
+ const emit = (value) => {
487
+ c.last = { value };
488
+ for (const sink of c.sinks.values()) sink.latest.set(value);
489
+ for (const push of c.targets) push(value);
490
+ };
491
+ const onMessage = (ev) => {
492
+ const raw = ev.data;
493
+ if (!isString(raw)) return;
494
+ let value;
495
+ try {
496
+ value = decode(raw);
497
+ } catch {
498
+ return;
499
+ }
500
+ emit(value);
501
+ };
502
+ const open = () => {
503
+ if (disposed) return;
504
+ setStatus(c, attempt === 0 ? "connecting" : "retrying");
505
+ const es = new EventSource(url);
506
+ source = es;
507
+ const onOpen = () => {
508
+ attempt = 0;
509
+ setStatus(c, "open");
510
+ };
511
+ const onError = () => {
512
+ if (disposed || es.readyState !== EventSource.CLOSED) return;
513
+ detach?.();
514
+ es.close();
515
+ attempt += 1;
516
+ setStatus(c, "retrying");
517
+ timer = setTimeout(open, Math.min(retryBase * 2 ** (attempt - 1), MAX_BACKOFF_MS));
518
+ };
519
+ const messageEvent = event ?? "message";
520
+ es.addEventListener(messageEvent, onMessage);
521
+ es.addEventListener("open", onOpen);
522
+ es.addEventListener("error", onError);
523
+ detach = () => {
524
+ es.removeEventListener(messageEvent, onMessage);
525
+ es.removeEventListener("open", onOpen);
526
+ es.removeEventListener("error", onError);
527
+ };
528
+ };
529
+ open();
530
+ return () => {
531
+ disposed = true;
532
+ clearTimeout(timer);
533
+ detach?.();
534
+ source?.close();
535
+ setStatus(c, "closed");
536
+ };
537
+ };
538
+ /**
539
+ * Subscribe to server-sent events. Opens one connection per `(url, event)`
540
+ * per component, reconnects with backoff, and closes on unmount. SSR renders
541
+ * an empty stream with status `"closed"`. Call unconditionally at the top
542
+ * level of a component.
543
+ */
544
+ const fromEventSource = (url, opts) => {
545
+ const fiber = getFiber();
546
+ const status = atom("connecting");
547
+ const latest = atom(void 0);
548
+ const feedKey = `${url}\n${opts?.event ?? ""}`;
549
+ let byKey = cells.get(fiber);
550
+ if (!byKey) {
551
+ byKey = /* @__PURE__ */ new Map();
552
+ cells.set(fiber, byKey);
553
+ }
554
+ let cell = byKey.get(feedKey);
555
+ if (!cell) {
556
+ const targets = /* @__PURE__ */ new Set();
557
+ const stream = fiber.runtime.ssr ? Stream.empty : Stream.callback((queue) => {
558
+ const push = (value) => {
559
+ Queue.offerUnsafe(queue, value);
560
+ };
561
+ targets.add(push);
562
+ return Effect.acquireRelease(Effect.void, () => Effect.sync(() => targets.delete(push)));
563
+ });
564
+ cell = {
565
+ sinks: /* @__PURE__ */ new Map(),
566
+ state: "connecting",
567
+ stream,
568
+ targets,
569
+ users: 0
570
+ };
571
+ byKey.set(feedKey, cell);
572
+ }
573
+ const sinkKey = latest.atom;
574
+ cell.sinks.set(sinkKey, {
575
+ latest,
576
+ status
577
+ });
578
+ const c = cell;
579
+ const decode = opts?.schema ?? ((raw) => raw);
580
+ const retryBase = opts?.retryBaseMs ?? 1e3;
581
+ watch.once(() => {
582
+ if (fiber.runtime.ssr || globalThis.EventSource === void 0) {
583
+ status.set("closed");
584
+ return;
585
+ }
586
+ c.users += 1;
587
+ if (c.stop) {
588
+ status.set(c.state);
589
+ if (c.last) latest.set(c.last.value);
590
+ } else c.stop = connect({
591
+ cell: c,
592
+ decode,
593
+ event: opts?.event,
594
+ retryBase,
595
+ url
596
+ });
597
+ return () => {
598
+ c.users -= 1;
599
+ if (c.users === 0) {
600
+ const { stop } = c;
601
+ c.stop = void 0;
602
+ stop?.();
603
+ }
604
+ c.sinks.delete(sinkKey);
605
+ };
606
+ });
607
+ return {
608
+ latest,
609
+ status,
610
+ stream: c.stream
611
+ };
612
+ };
613
+
161
614
  //#endregion
162
615
  //#region src/when.ts
163
616
  const when = (stream, body) => instr(Effect.gen(function* whenBody() {
@@ -180,4 +633,4 @@ const when = (stream, body) => instr(Effect.gen(function* whenBody() {
180
633
  }));
181
634
 
182
635
  //#endregion
183
- export { ErrorBoundary, Fragment, atom, batch, context, createContext, h, mount, renderToString, unsafe, untrack, watch, when };
636
+ export { ErrorBoundary, Fragment, atom, batch, context, createContext, fromEventSource, h, invalidate, mount, renderToString, resource, structuralEqual, unsafe, untrack, watch, when };
@@ -1,4 +1,4 @@
1
- import { i as Fragment } from "./types-CyoYWhJf.js";
2
- import { n as JSX } from "./vnode-C_BdUnIA.js";
1
+ import { l as Fragment } from "./types-CJF2qMK6.js";
2
+ import { n as JSX } from "./vnode-CUTNGblA.js";
3
3
  import { jsx, jsxDEV, jsxs } from "./jsx-runtime.js";
4
4
  export { Fragment, type JSX, jsx, jsxDEV, jsxs };
@@ -1,4 +1,4 @@
1
- import { i as Fragment } from "./vnode-DEMDQ0eA.js";
1
+ import { i as Fragment } from "./vnode-M-oJMKcv.js";
2
2
  import { jsx, jsxDEV, jsxs } from "./jsx-runtime.js";
3
3
 
4
4
  export { Fragment, jsx, jsxDEV, jsxs };
@@ -1,5 +1,5 @@
1
- import { i as Fragment, o as JsxComponent, r as ComponentFn, s as PropBag, u as View } from "./types-CyoYWhJf.js";
2
- import { n as JSX, t as h } from "./vnode-C_BdUnIA.js";
1
+ import { a as ComponentFn, d as JsxComponent, f as PropBag, l as Fragment, v as View } from "./types-CJF2qMK6.js";
2
+ import { n as JSX, t as h } from "./vnode-CUTNGblA.js";
3
3
  //#region src/jsx-runtime.d.ts
4
4
  declare const jsx: (type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
5
5
  declare const jsxs: (type: string | Fragment | ComponentFn | JsxComponent, props: PropBag | null, key?: string | number) => ReturnType<typeof h>;
@@ -1,4 +1,4 @@
1
- import { i as Fragment, t as h } from "./vnode-DEMDQ0eA.js";
1
+ import { i as Fragment, t as h } from "./vnode-M-oJMKcv.js";
2
2
 
3
3
  //#region src/jsx-runtime.ts
4
4
  const jsx = (type, props, key) => h(type, key === void 0 ? props : {