ilha 0.14.0 → 0.14.2
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 +25 -20
- package/dist/define.d.ts +1 -1
- package/dist/define.js +1 -1
- package/dist/index.d.ts +13 -11
- package/dist/index.js +62 -40
- package/dist/jsx-dev-runtime.d.ts +2 -2
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/{mount-Dcy4teaf.js → mount-CCMOkDSb.js} +803 -134
- package/dist/{types-DU46exHq.d.ts → types-Cie0Ihl6.d.ts} +1 -2
- package/dist/{vnode-BZLcG8lC.d.ts → vnode-DyFwtMVw.d.ts} +1 -1
- package/package.json +2 -4
package/README.md
CHANGED
|
@@ -48,43 +48,48 @@ count.set(1); // replace
|
|
|
48
48
|
count.update((n) => n + 1); // patch
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
In JSX, `{count}` subscribes the render.
|
|
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";
|
|
55
|
+
import { atom } from "ilha";
|
|
56
|
+
|
|
54
57
|
const items = atom([{ n: 1 }, { n: 2 }]);
|
|
55
|
-
const total = atom(() =>
|
|
58
|
+
const total = atom(Atom.map(items.atom, (list) => list.reduce((sum, item) => sum + item.n, 0)));
|
|
56
59
|
```
|
|
57
60
|
|
|
61
|
+
Wrap multiple writes in `batch()`. Derived and mutation atoms use Effect's `Atom.map`, `Atom.transform`, and `Atom.fn` — pass `handle.atom`, then wrap in `atom()`. Use `watch(source, fn)` for side effects on atom changes.
|
|
62
|
+
|
|
63
|
+
Use `atom.lazy(() => …)` for one-time initialization or to store a function value.
|
|
64
|
+
|
|
58
65
|
Atoms hold data. Do not store JSX in an atom.
|
|
59
66
|
|
|
60
67
|
---
|
|
61
68
|
|
|
62
|
-
## Streams
|
|
69
|
+
## Streams and when
|
|
63
70
|
|
|
64
|
-
|
|
71
|
+
Paint [Effect `Stream`](https://www.effect.website/docs/v4/api/effect/Stream) values. Yield a stream of views from a generator, or return one from an async component:
|
|
65
72
|
|
|
66
73
|
```tsx
|
|
67
74
|
import * as Stream from "effect/Stream";
|
|
68
75
|
import * as Atom from "effect/unstable/reactivity/Atom";
|
|
69
|
-
import { atom
|
|
76
|
+
import { atom } from "ilha";
|
|
70
77
|
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
yield (
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
78
|
+
function* List() {
|
|
79
|
+
const items = atom(["a", "b"]);
|
|
80
|
+
yield Stream.map(Atom.toStream(items.atom), (list) => (
|
|
81
|
+
<ul>
|
|
82
|
+
{list.map((item) => (
|
|
83
|
+
<li key={item}>{item}</li>
|
|
84
|
+
))}
|
|
85
|
+
</ul>
|
|
86
|
+
));
|
|
87
|
+
}
|
|
81
88
|
```
|
|
82
89
|
|
|
83
|
-
|
|
84
|
-
- `watch(source, fn)` — side effect on an atom or Stream.
|
|
85
|
-
- `wait(body)` — paint until `done(value)`, then continue the generator.
|
|
90
|
+
SSR takes the first emission (`take(1)`); the client keeps listening after `mount`.
|
|
86
91
|
|
|
87
|
-
|
|
92
|
+
Ilha also ships `when` for per-emission generator bodies (stale work interrupted). Side effects and pauses use Effect directly — see the [Streams guide](https://ilha.build/guide/ui/streams).
|
|
88
93
|
|
|
89
94
|
---
|
|
90
95
|
|
|
@@ -112,7 +117,7 @@ if (host) mount(host, Counter, { hydrate: true });
|
|
|
112
117
|
| `timeout` | none | Serialize after this many ms |
|
|
113
118
|
| `captureActions` | `false` | Probe handlers for server RPCs |
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
`renderToString` builds HTML without a DOM — no polyfill in Node, Bun, or edge runtimes.
|
|
116
121
|
|
|
117
122
|
---
|
|
118
123
|
|
package/dist/define.d.ts
CHANGED
package/dist/define.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as h } from "./vnode-
|
|
1
|
+
import { i as Instruction, n as Component, o as View, r as Fragment, s as Yielded, t as AtomHandle } from "./types-Cie0Ihl6.js";
|
|
2
|
+
import { t as h } from "./vnode-DyFwtMVw.js";
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Stream from "effect/Stream";
|
|
5
5
|
import * as Atom from "effect/unstable/reactivity/Atom";
|
|
6
6
|
import "effect/Scope";
|
|
7
7
|
import { AtomRegistry } from "effect/unstable/reactivity/AtomRegistry";
|
|
8
|
+
//#region src/watch.d.ts
|
|
9
|
+
/** Run `fn` when `source` changes — and once on mount. Sync, async, and generator components. */
|
|
10
|
+
declare function watch<A>(source: AtomHandle<A> | Atom.Atom<A> | Stream.Stream<A, any, any>, fn: (value: A) => void): Instruction<void>;
|
|
11
|
+
//#endregion
|
|
8
12
|
//#region src/atom.d.ts
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
interface AtomFn {
|
|
14
|
+
<A>(init: A | Atom.Atom<A> | Effect.Effect<A, any, any> | Stream.Stream<A, any, any>): AtomHandle<A>;
|
|
15
|
+
lazy<A>(init: () => A): AtomHandle<A>;
|
|
16
|
+
}
|
|
17
|
+
declare const atom: AtomFn;
|
|
18
|
+
declare const batch: (f: () => void) => void;
|
|
11
19
|
//#endregion
|
|
12
20
|
//#region src/when.d.ts
|
|
13
21
|
declare function when<A, E = never, R = never>(stream: Stream.Stream<A, E, R>, body: (value: A) => Generator<Yielded, View | void, unknown>): Instruction<void, E>;
|
|
14
22
|
//#endregion
|
|
15
|
-
//#region src/watch.d.ts
|
|
16
|
-
declare function watch<A>(source: AtomHandle<A> | Atom.Atom<A> | Stream.Stream<A, any, any>, fn: (value: A) => void): Instruction<void>;
|
|
17
|
-
//#endregion
|
|
18
|
-
//#region src/wait.d.ts
|
|
19
|
-
declare function wait<A>(body: (done: Done<A>) => Generator<Yielded, View | void, unknown>): Instruction<A>;
|
|
20
|
-
//#endregion
|
|
21
23
|
//#region src/mount.d.ts
|
|
22
24
|
type RenderToStringOptions = {
|
|
23
25
|
snapshot?: boolean;
|
|
@@ -32,4 +34,4 @@ type MountOptions = {
|
|
|
32
34
|
declare function mount(el: Element, fn: Component, opts?: MountOptions): () => void;
|
|
33
35
|
declare function renderToString(fn: Component, opts?: RenderToStringOptions): Promise<string>;
|
|
34
36
|
//#endregion
|
|
35
|
-
export { type AtomHandle, type Component,
|
|
37
|
+
export { type AtomHandle, type Component, Fragment, type MountOptions, type RenderToStringOptions, type View, atom, batch, h, mount, renderToString, watch, when };
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,69 @@
|
|
|
1
|
-
import { a as atom, c as
|
|
1
|
+
import { a as atom, c as instr, d as getFiber, f as makeFiber, i as interpret, l as isAtomHandle, n as renderToString, o as batch, r as paint, s as handleOwner, t as mount, u as closeFiber } from "./mount-CCMOkDSb.js";
|
|
2
2
|
import { i as Fragment, t as h } from "./vnode-DHN2EFHi.js";
|
|
3
3
|
import * as Effect from "effect/Effect";
|
|
4
4
|
import * as Stream from "effect/Stream";
|
|
5
|
+
import * as Exit from "effect/Exit";
|
|
6
|
+
import * as Fiber from "effect/Fiber";
|
|
5
7
|
import * as Scope from "effect/Scope";
|
|
6
|
-
import * as
|
|
8
|
+
import * as Registry from "effect/unstable/reactivity/AtomRegistry";
|
|
7
9
|
|
|
10
|
+
//#region src/watch.ts
|
|
11
|
+
function runStreamWatch(fiber, effect) {
|
|
12
|
+
const scope = Scope.forkUnsafe(fiber.scope);
|
|
13
|
+
const provided = effect.pipe(Effect.provideService(Registry.AtomRegistry, fiber.registry));
|
|
14
|
+
const fork = Effect.runFork(provided);
|
|
15
|
+
Effect.runSync(Scope.addFinalizer(scope, Fiber.interrupt(fork)));
|
|
16
|
+
return () => {
|
|
17
|
+
Effect.runFork(Scope.close(scope, Exit.void));
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function useWatchSlot(fiber, key, mount, fn) {
|
|
21
|
+
const i = fiber.watchI ?? 0;
|
|
22
|
+
fiber.watchI = i + 1;
|
|
23
|
+
fiber.watchSlots ??= [];
|
|
24
|
+
const existing = fiber.watchSlots[i];
|
|
25
|
+
if (existing) {
|
|
26
|
+
if (existing.key !== key) {
|
|
27
|
+
existing.dispose();
|
|
28
|
+
fiber.watchSlots[i] = void 0;
|
|
29
|
+
} else {
|
|
30
|
+
existing.fnRef.current = fn;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const fnRef = { current: fn };
|
|
35
|
+
const unsub = mount((value) => fnRef.current(value));
|
|
36
|
+
const dispose = () => unsub();
|
|
37
|
+
fiber.watchSlots[i] = {
|
|
38
|
+
key,
|
|
39
|
+
fnRef,
|
|
40
|
+
dispose
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function registerWatch(fiber, source, fn) {
|
|
44
|
+
if (Stream.isStream(source)) {
|
|
45
|
+
useWatchSlot(fiber, source, (run) => {
|
|
46
|
+
let active = true;
|
|
47
|
+
const stop = runStreamWatch(fiber, Stream.runForEach(source, (v) => Effect.sync(() => {
|
|
48
|
+
if (active) run(v);
|
|
49
|
+
})));
|
|
50
|
+
return () => {
|
|
51
|
+
active = false;
|
|
52
|
+
stop();
|
|
53
|
+
};
|
|
54
|
+
}, fn);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const atom = isAtomHandle(source) ? source.atom : source;
|
|
58
|
+
useWatchSlot(fiber, atom, (run) => fiber.registry.subscribe(atom, run, { immediate: true }), fn);
|
|
59
|
+
}
|
|
60
|
+
/** Run `fn` when `source` changes — and once on mount. Sync, async, and generator components. */
|
|
61
|
+
function watch(source, fn) {
|
|
62
|
+
registerWatch((isAtomHandle(source) ? handleOwner.get(source) : void 0) ?? getFiber(), source, fn);
|
|
63
|
+
return instr(Effect.void);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
8
67
|
//#region src/when.ts
|
|
9
68
|
function when(stream, body) {
|
|
10
69
|
return instr(Effect.gen(function* () {
|
|
@@ -25,41 +84,4 @@ function when(stream, body) {
|
|
|
25
84
|
}
|
|
26
85
|
|
|
27
86
|
//#endregion
|
|
28
|
-
|
|
29
|
-
function watch(source, fn) {
|
|
30
|
-
return instr(Effect.sync(() => {
|
|
31
|
-
const fiber = (isAtomHandle(source) ? handleOwner.get(source) : void 0) ?? getFiber();
|
|
32
|
-
if (Stream.isStream(source)) {
|
|
33
|
-
fiber.run(Stream.runForEach(source, (v) => Effect.sync(() => fn(v))));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const a = isAtomHandle(source) ? source.atom : source;
|
|
37
|
-
const unsub = fiber.registry.subscribe(a, fn, { immediate: true });
|
|
38
|
-
Effect.runSync(Scope.addFinalizer(fiber.scope, Effect.sync(unsub)));
|
|
39
|
-
}));
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/wait.ts
|
|
44
|
-
function wait(body) {
|
|
45
|
-
return instr(Effect.gen(function* () {
|
|
46
|
-
const parent = getFiber();
|
|
47
|
-
const deferred = yield* Deferred.make();
|
|
48
|
-
let settled = false;
|
|
49
|
-
const done = (value) => {
|
|
50
|
-
if (settled) return;
|
|
51
|
-
settled = true;
|
|
52
|
-
Effect.runSync(Deferred.succeed(deferred, value));
|
|
53
|
-
};
|
|
54
|
-
const child = makeFiber(parent.runtime, parent.root, paint, { onFail: parent.fail });
|
|
55
|
-
interpret((() => body(done)), child);
|
|
56
|
-
try {
|
|
57
|
-
return yield* Deferred.await(deferred);
|
|
58
|
-
} finally {
|
|
59
|
-
closeFiber(child);
|
|
60
|
-
}
|
|
61
|
-
}));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
//#endregion
|
|
65
|
-
export { Fragment, atom, h, mount, renderToString, wait, watch, when };
|
|
87
|
+
export { Fragment, atom, batch, h, mount, renderToString, watch, when };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as JSX } from "./vnode-
|
|
1
|
+
import { r as Fragment } from "./types-Cie0Ihl6.js";
|
|
2
|
+
import { n as JSX } from "./vnode-DyFwtMVw.js";
|
|
3
3
|
import { jsx, jsxDEV, jsxs } from "./jsx-runtime.js";
|
|
4
4
|
export { Fragment, type JSX, jsx, jsxDEV, jsxs };
|
package/dist/jsx-runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as JSX, t as h } from "./vnode-
|
|
1
|
+
import { o as View, r as Fragment } from "./types-Cie0Ihl6.js";
|
|
2
|
+
import { n as JSX, t as h } from "./vnode-DyFwtMVw.js";
|
|
3
3
|
//#region src/jsx-runtime.d.ts
|
|
4
4
|
declare const jsx: (type: string | typeof Fragment | ((p: Record<string, unknown>) => unknown), props: Record<string, unknown> | null, key?: string | number) => ReturnType<typeof h>;
|
|
5
5
|
declare const jsxs: (type: string | typeof Fragment | ((p: Record<string, unknown>) => unknown), props: Record<string, unknown> | null, key?: string | number) => ReturnType<typeof h>;
|