ilha 0.15.2 → 0.16.0
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/AGENTS.md +102 -0
- package/LICENSE +21 -0
- package/MIGRATIONS.json +96 -0
- package/MIGRATIONS.md +198 -0
- package/README.md +9 -26
- package/dist/debug.d.ts +45 -0
- package/dist/debug.js +217 -0
- package/dist/index.d.ts +30 -50
- package/dist/index.js +105 -586
- package/dist/jsx-dev-runtime.d.ts +2 -2
- package/dist/jsx-dev-runtime.js +1 -1
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/jsx-runtime.js +1 -1
- package/dist/mount-3Ggg67T0.d.ts +14 -0
- package/dist/mount-BwuJsiDH.js +585 -0
- package/dist/paint-core-D1n4lbpF.js +350 -0
- package/dist/renderer.d.ts +30 -66
- package/dist/renderer.js +18 -50
- package/dist/{runtime-Baq7MtkE.d.ts → runtime-BCdKBqYp.d.ts} +29 -35
- package/dist/shared-vpgYXytd.js +36 -0
- package/dist/snapshot-BDC74QwI.d.ts +9 -0
- package/dist/start-B-e1dG5U.js +515 -0
- package/dist/test.d.ts +66 -0
- package/dist/test.js +179 -0
- package/dist/{types-DsdtZMhG.d.ts → types-NhEiBvwy.d.ts} +39 -46
- package/dist/vnode-DJ25Jn3D.js +22 -0
- package/dist/{vnode-CpSQZnLM.d.ts → vnode-DskooLPD.d.ts} +10 -2
- package/llms.txt +37 -0
- package/oxlint.cjs +596 -0
- package/package.json +20 -14
- package/dist/define.d.ts +0 -4
- package/dist/define.js +0 -18
- package/dist/mount-DKY_jSsG.js +0 -513
- package/dist/paint-core-B7jENZwS.js +0 -1693
- package/dist/vnode-BHUY61Rm.js +0 -100
package/dist/index.js
CHANGED
|
@@ -1,51 +1,43 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
import {
|
|
3
|
-
import { n as
|
|
1
|
+
import { _ as IlhaError, c as batch, d as untrack, f as wrapHandle, g as makeFiber, h as getFiber, i as runSetup, l as instr, m as closeFiber, o as interpret, p as clearFiberView, s as atom } from "./start-B-e1dG5U.js";
|
|
2
|
+
import { i as errorView, o as isFunction } from "./shared-vpgYXytd.js";
|
|
3
|
+
import { n as unsafe } from "./paint-core-D1n4lbpF.js";
|
|
4
|
+
import { r as renderToString, t as mount } from "./mount-BwuJsiDH.js";
|
|
5
|
+
import { r as Fragment, t as h } from "./vnode-DJ25Jn3D.js";
|
|
4
6
|
import * as Atom from "effect/reactivity/Atom";
|
|
5
7
|
import * as Effect from "effect/Effect";
|
|
6
8
|
import * as Stream from "effect/Stream";
|
|
7
|
-
import * as Exit from "effect/Exit";
|
|
8
|
-
import * as Fiber from "effect/Fiber";
|
|
9
|
-
import * as Registry from "effect/reactivity/AtomRegistry";
|
|
10
|
-
import * as Scope from "effect/Scope";
|
|
11
|
-
import * as Queue from "effect/Queue";
|
|
12
9
|
|
|
13
10
|
//#region src/context.ts
|
|
14
|
-
const
|
|
15
|
-
const getDefaultAtom = (ctx) => {
|
|
16
|
-
const existing = defaults.get(ctx);
|
|
17
|
-
if (existing) return existing;
|
|
18
|
-
const made = Atom.make(ctx.defaultValue);
|
|
19
|
-
defaults.set(ctx, made);
|
|
20
|
-
return made;
|
|
21
|
-
};
|
|
11
|
+
const fallbacks = /* @__PURE__ */ new WeakMap();
|
|
22
12
|
const createContext = (defaultValue) => {
|
|
23
13
|
const id = Symbol("ilha.context");
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
Provider,
|
|
14
|
+
const ctx = {
|
|
15
|
+
Provider: ({ value, children }) => {
|
|
16
|
+
const fiber = getFiber();
|
|
17
|
+
const handle = atom.lazy(() => value, { snapshot: false });
|
|
18
|
+
if (!Object.is(untrack(handle), value)) handle.set(value);
|
|
19
|
+
fiber.contexts ??= /* @__PURE__ */ new Map();
|
|
20
|
+
fiber.contexts.set(id, handle.atom);
|
|
21
|
+
return children;
|
|
22
|
+
},
|
|
35
23
|
defaultValue,
|
|
36
24
|
id
|
|
37
25
|
};
|
|
26
|
+
fallbacks.set(ctx, Atom.make(defaultValue));
|
|
27
|
+
return ctx;
|
|
38
28
|
};
|
|
39
29
|
/** Read a context value as an atom handle. Subscribes the active fiber on read. */
|
|
40
30
|
const context = (ctx) => {
|
|
41
31
|
const fiber = getFiber();
|
|
42
|
-
let
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
46
|
-
|
|
32
|
+
let found = fallbacks.get(ctx);
|
|
33
|
+
for (let f = fiber; f; f = f.parent) {
|
|
34
|
+
const provided = f.contexts?.get(ctx.id);
|
|
35
|
+
if (provided) {
|
|
36
|
+
found = provided;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
47
39
|
}
|
|
48
|
-
return wrapHandle(
|
|
40
|
+
return wrapHandle(found ?? Atom.make(ctx.defaultValue), fiber);
|
|
49
41
|
};
|
|
50
42
|
|
|
51
43
|
//#endregion
|
|
@@ -53,591 +45,118 @@ const context = (ctx) => {
|
|
|
53
45
|
/** Catch subtree failures and paint a fallback. Supports reset(). */
|
|
54
46
|
const ErrorBoundary = (props) => {
|
|
55
47
|
const fiber = getFiber();
|
|
56
|
-
const
|
|
57
|
-
if (fiber.closed) return;
|
|
58
|
-
clearFiberView(fiber);
|
|
59
|
-
const current = fiber.propsBox?.current ?? props;
|
|
60
|
-
runSetup(fiber, () => ErrorBoundary(current));
|
|
61
|
-
};
|
|
48
|
+
const current = () => fiber.propsBox?.current ?? props;
|
|
62
49
|
fiber.handleError = (error) => {
|
|
63
50
|
if (fiber.closed) return;
|
|
64
51
|
clearFiberView(fiber);
|
|
65
|
-
const { onError, fallback } =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
52
|
+
const { onError, fallback } = current();
|
|
53
|
+
onError?.(error);
|
|
54
|
+
fiber.paint(fallback ? fallback({
|
|
55
|
+
error,
|
|
56
|
+
reset: () => {
|
|
57
|
+
if (!fiber.closed) {
|
|
58
|
+
clearFiberView(fiber);
|
|
59
|
+
runSetup(fiber, () => ErrorBoundary(current()));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}) : errorView(error));
|
|
75
63
|
};
|
|
76
64
|
return props.children;
|
|
77
65
|
};
|
|
78
66
|
|
|
79
67
|
//#endregion
|
|
80
68
|
//#region src/watch.ts
|
|
81
|
-
const
|
|
82
|
-
const newRunCell = () => ({
|
|
83
|
-
cleanup: void 0,
|
|
84
|
-
ctrl: null,
|
|
85
|
-
extra: [],
|
|
86
|
-
gen: 0
|
|
87
|
-
});
|
|
88
|
-
const cancelRun = (cell) => {
|
|
89
|
-
cell.gen += 1;
|
|
90
|
-
cell.ctrl?.abort();
|
|
91
|
-
cell.ctrl = null;
|
|
92
|
-
const run = cell.cleanup;
|
|
93
|
-
cell.cleanup = void 0;
|
|
94
|
-
run?.();
|
|
95
|
-
for (const extra of cell.extra.splice(0)) extra();
|
|
96
|
-
};
|
|
97
|
-
const asCleanup = (value) => isFunction(value) ? value : void 0;
|
|
98
|
-
const settleAsync = async (cell, gen, out) => {
|
|
99
|
-
try {
|
|
100
|
-
const c = await out;
|
|
101
|
-
if (cell.gen === gen) cell.cleanup = asCleanup(c);
|
|
102
|
-
else asCleanup(c)?.();
|
|
103
|
-
} catch (error) {
|
|
104
|
-
console.error(error);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
const settleRun = (cell, gen, out) => {
|
|
108
|
-
if (out instanceof Promise) {
|
|
109
|
-
settleAsync(cell, gen, out);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
if (cell.gen === gen) cell.cleanup = asCleanup(out);
|
|
113
|
-
else asCleanup(out)?.();
|
|
114
|
-
};
|
|
115
|
-
const runWithSignal = (cell, fn) => {
|
|
116
|
-
cancelRun(cell);
|
|
117
|
-
const { gen } = cell;
|
|
118
|
-
const ctrl = new AbortController();
|
|
119
|
-
cell.ctrl = ctrl;
|
|
120
|
-
const ctx = {
|
|
121
|
-
onCleanup: (extra) => {
|
|
122
|
-
if (ctrl.signal.aborted || cell.gen !== gen) {
|
|
123
|
-
extra();
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
cell.extra.push(extra);
|
|
127
|
-
},
|
|
128
|
-
signal: ctrl.signal
|
|
129
|
-
};
|
|
130
|
-
settleRun(cell, gen, fn(ctx));
|
|
131
|
-
};
|
|
132
|
-
const runStreamWatch = (fiber, effect) => {
|
|
133
|
-
const scope = Scope.forkUnsafe(fiber.scope);
|
|
134
|
-
const provided = effect.pipe(Effect.provideService(Registry.AtomRegistry, fiber.registry));
|
|
135
|
-
const fork = Effect.runFork(provided);
|
|
136
|
-
Effect.runSync(Scope.addFinalizer(scope, Fiber.interrupt(fork)));
|
|
137
|
-
return () => {
|
|
138
|
-
Effect.runFork(Scope.close(scope, Exit.void));
|
|
139
|
-
};
|
|
140
|
-
};
|
|
141
|
-
const useWatchSlot = (fiber, key, mount, fn) => {
|
|
142
|
-
const i = fiber.watchI ?? 0;
|
|
143
|
-
fiber.watchI = i + 1;
|
|
144
|
-
fiber.watchSlots ??= [];
|
|
145
|
-
const existing = fiber.watchSlots[i];
|
|
146
|
-
if (existing) {
|
|
147
|
-
if (existing.key === key) {
|
|
148
|
-
if (existing.fnRef) existing.fnRef.current = fn;
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
existing.dispose();
|
|
152
|
-
fiber.watchSlots[i] = void 0;
|
|
153
|
-
}
|
|
154
|
-
const fnRef = { current: fn };
|
|
155
|
-
const unsub = mount((value) => {
|
|
156
|
-
fnRef.current(value);
|
|
157
|
-
});
|
|
158
|
-
const dispose = () => unsub();
|
|
159
|
-
fiber.watchSlots[i] = {
|
|
160
|
-
dispose,
|
|
161
|
-
fnRef,
|
|
162
|
-
key
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
const sourceCells = /* @__PURE__ */ new WeakMap();
|
|
166
|
-
const registerWatch = (fiber, source, fn) => {
|
|
167
|
-
const slot = fiber.watchI ?? 0;
|
|
168
|
-
let cells = sourceCells.get(fiber);
|
|
169
|
-
if (!cells) {
|
|
170
|
-
cells = [];
|
|
171
|
-
sourceCells.set(fiber, cells);
|
|
172
|
-
}
|
|
173
|
-
const entry = cells[slot] ?? {
|
|
174
|
-
cell: newRunCell(),
|
|
175
|
-
fn: void 0
|
|
176
|
-
};
|
|
177
|
-
cells[slot] = entry;
|
|
178
|
-
entry.fn = fn;
|
|
179
|
-
const { cell } = entry;
|
|
180
|
-
const wrapped = (value) => {
|
|
181
|
-
const latest = entry.fn ?? fn;
|
|
182
|
-
runWithSignal(cell, (ctx) => latest(value, ctx));
|
|
183
|
-
};
|
|
184
|
-
const disposeCell = () => {
|
|
185
|
-
cancelRun(cell);
|
|
186
|
-
};
|
|
187
|
-
if (Stream.isStream(source)) {
|
|
188
|
-
useWatchSlot(fiber, source, (run) => {
|
|
189
|
-
let active = true;
|
|
190
|
-
const stop = runStreamWatch(fiber, Stream.runForEach(source, (v) => Effect.sync(() => {
|
|
191
|
-
if (active) run(v);
|
|
192
|
-
})));
|
|
193
|
-
return () => {
|
|
194
|
-
active = false;
|
|
195
|
-
disposeCell();
|
|
196
|
-
stop();
|
|
197
|
-
};
|
|
198
|
-
}, wrapped);
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
const atom = isAtomHandle(source) ? source.atom : source;
|
|
202
|
-
useWatchSlot(fiber, atom, (run) => {
|
|
203
|
-
const unsub = fiber.registry.subscribe(atom, run, { immediate: true });
|
|
204
|
-
return () => {
|
|
205
|
-
disposeCell();
|
|
206
|
-
unsub();
|
|
207
|
-
};
|
|
208
|
-
}, wrapped);
|
|
209
|
-
};
|
|
210
|
-
/** Run `fn` once on mount. Return a function (or a promise of one) to clean up on unmount. */
|
|
211
|
-
const watchOnce = (fn) => {
|
|
212
|
-
const fiber = getFiber();
|
|
213
|
-
const i = fiber.watchI ?? 0;
|
|
214
|
-
fiber.watchI = i + 1;
|
|
215
|
-
fiber.watchSlots ??= [];
|
|
216
|
-
const existing = fiber.watchSlots[i];
|
|
217
|
-
if (existing) {
|
|
218
|
-
if (existing.key === WATCH_ONCE) return instr(Effect.void);
|
|
219
|
-
existing.dispose();
|
|
220
|
-
fiber.watchSlots[i] = void 0;
|
|
221
|
-
}
|
|
222
|
-
const cell = newRunCell();
|
|
223
|
-
runWithSignal(cell, fn);
|
|
224
|
-
fiber.watchSlots[i] = {
|
|
225
|
-
dispose: () => {
|
|
226
|
-
cancelRun(cell);
|
|
227
|
-
},
|
|
228
|
-
key: WATCH_ONCE
|
|
229
|
-
};
|
|
230
|
-
return instr(Effect.void);
|
|
231
|
-
};
|
|
232
|
-
/** Run `fn` when `source` changes — and once on mount. Sync, async, and generator components. */
|
|
233
|
-
const watchSource = (source, fn) => {
|
|
234
|
-
const fiber = (isAtomHandle(source) ? handleOwner.get(source) : void 0) ?? getFiber();
|
|
235
|
-
registerWatch(fiber, source, fn);
|
|
236
|
-
return instr(Effect.void);
|
|
237
|
-
};
|
|
238
|
-
const watch = Object.assign(watchSource, { once: watchOnce });
|
|
239
|
-
|
|
240
|
-
//#endregion
|
|
241
|
-
//#region src/resource.ts
|
|
242
|
-
const sharedCache = /* @__PURE__ */ new Map();
|
|
243
|
-
const liveRefetchers = /* @__PURE__ */ new Map();
|
|
244
|
-
const cells$1 = /* @__PURE__ */ new WeakMap();
|
|
245
|
-
/** Unmount: stop every client cell of one fiber (all keys it ever used). */
|
|
246
|
-
const releaseCells = (byKey) => {
|
|
247
|
-
for (const [key, entry] of byKey) {
|
|
248
|
-
const c = entry;
|
|
249
|
-
c.alive = false;
|
|
250
|
-
c.ctrl?.abort();
|
|
251
|
-
const live = liveRefetchers.get(key);
|
|
252
|
-
live?.delete(c.onInvalidate);
|
|
253
|
-
if (live && live.size === 0) liveRefetchers.delete(key);
|
|
254
|
-
}
|
|
255
|
-
byKey.clear();
|
|
256
|
-
};
|
|
257
|
-
const entryFor = (key) => sharedCache.get(key);
|
|
258
|
-
/** Drop the cached value for `key` and refetch it in live components. */
|
|
259
|
-
const invalidate = (key) => {
|
|
260
|
-
sharedCache.delete(key);
|
|
261
|
-
const live = liveRefetchers.get(key);
|
|
262
|
-
if (!live) return;
|
|
263
|
-
for (const refetch of live) refetch();
|
|
264
|
-
};
|
|
69
|
+
const ONCE = Symbol("ilha.watch.once");
|
|
265
70
|
/**
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* (and revalidate when `staleWhileRevalidate` holds). Call unconditionally
|
|
269
|
-
* at the top level of a sync component, before any `await`.
|
|
71
|
+
* Serial runs of one watch slot. Each run gets a fresh signal; starting the
|
|
72
|
+
* next run or cancelling aborts it, which also runs its cleanups.
|
|
270
73
|
*/
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const { runtime } = fiber;
|
|
285
|
-
const sc = {
|
|
286
|
-
ctrl: null,
|
|
287
|
-
refetch: async () => {
|
|
288
|
-
sc.ctrl?.abort();
|
|
289
|
-
const ctrl = new AbortController();
|
|
290
|
-
sc.ctrl = ctrl;
|
|
291
|
-
sc.sink.loading.set(true);
|
|
292
|
-
runtime.begin();
|
|
293
|
-
try {
|
|
294
|
-
const v = await fetcher(key, { signal: ctrl.signal });
|
|
295
|
-
if (!ctrl.signal.aborted) {
|
|
296
|
-
sc.sink.data.set(v);
|
|
297
|
-
sc.sink.error.set(void 0);
|
|
298
|
-
sc.sink.loading.set(false);
|
|
299
|
-
}
|
|
300
|
-
return v;
|
|
301
|
-
} catch (fetchError) {
|
|
302
|
-
if (!ctrl.signal.aborted) {
|
|
303
|
-
sc.sink.error.set(fetchError);
|
|
304
|
-
sc.sink.loading.set(false);
|
|
305
|
-
}
|
|
306
|
-
throw fetchError;
|
|
307
|
-
} finally {
|
|
308
|
-
runtime.end();
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
sink: {
|
|
312
|
-
data,
|
|
313
|
-
error,
|
|
314
|
-
loading
|
|
315
|
-
}
|
|
74
|
+
const makeRunner = () => {
|
|
75
|
+
let ctrl;
|
|
76
|
+
const cancel = () => ctrl?.abort();
|
|
77
|
+
return {
|
|
78
|
+
cancel,
|
|
79
|
+
run: (fn) => {
|
|
80
|
+
cancel();
|
|
81
|
+
ctrl = new AbortController();
|
|
82
|
+
const { signal } = ctrl;
|
|
83
|
+
const onCleanup = (c) => {
|
|
84
|
+
if (!isFunction(c)) return;
|
|
85
|
+
if (signal.aborted) c();
|
|
86
|
+
else signal.addEventListener("abort", () => c(), { once: true });
|
|
316
87
|
};
|
|
317
|
-
|
|
318
|
-
byKey.set(key, sc);
|
|
319
|
-
ssrCell = sc;
|
|
320
|
-
const settleSsr = async () => {
|
|
88
|
+
const settle = async (pending) => {
|
|
321
89
|
try {
|
|
322
|
-
await
|
|
323
|
-
} catch {
|
|
324
|
-
|
|
325
|
-
settleSsr();
|
|
326
|
-
}
|
|
327
|
-
ssrCell.sink = {
|
|
328
|
-
data,
|
|
329
|
-
error,
|
|
330
|
-
loading
|
|
331
|
-
};
|
|
332
|
-
return {
|
|
333
|
-
data,
|
|
334
|
-
error,
|
|
335
|
-
loading,
|
|
336
|
-
refetch: ssrCell.refetch
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
let cell = byKey.get(key);
|
|
340
|
-
if (!cell) {
|
|
341
|
-
const fresh = {
|
|
342
|
-
alive: true,
|
|
343
|
-
ctrl: null,
|
|
344
|
-
onInvalidate: () => {
|
|
345
|
-
fresh.revalidate();
|
|
346
|
-
},
|
|
347
|
-
revalidate: () => {},
|
|
348
|
-
sink: {
|
|
349
|
-
data,
|
|
350
|
-
error,
|
|
351
|
-
loading
|
|
352
|
-
},
|
|
353
|
-
started: false
|
|
354
|
-
};
|
|
355
|
-
cell = fresh;
|
|
356
|
-
byKey.set(key, cell);
|
|
357
|
-
let live = liveRefetchers.get(key);
|
|
358
|
-
if (!live) {
|
|
359
|
-
live = /* @__PURE__ */ new Set();
|
|
360
|
-
liveRefetchers.set(key, live);
|
|
361
|
-
}
|
|
362
|
-
live.add(cell.onInvalidate);
|
|
363
|
-
}
|
|
364
|
-
cell.sink = {
|
|
365
|
-
data,
|
|
366
|
-
error,
|
|
367
|
-
loading
|
|
368
|
-
};
|
|
369
|
-
const c = cell;
|
|
370
|
-
const fiberCells = byKey;
|
|
371
|
-
const adoptShared = async (shared) => {
|
|
372
|
-
try {
|
|
373
|
-
const v = await shared;
|
|
374
|
-
if (c.alive) {
|
|
375
|
-
c.sink.data.set(v);
|
|
376
|
-
c.sink.error.set(void 0);
|
|
377
|
-
c.sink.loading.set(false);
|
|
378
|
-
}
|
|
379
|
-
} catch (fetchError) {
|
|
380
|
-
if (c.alive) {
|
|
381
|
-
c.sink.error.set(fetchError);
|
|
382
|
-
c.sink.loading.set(false);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
const start = () => {
|
|
387
|
-
if (!c.alive) return Promise.resolve(c.sink.data());
|
|
388
|
-
const hit = entryFor(key);
|
|
389
|
-
if (hit?.promise) {
|
|
390
|
-
c.sink.loading.set(true);
|
|
391
|
-
adoptShared(hit.promise);
|
|
392
|
-
return hit.promise;
|
|
393
|
-
}
|
|
394
|
-
c.ctrl?.abort();
|
|
395
|
-
const ctrl = new AbortController();
|
|
396
|
-
c.ctrl = ctrl;
|
|
397
|
-
c.sink.loading.set(true);
|
|
398
|
-
const flight = {};
|
|
399
|
-
const run = async () => {
|
|
400
|
-
try {
|
|
401
|
-
const v = await fetcher(key, { signal: ctrl.signal });
|
|
402
|
-
if (entryFor(key)?.promise === flight.promise) sharedCache.set(key, {
|
|
403
|
-
data: v,
|
|
404
|
-
has: true
|
|
405
|
-
});
|
|
406
|
-
if (c.alive && !ctrl.signal.aborted) {
|
|
407
|
-
c.sink.data.set(v);
|
|
408
|
-
c.sink.error.set(void 0);
|
|
409
|
-
c.sink.loading.set(false);
|
|
410
|
-
}
|
|
411
|
-
return v;
|
|
412
|
-
} catch (fetchError) {
|
|
413
|
-
const cur = entryFor(key);
|
|
414
|
-
if (cur?.promise === flight.promise) cur.promise = void 0;
|
|
415
|
-
if (c.alive && !ctrl.signal.aborted) {
|
|
416
|
-
c.sink.error.set(fetchError);
|
|
417
|
-
c.sink.loading.set(false);
|
|
90
|
+
onCleanup(await pending);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(error);
|
|
418
93
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
data: prev?.data,
|
|
427
|
-
has: prev?.has ?? false,
|
|
428
|
-
promise: p
|
|
429
|
-
});
|
|
430
|
-
return p;
|
|
431
|
-
};
|
|
432
|
-
const settleStart = async () => {
|
|
433
|
-
try {
|
|
434
|
-
await start();
|
|
435
|
-
} catch {}
|
|
436
|
-
};
|
|
437
|
-
c.revalidate = () => {
|
|
438
|
-
settleStart();
|
|
439
|
-
};
|
|
440
|
-
if (!c.started) {
|
|
441
|
-
c.started = true;
|
|
442
|
-
const swr = opts?.staleWhileRevalidate ?? true;
|
|
443
|
-
const hit = entryFor(key);
|
|
444
|
-
if (hit?.promise) {
|
|
445
|
-
if (hit.has) {
|
|
446
|
-
c.sink.data.set(hit.data);
|
|
447
|
-
c.sink.loading.set(false);
|
|
448
|
-
}
|
|
449
|
-
adoptShared(hit.promise);
|
|
450
|
-
} else if (hit?.has) {
|
|
451
|
-
c.sink.data.set(hit.data);
|
|
452
|
-
c.sink.error.set(void 0);
|
|
453
|
-
c.sink.loading.set(false);
|
|
454
|
-
if (swr) settleStart();
|
|
455
|
-
} else {
|
|
456
|
-
const seed = c.sink.data();
|
|
457
|
-
if (seed === void 0) settleStart();
|
|
458
|
-
else {
|
|
459
|
-
sharedCache.set(key, {
|
|
460
|
-
data: seed,
|
|
461
|
-
has: true
|
|
462
|
-
});
|
|
463
|
-
if (swr) settleStart();
|
|
464
|
-
else c.sink.loading.set(false);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
watch.once(() => () => {
|
|
469
|
-
releaseCells(fiberCells);
|
|
470
|
-
});
|
|
471
|
-
return {
|
|
472
|
-
data,
|
|
473
|
-
error,
|
|
474
|
-
loading,
|
|
475
|
-
refetch: () => start()
|
|
476
|
-
};
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
//#endregion
|
|
480
|
-
//#region src/sse.ts
|
|
481
|
-
const cells = /* @__PURE__ */ new WeakMap();
|
|
482
|
-
const MAX_BACKOFF_MS = 3e4;
|
|
483
|
-
const setStatus = (c, next) => {
|
|
484
|
-
c.state = next;
|
|
485
|
-
for (const sink of c.sinks.values()) sink.status.set(next);
|
|
486
|
-
};
|
|
487
|
-
const connect = ({ cell: c, decode, event, retryBase, url }) => {
|
|
488
|
-
let attempt = 0;
|
|
489
|
-
let timer;
|
|
490
|
-
let source;
|
|
491
|
-
let detach;
|
|
492
|
-
let disposed = false;
|
|
493
|
-
const emit = (value) => {
|
|
494
|
-
c.last = { value };
|
|
495
|
-
for (const sink of c.sinks.values()) sink.latest.set(value);
|
|
496
|
-
for (const push of c.targets) push(value);
|
|
497
|
-
};
|
|
498
|
-
const onMessage = (ev) => {
|
|
499
|
-
const raw = ev.data;
|
|
500
|
-
if (!isString(raw)) return;
|
|
501
|
-
let value;
|
|
502
|
-
try {
|
|
503
|
-
value = decode(raw);
|
|
504
|
-
} catch {
|
|
505
|
-
return;
|
|
94
|
+
};
|
|
95
|
+
const out = fn({
|
|
96
|
+
onCleanup,
|
|
97
|
+
signal
|
|
98
|
+
});
|
|
99
|
+
if (out instanceof Promise) settle(out);
|
|
100
|
+
else onCleanup(out);
|
|
506
101
|
}
|
|
507
|
-
emit(value);
|
|
508
|
-
};
|
|
509
|
-
const open = () => {
|
|
510
|
-
if (disposed) return;
|
|
511
|
-
setStatus(c, attempt === 0 ? "connecting" : "retrying");
|
|
512
|
-
const es = new EventSource(url);
|
|
513
|
-
source = es;
|
|
514
|
-
const onOpen = () => {
|
|
515
|
-
attempt = 0;
|
|
516
|
-
setStatus(c, "open");
|
|
517
|
-
};
|
|
518
|
-
const onError = () => {
|
|
519
|
-
if (disposed || es.readyState !== EventSource.CLOSED) return;
|
|
520
|
-
detach?.();
|
|
521
|
-
es.close();
|
|
522
|
-
attempt += 1;
|
|
523
|
-
setStatus(c, "retrying");
|
|
524
|
-
timer = setTimeout(open, Math.min(retryBase * 2 ** (attempt - 1), MAX_BACKOFF_MS));
|
|
525
|
-
};
|
|
526
|
-
const messageEvent = event ?? "message";
|
|
527
|
-
es.addEventListener(messageEvent, onMessage);
|
|
528
|
-
es.addEventListener("open", onOpen);
|
|
529
|
-
es.addEventListener("error", onError);
|
|
530
|
-
detach = () => {
|
|
531
|
-
es.removeEventListener(messageEvent, onMessage);
|
|
532
|
-
es.removeEventListener("open", onOpen);
|
|
533
|
-
es.removeEventListener("error", onError);
|
|
534
|
-
};
|
|
535
|
-
};
|
|
536
|
-
open();
|
|
537
|
-
return () => {
|
|
538
|
-
disposed = true;
|
|
539
|
-
clearTimeout(timer);
|
|
540
|
-
detach?.();
|
|
541
|
-
source?.close();
|
|
542
|
-
setStatus(c, "closed");
|
|
543
102
|
};
|
|
544
103
|
};
|
|
545
104
|
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
* an empty stream with status `"closed"`. Call unconditionally at the top
|
|
549
|
-
* level of a component.
|
|
105
|
+
* Claim the fiber's next watch slot. A slot whose key matches keeps its
|
|
106
|
+
* subscription and takes the new callback; otherwise `open` subscribes anew.
|
|
550
107
|
*/
|
|
551
|
-
const
|
|
108
|
+
const useWatchSlot = (key, ref, open) => {
|
|
552
109
|
const fiber = getFiber();
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
const stream = fiber.runtime.ssr ? Stream.empty : Stream.callback((queue) => {
|
|
565
|
-
const push = (value) => {
|
|
566
|
-
Queue.offerUnsafe(queue, value);
|
|
567
|
-
};
|
|
568
|
-
targets.add(push);
|
|
569
|
-
return Effect.acquireRelease(Effect.void, () => Effect.sync(() => targets.delete(push)));
|
|
570
|
-
});
|
|
571
|
-
cell = {
|
|
572
|
-
sinks: /* @__PURE__ */ new Map(),
|
|
573
|
-
state: "connecting",
|
|
574
|
-
stream,
|
|
575
|
-
targets,
|
|
576
|
-
users: 0
|
|
110
|
+
const i = fiber.watchI ?? 0;
|
|
111
|
+
fiber.watchI = i + 1;
|
|
112
|
+
fiber.watchSlots ??= [];
|
|
113
|
+
const existing = fiber.watchSlots[i];
|
|
114
|
+
if (existing && existing.key === key) existing.ref.fn = ref.fn;
|
|
115
|
+
else {
|
|
116
|
+
existing?.dispose();
|
|
117
|
+
fiber.watchSlots[i] = {
|
|
118
|
+
dispose: open(fiber),
|
|
119
|
+
key,
|
|
120
|
+
ref
|
|
577
121
|
};
|
|
578
|
-
byKey.set(feedKey, cell);
|
|
579
122
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
if (fiber.runtime.ssr || globalThis.EventSource === void 0) {
|
|
590
|
-
status.set("closed");
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
c.users += 1;
|
|
594
|
-
if (c.stop) {
|
|
595
|
-
status.set(c.state);
|
|
596
|
-
if (c.last) latest.set(c.last.value);
|
|
597
|
-
} else c.stop = connect({
|
|
598
|
-
cell: c,
|
|
599
|
-
decode,
|
|
600
|
-
event: opts?.event,
|
|
601
|
-
retryBase,
|
|
602
|
-
url
|
|
603
|
-
});
|
|
123
|
+
return instr(Effect.void);
|
|
124
|
+
};
|
|
125
|
+
const watch = Object.assign((source, fn) => {
|
|
126
|
+
const ref = { fn };
|
|
127
|
+
const key = isFunction(source) ? source.atom : source;
|
|
128
|
+
return useWatchSlot(key, ref, (fiber) => {
|
|
129
|
+
const runner = makeRunner();
|
|
130
|
+
const emit = (value) => runner.run((ctx) => ref.fn(value, ctx));
|
|
131
|
+
const stop = Stream.isStream(source) ? fiber.run(Stream.runForEach(source, (v) => Effect.sync(() => emit(v)))) : fiber.registry.subscribe(isFunction(source) ? source.atom : source, emit, { immediate: true });
|
|
604
132
|
return () => {
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
const { stop } = c;
|
|
608
|
-
c.stop = void 0;
|
|
609
|
-
stop?.();
|
|
610
|
-
}
|
|
611
|
-
c.sinks.delete(sinkKey);
|
|
133
|
+
runner.cancel();
|
|
134
|
+
stop();
|
|
612
135
|
};
|
|
613
136
|
});
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
};
|
|
137
|
+
}, { once: (fn) => useWatchSlot(ONCE, {}, () => {
|
|
138
|
+
const runner = makeRunner();
|
|
139
|
+
runner.run(fn);
|
|
140
|
+
return runner.cancel;
|
|
141
|
+
}) });
|
|
620
142
|
|
|
621
143
|
//#endregion
|
|
622
144
|
//#region src/when.ts
|
|
623
|
-
|
|
145
|
+
/** Rerun `body` for every value of `stream`, replacing the previous run. */
|
|
146
|
+
const when = (stream, body) => instr(Effect.suspend(() => {
|
|
624
147
|
const parent = getFiber();
|
|
625
148
|
let child;
|
|
626
|
-
|
|
149
|
+
const stop = () => {
|
|
627
150
|
if (child) closeFiber(child);
|
|
628
|
-
|
|
629
|
-
|
|
151
|
+
};
|
|
152
|
+
parent.holes.push({ dispose: stop });
|
|
630
153
|
const base = stream;
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
parent
|
|
637
|
-
});
|
|
638
|
-
interpret((() => body(value)), child);
|
|
639
|
-
}));
|
|
154
|
+
return Stream.runForEach(parent.runtime.ssr ? Stream.take(base, 1) : base, (value) => Effect.sync(() => {
|
|
155
|
+
stop();
|
|
156
|
+
child = makeFiber(parent.runtime, parent.root, parent.paintFn, { parent });
|
|
157
|
+
interpret(() => body(value), child);
|
|
158
|
+
})).pipe(Effect.asVoid);
|
|
640
159
|
}));
|
|
641
160
|
|
|
642
161
|
//#endregion
|
|
643
|
-
export { ErrorBoundary, Fragment, atom, batch, context, createContext,
|
|
162
|
+
export { ErrorBoundary, Fragment, IlhaError, atom, batch, context, createContext, h, mount, renderToString, unsafe, untrack, watch, when };
|