@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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/Store.d.ts +154 -0
  4. package/dist/Store.js +183 -0
  5. package/dist/Store.js.map +1 -0
  6. package/dist/_virtual/_rolldown/runtime.js +13 -0
  7. package/dist/context.svelte.d.ts +37 -0
  8. package/dist/context.svelte.js +53 -0
  9. package/dist/context.svelte.js.map +1 -0
  10. package/dist/derived.svelte.d.ts +43 -0
  11. package/dist/derived.svelte.js +65 -0
  12. package/dist/derived.svelte.js.map +1 -0
  13. package/dist/effect.svelte.d.ts +51 -0
  14. package/dist/effect.svelte.js +68 -0
  15. package/dist/effect.svelte.js.map +1 -0
  16. package/dist/emitter.d.ts +45 -0
  17. package/dist/emitter.js +60 -0
  18. package/dist/emitter.js.map +1 -0
  19. package/dist/index.d.ts +14 -0
  20. package/dist/index.js +13 -0
  21. package/dist/internal/result.svelte.js +36 -0
  22. package/dist/internal/result.svelte.js.map +1 -0
  23. package/dist/internal/run.d.ts +15 -0
  24. package/dist/internal/run.js +25 -0
  25. package/dist/internal/run.js.map +1 -0
  26. package/dist/internal/subscribe.js +79 -0
  27. package/dist/internal/subscribe.js.map +1 -0
  28. package/dist/query.svelte.d.ts +45 -0
  29. package/dist/query.svelte.js +41 -0
  30. package/dist/query.svelte.js.map +1 -0
  31. package/dist/reactivity.svelte.d.ts +123 -0
  32. package/dist/reactivity.svelte.js +186 -0
  33. package/dist/reactivity.svelte.js.map +1 -0
  34. package/dist/runtime.d.ts +81 -0
  35. package/dist/runtime.js +85 -0
  36. package/dist/runtime.js.map +1 -0
  37. package/dist/scope.svelte.d.ts +49 -0
  38. package/dist/scope.svelte.js +68 -0
  39. package/dist/scope.svelte.js.map +1 -0
  40. package/dist/stream.svelte.d.ts +48 -0
  41. package/dist/stream.svelte.js +34 -0
  42. package/dist/stream.svelte.js.map +1 -0
  43. package/dist/subscription.svelte.d.ts +69 -0
  44. package/dist/subscription.svelte.js +69 -0
  45. package/dist/subscription.svelte.js.map +1 -0
  46. 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
@@ -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"}