@thomasfosterau/effect-svelte 0.0.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/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/Store.d.ts +154 -0
- package/dist/Store.js +183 -0
- package/dist/Store.js.map +1 -0
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/context.svelte.d.ts +37 -0
- package/dist/context.svelte.js +53 -0
- package/dist/context.svelte.js.map +1 -0
- package/dist/derived.svelte.d.ts +43 -0
- package/dist/derived.svelte.js +65 -0
- package/dist/derived.svelte.js.map +1 -0
- package/dist/effect.svelte.d.ts +51 -0
- package/dist/effect.svelte.js +68 -0
- package/dist/effect.svelte.js.map +1 -0
- package/dist/emitter.d.ts +45 -0
- package/dist/emitter.js +60 -0
- package/dist/emitter.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +13 -0
- package/dist/internal/result.svelte.js +36 -0
- package/dist/internal/result.svelte.js.map +1 -0
- package/dist/internal/run.d.ts +15 -0
- package/dist/internal/run.js +25 -0
- package/dist/internal/run.js.map +1 -0
- package/dist/internal/subscribe.js +79 -0
- package/dist/internal/subscribe.js.map +1 -0
- package/dist/query.svelte.d.ts +45 -0
- package/dist/query.svelte.js +41 -0
- package/dist/query.svelte.js.map +1 -0
- package/dist/reactivity.svelte.d.ts +123 -0
- package/dist/reactivity.svelte.js +186 -0
- package/dist/reactivity.svelte.js.map +1 -0
- package/dist/runtime.d.ts +81 -0
- package/dist/runtime.js +85 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scope.svelte.d.ts +49 -0
- package/dist/scope.svelte.js +68 -0
- package/dist/scope.svelte.js.map +1 -0
- package/dist/stream.svelte.d.ts +48 -0
- package/dist/stream.svelte.js +34 -0
- package/dist/stream.svelte.js.map +1 -0
- package/dist/subscription.svelte.d.ts +69 -0
- package/dist/subscription.svelte.js +69 -0
- package/dist/subscription.svelte.js.map +1 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Foster
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# @thomasfosterau/effect-svelte
|
|
2
|
+
|
|
3
|
+
Effect v4-native integration for **Svelte 5 runes**. Run Effects from
|
|
4
|
+
components and bridge Effect's reactive primitives to runes — results are
|
|
5
|
+
exposed as Effect's native
|
|
6
|
+
[`AsyncResult`](https://effect.website) (`effect/unstable/reactivity/AsyncResult`)
|
|
7
|
+
with stale-while-revalidate `waiting` semantics.
|
|
8
|
+
|
|
9
|
+
Part of the [`effect-svelte`](https://github.com/thomasfosterau/effect-svelte)
|
|
10
|
+
monorepo. For the SvelteKit integration, see
|
|
11
|
+
[`@thomasfosterau/effect-sveltekit`](https://www.npmjs.com/package/@thomasfosterau/effect-sveltekit).
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @thomasfosterau/effect-svelte effect@beta svelte
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```svelte
|
|
22
|
+
<script lang="ts">
|
|
23
|
+
import { provideRuntime, useDerived, AsyncResult } from "@thomasfosterau/effect-svelte";
|
|
24
|
+
import { Effect } from "effect";
|
|
25
|
+
|
|
26
|
+
provideRuntime();
|
|
27
|
+
|
|
28
|
+
let userId = $state(1);
|
|
29
|
+
const user = useDerived(() => fetchUser(userId));
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<button onclick={() => userId++}>Next</button>
|
|
33
|
+
|
|
34
|
+
{#if AsyncResult.isSuccess(user.current)}
|
|
35
|
+
<!-- `waiting` stays true while re-fetching, so the previous value is kept -->
|
|
36
|
+
<p class:stale={user.current.waiting}>{user.current.value.name}</p>
|
|
37
|
+
{/if}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## What's included
|
|
41
|
+
|
|
42
|
+
- **Runtime context** — `provideRuntime` / `getRuntime`, `SvelteRuntime`, and
|
|
43
|
+
component-lifecycle scopes (`useScope` / `useScopeCallback`).
|
|
44
|
+
- **Reactive hooks** (all expose a `.current` `AsyncResult`): `useEffect`,
|
|
45
|
+
`useQuery`, `useDerived`, `useStream`, `useSubscriptionRef`, `usePubSub`, and
|
|
46
|
+
the keyed-reactivity hooks `reactiveQuery` / `reactiveStream` /
|
|
47
|
+
`reactiveMutation` / `useInvalidateKeys`.
|
|
48
|
+
- **Store interop** — the `Store` namespace (`Store.toStream` for
|
|
49
|
+
Svelte → Effect; `Store.fromStream` / `fromSubscriptionRef` / `fromPubSub` /
|
|
50
|
+
`fromQueue` / `fromRef` for Effect → Svelte stores), plus `SignalEmitter`.
|
|
51
|
+
- The native `AsyncResult` namespace is re-exported from the package root.
|
|
52
|
+
|
|
53
|
+
See the [monorepo README](https://github.com/thomasfosterau/effect-svelte#readme)
|
|
54
|
+
for the full API and runnable examples.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT
|
package/dist/Store.d.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { RuntimeLike } from "./internal/run.js";
|
|
2
|
+
import { PubSub, Queue, Ref, Stream, SubscriptionRef } from "effect";
|
|
3
|
+
import { Readable } from "svelte/store";
|
|
4
|
+
|
|
5
|
+
//#region src/Store.d.ts
|
|
6
|
+
declare namespace Store_d_exports {
|
|
7
|
+
export { fromPubSub, fromQueue, fromRef, fromStream, fromSubscriptionRef, toStream };
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Converts a Svelte [store](https://svelte.dev/docs/svelte/stores) (anything
|
|
11
|
+
* implementing the readable store contract) into an Effect `Stream` that emits
|
|
12
|
+
* the store's current value and every subsequent update.
|
|
13
|
+
*
|
|
14
|
+
* This is the Svelte→Effect counterpart to the `from*` helpers in this module
|
|
15
|
+
* (which go Effect→Svelte): it lets store-based state — including third-party
|
|
16
|
+
* libraries that expose Svelte stores — feed Effect pipelines. The subscription
|
|
17
|
+
* is opened when the stream is run and closed when its scope closes.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
22
|
+
* import { Stream } from 'effect';
|
|
23
|
+
*
|
|
24
|
+
* // `query` is a Svelte store from some library
|
|
25
|
+
* const debounced = Store.toStream(query).pipe(
|
|
26
|
+
* Stream.debounce('300 millis'),
|
|
27
|
+
* );
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare function toStream<A>(store: Readable<A>): Stream.Stream<A>;
|
|
31
|
+
/**
|
|
32
|
+
* Converts an Effect `Stream` into a Svelte
|
|
33
|
+
* [readable store](https://svelte.dev/docs/svelte/stores), tracking the latest
|
|
34
|
+
* emitted value. The given `initial` value is used until the stream emits.
|
|
35
|
+
*
|
|
36
|
+
* This is the base Effect→Svelte helper that the other `from*` functions build
|
|
37
|
+
* on. Unlike the `useX` hooks, it takes an explicit `runtime` rather than
|
|
38
|
+
* reading the Svelte context, so it can be called outside component
|
|
39
|
+
* initialization (e.g. in module scope or a load function) and is
|
|
40
|
+
* straightforward to test.
|
|
41
|
+
*
|
|
42
|
+
* The Effect fiber driving the store is forked lazily on the first subscription
|
|
43
|
+
* and interrupted when the last subscriber unsubscribes, mirroring Svelte's
|
|
44
|
+
* start/stop notifier semantics.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
49
|
+
* import { Stream } from 'effect';
|
|
50
|
+
*
|
|
51
|
+
* const ticks = Store.fromStream(
|
|
52
|
+
* Stream.tick('1 second').pipe(Stream.scan(0, (n) => n + 1)),
|
|
53
|
+
* 0,
|
|
54
|
+
* runtime,
|
|
55
|
+
* );
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare function fromStream<A, E>(stream: Stream.Stream<A, E, never>, initial: A, runtime: RuntimeLike<never>): Readable<A>;
|
|
59
|
+
/**
|
|
60
|
+
* Converts an Effect `SubscriptionRef` into a Svelte
|
|
61
|
+
* [readable store](https://svelte.dev/docs/svelte/stores), so Effect-managed
|
|
62
|
+
* reactive state can be consumed anywhere a store is expected (including the
|
|
63
|
+
* `$store` auto-subscription syntax in components, and store-based libraries).
|
|
64
|
+
*
|
|
65
|
+
* This is the Effect→Svelte counterpart to {@link toStream}. Like
|
|
66
|
+
* {@link fromStream}, it takes an explicit `runtime` rather than reading the
|
|
67
|
+
* Svelte context, so it can be called outside component initialization.
|
|
68
|
+
*
|
|
69
|
+
* The fiber driving the store is forked lazily on the first subscription and
|
|
70
|
+
* interrupted when the last subscriber unsubscribes. Because
|
|
71
|
+
* `SubscriptionRef.changes` re-emits the current value on subscribe,
|
|
72
|
+
* subscribers receive the up-to-date value immediately; the ref's current value
|
|
73
|
+
* is also used as the store's initial value for reads that occur before the
|
|
74
|
+
* fiber has emitted.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```svelte
|
|
78
|
+
* <script lang="ts">
|
|
79
|
+
* import { Store, getRuntime } from '@thomasfosterau/effect-svelte';
|
|
80
|
+
*
|
|
81
|
+
* // Assume counterRef is a SubscriptionRef<number>
|
|
82
|
+
* const count = Store.fromSubscriptionRef(counterRef, getRuntime());
|
|
83
|
+
* </script>
|
|
84
|
+
*
|
|
85
|
+
* <p>Count: {$count}</p>
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
declare function fromSubscriptionRef<A>(ref: SubscriptionRef.SubscriptionRef<A>, runtime: RuntimeLike<never>): Readable<A>;
|
|
89
|
+
/**
|
|
90
|
+
* Converts an Effect `PubSub` into a Svelte
|
|
91
|
+
* [readable store](https://svelte.dev/docs/svelte/stores) that tracks the most
|
|
92
|
+
* recently published message. The given `initial` value is used until the
|
|
93
|
+
* first message is published.
|
|
94
|
+
*
|
|
95
|
+
* A `PubSub` has no notion of a "current" value, so an explicit `initial` is
|
|
96
|
+
* required; only messages published while the store has at least one subscriber
|
|
97
|
+
* are observed. Built on {@link fromStream} via `Stream.fromPubSub`.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```ts
|
|
101
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
102
|
+
*
|
|
103
|
+
* // Assume eventsPubSub is a PubSub<Event>
|
|
104
|
+
* const latestEvent = Store.fromPubSub(eventsPubSub, null, runtime);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
declare function fromPubSub<A>(pubsub: PubSub.PubSub<A>, initial: A, runtime: RuntimeLike<never>): Readable<A>;
|
|
108
|
+
/**
|
|
109
|
+
* Converts an Effect `Queue` (its consumer/`Dequeue` side) into a Svelte
|
|
110
|
+
* [readable store](https://svelte.dev/docs/svelte/stores) that tracks the most
|
|
111
|
+
* recently taken value. The given `initial` value is used until the first value
|
|
112
|
+
* is taken.
|
|
113
|
+
*
|
|
114
|
+
* A queue has no notion of a "current" value, so an explicit `initial` is
|
|
115
|
+
* required. Built on {@link fromStream} via `Stream.fromQueue`, which takes from
|
|
116
|
+
* the queue while the store has at least one subscriber and ends the stream
|
|
117
|
+
* when the queue completes (`Cause.Done` is treated as end-of-stream and is
|
|
118
|
+
* therefore excluded from the store's error channel).
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```ts
|
|
122
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
123
|
+
*
|
|
124
|
+
* // Assume workQueue is a Queue<Job>
|
|
125
|
+
* const currentJob = Store.fromQueue(workQueue, null, runtime);
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
declare function fromQueue<A, E>(queue: Queue.Dequeue<A, E>, initial: A, runtime: RuntimeLike<never>): Readable<A>;
|
|
129
|
+
/**
|
|
130
|
+
* Reads the current value of an Effect `Ref` and exposes it as a Svelte
|
|
131
|
+
* [readable store](https://svelte.dev/docs/svelte/stores).
|
|
132
|
+
*
|
|
133
|
+
* **Caveat — this is a static snapshot.** A plain `Ref` has no change
|
|
134
|
+
* notifications, so the returned store reads the ref's value synchronously when
|
|
135
|
+
* a subscriber first subscribes and never updates afterwards: later mutations
|
|
136
|
+
* to the ref are *not* tracked. Two subscribers that subscribe at different
|
|
137
|
+
* times may therefore observe different snapshots, and a single subscriber will
|
|
138
|
+
* never see a value change.
|
|
139
|
+
*
|
|
140
|
+
* For a ref whose changes should be reflected reactively, use a
|
|
141
|
+
* `SubscriptionRef` with {@link fromSubscriptionRef} instead.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
146
|
+
*
|
|
147
|
+
* // Assume configRef is a Ref<Config> — snapshot only, not reactive.
|
|
148
|
+
* const config = Store.fromRef(configRef, runtime);
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
declare function fromRef<A>(ref: Ref.Ref<A>, runtime: RuntimeLike<never>): Readable<A>;
|
|
152
|
+
//#endregion
|
|
153
|
+
export { Store_d_exports };
|
|
154
|
+
//# sourceMappingURL=Store.d.ts.map
|
package/dist/Store.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.js";
|
|
2
|
+
import { interruptFiber, runFork } from "./internal/run.js";
|
|
3
|
+
import { Effect, Queue, Ref, Stream, SubscriptionRef } from "effect";
|
|
4
|
+
import { readable } from "svelte/store";
|
|
5
|
+
//#region src/Store.ts
|
|
6
|
+
var Store_exports = /* @__PURE__ */ __exportAll({
|
|
7
|
+
fromPubSub: () => fromPubSub,
|
|
8
|
+
fromQueue: () => fromQueue,
|
|
9
|
+
fromRef: () => fromRef,
|
|
10
|
+
fromStream: () => fromStream,
|
|
11
|
+
fromSubscriptionRef: () => fromSubscriptionRef,
|
|
12
|
+
toStream: () => toStream
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Converts a Svelte [store](https://svelte.dev/docs/svelte/stores) (anything
|
|
16
|
+
* implementing the readable store contract) into an Effect `Stream` that emits
|
|
17
|
+
* the store's current value and every subsequent update.
|
|
18
|
+
*
|
|
19
|
+
* This is the Svelte→Effect counterpart to the `from*` helpers in this module
|
|
20
|
+
* (which go Effect→Svelte): it lets store-based state — including third-party
|
|
21
|
+
* libraries that expose Svelte stores — feed Effect pipelines. The subscription
|
|
22
|
+
* is opened when the stream is run and closed when its scope closes.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
27
|
+
* import { Stream } from 'effect';
|
|
28
|
+
*
|
|
29
|
+
* // `query` is a Svelte store from some library
|
|
30
|
+
* const debounced = Store.toStream(query).pipe(
|
|
31
|
+
* Stream.debounce('300 millis'),
|
|
32
|
+
* );
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function toStream(store) {
|
|
36
|
+
return Stream.callback((queue) => {
|
|
37
|
+
const unsubscribe = store.subscribe((value) => {
|
|
38
|
+
Queue.offerUnsafe(queue, value);
|
|
39
|
+
});
|
|
40
|
+
return Effect.sync(() => unsubscribe());
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Converts an Effect `Stream` into a Svelte
|
|
45
|
+
* [readable store](https://svelte.dev/docs/svelte/stores), tracking the latest
|
|
46
|
+
* emitted value. The given `initial` value is used until the stream emits.
|
|
47
|
+
*
|
|
48
|
+
* This is the base Effect→Svelte helper that the other `from*` functions build
|
|
49
|
+
* on. Unlike the `useX` hooks, it takes an explicit `runtime` rather than
|
|
50
|
+
* reading the Svelte context, so it can be called outside component
|
|
51
|
+
* initialization (e.g. in module scope or a load function) and is
|
|
52
|
+
* straightforward to test.
|
|
53
|
+
*
|
|
54
|
+
* The Effect fiber driving the store is forked lazily on the first subscription
|
|
55
|
+
* and interrupted when the last subscriber unsubscribes, mirroring Svelte's
|
|
56
|
+
* start/stop notifier semantics.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
61
|
+
* import { Stream } from 'effect';
|
|
62
|
+
*
|
|
63
|
+
* const ticks = Store.fromStream(
|
|
64
|
+
* Stream.tick('1 second').pipe(Stream.scan(0, (n) => n + 1)),
|
|
65
|
+
* 0,
|
|
66
|
+
* runtime,
|
|
67
|
+
* );
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
function fromStream(stream, initial, runtime) {
|
|
71
|
+
return readable(initial, (set) => {
|
|
72
|
+
const fiber = runFork(runtime)(Stream.runForEach(stream, (value) => Effect.sync(() => set(value))));
|
|
73
|
+
return () => interruptFiber(fiber);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Converts an Effect `SubscriptionRef` into a Svelte
|
|
78
|
+
* [readable store](https://svelte.dev/docs/svelte/stores), so Effect-managed
|
|
79
|
+
* reactive state can be consumed anywhere a store is expected (including the
|
|
80
|
+
* `$store` auto-subscription syntax in components, and store-based libraries).
|
|
81
|
+
*
|
|
82
|
+
* This is the Effect→Svelte counterpart to {@link toStream}. Like
|
|
83
|
+
* {@link fromStream}, it takes an explicit `runtime` rather than reading the
|
|
84
|
+
* Svelte context, so it can be called outside component initialization.
|
|
85
|
+
*
|
|
86
|
+
* The fiber driving the store is forked lazily on the first subscription and
|
|
87
|
+
* interrupted when the last subscriber unsubscribes. Because
|
|
88
|
+
* `SubscriptionRef.changes` re-emits the current value on subscribe,
|
|
89
|
+
* subscribers receive the up-to-date value immediately; the ref's current value
|
|
90
|
+
* is also used as the store's initial value for reads that occur before the
|
|
91
|
+
* fiber has emitted.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```svelte
|
|
95
|
+
* <script lang="ts">
|
|
96
|
+
* import { Store, getRuntime } from '@thomasfosterau/effect-svelte';
|
|
97
|
+
*
|
|
98
|
+
* // Assume counterRef is a SubscriptionRef<number>
|
|
99
|
+
* const count = Store.fromSubscriptionRef(counterRef, getRuntime());
|
|
100
|
+
* <\/script>
|
|
101
|
+
*
|
|
102
|
+
* <p>Count: {$count}</p>
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
function fromSubscriptionRef(ref, runtime) {
|
|
106
|
+
const initial = SubscriptionRef.getUnsafe(ref);
|
|
107
|
+
return fromStream(SubscriptionRef.changes(ref), initial, runtime);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Converts an Effect `PubSub` into a Svelte
|
|
111
|
+
* [readable store](https://svelte.dev/docs/svelte/stores) that tracks the most
|
|
112
|
+
* recently published message. The given `initial` value is used until the
|
|
113
|
+
* first message is published.
|
|
114
|
+
*
|
|
115
|
+
* A `PubSub` has no notion of a "current" value, so an explicit `initial` is
|
|
116
|
+
* required; only messages published while the store has at least one subscriber
|
|
117
|
+
* are observed. Built on {@link fromStream} via `Stream.fromPubSub`.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
122
|
+
*
|
|
123
|
+
* // Assume eventsPubSub is a PubSub<Event>
|
|
124
|
+
* const latestEvent = Store.fromPubSub(eventsPubSub, null, runtime);
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
function fromPubSub(pubsub, initial, runtime) {
|
|
128
|
+
return fromStream(Stream.fromPubSub(pubsub), initial, runtime);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Converts an Effect `Queue` (its consumer/`Dequeue` side) into a Svelte
|
|
132
|
+
* [readable store](https://svelte.dev/docs/svelte/stores) that tracks the most
|
|
133
|
+
* recently taken value. The given `initial` value is used until the first value
|
|
134
|
+
* is taken.
|
|
135
|
+
*
|
|
136
|
+
* A queue has no notion of a "current" value, so an explicit `initial` is
|
|
137
|
+
* required. Built on {@link fromStream} via `Stream.fromQueue`, which takes from
|
|
138
|
+
* the queue while the store has at least one subscriber and ends the stream
|
|
139
|
+
* when the queue completes (`Cause.Done` is treated as end-of-stream and is
|
|
140
|
+
* therefore excluded from the store's error channel).
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
145
|
+
*
|
|
146
|
+
* // Assume workQueue is a Queue<Job>
|
|
147
|
+
* const currentJob = Store.fromQueue(workQueue, null, runtime);
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
function fromQueue(queue, initial, runtime) {
|
|
151
|
+
return fromStream(Stream.fromQueue(queue), initial, runtime);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Reads the current value of an Effect `Ref` and exposes it as a Svelte
|
|
155
|
+
* [readable store](https://svelte.dev/docs/svelte/stores).
|
|
156
|
+
*
|
|
157
|
+
* **Caveat — this is a static snapshot.** A plain `Ref` has no change
|
|
158
|
+
* notifications, so the returned store reads the ref's value synchronously when
|
|
159
|
+
* a subscriber first subscribes and never updates afterwards: later mutations
|
|
160
|
+
* to the ref are *not* tracked. Two subscribers that subscribe at different
|
|
161
|
+
* times may therefore observe different snapshots, and a single subscriber will
|
|
162
|
+
* never see a value change.
|
|
163
|
+
*
|
|
164
|
+
* For a ref whose changes should be reflected reactively, use a
|
|
165
|
+
* `SubscriptionRef` with {@link fromSubscriptionRef} instead.
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* import { Store } from '@thomasfosterau/effect-svelte';
|
|
170
|
+
*
|
|
171
|
+
* // Assume configRef is a Ref<Config> — snapshot only, not reactive.
|
|
172
|
+
* const config = Store.fromRef(configRef, runtime);
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
function fromRef(ref, runtime) {
|
|
176
|
+
return readable(Ref.getUnsafe(ref), (set) => {
|
|
177
|
+
set(Ref.getUnsafe(ref));
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
181
|
+
export { Store_exports };
|
|
182
|
+
|
|
183
|
+
//# sourceMappingURL=Store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Store.js","names":[],"sources":["../src/Store.ts"],"sourcesContent":["import { Effect, type PubSub, Queue, Ref, Stream, SubscriptionRef } from \"effect\";\nimport { type Readable, readable } from \"svelte/store\";\nimport { interruptFiber, runFork, type RuntimeLike } from \"./internal/run.js\";\n\n/**\n * Converts a Svelte [store](https://svelte.dev/docs/svelte/stores) (anything\n * implementing the readable store contract) into an Effect `Stream` that emits\n * the store's current value and every subsequent update.\n *\n * This is the Svelte→Effect counterpart to the `from*` helpers in this module\n * (which go Effect→Svelte): it lets store-based state — including third-party\n * libraries that expose Svelte stores — feed Effect pipelines. The subscription\n * is opened when the stream is run and closed when its scope closes.\n *\n * @example\n * ```ts\n * import { Store } from '@thomasfosterau/effect-svelte';\n * import { Stream } from 'effect';\n *\n * // `query` is a Svelte store from some library\n * const debounced = Store.toStream(query).pipe(\n * Stream.debounce('300 millis'),\n * );\n * ```\n */\nexport function toStream<A>(store: Readable<A>): Stream.Stream<A> {\n return Stream.callback<A>((queue) => {\n const unsubscribe = store.subscribe((value) => {\n Queue.offerUnsafe(queue, value);\n });\n return Effect.sync(() => unsubscribe());\n });\n}\n\n/**\n * Converts an Effect `Stream` into a Svelte\n * [readable store](https://svelte.dev/docs/svelte/stores), tracking the latest\n * emitted value. The given `initial` value is used until the stream emits.\n *\n * This is the base Effect→Svelte helper that the other `from*` functions build\n * on. Unlike the `useX` hooks, it takes an explicit `runtime` rather than\n * reading the Svelte context, so it can be called outside component\n * initialization (e.g. in module scope or a load function) and is\n * straightforward to test.\n *\n * The Effect fiber driving the store is forked lazily on the first subscription\n * and interrupted when the last subscriber unsubscribes, mirroring Svelte's\n * start/stop notifier semantics.\n *\n * @example\n * ```ts\n * import { Store } from '@thomasfosterau/effect-svelte';\n * import { Stream } from 'effect';\n *\n * const ticks = Store.fromStream(\n * Stream.tick('1 second').pipe(Stream.scan(0, (n) => n + 1)),\n * 0,\n * runtime,\n * );\n * ```\n */\nexport function fromStream<A, E>(\n stream: Stream.Stream<A, E, never>,\n initial: A,\n runtime: RuntimeLike<never>,\n): Readable<A> {\n return readable<A>(initial, (set) => {\n const fiber = runFork(runtime)(\n Stream.runForEach(stream, (value) => Effect.sync(() => set(value))),\n );\n return () => interruptFiber(fiber);\n });\n}\n\n/**\n * Converts an Effect `SubscriptionRef` into a Svelte\n * [readable store](https://svelte.dev/docs/svelte/stores), so Effect-managed\n * reactive state can be consumed anywhere a store is expected (including the\n * `$store` auto-subscription syntax in components, and store-based libraries).\n *\n * This is the Effect→Svelte counterpart to {@link toStream}. Like\n * {@link fromStream}, it takes an explicit `runtime` rather than reading the\n * Svelte context, so it can be called outside component initialization.\n *\n * The fiber driving the store is forked lazily on the first subscription and\n * interrupted when the last subscriber unsubscribes. Because\n * `SubscriptionRef.changes` re-emits the current value on subscribe,\n * subscribers receive the up-to-date value immediately; the ref's current value\n * is also used as the store's initial value for reads that occur before the\n * fiber has emitted.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { Store, getRuntime } from '@thomasfosterau/effect-svelte';\n *\n * // Assume counterRef is a SubscriptionRef<number>\n * const count = Store.fromSubscriptionRef(counterRef, getRuntime());\n * </script>\n *\n * <p>Count: {$count}</p>\n * ```\n */\nexport function fromSubscriptionRef<A>(\n ref: SubscriptionRef.SubscriptionRef<A>,\n runtime: RuntimeLike<never>,\n): Readable<A> {\n // Read the current value synchronously so the store has a sensible initial\n // value before the fiber starts. `SubscriptionRef.changes` re-emits the\n // current value on subscribe, so no update is missed.\n const initial = SubscriptionRef.getUnsafe(ref);\n return fromStream(SubscriptionRef.changes(ref), initial, runtime);\n}\n\n/**\n * Converts an Effect `PubSub` into a Svelte\n * [readable store](https://svelte.dev/docs/svelte/stores) that tracks the most\n * recently published message. The given `initial` value is used until the\n * first message is published.\n *\n * A `PubSub` has no notion of a \"current\" value, so an explicit `initial` is\n * required; only messages published while the store has at least one subscriber\n * are observed. Built on {@link fromStream} via `Stream.fromPubSub`.\n *\n * @example\n * ```ts\n * import { Store } from '@thomasfosterau/effect-svelte';\n *\n * // Assume eventsPubSub is a PubSub<Event>\n * const latestEvent = Store.fromPubSub(eventsPubSub, null, runtime);\n * ```\n */\nexport function fromPubSub<A>(\n pubsub: PubSub.PubSub<A>,\n initial: A,\n runtime: RuntimeLike<never>,\n): Readable<A> {\n return fromStream(Stream.fromPubSub(pubsub), initial, runtime);\n}\n\n/**\n * Converts an Effect `Queue` (its consumer/`Dequeue` side) into a Svelte\n * [readable store](https://svelte.dev/docs/svelte/stores) that tracks the most\n * recently taken value. The given `initial` value is used until the first value\n * is taken.\n *\n * A queue has no notion of a \"current\" value, so an explicit `initial` is\n * required. Built on {@link fromStream} via `Stream.fromQueue`, which takes from\n * the queue while the store has at least one subscriber and ends the stream\n * when the queue completes (`Cause.Done` is treated as end-of-stream and is\n * therefore excluded from the store's error channel).\n *\n * @example\n * ```ts\n * import { Store } from '@thomasfosterau/effect-svelte';\n *\n * // Assume workQueue is a Queue<Job>\n * const currentJob = Store.fromQueue(workQueue, null, runtime);\n * ```\n */\nexport function fromQueue<A, E>(\n queue: Queue.Dequeue<A, E>,\n initial: A,\n runtime: RuntimeLike<never>,\n): Readable<A> {\n return fromStream(Stream.fromQueue(queue), initial, runtime);\n}\n\n/**\n * Reads the current value of an Effect `Ref` and exposes it as a Svelte\n * [readable store](https://svelte.dev/docs/svelte/stores).\n *\n * **Caveat — this is a static snapshot.** A plain `Ref` has no change\n * notifications, so the returned store reads the ref's value synchronously when\n * a subscriber first subscribes and never updates afterwards: later mutations\n * to the ref are *not* tracked. Two subscribers that subscribe at different\n * times may therefore observe different snapshots, and a single subscriber will\n * never see a value change.\n *\n * For a ref whose changes should be reflected reactively, use a\n * `SubscriptionRef` with {@link fromSubscriptionRef} instead.\n *\n * @example\n * ```ts\n * import { Store } from '@thomasfosterau/effect-svelte';\n *\n * // Assume configRef is a Ref<Config> — snapshot only, not reactive.\n * const config = Store.fromRef(configRef, runtime);\n * ```\n */\nexport function fromRef<A>(ref: Ref.Ref<A>, runtime: RuntimeLike<never>): Readable<A> {\n // A plain Ref offers no change feed, so we snapshot the value and never emit\n // again. `Ref.getUnsafe` reads synchronously without a fiber, so both the\n // `readable` initial value and the value re-read on subscribe come straight\n // from the ref; the `runtime` is accepted only for API symmetry with the\n // other `from*` helpers.\n void runtime;\n return readable<A>(Ref.getUnsafe(ref), (set) => {\n set(Ref.getUnsafe(ref));\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,SAAY,OAAsC;CAChE,OAAO,OAAO,UAAa,UAAU;EACnC,MAAM,cAAc,MAAM,WAAW,UAAU;GAC7C,MAAM,YAAY,OAAO,KAAK;EAChC,CAAC;EACD,OAAO,OAAO,WAAW,YAAY,CAAC;CACxC,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,WACd,QACA,SACA,SACa;CACb,OAAO,SAAY,UAAU,QAAQ;EACnC,MAAM,QAAQ,QAAQ,OAAO,EAC3B,OAAO,WAAW,SAAS,UAAU,OAAO,WAAW,IAAI,KAAK,CAAC,CAAC,CACpE;EACA,aAAa,eAAe,KAAK;CACnC,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,oBACd,KACA,SACa;CAIb,MAAM,UAAU,gBAAgB,UAAU,GAAG;CAC7C,OAAO,WAAW,gBAAgB,QAAQ,GAAG,GAAG,SAAS,OAAO;AAClE;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,WACd,QACA,SACA,SACa;CACb,OAAO,WAAW,OAAO,WAAW,MAAM,GAAG,SAAS,OAAO;AAC/D;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,UACd,OACA,SACA,SACa;CACb,OAAO,WAAW,OAAO,UAAU,KAAK,GAAG,SAAS,OAAO;AAC7D;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,QAAW,KAAiB,SAA0C;CAOpF,OAAO,SAAY,IAAI,UAAU,GAAG,IAAI,QAAQ;EAC9C,IAAI,IAAI,UAAU,GAAG,CAAC;CACxB,CAAC;AACH"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RuntimeLike } from "./internal/run.js";
|
|
2
|
+
|
|
3
|
+
//#region src/context.svelte.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Retrieves the Effect runtime from Svelte context.
|
|
6
|
+
*
|
|
7
|
+
* @returns The runtime provided via provideRuntime
|
|
8
|
+
* @throws Will throw if called outside of a component with a provided runtime
|
|
9
|
+
*/
|
|
10
|
+
declare function getRuntimeContext(): RuntimeLike<never>;
|
|
11
|
+
/**
|
|
12
|
+
* Provides an Effect runtime to the Svelte context tree.
|
|
13
|
+
* This should be called at the root of your component tree, typically in your root layout.
|
|
14
|
+
*
|
|
15
|
+
* The default runtime (SvelteRuntime.defaultRuntime) is recommended for most applications.
|
|
16
|
+
* For applications requiring custom services, extend the runtime using SvelteRuntime.extend().
|
|
17
|
+
*
|
|
18
|
+
* @param runtime - The Effect runtime to provide. Defaults to SvelteRuntime.defaultRuntime
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```svelte
|
|
22
|
+
* <script lang="ts">
|
|
23
|
+
* import { provideRuntime, SvelteRuntime } from '@thomasfosterau/effect-svelte';
|
|
24
|
+
*
|
|
25
|
+
* // Use default runtime
|
|
26
|
+
* provideRuntime();
|
|
27
|
+
*
|
|
28
|
+
* // Or provide a custom runtime
|
|
29
|
+
* const customRuntime = SvelteRuntime.extend(myCustomLayer);
|
|
30
|
+
* provideRuntime(customRuntime);
|
|
31
|
+
* </script>
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
declare function provideRuntime<R = never>(runtime?: RuntimeLike<R>): void;
|
|
35
|
+
//#endregion
|
|
36
|
+
export { getRuntimeContext, provideRuntime };
|
|
37
|
+
//# sourceMappingURL=context.svelte.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { defaultSvelteRuntime } from "./runtime.js";
|
|
2
|
+
import { createContext } from "svelte";
|
|
3
|
+
//#region src/context.svelte.ts
|
|
4
|
+
/**
|
|
5
|
+
* Type-safe context for Effect runtime management.
|
|
6
|
+
* Uses Svelte 5's createContext API for better type safety and developer experience.
|
|
7
|
+
*
|
|
8
|
+
* The context accepts either a plain services `Context.Context` (e.g. `Context.empty()`)
|
|
9
|
+
* or a `ManagedRuntime` (e.g. `SvelteRuntime.defaultRuntime` / `SvelteRuntime.extend(...)`).
|
|
10
|
+
* Users must ensure their runtime provides all required services for the effects they run.
|
|
11
|
+
*/
|
|
12
|
+
const [getRuntime, provideRuntimeInternal] = createContext();
|
|
13
|
+
/**
|
|
14
|
+
* Retrieves the Effect runtime from Svelte context.
|
|
15
|
+
*
|
|
16
|
+
* @returns The runtime provided via provideRuntime
|
|
17
|
+
* @throws Will throw if called outside of a component with a provided runtime
|
|
18
|
+
*/
|
|
19
|
+
function getRuntimeContext() {
|
|
20
|
+
const runtime = getRuntime();
|
|
21
|
+
if (runtime === void 0) throw new Error("effect-svelte: No Effect runtime found in context. Make sure you have called provideRuntime() in a parent component.");
|
|
22
|
+
return runtime;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Provides an Effect runtime to the Svelte context tree.
|
|
26
|
+
* This should be called at the root of your component tree, typically in your root layout.
|
|
27
|
+
*
|
|
28
|
+
* The default runtime (SvelteRuntime.defaultRuntime) is recommended for most applications.
|
|
29
|
+
* For applications requiring custom services, extend the runtime using SvelteRuntime.extend().
|
|
30
|
+
*
|
|
31
|
+
* @param runtime - The Effect runtime to provide. Defaults to SvelteRuntime.defaultRuntime
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```svelte
|
|
35
|
+
* <script lang="ts">
|
|
36
|
+
* import { provideRuntime, SvelteRuntime } from '@thomasfosterau/effect-svelte';
|
|
37
|
+
*
|
|
38
|
+
* // Use default runtime
|
|
39
|
+
* provideRuntime();
|
|
40
|
+
*
|
|
41
|
+
* // Or provide a custom runtime
|
|
42
|
+
* const customRuntime = SvelteRuntime.extend(myCustomLayer);
|
|
43
|
+
* provideRuntime(customRuntime);
|
|
44
|
+
* <\/script>
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function provideRuntime(runtime = defaultSvelteRuntime) {
|
|
48
|
+
provideRuntimeInternal(runtime);
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { getRuntimeContext, provideRuntime };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=context.svelte.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.svelte.js","names":[],"sources":["../src/context.svelte.ts"],"sourcesContent":["import { createContext } from \"svelte\";\nimport { defaultSvelteRuntime } from \"./runtime.js\";\nimport type { RuntimeLike } from \"./internal/run.js\";\n\n/**\n * Type-safe context for Effect runtime management.\n * Uses Svelte 5's createContext API for better type safety and developer experience.\n *\n * The context accepts either a plain services `Context.Context` (e.g. `Context.empty()`)\n * or a `ManagedRuntime` (e.g. `SvelteRuntime.defaultRuntime` / `SvelteRuntime.extend(...)`).\n * Users must ensure their runtime provides all required services for the effects they run.\n */\nconst [getRuntime, provideRuntimeInternal] = createContext<RuntimeLike<never>>();\n\n/**\n * Retrieves the Effect runtime from Svelte context.\n *\n * @returns The runtime provided via provideRuntime\n * @throws Will throw if called outside of a component with a provided runtime\n */\nfunction getRuntimeContext(): RuntimeLike<never> {\n const runtime = getRuntime();\n if (runtime === undefined) {\n throw new Error(\n \"effect-svelte: No Effect runtime found in context. \" +\n \"Make sure you have called provideRuntime() in a parent component.\",\n );\n }\n return runtime;\n}\n\n/**\n * Provides an Effect runtime to the Svelte context tree.\n * This should be called at the root of your component tree, typically in your root layout.\n *\n * The default runtime (SvelteRuntime.defaultRuntime) is recommended for most applications.\n * For applications requiring custom services, extend the runtime using SvelteRuntime.extend().\n *\n * @param runtime - The Effect runtime to provide. Defaults to SvelteRuntime.defaultRuntime\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { provideRuntime, SvelteRuntime } from '@thomasfosterau/effect-svelte';\n *\n * // Use default runtime\n * provideRuntime();\n *\n * // Or provide a custom runtime\n * const customRuntime = SvelteRuntime.extend(myCustomLayer);\n * provideRuntime(customRuntime);\n * </script>\n * ```\n */\nfunction provideRuntime<R = never>(\n runtime: RuntimeLike<R> = defaultSvelteRuntime as RuntimeLike<R>,\n): void {\n provideRuntimeInternal(runtime as RuntimeLike<never>);\n}\n\nexport { getRuntimeContext as getRuntime, provideRuntime };\nexport type { RuntimeLike };\n"],"mappings":";;;;;;;;;;;AAYA,MAAM,CAAC,YAAY,0BAA0B,cAAkC;;;;;;;AAQ/E,SAAS,oBAAwC;CAC/C,MAAM,UAAU,WAAW;CAC3B,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,MACR,sHAEF;CAEF,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAS,eACP,UAA0B,sBACpB;CACN,uBAAuB,OAA6B;AACtD"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
3
|
+
|
|
4
|
+
//#region src/derived.svelte.d.ts
|
|
5
|
+
interface DerivedReturn<A, E> {
|
|
6
|
+
/**
|
|
7
|
+
* The current result. While re-running after a dependency change, the
|
|
8
|
+
* previous value is kept as a waiting `Success` (stale-while-revalidate).
|
|
9
|
+
*/
|
|
10
|
+
readonly current: AsyncResult.AsyncResult<A, E>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Re-run an effect whenever reactive dependencies change.
|
|
14
|
+
* The function parameter is called inside $effect, so it automatically tracks dependencies.
|
|
15
|
+
* When dependencies change, the previous fiber is interrupted and a new one starts.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```svelte
|
|
19
|
+
* <script lang="ts">
|
|
20
|
+
* import { useDerived, AsyncResult } from '@thomasfosterau/effect-svelte';
|
|
21
|
+
* import { Effect } from 'effect';
|
|
22
|
+
*
|
|
23
|
+
* let userId = $state(1);
|
|
24
|
+
*
|
|
25
|
+
* const user = useDerived(() =>
|
|
26
|
+
* Effect.gen(function* () {
|
|
27
|
+
* const response = yield* fetchUser(userId);
|
|
28
|
+
* return response;
|
|
29
|
+
* })
|
|
30
|
+
* );
|
|
31
|
+
* </script>
|
|
32
|
+
*
|
|
33
|
+
* <button onclick={() => userId++}>Next User</button>
|
|
34
|
+
*
|
|
35
|
+
* {#if AsyncResult.isSuccess(user.current)}
|
|
36
|
+
* <p>User: {user.current.value.name}</p>
|
|
37
|
+
* {/if}
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function useDerived<A, E, R>(fn: () => Effect.Effect<A, E, R>): DerivedReturn<A, E>;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { DerivedReturn, useDerived };
|
|
43
|
+
//# sourceMappingURL=derived.svelte.d.ts.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getRuntimeContext } from "./context.svelte.js";
|
|
2
|
+
import { interruptFiber, runFork } from "./internal/run.js";
|
|
3
|
+
import { makeResult } from "./internal/result.svelte.js";
|
|
4
|
+
import { Effect, Fiber } from "effect";
|
|
5
|
+
//#region src/derived.svelte.ts
|
|
6
|
+
/**
|
|
7
|
+
* Re-run an effect whenever reactive dependencies change.
|
|
8
|
+
* The function parameter is called inside $effect, so it automatically tracks dependencies.
|
|
9
|
+
* When dependencies change, the previous fiber is interrupted and a new one starts.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <script lang="ts">
|
|
14
|
+
* import { useDerived, AsyncResult } from '@thomasfosterau/effect-svelte';
|
|
15
|
+
* import { Effect } from 'effect';
|
|
16
|
+
*
|
|
17
|
+
* let userId = $state(1);
|
|
18
|
+
*
|
|
19
|
+
* const user = useDerived(() =>
|
|
20
|
+
* Effect.gen(function* () {
|
|
21
|
+
* const response = yield* fetchUser(userId);
|
|
22
|
+
* return response;
|
|
23
|
+
* })
|
|
24
|
+
* );
|
|
25
|
+
* <\/script>
|
|
26
|
+
*
|
|
27
|
+
* <button onclick={() => userId++}>Next User</button>
|
|
28
|
+
*
|
|
29
|
+
* {#if AsyncResult.isSuccess(user.current)}
|
|
30
|
+
* <p>User: {user.current.value.name}</p>
|
|
31
|
+
* {/if}
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function useDerived(fn) {
|
|
35
|
+
const runtime = getRuntimeContext();
|
|
36
|
+
const state = makeResult();
|
|
37
|
+
let currentFiber = null;
|
|
38
|
+
$effect(() => {
|
|
39
|
+
if (currentFiber !== null) interruptFiber(currentFiber);
|
|
40
|
+
const effect = fn();
|
|
41
|
+
state.startWaiting();
|
|
42
|
+
const runningFiber = runFork(runtime)(effect);
|
|
43
|
+
currentFiber = runningFiber;
|
|
44
|
+
runFork(runtime)(Effect.gen(function* () {
|
|
45
|
+
const exit = yield* Fiber.await(runningFiber);
|
|
46
|
+
if (currentFiber === runningFiber) {
|
|
47
|
+
state.settle(exit);
|
|
48
|
+
currentFiber = null;
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
return () => {
|
|
52
|
+
if (currentFiber !== null) {
|
|
53
|
+
interruptFiber(currentFiber);
|
|
54
|
+
currentFiber = null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
return { get current() {
|
|
59
|
+
return state.current;
|
|
60
|
+
} };
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { useDerived };
|
|
64
|
+
|
|
65
|
+
//# sourceMappingURL=derived.svelte.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derived.svelte.js","names":["getRuntime"],"sources":["../src/derived.svelte.ts"],"sourcesContent":["import { Effect, Fiber } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { interruptFiber, runFork, type RuntimeLike } from \"./internal/run.js\";\nimport { makeResult } from \"./internal/result.svelte.js\";\n\nexport interface DerivedReturn<A, E> {\n /**\n * The current result. While re-running after a dependency change, the\n * previous value is kept as a waiting `Success` (stale-while-revalidate).\n */\n readonly current: AsyncResult.AsyncResult<A, E>;\n}\n\n/**\n * Re-run an effect whenever reactive dependencies change.\n * The function parameter is called inside $effect, so it automatically tracks dependencies.\n * When dependencies change, the previous fiber is interrupted and a new one starts.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useDerived, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * let userId = $state(1);\n *\n * const user = useDerived(() =>\n * Effect.gen(function* () {\n * const response = yield* fetchUser(userId);\n * return response;\n * })\n * );\n * </script>\n *\n * <button onclick={() => userId++}>Next User</button>\n *\n * {#if AsyncResult.isSuccess(user.current)}\n * <p>User: {user.current.value.name}</p>\n * {/if}\n * ```\n */\nexport function useDerived<A, E, R>(fn: () => Effect.Effect<A, E, R>): DerivedReturn<A, E> {\n const runtime = getRuntime() as RuntimeLike<R>;\n\n const state = makeResult<A, E>();\n let currentFiber: Fiber.Fiber<A, E> | null = null;\n\n $effect(() => {\n // Interrupt previous fiber if it exists\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n }\n\n // Get the effect (this tracks reactive dependencies)\n const effect = fn();\n\n // Mark as waiting, preserving the previous value if there is one.\n state.startWaiting();\n\n // Run the effect and capture the fiber reference\n const runningFiber = runFork(runtime)(effect);\n currentFiber = runningFiber;\n\n // Settle the result when the fiber completes, unless deps changed first.\n runFork(runtime)(\n Effect.gen(function* () {\n const exit = yield* Fiber.await(runningFiber);\n if (currentFiber === runningFiber) {\n state.settle(exit);\n currentFiber = null;\n }\n }),\n );\n\n // Cleanup: interrupt the fiber when dependencies change or component unmounts\n return () => {\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n currentFiber = null;\n }\n };\n });\n\n return {\n get current() {\n return state.current;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,SAAgB,WAAoB,IAAuD;CACzF,MAAM,UAAUA,kBAAW;CAE3B,MAAM,QAAQ,WAAiB;CAC/B,IAAI,eAAyC;CAE7C,cAAc;EAEZ,IAAI,iBAAiB,MACnB,eAAe,YAAY;EAI7B,MAAM,SAAS,GAAG;EAGlB,MAAM,aAAa;EAGnB,MAAM,eAAe,QAAQ,OAAO,EAAE,MAAM;EAC5C,eAAe;EAGf,QAAQ,OAAO,EACb,OAAO,IAAI,aAAa;GACtB,MAAM,OAAO,OAAO,MAAM,MAAM,YAAY;GAC5C,IAAI,iBAAiB,cAAc;IACjC,MAAM,OAAO,IAAI;IACjB,eAAe;GACjB;EACF,CAAC,CACH;EAGA,aAAa;GACX,IAAI,iBAAiB,MAAM;IACzB,eAAe,YAAY;IAC3B,eAAe;GACjB;EACF;CACF,CAAC;CAED,OAAO,EACL,IAAI,UAAU;EACZ,OAAO,MAAM;CACf,EACF;AACF"}
|