@thomasfosterau/effect-svelte 0.2.0 → 0.3.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 (41) hide show
  1. package/dist/await.svelte.d.ts +10 -2
  2. package/dist/await.svelte.js +3 -3
  3. package/dist/await.svelte.js.map +1 -1
  4. package/dist/context.svelte.d.ts +51 -1
  5. package/dist/context.svelte.js +43 -1
  6. package/dist/context.svelte.js.map +1 -1
  7. package/dist/derived.svelte.d.ts +8 -2
  8. package/dist/derived.svelte.js +1 -1
  9. package/dist/derived.svelte.js.map +1 -1
  10. package/dist/effect.svelte.d.ts +8 -2
  11. package/dist/effect.svelte.js +1 -1
  12. package/dist/effect.svelte.js.map +1 -1
  13. package/dist/index.d.ts +10 -9
  14. package/dist/index.js +3 -2
  15. package/dist/internal/await.js +18 -5
  16. package/dist/internal/await.js.map +1 -1
  17. package/dist/internal/live-stream.js +43 -0
  18. package/dist/internal/live-stream.js.map +1 -0
  19. package/dist/internal/writable.js.map +1 -1
  20. package/dist/live-stream.svelte.d.ts +72 -0
  21. package/dist/live-stream.svelte.js +77 -0
  22. package/dist/live-stream.svelte.js.map +1 -0
  23. package/dist/mutation.svelte.d.ts +14 -2
  24. package/dist/mutation.svelte.js +1 -1
  25. package/dist/mutation.svelte.js.map +1 -1
  26. package/dist/query.svelte.d.ts +10 -2
  27. package/dist/query.svelte.js +5 -2
  28. package/dist/query.svelte.js.map +1 -1
  29. package/dist/reactivity.svelte.d.ts +15 -5
  30. package/dist/reactivity.svelte.js +8 -8
  31. package/dist/reactivity.svelte.js.map +1 -1
  32. package/dist/stream.svelte.d.ts +10 -2
  33. package/dist/stream.svelte.js +2 -2
  34. package/dist/stream.svelte.js.map +1 -1
  35. package/dist/subscription.svelte.d.ts +12 -3
  36. package/dist/subscription.svelte.js +4 -4
  37. package/dist/subscription.svelte.js.map +1 -1
  38. package/dist/writable-ref.svelte.d.ts +7 -2
  39. package/dist/writable-ref.svelte.js +2 -2
  40. package/dist/writable-ref.svelte.js.map +1 -1
  41. package/package.json +1 -1
@@ -1,7 +1,15 @@
1
+ import { RuntimeLike } from "./internal/run.js";
1
2
  import { Effect } from "effect";
2
3
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
3
4
 
4
5
  //#region src/await.svelte.d.ts
6
+ interface UseQueryPromiseOptions<R> {
7
+ /**
8
+ * Run against this runtime instead of the ambient context. When provided, the
9
+ * effect's `R` is constrained to what the runtime provides.
10
+ */
11
+ readonly runtime?: RuntimeLike<R>;
12
+ }
5
13
  interface UseQueryPromiseReturn<A, E> {
6
14
  /** The underlying query result state (same as {@link useQuery}). */
7
15
  readonly current: AsyncResult.AsyncResult<A, E>;
@@ -39,7 +47,7 @@ interface UseQueryPromiseReturn<A, E> {
39
47
  * <button onclick={user.refetch}>Reload</button>
40
48
  * ```
41
49
  */
42
- declare function useQueryPromise<A, E, R>(effect: Effect.Effect<A, E, R>): UseQueryPromiseReturn<A, E>;
50
+ declare function useQueryPromise<A, E, R>(effect: Effect.Effect<A, E, R>, options?: UseQueryPromiseOptions<R>): UseQueryPromiseReturn<A, E>;
43
51
  //#endregion
44
- export { UseQueryPromiseReturn, useQueryPromise };
52
+ export { UseQueryPromiseOptions, UseQueryPromiseReturn, useQueryPromise };
45
53
  //# sourceMappingURL=await.svelte.d.ts.map
@@ -1,5 +1,5 @@
1
- import { useQuery } from "./query.svelte.js";
2
1
  import { resultToPromise } from "./internal/await.js";
2
+ import { useQuery } from "./query.svelte.js";
3
3
  //#region src/await.svelte.ts
4
4
  /**
5
5
  * Run an effect and bridge its result to Svelte 5's native async, so you can
@@ -25,8 +25,8 @@ import { resultToPromise } from "./internal/await.js";
25
25
  * <button onclick={user.refetch}>Reload</button>
26
26
  * ```
27
27
  */
28
- function useQueryPromise(effect) {
29
- const query = useQuery(effect);
28
+ function useQueryPromise(effect, options = {}) {
29
+ const query = useQuery(effect, options);
30
30
  const promise = $derived(resultToPromise(query.current));
31
31
  return {
32
32
  get current() {
@@ -1 +1 @@
1
- {"version":3,"file":"await.svelte.js","names":[],"sources":["../src/await.svelte.ts"],"sourcesContent":["import type { Effect } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { resultToPromise } from \"./internal/await.js\";\nimport { useQuery } from \"./query.svelte.js\";\n\nexport interface UseQueryPromiseReturn<A, E> {\n /** The underlying query result state (same as {@link useQuery}). */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /**\n * A `Promise` reflecting `current`, for use with Svelte's `{#await}` (or\n * `<svelte:boundary>`). Its identity is stable until the result transitions:\n * pending while loading, resolved with the (possibly stale) value, rejected\n * on failure.\n */\n readonly promise: Promise<A>;\n\n /** Re-run the effect. A new `promise` is produced as the result transitions. */\n readonly refetch: () => void;\n}\n\n/**\n * Run an effect and bridge its result to Svelte 5's native async, so you can\n * render it with an `{#await}` block instead of `AsyncResult.match`.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useQueryPromise } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const user = useQueryPromise(fetchUser(1));\n * </script>\n *\n * {#await user.promise}\n * <p>Loading…</p>\n * {:then value}\n * <p>{value.name}</p>\n * {:catch error}\n * <p>Failed: {error.message}</p>\n * {/await}\n *\n * <button onclick={user.refetch}>Reload</button>\n * ```\n */\nexport function useQueryPromise<A, E, R>(\n effect: Effect.Effect<A, E, R>,\n): UseQueryPromiseReturn<A, E> {\n const query = useQuery(effect);\n\n // Recompute (and only then hand `{#await}` a new promise) when the underlying\n // result transitions — not on every unrelated re-render.\n const promise = $derived(resultToPromise(query.current));\n\n return {\n get current() {\n return query.current;\n },\n get promise() {\n return promise;\n },\n refetch: query.refetch,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,SAAgB,gBACd,QAC6B;CAC7B,MAAM,QAAQ,SAAS,MAAM;CAI7B,MAAM,UAAU,SAAS,gBAAgB,MAAM,OAAO,CAAC;CAEvD,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA,IAAI,UAAU;GACZ,OAAO;EACT;EACA,SAAS,MAAM;CACjB;AACF"}
1
+ {"version":3,"file":"await.svelte.js","names":[],"sources":["../src/await.svelte.ts"],"sourcesContent":["import type { Effect } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { resultToPromise } from \"./internal/await.js\";\nimport type { RuntimeLike } from \"./internal/run.js\";\nimport { useQuery } from \"./query.svelte.js\";\n\nexport interface UseQueryPromiseOptions<R> {\n /**\n * Run against this runtime instead of the ambient context. When provided, the\n * effect's `R` is constrained to what the runtime provides.\n */\n readonly runtime?: RuntimeLike<R>;\n}\n\nexport interface UseQueryPromiseReturn<A, E> {\n /** The underlying query result state (same as {@link useQuery}). */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /**\n * A `Promise` reflecting `current`, for use with Svelte's `{#await}` (or\n * `<svelte:boundary>`). Its identity is stable until the result transitions:\n * pending while loading, resolved with the (possibly stale) value, rejected\n * on failure.\n */\n readonly promise: Promise<A>;\n\n /** Re-run the effect. A new `promise` is produced as the result transitions. */\n readonly refetch: () => void;\n}\n\n/**\n * Run an effect and bridge its result to Svelte 5's native async, so you can\n * render it with an `{#await}` block instead of `AsyncResult.match`.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useQueryPromise } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const user = useQueryPromise(fetchUser(1));\n * </script>\n *\n * {#await user.promise}\n * <p>Loading…</p>\n * {:then value}\n * <p>{value.name}</p>\n * {:catch error}\n * <p>Failed: {error.message}</p>\n * {/await}\n *\n * <button onclick={user.refetch}>Reload</button>\n * ```\n */\nexport function useQueryPromise<A, E, R>(\n effect: Effect.Effect<A, E, R>,\n options: UseQueryPromiseOptions<R> = {},\n): UseQueryPromiseReturn<A, E> {\n const query = useQuery(effect, options);\n\n // Recompute (and only then hand `{#await}` a new promise) when the underlying\n // result transitions — not on every unrelated re-render.\n const promise = $derived(resultToPromise(query.current));\n\n return {\n get current() {\n return query.current;\n },\n get promise() {\n return promise;\n },\n refetch: query.refetch,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,SAAgB,gBACd,QACA,UAAqC,CAAC,GACT;CAC7B,MAAM,QAAQ,SAAS,QAAQ,OAAO;CAItC,MAAM,UAAU,SAAS,gBAAgB,MAAM,OAAO,CAAC;CAEvD,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA,IAAI,UAAU;GACZ,OAAO;EACT;EACA,SAAS,MAAM;CACjB;AACF"}
@@ -32,6 +32,56 @@ declare function getRuntimeContext(): RuntimeLike<never>;
32
32
  * ```
33
33
  */
34
34
  declare function provideRuntime<R = never>(runtime?: RuntimeLike<R>): void;
35
+ /**
36
+ * A dedicated, type-safe runtime context.
37
+ *
38
+ * Unlike the shared default context ({@link provideRuntime} / {@link getRuntime},
39
+ * which is untyped — `RuntimeLike<never>`), each call to `createRuntimeContext`
40
+ * mints its *own* context instance carrying a fully-typed `RuntimeLike<R>`. A
41
+ * library whose whole point is a service-typed runtime (e.g.
42
+ * `ManagedRuntime<ClientView, never>`) can therefore expose its own context
43
+ * without colliding with the default context or with other libraries, and
44
+ * `get()` returns the precise `RuntimeLike<R>` rather than `RuntimeLike<never>`.
45
+ */
46
+ interface RuntimeContext<R> {
47
+ /**
48
+ * Provide the runtime to the component subtree. Like `setContext`, this must
49
+ * be called during component initialisation.
50
+ */
51
+ readonly provide: (runtime: RuntimeLike<R>) => void;
52
+ /**
53
+ * Retrieve the runtime. Must be called during component initialisation.
54
+ * Throws an actionable error when no ancestor called `provide`.
55
+ */
56
+ readonly get: () => RuntimeLike<R>;
57
+ }
58
+ /**
59
+ * Create a typed runtime context, built on Svelte's
60
+ * [`createContext`](https://svelte.dev/docs/svelte/svelte#createContext) (one
61
+ * context instance per call).
62
+ *
63
+ * @param name - an optional label used in the "runtime not provided" error, to
64
+ * help downstream users find the missing `provide` call.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * // library.ts — a library exposing a service-typed runtime
69
+ * import { createRuntimeContext } from '@thomasfosterau/effect-svelte';
70
+ * import type { ManagedRuntime } from 'effect';
71
+ * import type { ClientView } from './client.js';
72
+ *
73
+ * export const ClientContext =
74
+ * createRuntimeContext<ClientView>('@legation/svelte client');
75
+ * ```
76
+ * ```svelte
77
+ * <script lang="ts">
78
+ * // Root.svelte
79
+ * import { ClientContext } from './library.js';
80
+ * ClientContext.provide(myClientRuntime);
81
+ * </script>
82
+ * ```
83
+ */
84
+ declare function createRuntimeContext<R>(name?: string): RuntimeContext<R>;
35
85
  //#endregion
36
- export { getRuntimeContext, provideRuntime };
86
+ export { type RuntimeContext, createRuntimeContext, getRuntimeContext, provideRuntime };
37
87
  //# sourceMappingURL=context.svelte.d.ts.map
@@ -47,7 +47,49 @@ function getRuntimeContext() {
47
47
  function provideRuntime(runtime = defaultSvelteRuntime) {
48
48
  provideRuntimeInternal(runtime);
49
49
  }
50
+ /**
51
+ * Create a typed runtime context, built on Svelte's
52
+ * [`createContext`](https://svelte.dev/docs/svelte/svelte#createContext) (one
53
+ * context instance per call).
54
+ *
55
+ * @param name - an optional label used in the "runtime not provided" error, to
56
+ * help downstream users find the missing `provide` call.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * // library.ts — a library exposing a service-typed runtime
61
+ * import { createRuntimeContext } from '@thomasfosterau/effect-svelte';
62
+ * import type { ManagedRuntime } from 'effect';
63
+ * import type { ClientView } from './client.js';
64
+ *
65
+ * export const ClientContext =
66
+ * createRuntimeContext<ClientView>('@legation/svelte client');
67
+ * ```
68
+ * ```svelte
69
+ * <script lang="ts">
70
+ * // Root.svelte
71
+ * import { ClientContext } from './library.js';
72
+ * ClientContext.provide(myClientRuntime);
73
+ * <\/script>
74
+ * ```
75
+ */
76
+ function createRuntimeContext(name) {
77
+ const [get, set] = createContext();
78
+ const label = name === void 0 ? "this runtime context" : `the "${name}" runtime context`;
79
+ return {
80
+ provide(runtime) {
81
+ set(runtime);
82
+ },
83
+ get() {
84
+ try {
85
+ return get();
86
+ } catch {
87
+ throw new Error(`effect-svelte: No Effect runtime found for ${label}. Call its \`provide(runtime)\` in a parent component during initialisation.`);
88
+ }
89
+ }
90
+ };
91
+ }
50
92
  //#endregion
51
- export { getRuntimeContext, provideRuntime };
93
+ export { createRuntimeContext, getRuntimeContext, provideRuntime };
52
94
 
53
95
  //# sourceMappingURL=context.svelte.js.map
@@ -1 +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"}
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\n/**\n * A dedicated, type-safe runtime context.\n *\n * Unlike the shared default context ({@link provideRuntime} / {@link getRuntime},\n * which is untyped — `RuntimeLike<never>`), each call to `createRuntimeContext`\n * mints its *own* context instance carrying a fully-typed `RuntimeLike<R>`. A\n * library whose whole point is a service-typed runtime (e.g.\n * `ManagedRuntime<ClientView, never>`) can therefore expose its own context\n * without colliding with the default context or with other libraries, and\n * `get()` returns the precise `RuntimeLike<R>` rather than `RuntimeLike<never>`.\n */\ninterface RuntimeContext<R> {\n /**\n * Provide the runtime to the component subtree. Like `setContext`, this must\n * be called during component initialisation.\n */\n readonly provide: (runtime: RuntimeLike<R>) => void;\n\n /**\n * Retrieve the runtime. Must be called during component initialisation.\n * Throws an actionable error when no ancestor called `provide`.\n */\n readonly get: () => RuntimeLike<R>;\n}\n\n/**\n * Create a typed runtime context, built on Svelte's\n * [`createContext`](https://svelte.dev/docs/svelte/svelte#createContext) (one\n * context instance per call).\n *\n * @param name - an optional label used in the \"runtime not provided\" error, to\n * help downstream users find the missing `provide` call.\n *\n * @example\n * ```ts\n * // library.ts — a library exposing a service-typed runtime\n * import { createRuntimeContext } from '@thomasfosterau/effect-svelte';\n * import type { ManagedRuntime } from 'effect';\n * import type { ClientView } from './client.js';\n *\n * export const ClientContext =\n * createRuntimeContext<ClientView>('@legation/svelte client');\n * ```\n * ```svelte\n * <script lang=\"ts\">\n * // Root.svelte\n * import { ClientContext } from './library.js';\n * ClientContext.provide(myClientRuntime);\n * </script>\n * ```\n */\nfunction createRuntimeContext<R>(name?: string): RuntimeContext<R> {\n const [get, set] = createContext<RuntimeLike<R>>();\n const label = name === undefined ? \"this runtime context\" : `the \"${name}\" runtime context`;\n\n return {\n provide(runtime) {\n set(runtime);\n },\n get() {\n try {\n return get();\n } catch {\n throw new Error(\n `effect-svelte: No Effect runtime found for ${label}. ` +\n \"Call its `provide(runtime)` in a parent component during initialisation.\",\n );\n }\n },\n };\n}\n\nexport { createRuntimeContext, getRuntimeContext as getRuntime, provideRuntime };\nexport type { RuntimeContext, 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;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAS,qBAAwB,MAAkC;CACjE,MAAM,CAAC,KAAK,OAAO,cAA8B;CACjD,MAAM,QAAQ,SAAS,KAAA,IAAY,yBAAyB,QAAQ,KAAK;CAEzE,OAAO;EACL,QAAQ,SAAS;GACf,IAAI,OAAO;EACb;EACA,MAAM;GACJ,IAAI;IACF,OAAO,IAAI;GACb,QAAQ;IACN,MAAM,IAAI,MACR,8CAA8C,MAAM,6EAEtD;GACF;EACF;CACF;AACF"}
@@ -1,8 +1,9 @@
1
+ import { RuntimeLike } from "./internal/run.js";
1
2
  import { Duration, Effect } from "effect";
2
3
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
3
4
 
4
5
  //#region src/derived.svelte.d.ts
5
- interface UseDerivedOptions {
6
+ interface UseDerivedOptions<R = never> {
6
7
  /**
7
8
  * Debounce re-runs by this duration: when a dependency changes, wait this
8
9
  * long before running, and if another change arrives within the window,
@@ -13,6 +14,11 @@ interface UseDerivedOptions {
13
14
  * `Duration.seconds(1)`).
14
15
  */
15
16
  readonly debounce?: Duration.Input;
17
+ /**
18
+ * Run against this runtime instead of the ambient context. When provided, the
19
+ * effect's `R` is constrained to what the runtime provides.
20
+ */
21
+ readonly runtime?: RuntimeLike<R>;
16
22
  }
17
23
  interface DerivedReturn<A, E> {
18
24
  /**
@@ -51,7 +57,7 @@ interface DerivedReturn<A, E> {
51
57
  * {/if}
52
58
  * ```
53
59
  */
54
- declare function useDerived<A, E, R>(fn: () => Effect.Effect<A, E, R>, options?: UseDerivedOptions): DerivedReturn<A, E>;
60
+ declare function useDerived<A, E, R>(fn: () => Effect.Effect<A, E, R>, options?: UseDerivedOptions<R>): DerivedReturn<A, E>;
55
61
  //#endregion
56
62
  export { DerivedReturn, UseDerivedOptions, useDerived };
57
63
  //# sourceMappingURL=derived.svelte.d.ts.map
@@ -35,7 +35,7 @@ import { Effect, Fiber } from "effect";
35
35
  */
36
36
  function useDerived(fn, options = {}) {
37
37
  const { debounce } = options;
38
- const runtime = getRuntimeContext();
38
+ const runtime = options.runtime ?? getRuntimeContext();
39
39
  const state = makeResult();
40
40
  let currentFiber = null;
41
41
  $effect(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"derived.svelte.js","names":["getRuntime"],"sources":["../src/derived.svelte.ts"],"sourcesContent":["import { Duration, 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 UseDerivedOptions {\n /**\n * Debounce re-runs by this duration: when a dependency changes, wait this\n * long before running, and if another change arrives within the window,\n * restart the wait. The previous value stays visible during the window (the\n * result only flips to `waiting` once the run actually starts).\n *\n * Accepts any Effect `Duration.Input` (e.g. `\"300 millis\"`, `500`,\n * `Duration.seconds(1)`).\n */\n readonly debounce?: Duration.Input;\n}\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 * Pass `{ debounce }` to coalesce rapid dependency changes (e.g. a search box):\n * interrupting the previous fiber cancels its pending debounce sleep, so only\n * the last change in a burst actually runs.\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 query = $state('');\n *\n * const results = useDerived(\n * () => search(query),\n * { debounce: '300 millis' },\n * );\n * </script>\n *\n * <input bind:value={query} />\n *\n * {#if AsyncResult.isSuccess(results.current)}\n * <ul>{#each results.current.value as r}<li>{r}</li>{/each}</ul>\n * {/if}\n * ```\n */\nexport function useDerived<A, E, R>(\n fn: () => Effect.Effect<A, E, R>,\n options: UseDerivedOptions = {},\n): DerivedReturn<A, E> {\n const { debounce } = options;\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 (this also cancels a pending\n // debounce sleep, which is what makes debouncing work).\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n }\n\n // Get the effect (this tracks reactive dependencies).\n const effect = fn();\n\n let runningFiber: Fiber.Fiber<A, E>;\n if (debounce === undefined) {\n // Mark as waiting, preserving the previous value if there is one.\n state.startWaiting();\n runningFiber = runFork(runtime)(effect);\n } else {\n // Wait out the debounce window first; only then flip to waiting and run,\n // so the previous value stays visible until the run actually begins.\n runningFiber = runFork(runtime)(\n Effect.gen(function* () {\n yield* Effect.sleep(debounce);\n yield* Effect.sync(() => state.startWaiting());\n return yield* effect;\n }),\n );\n }\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAgB,WACd,IACA,UAA6B,CAAC,GACT;CACrB,MAAM,EAAE,aAAa;CACrB,MAAM,UAAUA,kBAAW;CAE3B,MAAM,QAAQ,WAAiB;CAC/B,IAAI,eAAyC;CAE7C,cAAc;EAGZ,IAAI,iBAAiB,MACnB,eAAe,YAAY;EAI7B,MAAM,SAAS,GAAG;EAElB,IAAI;EACJ,IAAI,aAAa,KAAA,GAAW;GAE1B,MAAM,aAAa;GACnB,eAAe,QAAQ,OAAO,EAAE,MAAM;EACxC,OAGE,eAAe,QAAQ,OAAO,EAC5B,OAAO,IAAI,aAAa;GACtB,OAAO,OAAO,MAAM,QAAQ;GAC5B,OAAO,OAAO,WAAW,MAAM,aAAa,CAAC;GAC7C,OAAO,OAAO;EAChB,CAAC,CACH;EAEF,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"}
1
+ {"version":3,"file":"derived.svelte.js","names":["getRuntime"],"sources":["../src/derived.svelte.ts"],"sourcesContent":["import { Duration, 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 UseDerivedOptions<R = never> {\n /**\n * Debounce re-runs by this duration: when a dependency changes, wait this\n * long before running, and if another change arrives within the window,\n * restart the wait. The previous value stays visible during the window (the\n * result only flips to `waiting` once the run actually starts).\n *\n * Accepts any Effect `Duration.Input` (e.g. `\"300 millis\"`, `500`,\n * `Duration.seconds(1)`).\n */\n readonly debounce?: Duration.Input;\n\n /**\n * Run against this runtime instead of the ambient context. When provided, the\n * effect's `R` is constrained to what the runtime provides.\n */\n readonly runtime?: RuntimeLike<R>;\n}\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 * Pass `{ debounce }` to coalesce rapid dependency changes (e.g. a search box):\n * interrupting the previous fiber cancels its pending debounce sleep, so only\n * the last change in a burst actually runs.\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 query = $state('');\n *\n * const results = useDerived(\n * () => search(query),\n * { debounce: '300 millis' },\n * );\n * </script>\n *\n * <input bind:value={query} />\n *\n * {#if AsyncResult.isSuccess(results.current)}\n * <ul>{#each results.current.value as r}<li>{r}</li>{/each}</ul>\n * {/if}\n * ```\n */\nexport function useDerived<A, E, R>(\n fn: () => Effect.Effect<A, E, R>,\n options: UseDerivedOptions<R> = {},\n): DerivedReturn<A, E> {\n const { debounce } = options;\n const runtime = options.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 (this also cancels a pending\n // debounce sleep, which is what makes debouncing work).\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n }\n\n // Get the effect (this tracks reactive dependencies).\n const effect = fn();\n\n let runningFiber: Fiber.Fiber<A, E>;\n if (debounce === undefined) {\n // Mark as waiting, preserving the previous value if there is one.\n state.startWaiting();\n runningFiber = runFork(runtime)(effect);\n } else {\n // Wait out the debounce window first; only then flip to waiting and run,\n // so the previous value stays visible until the run actually begins.\n runningFiber = runFork(runtime)(\n Effect.gen(function* () {\n yield* Effect.sleep(debounce);\n yield* Effect.sync(() => state.startWaiting());\n return yield* effect;\n }),\n );\n }\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DA,SAAgB,WACd,IACA,UAAgC,CAAC,GACZ;CACrB,MAAM,EAAE,aAAa;CACrB,MAAM,UAAU,QAAQ,WAAYA,kBAAW;CAE/C,MAAM,QAAQ,WAAiB;CAC/B,IAAI,eAAyC;CAE7C,cAAc;EAGZ,IAAI,iBAAiB,MACnB,eAAe,YAAY;EAI7B,MAAM,SAAS,GAAG;EAElB,IAAI;EACJ,IAAI,aAAa,KAAA,GAAW;GAE1B,MAAM,aAAa;GACnB,eAAe,QAAQ,OAAO,EAAE,MAAM;EACxC,OAGE,eAAe,QAAQ,OAAO,EAC5B,OAAO,IAAI,aAAa;GACtB,OAAO,OAAO,MAAM,QAAQ;GAC5B,OAAO,OAAO,WAAW,MAAM,aAAa,CAAC;GAC7C,OAAO,OAAO;EAChB,CAAC,CACH;EAEF,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"}
@@ -1,14 +1,20 @@
1
+ import { RuntimeLike } from "./internal/run.js";
1
2
  import { Effect } from "effect";
2
3
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
3
4
 
4
5
  //#region src/effect.svelte.d.ts
5
- interface UseEffectOptions {
6
+ interface UseEffectOptions<R = never> {
6
7
  /**
7
8
  * If true, the effect will run immediately on mount.
8
9
  * If false, the effect will only run when manually triggered via run().
9
10
  * @default false
10
11
  */
11
12
  immediate?: boolean;
13
+ /**
14
+ * Run against this runtime instead of the ambient context. When provided, the
15
+ * effect's `R` is constrained to what the runtime provides.
16
+ */
17
+ runtime?: RuntimeLike<R>;
12
18
  }
13
19
  interface UseEffectReturn<A, E> {
14
20
  /**
@@ -58,7 +64,7 @@ interface UseEffectReturn<A, E> {
58
64
  * {/if}
59
65
  * ```
60
66
  */
61
- declare function useEffect<A, E, R>(effect: Effect.Effect<A, E, R>, options?: UseEffectOptions): UseEffectReturn<A, E>;
67
+ declare function useEffect<A, E, R>(effect: Effect.Effect<A, E, R>, options?: UseEffectOptions<R>): UseEffectReturn<A, E>;
62
68
  //#endregion
63
69
  export { UseEffectOptions, UseEffectReturn, useEffect };
64
70
  //# sourceMappingURL=effect.svelte.d.ts.map
@@ -26,7 +26,7 @@ import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
26
26
  */
27
27
  function useEffect(effect, options = {}) {
28
28
  const { immediate = false } = options;
29
- const runtime = getRuntimeContext();
29
+ const runtime = options.runtime ?? getRuntimeContext();
30
30
  const state = makeResult();
31
31
  let currentFiber = null;
32
32
  const run = () => {
@@ -1 +1 @@
1
- {"version":3,"file":"effect.svelte.js","names":["getRuntime"],"sources":["../src/effect.svelte.ts"],"sourcesContent":["import { Effect, Fiber, Option } from \"effect\";\nimport * 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 UseEffectOptions {\n /**\n * If true, the effect will run immediately on mount.\n * If false, the effect will only run when manually triggered via run().\n * @default false\n */\n immediate?: boolean;\n}\n\nexport interface UseEffectReturn<A, E> {\n /**\n * The current result state of the effect. Re-running preserves the previous\n * value as a waiting `Success` (stale-while-revalidate).\n */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /**\n * Manually trigger execution of the effect\n */\n run: () => void;\n\n /**\n * Interrupt the currently running effect\n */\n interrupt: () => void;\n\n /**\n * Optimistically overwrite the current value with a non-waiting `Success`,\n * without running the effect. Accepts a value or an updater function that\n * receives the current value (`undefined` if there is none yet). Useful for\n * local cache writes after a mutation, reconciled by the next `run`.\n */\n setData: (updater: A | ((previous: A | undefined) => A)) => void;\n\n /**\n * `true` while a previous value is being shown but a re-run is in flight\n * (stale-while-revalidate) — i.e. the result is `waiting` and still holds a\n * value.\n */\n readonly isStale: boolean;\n}\n\n/**\n * Run a single Effect and track its result state.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useEffect, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const fetchData = useEffect(\n * Effect.delay(Effect.succeed(42), '1 second'),\n * { immediate: true }\n * );\n * </script>\n *\n * {#if AsyncResult.isSuccess(fetchData.current)}\n * <p>Result: {fetchData.current.value}</p>\n * {/if}\n * ```\n */\nexport function useEffect<A, E, R>(\n effect: Effect.Effect<A, E, R>,\n options: UseEffectOptions = {},\n): UseEffectReturn<A, E> {\n const { immediate = false } = options;\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 const run = () => {\n // Interrupt any existing fiber\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n }\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 a newer run replaced it.\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\n const interrupt = () => {\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n currentFiber = null;\n // Return to the initial state so the effect can be run again cleanly.\n state.reset();\n }\n };\n\n // Set up effect lifecycle\n $effect(() => {\n if (immediate) {\n run();\n }\n\n // Cleanup: interrupt any running fiber on unmount\n return () => {\n interrupt();\n };\n });\n\n const setData = (updater: A | ((previous: A | undefined) => A)): void => {\n const previous = Option.getOrUndefined(AsyncResult.value(state.current));\n const next =\n typeof updater === \"function\"\n ? (updater as (previous: A | undefined) => A)(previous)\n : updater;\n state.succeed(next);\n };\n\n return {\n get current() {\n return state.current;\n },\n run,\n interrupt,\n setData,\n get isStale() {\n return (\n AsyncResult.isWaiting(state.current) && Option.isSome(AsyncResult.value(state.current))\n );\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,SAAgB,UACd,QACA,UAA4B,CAAC,GACN;CACvB,MAAM,EAAE,YAAY,UAAU;CAC9B,MAAM,UAAUA,kBAAW;CAE3B,MAAM,QAAQ,WAAiB;CAC/B,IAAI,eAAyC;CAE7C,MAAM,YAAY;EAEhB,IAAI,iBAAiB,MACnB,eAAe,YAAY;EAI7B,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;CACF;CAEA,MAAM,kBAAkB;EACtB,IAAI,iBAAiB,MAAM;GACzB,eAAe,YAAY;GAC3B,eAAe;GAEf,MAAM,MAAM;EACd;CACF;CAGA,cAAc;EACZ,IAAI,WACF,IAAI;EAIN,aAAa;GACX,UAAU;EACZ;CACF,CAAC;CAED,MAAM,WAAW,YAAwD;EACvE,MAAM,WAAW,OAAO,eAAe,YAAY,MAAM,MAAM,OAAO,CAAC;EACvE,MAAM,OACJ,OAAO,YAAY,aACd,QAA2C,QAAQ,IACpD;EACN,MAAM,QAAQ,IAAI;CACpB;CAEA,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA;EACA;EACA;EACA,IAAI,UAAU;GACZ,OACE,YAAY,UAAU,MAAM,OAAO,KAAK,OAAO,OAAO,YAAY,MAAM,MAAM,OAAO,CAAC;EAE1F;CACF;AACF"}
1
+ {"version":3,"file":"effect.svelte.js","names":["getRuntime"],"sources":["../src/effect.svelte.ts"],"sourcesContent":["import { Effect, Fiber, Option } from \"effect\";\nimport * 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 UseEffectOptions<R = never> {\n /**\n * If true, the effect will run immediately on mount.\n * If false, the effect will only run when manually triggered via run().\n * @default false\n */\n immediate?: boolean;\n\n /**\n * Run against this runtime instead of the ambient context. When provided, the\n * effect's `R` is constrained to what the runtime provides.\n */\n runtime?: RuntimeLike<R>;\n}\n\nexport interface UseEffectReturn<A, E> {\n /**\n * The current result state of the effect. Re-running preserves the previous\n * value as a waiting `Success` (stale-while-revalidate).\n */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /**\n * Manually trigger execution of the effect\n */\n run: () => void;\n\n /**\n * Interrupt the currently running effect\n */\n interrupt: () => void;\n\n /**\n * Optimistically overwrite the current value with a non-waiting `Success`,\n * without running the effect. Accepts a value or an updater function that\n * receives the current value (`undefined` if there is none yet). Useful for\n * local cache writes after a mutation, reconciled by the next `run`.\n */\n setData: (updater: A | ((previous: A | undefined) => A)) => void;\n\n /**\n * `true` while a previous value is being shown but a re-run is in flight\n * (stale-while-revalidate) — i.e. the result is `waiting` and still holds a\n * value.\n */\n readonly isStale: boolean;\n}\n\n/**\n * Run a single Effect and track its result state.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useEffect, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const fetchData = useEffect(\n * Effect.delay(Effect.succeed(42), '1 second'),\n * { immediate: true }\n * );\n * </script>\n *\n * {#if AsyncResult.isSuccess(fetchData.current)}\n * <p>Result: {fetchData.current.value}</p>\n * {/if}\n * ```\n */\nexport function useEffect<A, E, R>(\n effect: Effect.Effect<A, E, R>,\n options: UseEffectOptions<R> = {},\n): UseEffectReturn<A, E> {\n const { immediate = false } = options;\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R>);\n\n const state = makeResult<A, E>();\n let currentFiber: Fiber.Fiber<A, E> | null = null;\n\n const run = () => {\n // Interrupt any existing fiber\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n }\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 a newer run replaced it.\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\n const interrupt = () => {\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n currentFiber = null;\n // Return to the initial state so the effect can be run again cleanly.\n state.reset();\n }\n };\n\n // Set up effect lifecycle\n $effect(() => {\n if (immediate) {\n run();\n }\n\n // Cleanup: interrupt any running fiber on unmount\n return () => {\n interrupt();\n };\n });\n\n const setData = (updater: A | ((previous: A | undefined) => A)): void => {\n const previous = Option.getOrUndefined(AsyncResult.value(state.current));\n const next =\n typeof updater === \"function\"\n ? (updater as (previous: A | undefined) => A)(previous)\n : updater;\n state.succeed(next);\n };\n\n return {\n get current() {\n return state.current;\n },\n run,\n interrupt,\n setData,\n get isStale() {\n return (\n AsyncResult.isWaiting(state.current) && Option.isSome(AsyncResult.value(state.current))\n );\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,SAAgB,UACd,QACA,UAA+B,CAAC,GACT;CACvB,MAAM,EAAE,YAAY,UAAU;CAC9B,MAAM,UAAU,QAAQ,WAAYA,kBAAW;CAE/C,MAAM,QAAQ,WAAiB;CAC/B,IAAI,eAAyC;CAE7C,MAAM,YAAY;EAEhB,IAAI,iBAAiB,MACnB,eAAe,YAAY;EAI7B,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;CACF;CAEA,MAAM,kBAAkB;EACtB,IAAI,iBAAiB,MAAM;GACzB,eAAe,YAAY;GAC3B,eAAe;GAEf,MAAM,MAAM;EACd;CACF;CAGA,cAAc;EACZ,IAAI,WACF,IAAI;EAIN,aAAa;GACX,UAAU;EACZ;CACF,CAAC;CAED,MAAM,WAAW,YAAwD;EACvE,MAAM,WAAW,OAAO,eAAe,YAAY,MAAM,MAAM,OAAO,CAAC;EACvE,MAAM,OACJ,OAAO,YAAY,aACd,QAA2C,QAAQ,IACpD;EACN,MAAM,QAAQ,IAAI;CACpB;CAEA,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA;EACA;EACA;EACA,IAAI,UAAU;GACZ,OACE,YAAY,UAAU,MAAM,OAAO,KAAK,OAAO,OAAO,YAAY,MAAM,MAAM,OAAO,CAAC;EAE1F;CACF;AACF"}
package/dist/index.d.ts CHANGED
@@ -1,18 +1,19 @@
1
1
  import { RuntimeLike } from "./internal/run.js";
2
- import { getRuntimeContext, provideRuntime } from "./context.svelte.js";
2
+ import { RuntimeContext, createRuntimeContext, getRuntimeContext, provideRuntime } from "./context.svelte.js";
3
3
  import { SvelteRuntime, defaultSvelteRuntime } from "./runtime.js";
4
4
  import { useScope, useScopeCallback } from "./scope.svelte.js";
5
5
  import { UseEffectOptions, UseEffectReturn, useEffect } from "./effect.svelte.js";
6
- import { UseStreamReturn, useStream } from "./stream.svelte.js";
6
+ import { UseStreamOptions, UseStreamReturn, useStream } from "./stream.svelte.js";
7
+ import { UseLiveStreamOptions, UseLiveStreamReturn, useLiveStream } from "./live-stream.svelte.js";
7
8
  import { DerivedReturn, UseDerivedOptions, useDerived } from "./derived.svelte.js";
8
- import { UseQueryReturn, useQuery } from "./query.svelte.js";
9
+ import { UseQueryOptions, UseQueryReturn, useQuery } from "./query.svelte.js";
9
10
  import { MutationCallbacks } from "./internal/mutation.js";
10
- import { UseMutationReturn, useMutation } from "./mutation.svelte.js";
11
- import { UseQueryPromiseReturn, useQueryPromise } from "./await.svelte.js";
12
- import { UsePubSubReturn, UseSubscriptionRefReturn, usePubSub, useSubscriptionRef } from "./subscription.svelte.js";
13
- import { UseWritableRefReturn, useWritableRef } from "./writable-ref.svelte.js";
14
- import { ReactiveMutationReturn, ReactiveQueryReturn, ReactiveStreamReturn, reactiveMutation, reactiveQuery, reactiveStream, useInvalidateKeys } from "./reactivity.svelte.js";
11
+ import { UseMutationOptions, UseMutationReturn, useMutation } from "./mutation.svelte.js";
12
+ import { UseQueryPromiseOptions, UseQueryPromiseReturn, useQueryPromise } from "./await.svelte.js";
13
+ import { UsePubSubOptions, UsePubSubReturn, UseSubscriptionRefOptions, UseSubscriptionRefReturn, usePubSub, useSubscriptionRef } from "./subscription.svelte.js";
14
+ import { UseWritableRefOptions, UseWritableRefReturn, useWritableRef } from "./writable-ref.svelte.js";
15
+ import { ReactiveMutationReturn, ReactiveOptions, ReactiveQueryReturn, ReactiveStreamReturn, reactiveMutation, reactiveQuery, reactiveStream, useInvalidateKeys } from "./reactivity.svelte.js";
15
16
  import { SignalEmitter } from "./emitter.js";
16
17
  import { Store_d_exports } from "./Store.js";
17
18
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
18
- export { AsyncResult, type DerivedReturn, type MutationCallbacks, type ReactiveMutationReturn, type ReactiveQueryReturn, type ReactiveStreamReturn, type RuntimeLike, SignalEmitter, Store_d_exports as Store, SvelteRuntime, type UseDerivedOptions, type UseEffectOptions, type UseEffectReturn, type UseMutationReturn, type UsePubSubReturn, type UseQueryPromiseReturn, type UseQueryReturn, type UseStreamReturn, type UseSubscriptionRefReturn, type UseWritableRefReturn, defaultSvelteRuntime, getRuntimeContext as getRuntime, provideRuntime, reactiveMutation, reactiveQuery, reactiveStream, useDerived, useEffect, useInvalidateKeys, useMutation, usePubSub, useQuery, useQueryPromise, useScope, useScopeCallback, useStream, useSubscriptionRef, useWritableRef };
19
+ export { AsyncResult, type DerivedReturn, type MutationCallbacks, type ReactiveMutationReturn, type ReactiveOptions, type ReactiveQueryReturn, type ReactiveStreamReturn, type RuntimeContext, type RuntimeLike, SignalEmitter, Store_d_exports as Store, SvelteRuntime, type UseDerivedOptions, type UseEffectOptions, type UseEffectReturn, type UseLiveStreamOptions, type UseLiveStreamReturn, type UseMutationOptions, type UseMutationReturn, type UsePubSubOptions, type UsePubSubReturn, type UseQueryOptions, type UseQueryPromiseOptions, type UseQueryPromiseReturn, type UseQueryReturn, type UseStreamOptions, type UseStreamReturn, type UseSubscriptionRefOptions, type UseSubscriptionRefReturn, type UseWritableRefOptions, type UseWritableRefReturn, createRuntimeContext, defaultSvelteRuntime, getRuntimeContext as getRuntime, provideRuntime, reactiveMutation, reactiveQuery, reactiveStream, useDerived, useEffect, useInvalidateKeys, useLiveStream, useMutation, usePubSub, useQuery, useQueryPromise, useScope, useScopeCallback, useStream, useSubscriptionRef, useWritableRef };
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { SvelteRuntime, defaultSvelteRuntime } from "./runtime.js";
2
- import { getRuntimeContext, provideRuntime } from "./context.svelte.js";
2
+ import { createRuntimeContext, getRuntimeContext, provideRuntime } from "./context.svelte.js";
3
3
  import { useScope, useScopeCallback } from "./scope.svelte.js";
4
4
  import { useEffect } from "./effect.svelte.js";
5
5
  import { useStream } from "./stream.svelte.js";
6
+ import { useLiveStream } from "./live-stream.svelte.js";
6
7
  import { useDerived } from "./derived.svelte.js";
7
8
  import { useQuery } from "./query.svelte.js";
8
9
  import { useMutation } from "./mutation.svelte.js";
@@ -13,4 +14,4 @@ import { reactiveMutation, reactiveQuery, reactiveStream, useInvalidateKeys } fr
13
14
  import { SignalEmitter } from "./emitter.js";
14
15
  import { Store_exports } from "./Store.js";
15
16
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
16
- export { AsyncResult, SignalEmitter, Store_exports as Store, SvelteRuntime, defaultSvelteRuntime, getRuntimeContext as getRuntime, provideRuntime, reactiveMutation, reactiveQuery, reactiveStream, useDerived, useEffect, useInvalidateKeys, useMutation, usePubSub, useQuery, useQueryPromise, useScope, useScopeCallback, useStream, useSubscriptionRef, useWritableRef };
17
+ export { AsyncResult, SignalEmitter, Store_exports as Store, SvelteRuntime, createRuntimeContext, defaultSvelteRuntime, getRuntimeContext as getRuntime, provideRuntime, reactiveMutation, reactiveQuery, reactiveStream, useDerived, useEffect, useInvalidateKeys, useLiveStream, useMutation, usePubSub, useQuery, useQueryPromise, useScope, useScopeCallback, useStream, useSubscriptionRef, useWritableRef };
@@ -23,16 +23,29 @@ import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
23
23
  function resultToPromise(result) {
24
24
  if (AsyncResult.isFailure(result)) {
25
25
  const cause = AsyncResult.cause(result);
26
- if (Option.isSome(cause)) {
27
- const error = Cause.findErrorOption(cause.value);
28
- return Promise.reject(Option.isSome(error) ? error.value : Cause.squash(cause.value));
29
- }
30
- return Promise.reject(/* @__PURE__ */ new Error("AsyncResult failure with no cause"));
26
+ return rejected(Option.isSome(cause) ? Option.match(Cause.findErrorOption(cause.value), {
27
+ onNone: () => Cause.squash(cause.value),
28
+ onSome: (error) => error
29
+ }) : /* @__PURE__ */ new Error("AsyncResult failure with no cause"));
31
30
  }
32
31
  const value = AsyncResult.value(result);
33
32
  if (Option.isSome(value)) return Promise.resolve(value.value);
34
33
  return new Promise(() => {});
35
34
  }
35
+ /**
36
+ * A rejected promise whose rejection is pre-observed with a no-op handler.
37
+ *
38
+ * Failures are surfaced as rejected promises from a `$derived`; a reactive
39
+ * re-render can produce (and then discard) such a promise before anything
40
+ * `await`s it. The no-op `catch` keeps that discarded value from tripping
41
+ * Node's `unhandledRejection` warning, while the returned promise still rejects
42
+ * for every real consumer (the `catch` runs on a separate branch).
43
+ */
44
+ function rejected(reason) {
45
+ const promise = Promise.reject(reason);
46
+ promise.catch(() => {});
47
+ return promise;
48
+ }
36
49
  //#endregion
37
50
  export { resultToPromise };
38
51
 
@@ -1 +1 @@
1
- {"version":3,"file":"await.js","names":[],"sources":["../../src/internal/await.ts"],"sourcesContent":["import { Cause, Option } from \"effect\";\nimport * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\n\n/**\n * Convert an {@link AsyncResult.AsyncResult} into a `Promise`, so Effect-backed\n * state can drive Svelte 5's native async (`{#await}` blocks and\n * `<svelte:boundary>` suspense), which both consume a real thenable.\n *\n * Mapping:\n * - **Failure** → a rejected promise (with the typed error `E` when present,\n * otherwise the squashed cause). Checked first, so a failure that carries a\n * previous success still surfaces the error in the `{:catch}` block.\n * - **Holds a value** (a `Success`, including a waiting one during\n * stale-while-revalidate) → a resolved promise, so the UI keeps showing data\n * while a refresh is in flight.\n * - **Initial / waiting with no value** → a perpetually-pending promise, so the\n * `{#await}` loading branch (or the boundary's `pending` snippet) shows.\n *\n * Callers should memoise the result (e.g. via `$derived`) so a fresh pending\n * promise is not produced on unrelated re-renders — a new promise identity is\n * only wanted when the underlying result transitions.\n */\nexport function resultToPromise<A, E>(result: AsyncResult.AsyncResult<A, E>): Promise<A> {\n if (AsyncResult.isFailure(result)) {\n const cause = AsyncResult.cause(result);\n if (Option.isSome(cause)) {\n const error = Cause.findErrorOption(cause.value);\n return Promise.reject(Option.isSome(error) ? error.value : Cause.squash(cause.value));\n }\n return Promise.reject(new Error(\"AsyncResult failure with no cause\"));\n }\n\n const value = AsyncResult.value(result);\n if (Option.isSome(value)) return Promise.resolve(value.value);\n\n // Initial / waiting with no value yet: stay pending.\n return new Promise<A>(() => {});\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,gBAAsB,QAAmD;CACvF,IAAI,YAAY,UAAU,MAAM,GAAG;EACjC,MAAM,QAAQ,YAAY,MAAM,MAAM;EACtC,IAAI,OAAO,OAAO,KAAK,GAAG;GACxB,MAAM,QAAQ,MAAM,gBAAgB,MAAM,KAAK;GAC/C,OAAO,QAAQ,OAAO,OAAO,OAAO,KAAK,IAAI,MAAM,QAAQ,MAAM,OAAO,MAAM,KAAK,CAAC;EACtF;EACA,OAAO,QAAQ,uBAAO,IAAI,MAAM,mCAAmC,CAAC;CACtE;CAEA,MAAM,QAAQ,YAAY,MAAM,MAAM;CACtC,IAAI,OAAO,OAAO,KAAK,GAAG,OAAO,QAAQ,QAAQ,MAAM,KAAK;CAG5D,OAAO,IAAI,cAAiB,CAAC,CAAC;AAChC"}
1
+ {"version":3,"file":"await.js","names":[],"sources":["../../src/internal/await.ts"],"sourcesContent":["import { Cause, Option } from \"effect\";\nimport * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\n\n/**\n * Convert an {@link AsyncResult.AsyncResult} into a `Promise`, so Effect-backed\n * state can drive Svelte 5's native async (`{#await}` blocks and\n * `<svelte:boundary>` suspense), which both consume a real thenable.\n *\n * Mapping:\n * - **Failure** → a rejected promise (with the typed error `E` when present,\n * otherwise the squashed cause). Checked first, so a failure that carries a\n * previous success still surfaces the error in the `{:catch}` block.\n * - **Holds a value** (a `Success`, including a waiting one during\n * stale-while-revalidate) → a resolved promise, so the UI keeps showing data\n * while a refresh is in flight.\n * - **Initial / waiting with no value** → a perpetually-pending promise, so the\n * `{#await}` loading branch (or the boundary's `pending` snippet) shows.\n *\n * Callers should memoise the result (e.g. via `$derived`) so a fresh pending\n * promise is not produced on unrelated re-renders — a new promise identity is\n * only wanted when the underlying result transitions.\n */\nexport function resultToPromise<A, E>(result: AsyncResult.AsyncResult<A, E>): Promise<A> {\n if (AsyncResult.isFailure(result)) {\n const cause = AsyncResult.cause(result);\n const reason = Option.isSome(cause)\n ? Option.match(Cause.findErrorOption(cause.value), {\n onNone: () => Cause.squash(cause.value),\n onSome: (error) => error,\n })\n : new Error(\"AsyncResult failure with no cause\");\n return rejected(reason);\n }\n\n const value = AsyncResult.value(result);\n if (Option.isSome(value)) return Promise.resolve(value.value);\n\n // Initial / waiting with no value yet: stay pending.\n return new Promise<A>(() => {});\n}\n\n/**\n * A rejected promise whose rejection is pre-observed with a no-op handler.\n *\n * Failures are surfaced as rejected promises from a `$derived`; a reactive\n * re-render can produce (and then discard) such a promise before anything\n * `await`s it. The no-op `catch` keeps that discarded value from tripping\n * Node's `unhandledRejection` warning, while the returned promise still rejects\n * for every real consumer (the `catch` runs on a separate branch).\n */\nfunction rejected<A>(reason: unknown): Promise<A> {\n const promise = Promise.reject<A>(reason);\n promise.catch(() => {});\n return promise;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,gBAAsB,QAAmD;CACvF,IAAI,YAAY,UAAU,MAAM,GAAG;EACjC,MAAM,QAAQ,YAAY,MAAM,MAAM;EAOtC,OAAO,SANQ,OAAO,OAAO,KAAK,IAC9B,OAAO,MAAM,MAAM,gBAAgB,MAAM,KAAK,GAAG;GAC/C,cAAc,MAAM,OAAO,MAAM,KAAK;GACtC,SAAS,UAAU;EACrB,CAAC,oBACD,IAAI,MAAM,mCAAmC,CAC3B;CACxB;CAEA,MAAM,QAAQ,YAAY,MAAM,MAAM;CACtC,IAAI,OAAO,OAAO,KAAK,GAAG,OAAO,QAAQ,QAAQ,MAAM,KAAK;CAG5D,OAAO,IAAI,cAAiB,CAAC,CAAC;AAChC;;;;;;;;;;AAWA,SAAS,SAAY,QAA6B;CAChD,MAAM,UAAU,QAAQ,OAAU,MAAM;CACxC,QAAQ,YAAY,CAAC,CAAC;CACtB,OAAO;AACT"}
@@ -0,0 +1,43 @@
1
+ import { interruptFiber, runFork } from "./run.js";
2
+ import { Cause, Effect, Exit, Stream } from "effect";
3
+ //#region src/internal/live-stream.ts
4
+ /**
5
+ * Orchestrates a live `Stream` subscription against a {@link LiveResultState}.
6
+ *
7
+ * A single {@link makeLiveStream} instance is reused across re-subscriptions
8
+ * (driven by the hook's `$effect`): every call to `subscribe` interrupts the
9
+ * previous run and starts a fresh one, keeping the previous value visible as a
10
+ * waiting `Success` in between (stale-while-revalidate).
11
+ *
12
+ * Interruption caused by the hook's own teardown / re-subscribe must never
13
+ * surface as a `Failure` — only genuine failures (`Cause.hasFails`) and defects
14
+ * (`Cause.hasDies`) settle the result to `Failure`.
15
+ */
16
+ function makeLiveStream(runtime, state) {
17
+ let active = null;
18
+ const subscribe = (stream) => {
19
+ const token = Symbol();
20
+ active = token;
21
+ state.startWaiting();
22
+ const consume = Stream.runForEach(stream, (value) => Effect.sync(() => {
23
+ if (active !== token) return;
24
+ state.emit(value);
25
+ }));
26
+ const program = Effect.flatMap(Effect.yieldNow, () => consume).pipe(Effect.onExit((exit) => Effect.sync(() => {
27
+ if (active !== token) return;
28
+ if (Exit.isSuccess(exit)) return;
29
+ const cause = exit.cause;
30
+ if (Cause.hasFails(cause) || Cause.hasDies(cause)) state.failCause(cause);
31
+ })));
32
+ const fiber = runFork(runtime)(program);
33
+ return () => {
34
+ if (active === token) active = null;
35
+ interruptFiber(fiber);
36
+ };
37
+ };
38
+ return { subscribe };
39
+ }
40
+ //#endregion
41
+ export { makeLiveStream };
42
+
43
+ //# sourceMappingURL=live-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-stream.js","names":[],"sources":["../../src/internal/live-stream.ts"],"sourcesContent":["import { Cause, Effect, Exit, Stream } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { interruptFiber, runFork, type RuntimeLike } from \"./run.js\";\n\n/**\n * The reactive holder driven by {@link makeLiveStream}. The rune-backed\n * implementation lives in `live-stream.svelte.ts` (a `$state.raw` cell); this\n * interface keeps the orchestrator below rune-free so it can be unit-tested\n * with a plain fake (the same split as `makeResult` / `makeMutation`).\n *\n * The transitions follow the same stale-while-revalidate semantics as the rest\n * of the package: marking a result waiting preserves whatever value it already\n * holds, so the previous emission stays visible while a new subscription spins\n * up.\n */\nexport interface LiveResultState<A, E> {\n /** The current result. Read inside an effect or template to track it. */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /**\n * Mark the current result as waiting, preserving any value it already holds\n * (a seeded `Success` becomes a waiting `Success`, an `Initial` a waiting\n * `Initial`).\n */\n startWaiting(): void;\n\n /**\n * Replace the result with a non-waiting `Success`. Each emission replaces the\n * previous one — live queries emit whole result sets, never appended deltas.\n */\n emit(value: A): void;\n\n /** Replace the result with a `Failure`, carrying the previous success forward. */\n failCause(cause: Cause.Cause<E>): void;\n}\n\n/**\n * Orchestrates a live `Stream` subscription against a {@link LiveResultState}.\n *\n * A single {@link makeLiveStream} instance is reused across re-subscriptions\n * (driven by the hook's `$effect`): every call to `subscribe` interrupts the\n * previous run and starts a fresh one, keeping the previous value visible as a\n * waiting `Success` in between (stale-while-revalidate).\n *\n * Interruption caused by the hook's own teardown / re-subscribe must never\n * surface as a `Failure` — only genuine failures (`Cause.hasFails`) and defects\n * (`Cause.hasDies`) settle the result to `Failure`.\n */\nexport function makeLiveStream<A, E, R>(\n runtime: RuntimeLike<R>,\n state: LiveResultState<A, E>,\n): {\n /** Start a new subscription, returning a teardown that interrupts it. */\n readonly subscribe: (stream: Stream.Stream<A, E, R>) => () => void;\n} {\n // Identifies the live subscription. A superseded run's late emission (or\n // exit) is dropped by comparing against this token, so a fiber that is being\n // interrupted cannot clobber the value the newer subscription just set.\n let active: symbol | null = null;\n\n const subscribe = (stream: Stream.Stream<A, E, R>): (() => void) => {\n const token = Symbol();\n active = token;\n\n // Keep the previous value visible as a waiting `Success` while the new\n // subscription starts (stale-while-revalidate). On the very first run this\n // marks a seeded `Success` / bare `Initial` as waiting.\n state.startWaiting();\n\n const consume = Stream.runForEach(stream, (value) =>\n Effect.sync(() => {\n if (active !== token) return;\n // Each emission replaces the current value — never appends.\n state.emit(value);\n }),\n );\n\n // Yield once before consuming, so a synchronously-resolving stream cannot\n // write state *during* the `$effect` flush that started it (Svelte flags a\n // write-during-flush that the awaited `promise` reads back as an update\n // cycle). The subscription still starts on the very next microtask.\n const program = Effect.flatMap(Effect.yieldNow, () => consume).pipe(\n Effect.onExit((exit) =>\n Effect.sync(() => {\n if (active !== token) return;\n // A completed stream leaves the last emission in place.\n if (Exit.isSuccess(exit)) return;\n const cause = exit.cause;\n // Teardown / re-subscribe interruption is normal lifecycle churn and\n // must not surface as a `Failure`; only real failures and defects do.\n if (Cause.hasFails(cause) || Cause.hasDies(cause)) {\n state.failCause(cause);\n }\n }),\n ),\n );\n\n const fiber = runFork(runtime)(program);\n\n return () => {\n if (active === token) active = null;\n interruptFiber(fiber);\n };\n };\n\n return { subscribe };\n}\n"],"mappings":";;;;;;;;;;;;;;;AAgDA,SAAgB,eACd,SACA,OAIA;CAIA,IAAI,SAAwB;CAE5B,MAAM,aAAa,WAAiD;EAClE,MAAM,QAAQ,OAAO;EACrB,SAAS;EAKT,MAAM,aAAa;EAEnB,MAAM,UAAU,OAAO,WAAW,SAAS,UACzC,OAAO,WAAW;GAChB,IAAI,WAAW,OAAO;GAEtB,MAAM,KAAK,KAAK;EAClB,CAAC,CACH;EAMA,MAAM,UAAU,OAAO,QAAQ,OAAO,gBAAgB,OAAO,EAAE,KAC7D,OAAO,QAAQ,SACb,OAAO,WAAW;GAChB,IAAI,WAAW,OAAO;GAEtB,IAAI,KAAK,UAAU,IAAI,GAAG;GAC1B,MAAM,QAAQ,KAAK;GAGnB,IAAI,MAAM,SAAS,KAAK,KAAK,MAAM,QAAQ,KAAK,GAC9C,MAAM,UAAU,KAAK;EAEzB,CAAC,CACH,CACF;EAEA,MAAM,QAAQ,QAAQ,OAAO,EAAE,OAAO;EAEtC,aAAa;GACX,IAAI,WAAW,OAAO,SAAS;GAC/B,eAAe,KAAK;EACtB;CACF;CAEA,OAAO,EAAE,UAAU;AACrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"writable.js","names":[],"sources":["../../src/internal/writable.ts"],"sourcesContent":["import { SubscriptionRef } from \"effect\";\nimport { runFork, type RuntimeLike } from \"./run.js\";\n\n/**\n * The imperative write half of a two-way `SubscriptionRef` binding, factored\n * out of the rune wrapper so the write semantics can be unit-tested without a\n * Svelte component context.\n *\n * Both `set` and `update` fork the write onto the runtime and return\n * immediately — the value the caller sees update is driven by the ref's\n * `changes` stream (or, in the rune wrapper, an optimistic local assignment).\n */\nexport interface RefWriter<A> {\n /** Replace the ref's value. */\n readonly set: (value: A) => void;\n /** Apply a function to the ref's current value. */\n readonly update: (f: (current: A) => A) => void;\n}\n\n/**\n * Build a {@link RefWriter} for a `SubscriptionRef`, running writes on the\n * given runtime.\n */\nexport function refWriter<A>(\n runtime: RuntimeLike<never>,\n ref: SubscriptionRef.SubscriptionRef<A>,\n): RefWriter<A> {\n return {\n set: (value) => {\n runFork(runtime)(SubscriptionRef.set(ref, value));\n },\n update: (f) => {\n runFork(runtime)(SubscriptionRef.update(ref, f));\n },\n };\n}\n"],"mappings":";;;;;;;AAuBA,SAAgB,UACd,SACA,KACc;CACd,OAAO;EACL,MAAM,UAAU;GACd,QAAQ,OAAO,EAAE,gBAAgB,IAAI,KAAK,KAAK,CAAC;EAClD;EACA,SAAS,MAAM;GACb,QAAQ,OAAO,EAAE,gBAAgB,OAAO,KAAK,CAAC,CAAC;EACjD;CACF;AACF"}
1
+ {"version":3,"file":"writable.js","names":[],"sources":["../../src/internal/writable.ts"],"sourcesContent":["import { SubscriptionRef } from \"effect\";\nimport { runFork, type RuntimeLike } from \"./run.js\";\n\n/**\n * The imperative write half of a two-way `SubscriptionRef` binding, factored\n * out of the rune wrapper so the write semantics can be unit-tested without a\n * Svelte component context.\n *\n * Both `set` and `update` fork the write onto the runtime and return\n * immediately — the value the caller sees update is driven by the ref's\n * `changes` stream (or, in the rune wrapper, an optimistic local assignment).\n */\nexport interface RefWriter<A> {\n /** Replace the ref's value. */\n readonly set: (value: A) => void;\n /** Apply a function to the ref's current value. */\n readonly update: (f: (current: A) => A) => void;\n}\n\n/**\n * Build a {@link RefWriter} for a `SubscriptionRef`, running writes on the\n * given runtime.\n */\nexport function refWriter<A, R = never>(\n runtime: RuntimeLike<R>,\n ref: SubscriptionRef.SubscriptionRef<A>,\n): RefWriter<A> {\n return {\n set: (value) => {\n runFork(runtime)(SubscriptionRef.set(ref, value));\n },\n update: (f) => {\n runFork(runtime)(SubscriptionRef.update(ref, f));\n },\n };\n}\n"],"mappings":";;;;;;;AAuBA,SAAgB,UACd,SACA,KACc;CACd,OAAO;EACL,MAAM,UAAU;GACd,QAAQ,OAAO,EAAE,gBAAgB,IAAI,KAAK,KAAK,CAAC;EAClD;EACA,SAAS,MAAM;GACb,QAAQ,OAAO,EAAE,gBAAgB,OAAO,KAAK,CAAC,CAAC;EACjD;CACF;AACF"}
@@ -0,0 +1,72 @@
1
+ import { RuntimeLike } from "./internal/run.js";
2
+ import { Stream } from "effect";
3
+ import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
4
+
5
+ //#region src/live-stream.svelte.d.ts
6
+ interface UseLiveStreamOptions<A, R> {
7
+ /**
8
+ * Run against this runtime instead of the ambient context. When provided, the
9
+ * stream's `R` is constrained to what the runtime provides (a service-typed
10
+ * `ManagedRuntime` gives a fully-checked `R`).
11
+ */
12
+ readonly runtime?: RuntimeLike<R>;
13
+ /**
14
+ * A synchronous seed value. When provided, `current` starts as a non-waiting
15
+ * `Success(initial)` (and `promise` starts already-resolved) instead of
16
+ * `Initial`, so server renders and the first client render show the data the
17
+ * server already had — the first real emission then replaces it. This is the
18
+ * SSR seam: without a synchronous seed, SSR output and the first client render
19
+ * would show empty state and hydration would mismatch.
20
+ */
21
+ readonly initial?: A;
22
+ }
23
+ interface UseLiveStreamReturn<A, E> {
24
+ /**
25
+ * The latest emission as an {@link AsyncResult.AsyncResult}. Each emission
26
+ * replaces the previous value (never appends). While re-subscribing after a
27
+ * reactive-input change the previous value stays visible as a waiting
28
+ * `Success` (stale-while-revalidate). Held in `$state.raw`, so a large array's
29
+ * reference identity is preserved for downstream dedup.
30
+ */
31
+ readonly current: AsyncResult.AsyncResult<A, E>;
32
+ /**
33
+ * A reactive `Promise` reflecting `current`, for `{#await}`-free async
34
+ * templates (`experimental.async` + `<svelte:boundary>`). Pending until the
35
+ * first emission, then replaced with an already-resolved promise on every
36
+ * subsequent emission (no pending flash after first load); failures reject it.
37
+ * With `initial` it starts already-resolved, so seeded pages never suspend
38
+ * during SSR.
39
+ */
40
+ readonly promise: Promise<A>;
41
+ }
42
+ /**
43
+ * Subscribe to a live `Stream` and track its latest emission, re-subscribing
44
+ * whenever the reactive inputs it reads change.
45
+ *
46
+ * The `stream` thunk is called inside `$effect`, so any reactive state it reads
47
+ * (e.g. a pagination cursor in `$state`) is dependency-tracked; when it changes,
48
+ * the previous subscription is interrupted and a new one starts, keeping the
49
+ * previous value visible as a waiting `Success` (stale-while-revalidate). This
50
+ * is distinct from {@link useStream} (accumulates a static stream) and
51
+ * `reactiveStream` (re-runs an *Effect* on Reactivity-key invalidation).
52
+ *
53
+ * @example
54
+ * ```svelte
55
+ * <script lang="ts">
56
+ * import { useLiveStream } from '@thomasfosterau/effect-svelte';
57
+ *
58
+ * let cursor = $state(0);
59
+ *
60
+ * // Re-subscribes whenever `cursor` changes.
61
+ * const todos = useLiveStream(() => liveTodos(cursor), { initial: seededTodos });
62
+ * </script>
63
+ *
64
+ * <svelte:boundary>
65
+ * <ul>{#each await todos.promise as todo}<li>{todo.title}</li>{/each}</ul>
66
+ * </svelte:boundary>
67
+ * ```
68
+ */
69
+ declare function useLiveStream<A, E, R>(stream: () => Stream.Stream<A, E, R>, options?: UseLiveStreamOptions<A, R>): UseLiveStreamReturn<A, E>;
70
+ //#endregion
71
+ export { UseLiveStreamOptions, UseLiveStreamReturn, useLiveStream };
72
+ //# sourceMappingURL=live-stream.svelte.d.ts.map
@@ -0,0 +1,77 @@
1
+ import { getRuntimeContext } from "./context.svelte.js";
2
+ import { resultToPromise } from "./internal/await.js";
3
+ import { makeLiveStream } from "./internal/live-stream.js";
4
+ import { Option } from "effect";
5
+ import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
6
+ //#region src/live-stream.svelte.ts
7
+ /**
8
+ * A rune-backed {@link LiveResultState}. Holds the result in `$state.raw`:
9
+ * emissions can be large arrays whose reference equality is meaningful
10
+ * downstream, so deep proxying would break dedup and waste work.
11
+ */
12
+ function makeLiveResult(seed) {
13
+ let current = $state.raw(Option.isSome(seed) ? AsyncResult.success(seed.value) : AsyncResult.initial());
14
+ return {
15
+ get current() {
16
+ return current;
17
+ },
18
+ startWaiting() {
19
+ current = AsyncResult.waiting(current);
20
+ },
21
+ emit(value) {
22
+ current = AsyncResult.success(value);
23
+ },
24
+ failCause(cause) {
25
+ current = AsyncResult.failureWithPrevious(cause, { previous: Option.some(current) });
26
+ }
27
+ };
28
+ }
29
+ /**
30
+ * Subscribe to a live `Stream` and track its latest emission, re-subscribing
31
+ * whenever the reactive inputs it reads change.
32
+ *
33
+ * The `stream` thunk is called inside `$effect`, so any reactive state it reads
34
+ * (e.g. a pagination cursor in `$state`) is dependency-tracked; when it changes,
35
+ * the previous subscription is interrupted and a new one starts, keeping the
36
+ * previous value visible as a waiting `Success` (stale-while-revalidate). This
37
+ * is distinct from {@link useStream} (accumulates a static stream) and
38
+ * `reactiveStream` (re-runs an *Effect* on Reactivity-key invalidation).
39
+ *
40
+ * @example
41
+ * ```svelte
42
+ * <script lang="ts">
43
+ * import { useLiveStream } from '@thomasfosterau/effect-svelte';
44
+ *
45
+ * let cursor = $state(0);
46
+ *
47
+ * // Re-subscribes whenever `cursor` changes.
48
+ * const todos = useLiveStream(() => liveTodos(cursor), { initial: seededTodos });
49
+ * <\/script>
50
+ *
51
+ * <svelte:boundary>
52
+ * <ul>{#each await todos.promise as todo}<li>{todo.title}</li>{/each}</ul>
53
+ * </svelte:boundary>
54
+ * ```
55
+ */
56
+ function useLiveStream(stream, options = {}) {
57
+ const runtime = options.runtime ?? getRuntimeContext();
58
+ const state = makeLiveResult("initial" in options ? Option.some(options.initial) : Option.none());
59
+ const core = makeLiveStream(runtime, state);
60
+ $effect(() => {
61
+ const current = stream();
62
+ return core.subscribe(current);
63
+ });
64
+ const promise = $derived(resultToPromise(state.current));
65
+ return {
66
+ get current() {
67
+ return state.current;
68
+ },
69
+ get promise() {
70
+ return promise;
71
+ }
72
+ };
73
+ }
74
+ //#endregion
75
+ export { useLiveStream };
76
+
77
+ //# sourceMappingURL=live-stream.svelte.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-stream.svelte.js","names":["getRuntime"],"sources":["../src/live-stream.svelte.ts"],"sourcesContent":["import { Option, type Stream } from \"effect\";\nimport * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { resultToPromise } from \"./internal/await.js\";\nimport { makeLiveStream, type LiveResultState } from \"./internal/live-stream.js\";\nimport type { RuntimeLike } from \"./internal/run.js\";\n\nexport interface UseLiveStreamOptions<A, R> {\n /**\n * Run against this runtime instead of the ambient context. When provided, the\n * stream's `R` is constrained to what the runtime provides (a service-typed\n * `ManagedRuntime` gives a fully-checked `R`).\n */\n readonly runtime?: RuntimeLike<R>;\n\n /**\n * A synchronous seed value. When provided, `current` starts as a non-waiting\n * `Success(initial)` (and `promise` starts already-resolved) instead of\n * `Initial`, so server renders and the first client render show the data the\n * server already had — the first real emission then replaces it. This is the\n * SSR seam: without a synchronous seed, SSR output and the first client render\n * would show empty state and hydration would mismatch.\n */\n readonly initial?: A;\n}\n\nexport interface UseLiveStreamReturn<A, E> {\n /**\n * The latest emission as an {@link AsyncResult.AsyncResult}. Each emission\n * replaces the previous value (never appends). While re-subscribing after a\n * reactive-input change the previous value stays visible as a waiting\n * `Success` (stale-while-revalidate). Held in `$state.raw`, so a large array's\n * reference identity is preserved for downstream dedup.\n */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /**\n * A reactive `Promise` reflecting `current`, for `{#await}`-free async\n * templates (`experimental.async` + `<svelte:boundary>`). Pending until the\n * first emission, then replaced with an already-resolved promise on every\n * subsequent emission (no pending flash after first load); failures reject it.\n * With `initial` it starts already-resolved, so seeded pages never suspend\n * during SSR.\n */\n readonly promise: Promise<A>;\n}\n\n/**\n * A rune-backed {@link LiveResultState}. Holds the result in `$state.raw`:\n * emissions can be large arrays whose reference equality is meaningful\n * downstream, so deep proxying would break dedup and waste work.\n */\nfunction makeLiveResult<A, E>(seed: Option.Option<A>): LiveResultState<A, E> {\n let current = $state.raw<AsyncResult.AsyncResult<A, E>>(\n Option.isSome(seed) ? AsyncResult.success<A, E>(seed.value) : AsyncResult.initial<A, E>(),\n );\n\n return {\n get current() {\n return current;\n },\n startWaiting() {\n current = AsyncResult.waiting(current);\n },\n emit(value) {\n current = AsyncResult.success<A, E>(value);\n },\n failCause(cause) {\n current = AsyncResult.failureWithPrevious<A, E>(cause, { previous: Option.some(current) });\n },\n };\n}\n\n/**\n * Subscribe to a live `Stream` and track its latest emission, re-subscribing\n * whenever the reactive inputs it reads change.\n *\n * The `stream` thunk is called inside `$effect`, so any reactive state it reads\n * (e.g. a pagination cursor in `$state`) is dependency-tracked; when it changes,\n * the previous subscription is interrupted and a new one starts, keeping the\n * previous value visible as a waiting `Success` (stale-while-revalidate). This\n * is distinct from {@link useStream} (accumulates a static stream) and\n * `reactiveStream` (re-runs an *Effect* on Reactivity-key invalidation).\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useLiveStream } from '@thomasfosterau/effect-svelte';\n *\n * let cursor = $state(0);\n *\n * // Re-subscribes whenever `cursor` changes.\n * const todos = useLiveStream(() => liveTodos(cursor), { initial: seededTodos });\n * </script>\n *\n * <svelte:boundary>\n * <ul>{#each await todos.promise as todo}<li>{todo.title}</li>{/each}</ul>\n * </svelte:boundary>\n * ```\n */\nexport function useLiveStream<A, E, R>(\n stream: () => Stream.Stream<A, E, R>,\n options: UseLiveStreamOptions<A, R> = {},\n): UseLiveStreamReturn<A, E> {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R>);\n const seed: Option.Option<A> =\n \"initial\" in options ? Option.some(options.initial as A) : Option.none();\n\n const state = makeLiveResult<A, E>(seed);\n const core = makeLiveStream(runtime, state);\n\n $effect(() => {\n // Calling the thunk inside `$effect` tracks the reactive inputs it reads;\n // when they change, the effect re-runs — tearing down the old subscription\n // (interrupt, no Failure) and starting a fresh one.\n const current = stream();\n return core.subscribe(current);\n });\n\n // A new promise identity is produced only when the underlying result\n // transitions, so awaited template expressions re-run with fresh data instead\n // of on every unrelated re-render.\n const promise = $derived(resultToPromise(state.current));\n\n return {\n get current() {\n return state.current;\n },\n get promise() {\n return promise;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAoDA,SAAS,eAAqB,MAA+C;CAC3E,IAAI,UAAU,OAAO,IACnB,OAAO,OAAO,IAAI,IAAI,YAAY,QAAc,KAAK,KAAK,IAAI,YAAY,QAAc,CAC1F;CAEA,OAAO;EACL,IAAI,UAAU;GACZ,OAAO;EACT;EACA,eAAe;GACb,UAAU,YAAY,QAAQ,OAAO;EACvC;EACA,KAAK,OAAO;GACV,UAAU,YAAY,QAAc,KAAK;EAC3C;EACA,UAAU,OAAO;GACf,UAAU,YAAY,oBAA0B,OAAO,EAAE,UAAU,OAAO,KAAK,OAAO,EAAE,CAAC;EAC3F;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAgB,cACd,QACA,UAAsC,CAAC,GACZ;CAC3B,MAAM,UAAU,QAAQ,WAAYA,kBAAW;CAI/C,MAAM,QAAQ,eAFZ,aAAa,UAAU,OAAO,KAAK,QAAQ,OAAY,IAAI,OAAO,KAAK,CAElC;CACvC,MAAM,OAAO,eAAe,SAAS,KAAK;CAE1C,cAAc;EAIZ,MAAM,UAAU,OAAO;EACvB,OAAO,KAAK,UAAU,OAAO;CAC/B,CAAC;CAKD,MAAM,UAAU,SAAS,gBAAgB,MAAM,OAAO,CAAC;CAEvD,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA,IAAI,UAAU;GACZ,OAAO;EACT;CACF;AACF"}
@@ -1,8 +1,20 @@
1
+ import { RuntimeLike } from "./internal/run.js";
1
2
  import { MutationCallbacks } from "./internal/mutation.js";
2
3
  import { Effect } from "effect";
3
4
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
4
5
 
5
6
  //#region src/mutation.svelte.d.ts
7
+ /**
8
+ * Options for {@link useMutation}: the lifecycle {@link MutationCallbacks} plus
9
+ * an optional explicit `runtime`.
10
+ */
11
+ type UseMutationOptions<A, E, I, R, C> = MutationCallbacks<A, E, I, C> & {
12
+ /**
13
+ * Run against this runtime instead of the ambient context. When provided, the
14
+ * mutation's `R` is constrained to what the runtime provides.
15
+ */
16
+ readonly runtime?: RuntimeLike<R>;
17
+ };
6
18
  interface UseMutationReturn<A, E, I> {
7
19
  /**
8
20
  * The current result state of the mutation. Starts `Initial`; each `mutate`
@@ -47,7 +59,7 @@ interface UseMutationReturn<A, E, I> {
47
59
  * {/if}
48
60
  * ```
49
61
  */
50
- declare function useMutation<A, E, I, R, C = unknown>(fn: (input: I) => Effect.Effect<A, E, R>, options?: MutationCallbacks<A, E, I, C>): UseMutationReturn<A, E, I>;
62
+ declare function useMutation<A, E, I, R, C = unknown>(fn: (input: I) => Effect.Effect<A, E, R>, options?: UseMutationOptions<A, E, I, R, C>): UseMutationReturn<A, E, I>;
51
63
  //#endregion
52
- export { UseMutationReturn, useMutation };
64
+ export { UseMutationOptions, UseMutationReturn, useMutation };
53
65
  //# sourceMappingURL=mutation.svelte.d.ts.map
@@ -35,7 +35,7 @@ import { makeMutation } from "./internal/mutation.js";
35
35
  * ```
36
36
  */
37
37
  function useMutation(fn, options = {}) {
38
- const runtime = getRuntimeContext();
38
+ const runtime = options.runtime ?? getRuntimeContext();
39
39
  const state = makeResult();
40
40
  const core = makeMutation(runtime, state, fn, options);
41
41
  $effect(() => () => core.interrupt());
@@ -1 +1 @@
1
- {"version":3,"file":"mutation.svelte.js","names":["getRuntime"],"sources":["../src/mutation.svelte.ts"],"sourcesContent":["import type { Effect } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { makeMutation, type MutationCallbacks } from \"./internal/mutation.js\";\nimport type { RuntimeLike } from \"./internal/run.js\";\nimport { makeResult } from \"./internal/result.svelte.js\";\n\nexport type { MutationCallbacks } from \"./internal/mutation.js\";\n\nexport interface UseMutationReturn<A, E, I> {\n /**\n * The current result state of the mutation. Starts `Initial`; each `mutate`\n * marks it waiting (preserving the previous value) and settles to\n * `Success` / `Failure`.\n */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /** Run the mutation with `input`. A new call interrupts any in-flight run. */\n readonly mutate: (input: I) => void;\n\n /** Interrupt any in-flight run and reset `current` back to `Initial`. */\n readonly reset: () => void;\n}\n\n/**\n * A TanStack-Query-shaped mutation hook: run an effectful `fn(input)` on\n * demand, track its {@link AsyncResult.AsyncResult}, and fire lifecycle\n * callbacks.\n *\n * Optimistic updates follow the TanStack model — apply the change in `onMutate`\n * and return a snapshot as `context`, then roll back in `onError`. `invalidates`\n * re-runs any reactive queries depending on the given keys after success (this\n * requires the runtime to include `Reactivity.layer`).\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useMutation, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const addTodo = useMutation(\n * (title: string) => saveTodo(title),\n * {\n * invalidates: ['todos'],\n * onSuccess: (todo) => console.log('added', todo.id),\n * },\n * );\n * </script>\n *\n * <button onclick={() => addTodo.mutate('Buy milk')}>Add</button>\n *\n * {#if AsyncResult.isWaiting(addTodo.current)}\n * <p>Saving…</p>\n * {/if}\n * ```\n */\nexport function useMutation<A, E, I, R, C = unknown>(\n fn: (input: I) => Effect.Effect<A, E, R>,\n options: MutationCallbacks<A, E, I, C> = {},\n): UseMutationReturn<A, E, I> {\n const runtime = getRuntime() as RuntimeLike<R>;\n const state = makeResult<A, E>();\n const core = makeMutation(runtime, state, fn, options);\n\n // Interrupt any in-flight mutation when the component unmounts.\n $effect(() => () => core.interrupt());\n\n return {\n get current() {\n return state.current;\n },\n mutate: core.mutate,\n reset: core.reset,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,SAAgB,YACd,IACA,UAAyC,CAAC,GACd;CAC5B,MAAM,UAAUA,kBAAW;CAC3B,MAAM,QAAQ,WAAiB;CAC/B,MAAM,OAAO,aAAa,SAAS,OAAO,IAAI,OAAO;CAGrD,oBAAoB,KAAK,UAAU,CAAC;CAEpC,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA,QAAQ,KAAK;EACb,OAAO,KAAK;CACd;AACF"}
1
+ {"version":3,"file":"mutation.svelte.js","names":["getRuntime"],"sources":["../src/mutation.svelte.ts"],"sourcesContent":["import type { Effect } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { makeMutation, type MutationCallbacks } from \"./internal/mutation.js\";\nimport type { RuntimeLike } from \"./internal/run.js\";\nimport { makeResult } from \"./internal/result.svelte.js\";\n\nexport type { MutationCallbacks } from \"./internal/mutation.js\";\n\n/**\n * Options for {@link useMutation}: the lifecycle {@link MutationCallbacks} plus\n * an optional explicit `runtime`.\n */\nexport type UseMutationOptions<A, E, I, R, C> = MutationCallbacks<A, E, I, C> & {\n /**\n * Run against this runtime instead of the ambient context. When provided, the\n * mutation's `R` is constrained to what the runtime provides.\n */\n readonly runtime?: RuntimeLike<R>;\n};\n\nexport interface UseMutationReturn<A, E, I> {\n /**\n * The current result state of the mutation. Starts `Initial`; each `mutate`\n * marks it waiting (preserving the previous value) and settles to\n * `Success` / `Failure`.\n */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /** Run the mutation with `input`. A new call interrupts any in-flight run. */\n readonly mutate: (input: I) => void;\n\n /** Interrupt any in-flight run and reset `current` back to `Initial`. */\n readonly reset: () => void;\n}\n\n/**\n * A TanStack-Query-shaped mutation hook: run an effectful `fn(input)` on\n * demand, track its {@link AsyncResult.AsyncResult}, and fire lifecycle\n * callbacks.\n *\n * Optimistic updates follow the TanStack model — apply the change in `onMutate`\n * and return a snapshot as `context`, then roll back in `onError`. `invalidates`\n * re-runs any reactive queries depending on the given keys after success (this\n * requires the runtime to include `Reactivity.layer`).\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useMutation, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const addTodo = useMutation(\n * (title: string) => saveTodo(title),\n * {\n * invalidates: ['todos'],\n * onSuccess: (todo) => console.log('added', todo.id),\n * },\n * );\n * </script>\n *\n * <button onclick={() => addTodo.mutate('Buy milk')}>Add</button>\n *\n * {#if AsyncResult.isWaiting(addTodo.current)}\n * <p>Saving…</p>\n * {/if}\n * ```\n */\nexport function useMutation<A, E, I, R, C = unknown>(\n fn: (input: I) => Effect.Effect<A, E, R>,\n options: UseMutationOptions<A, E, I, R, C> = {},\n): UseMutationReturn<A, E, I> {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R>);\n const state = makeResult<A, E>();\n const core = makeMutation(runtime, state, fn, options);\n\n // Interrupt any in-flight mutation when the component unmounts.\n $effect(() => () => core.interrupt());\n\n return {\n get current() {\n return state.current;\n },\n mutate: core.mutate,\n reset: core.reset,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoEA,SAAgB,YACd,IACA,UAA6C,CAAC,GAClB;CAC5B,MAAM,UAAU,QAAQ,WAAYA,kBAAW;CAC/C,MAAM,QAAQ,WAAiB;CAC/B,MAAM,OAAO,aAAa,SAAS,OAAO,IAAI,OAAO;CAGrD,oBAAoB,KAAK,UAAU,CAAC;CAEpC,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA,QAAQ,KAAK;EACb,OAAO,KAAK;CACd;AACF"}
@@ -1,7 +1,15 @@
1
+ import { RuntimeLike } from "./internal/run.js";
1
2
  import { Effect } from "effect";
2
3
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
3
4
 
4
5
  //#region src/query.svelte.d.ts
6
+ interface UseQueryOptions<R> {
7
+ /**
8
+ * Run against this runtime instead of the ambient context. When provided, the
9
+ * effect's `R` is constrained to what the runtime provides.
10
+ */
11
+ readonly runtime?: RuntimeLike<R>;
12
+ }
5
13
  interface UseQueryReturn<A, E> {
6
14
  /**
7
15
  * The current result state of the query. Refetching preserves the previous
@@ -50,7 +58,7 @@ interface UseQueryReturn<A, E> {
50
58
  * {/if}
51
59
  * ```
52
60
  */
53
- declare function useQuery<A, E, R>(effect: Effect.Effect<A, E, R>): UseQueryReturn<A, E>;
61
+ declare function useQuery<A, E, R>(effect: Effect.Effect<A, E, R>, options?: UseQueryOptions<R>): UseQueryReturn<A, E>;
54
62
  //#endregion
55
- export { UseQueryReturn, useQuery };
63
+ export { UseQueryOptions, UseQueryReturn, useQuery };
56
64
  //# sourceMappingURL=query.svelte.d.ts.map
@@ -26,8 +26,11 @@ import { useEffect } from "./effect.svelte.js";
26
26
  * {/if}
27
27
  * ```
28
28
  */
29
- function useQuery(effect) {
30
- const effectHook = useEffect(effect, { immediate: true });
29
+ function useQuery(effect, options = {}) {
30
+ const effectHook = useEffect(effect, {
31
+ immediate: true,
32
+ ...options
33
+ });
31
34
  return {
32
35
  get current() {
33
36
  return effectHook.current;
@@ -1 +1 @@
1
- {"version":3,"file":"query.svelte.js","names":[],"sources":["../src/query.svelte.ts"],"sourcesContent":["import type { Effect } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { useEffect } from \"./effect.svelte.js\";\n\nexport interface UseQueryReturn<A, E> {\n /**\n * The current result state of the query. Refetching preserves the previous\n * value as a waiting `Success` (stale-while-revalidate).\n */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /**\n * Re-fetch the data by re-running the effect\n */\n refetch: () => void;\n\n /**\n * Optimistically overwrite the query's data locally without refetching.\n * Accepts a value or an updater over the current value (`undefined` if none\n * yet). The next `refetch` reconciles it with the server.\n */\n setData: (updater: A | ((previous: A | undefined) => A)) => void;\n\n /**\n * `true` while showing a previous value during a refetch\n * (stale-while-revalidate).\n */\n readonly isStale: boolean;\n}\n\n/**\n * Query pattern with refetch capability.\n * Runs the effect immediately on mount and provides a refetch function.\n * Built on top of useEffect with immediate: true.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useQuery, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const query = useQuery(\n * Effect.gen(function* () {\n * const data = yield* fetchData();\n * return data;\n * })\n * );\n * </script>\n *\n * <button onclick={query.refetch}>Refresh</button>\n *\n * {#if AsyncResult.isSuccess(query.current)}\n * <p>Data: {JSON.stringify(query.current.value)}</p>\n * {/if}\n * ```\n */\nexport function useQuery<A, E, R>(effect: Effect.Effect<A, E, R>): UseQueryReturn<A, E> {\n const effectHook = useEffect(effect, { immediate: true });\n\n return {\n get current() {\n return effectHook.current;\n },\n refetch: effectHook.run,\n setData: effectHook.setData,\n get isStale() {\n return effectHook.isStale;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,SAAgB,SAAkB,QAAsD;CACtF,MAAM,aAAa,UAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;CAExD,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,WAAW;EACpB;EACA,SAAS,WAAW;EACpB,SAAS,WAAW;EACpB,IAAI,UAAU;GACZ,OAAO,WAAW;EACpB;CACF;AACF"}
1
+ {"version":3,"file":"query.svelte.js","names":[],"sources":["../src/query.svelte.ts"],"sourcesContent":["import type { Effect } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { useEffect } from \"./effect.svelte.js\";\nimport type { RuntimeLike } from \"./internal/run.js\";\n\nexport interface UseQueryOptions<R> {\n /**\n * Run against this runtime instead of the ambient context. When provided, the\n * effect's `R` is constrained to what the runtime provides.\n */\n readonly runtime?: RuntimeLike<R>;\n}\n\nexport interface UseQueryReturn<A, E> {\n /**\n * The current result state of the query. Refetching preserves the previous\n * value as a waiting `Success` (stale-while-revalidate).\n */\n readonly current: AsyncResult.AsyncResult<A, E>;\n\n /**\n * Re-fetch the data by re-running the effect\n */\n refetch: () => void;\n\n /**\n * Optimistically overwrite the query's data locally without refetching.\n * Accepts a value or an updater over the current value (`undefined` if none\n * yet). The next `refetch` reconciles it with the server.\n */\n setData: (updater: A | ((previous: A | undefined) => A)) => void;\n\n /**\n * `true` while showing a previous value during a refetch\n * (stale-while-revalidate).\n */\n readonly isStale: boolean;\n}\n\n/**\n * Query pattern with refetch capability.\n * Runs the effect immediately on mount and provides a refetch function.\n * Built on top of useEffect with immediate: true.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useQuery, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const query = useQuery(\n * Effect.gen(function* () {\n * const data = yield* fetchData();\n * return data;\n * })\n * );\n * </script>\n *\n * <button onclick={query.refetch}>Refresh</button>\n *\n * {#if AsyncResult.isSuccess(query.current)}\n * <p>Data: {JSON.stringify(query.current.value)}</p>\n * {/if}\n * ```\n */\nexport function useQuery<A, E, R>(\n effect: Effect.Effect<A, E, R>,\n options: UseQueryOptions<R> = {},\n): UseQueryReturn<A, E> {\n const effectHook = useEffect(effect, { immediate: true, ...options });\n\n return {\n get current() {\n return effectHook.current;\n },\n refetch: effectHook.run,\n setData: effectHook.setData,\n get isStale() {\n return effectHook.isStale;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiEA,SAAgB,SACd,QACA,UAA8B,CAAC,GACT;CACtB,MAAM,aAAa,UAAU,QAAQ;EAAE,WAAW;EAAM,GAAG;CAAQ,CAAC;CAEpE,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,WAAW;EACpB;EACA,SAAS,WAAW;EACpB,SAAS,WAAW;EACpB,IAAI,UAAU;GACZ,OAAO,WAAW;EACpB;CACF;AACF"}
@@ -1,11 +1,21 @@
1
+ import { RuntimeLike } from "./internal/run.js";
1
2
  import { Effect } from "effect";
2
3
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
4
+ import { Reactivity } from "effect/unstable/reactivity";
3
5
 
4
6
  //#region src/reactivity.svelte.d.ts
5
7
  interface ReactiveQueryReturn<A, E> {
6
8
  /** The current result, re-derived whenever the keys are invalidated. */
7
9
  readonly current: AsyncResult.AsyncResult<A, E>;
8
10
  }
11
+ interface ReactiveOptions<R> {
12
+ /**
13
+ * Run against this runtime instead of the ambient context. Must still provide
14
+ * `Reactivity.Reactivity`; when provided, the effect's `R` is otherwise
15
+ * constrained to what the runtime provides.
16
+ */
17
+ readonly runtime?: RuntimeLike<R | Reactivity.Reactivity>;
18
+ }
9
19
  /**
10
20
  * Creates a reactive query that automatically re-runs when the specified keys are invalidated.
11
21
  *
@@ -37,7 +47,7 @@ interface ReactiveQueryReturn<A, E> {
37
47
  * {/if}
38
48
  * ```
39
49
  */
40
- declare function reactiveQuery<A, E, R>(keys: () => ReadonlyArray<unknown>, effectFn: () => Effect.Effect<A, E, R>): ReactiveQueryReturn<A, E>;
50
+ declare function reactiveQuery<A, E, R>(keys: () => ReadonlyArray<unknown>, effectFn: () => Effect.Effect<A, E, R>, options?: ReactiveOptions<R>): ReactiveQueryReturn<A, E>;
41
51
  interface ReactiveStreamReturn<A, E> {
42
52
  readonly current: AsyncResult.AsyncResult<ReadonlyArray<A>, E>;
43
53
  readonly values: ReadonlyArray<A>;
@@ -72,7 +82,7 @@ interface ReactiveStreamReturn<A, E> {
72
82
  * </ul>
73
83
  * ```
74
84
  */
75
- declare function reactiveStream<A, E, R>(keys: ReadonlyArray<unknown> | (() => ReadonlyArray<unknown>), effect: Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>)): ReactiveStreamReturn<A, E>;
85
+ declare function reactiveStream<A, E, R>(keys: ReadonlyArray<unknown> | (() => ReadonlyArray<unknown>), effect: Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>), options?: ReactiveOptions<R>): ReactiveStreamReturn<A, E>;
76
86
  interface ReactiveMutationReturn<A, E> {
77
87
  readonly current: AsyncResult.AsyncResult<A, E>;
78
88
  run: () => void;
@@ -101,7 +111,7 @@ interface ReactiveMutationReturn<A, E> {
101
111
  * <button onclick={() => updateUser.run()}>Update</button>
102
112
  * ```
103
113
  */
104
- declare function reactiveMutation<A, E, R>(keys: ReadonlyArray<unknown> | (() => ReadonlyArray<unknown>), effect: Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>)): ReactiveMutationReturn<A, E>;
114
+ declare function reactiveMutation<A, E, R>(keys: ReadonlyArray<unknown> | (() => ReadonlyArray<unknown>), effect: Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>), options?: ReactiveOptions<R>): ReactiveMutationReturn<A, E>;
105
115
  /**
106
116
  * Returns a function that manually invalidates reactivity keys, causing any
107
117
  * reactive queries depending on those keys to re-run.
@@ -117,7 +127,7 @@ declare function reactiveMutation<A, E, R>(keys: ReadonlyArray<unknown> | (() =>
117
127
  * <button onclick={() => invalidate(['users'])}>Refresh Users</button>
118
128
  * ```
119
129
  */
120
- declare function useInvalidateKeys(): (keys: ReadonlyArray<unknown>) => void;
130
+ declare function useInvalidateKeys(options?: ReactiveOptions<never>): (keys: ReadonlyArray<unknown>) => void;
121
131
  //#endregion
122
- export { ReactiveMutationReturn, ReactiveQueryReturn, ReactiveStreamReturn, reactiveMutation, reactiveQuery, reactiveStream, useInvalidateKeys };
132
+ export { ReactiveMutationReturn, ReactiveOptions, ReactiveQueryReturn, ReactiveStreamReturn, reactiveMutation, reactiveQuery, reactiveStream, useInvalidateKeys };
123
133
  //# sourceMappingURL=reactivity.svelte.d.ts.map
@@ -36,8 +36,8 @@ import { Reactivity } from "effect/unstable/reactivity";
36
36
  * {/if}
37
37
  * ```
38
38
  */
39
- function reactiveQuery(keys, effectFn) {
40
- const runtime = getRuntimeContext();
39
+ function reactiveQuery(keys, effectFn, options = {}) {
40
+ const runtime = options.runtime ?? getRuntimeContext();
41
41
  const state = makeResult();
42
42
  let fiber = null;
43
43
  $effect(() => {
@@ -93,8 +93,8 @@ function reactiveQuery(keys, effectFn) {
93
93
  * </ul>
94
94
  * ```
95
95
  */
96
- function reactiveStream(keys, effect) {
97
- return streamState(getRuntimeContext(), () => {
96
+ function reactiveStream(keys, effect, options = {}) {
97
+ return streamState(options.runtime ?? getRuntimeContext(), () => {
98
98
  const currentKeys = typeof keys === "function" ? keys() : keys;
99
99
  const currentEffect = typeof effect === "function" ? effect() : effect;
100
100
  return Reactivity.stream(currentEffect, currentKeys);
@@ -124,8 +124,8 @@ function reactiveStream(keys, effect) {
124
124
  * <button onclick={() => updateUser.run()}>Update</button>
125
125
  * ```
126
126
  */
127
- function reactiveMutation(keys, effect) {
128
- const runtime = getRuntimeContext();
127
+ function reactiveMutation(keys, effect, options = {}) {
128
+ const runtime = options.runtime ?? getRuntimeContext();
129
129
  const state = makeResult();
130
130
  let currentFiber = null;
131
131
  const run = () => {
@@ -174,8 +174,8 @@ function reactiveMutation(keys, effect) {
174
174
  * <button onclick={() => invalidate(['users'])}>Refresh Users</button>
175
175
  * ```
176
176
  */
177
- function useInvalidateKeys() {
178
- const runtime = getRuntimeContext();
177
+ function useInvalidateKeys(options = {}) {
178
+ const runtime = options.runtime ?? getRuntimeContext();
179
179
  return (keys) => {
180
180
  runSync(runtime)(Reactivity.invalidate(keys));
181
181
  };
@@ -1 +1 @@
1
- {"version":3,"file":"reactivity.svelte.js","names":["getRuntime"],"sources":["../src/reactivity.svelte.ts"],"sourcesContent":["import { Effect, Fiber, Stream } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { Reactivity } from \"effect/unstable/reactivity\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { interruptFiber, runFork, runSync, type RuntimeLike } from \"./internal/run.js\";\nimport { makeResult } from \"./internal/result.svelte.js\";\nimport { streamState } from \"./internal/subscribe.js\";\n\nexport interface ReactiveQueryReturn<A, E> {\n /** The current result, re-derived whenever the keys are invalidated. */\n readonly current: AsyncResult.AsyncResult<A, E>;\n}\n\n/**\n * Creates a reactive query that automatically re-runs when the specified keys are invalidated.\n *\n * Note: Make sure your runtime includes `Reactivity.layer` from `effect/unstable/reactivity`.\n *\n * @param keys - Reactivity keys that this query depends on\n * @param effectFn - The Effect to run when keys are invalidated\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { reactiveQuery, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * // Query that depends on a user ID key\n * let userId = $state(1);\n *\n * const user = reactiveQuery(\n * () => ['user', userId],\n * () => Effect.gen(function* () {\n * const data = yield* fetchUser(userId);\n * return data;\n * })\n * );\n * </script>\n *\n * {#if AsyncResult.isSuccess(user.current)}\n * <p>User: {user.current.value.name}</p>\n * {/if}\n * ```\n */\nexport function reactiveQuery<A, E, R>(\n keys: () => ReadonlyArray<unknown>,\n effectFn: () => Effect.Effect<A, E, R>,\n): ReactiveQueryReturn<A, E> {\n const runtime = getRuntime() as RuntimeLike<R | Reactivity.Reactivity>;\n\n const state = makeResult<A, E>();\n let fiber: Fiber.Fiber<void, never> | null = null;\n\n $effect(() => {\n // Get the current keys (tracks Svelte dependencies)\n const currentKeys = keys();\n const effect = effectFn();\n\n // Mark as waiting, preserving any previous value.\n state.startWaiting();\n\n // Run the reactive query inside a scope so that the registered\n // invalidation handlers are cleaned up when the fiber is interrupted.\n const queryEffect = Effect.scoped(\n Effect.gen(function* () {\n const queue = yield* Reactivity.query(effect, currentKeys);\n yield* Stream.runForEach(Stream.fromQueue(queue), (value) =>\n Effect.sync(() => {\n state.succeed(value);\n }),\n );\n }),\n ).pipe(\n Effect.catchCause((cause) =>\n Effect.sync(() => {\n state.failCause(cause);\n }),\n ),\n );\n\n const runningFiber = runFork(runtime)(queryEffect);\n fiber = runningFiber;\n\n // Cleanup: interrupt the fiber when dependencies change or component unmounts\n return () => {\n if (fiber !== null) {\n interruptFiber(fiber);\n fiber = null;\n }\n };\n });\n\n return {\n get current() {\n return state.current;\n },\n };\n}\n\nexport interface ReactiveStreamReturn<A, E> {\n readonly current: AsyncResult.AsyncResult<ReadonlyArray<A>, E>;\n readonly values: ReadonlyArray<A>;\n readonly latest: A | undefined;\n}\n\n/**\n * Creates a reactive stream that emits values whenever the specified keys are invalidated.\n * The effect re-runs automatically when keys change.\n *\n * @param keys - Reactivity keys that this stream depends on\n * @param effect - The Effect to run when keys are invalidated\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { reactiveStream } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const messages = reactiveStream(\n * ['messages'],\n * Effect.gen(function* () {\n * const data = yield* fetchMessages();\n * return data;\n * })\n * );\n * </script>\n *\n * <ul>\n * {#each messages.values as msg}\n * <li>{msg}</li>\n * {/each}\n * </ul>\n * ```\n */\nexport function reactiveStream<A, E, R>(\n keys: ReadonlyArray<unknown> | (() => ReadonlyArray<unknown>),\n effect: Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>),\n): ReactiveStreamReturn<A, E> {\n const runtime = getRuntime() as RuntimeLike<R | Reactivity.Reactivity>;\n return streamState(runtime, () => {\n const currentKeys = typeof keys === \"function\" ? keys() : keys;\n const currentEffect = typeof effect === \"function\" ? effect() : effect;\n return Reactivity.stream(currentEffect, currentKeys);\n });\n}\n\nexport interface ReactiveMutationReturn<A, E> {\n readonly current: AsyncResult.AsyncResult<A, E>;\n run: () => void;\n}\n\n/**\n * Runs a mutation effect and invalidates the specified keys.\n * This will cause any reactive queries depending on these keys to re-run.\n *\n * @param keys - Reactivity keys to invalidate after the mutation\n * @param effect - The mutation effect to run\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { reactiveMutation } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const updateUser = reactiveMutation(\n * ['user', userId],\n * Effect.gen(function* () {\n * yield* updateUserInDB(userId, data);\n * })\n * );\n * </script>\n *\n * <button onclick={() => updateUser.run()}>Update</button>\n * ```\n */\nexport function reactiveMutation<A, E, R>(\n keys: ReadonlyArray<unknown> | (() => ReadonlyArray<unknown>),\n effect: Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>),\n): ReactiveMutationReturn<A, E> {\n const runtime = getRuntime() as RuntimeLike<R | Reactivity.Reactivity>;\n\n const state = makeResult<A, E>();\n let currentFiber: Fiber.Fiber<A, E> | null = null;\n\n const run = () => {\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n }\n\n const currentKeys = typeof keys === \"function\" ? keys() : keys;\n const currentEffect = typeof effect === \"function\" ? effect() : effect;\n\n state.startWaiting();\n\n const mutationEffect = Reactivity.mutation(currentEffect, currentKeys);\n const runningFiber = runFork(runtime)(mutationEffect);\n currentFiber = runningFiber;\n\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\n $effect(() => {\n // Cleanup: interrupt any running fiber on unmount\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 run,\n };\n}\n\n/**\n * Returns a function that manually invalidates reactivity keys, causing any\n * reactive queries depending on those keys to re-run.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useInvalidateKeys } from '@thomasfosterau/effect-svelte';\n *\n * const invalidate = useInvalidateKeys();\n * </script>\n *\n * <button onclick={() => invalidate(['users'])}>Refresh Users</button>\n * ```\n */\nexport function useInvalidateKeys(): (keys: ReadonlyArray<unknown>) => void {\n const runtime = getRuntime() as RuntimeLike<Reactivity.Reactivity>;\n return (keys) => {\n runSync(runtime)(Reactivity.invalidate(keys));\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,SAAgB,cACd,MACA,UAC2B;CAC3B,MAAM,UAAUA,kBAAW;CAE3B,MAAM,QAAQ,WAAiB;CAC/B,IAAI,QAAyC;CAE7C,cAAc;EAEZ,MAAM,cAAc,KAAK;EACzB,MAAM,SAAS,SAAS;EAGxB,MAAM,aAAa;EAInB,MAAM,cAAc,OAAO,OACzB,OAAO,IAAI,aAAa;GACtB,MAAM,QAAQ,OAAO,WAAW,MAAM,QAAQ,WAAW;GACzD,OAAO,OAAO,WAAW,OAAO,UAAU,KAAK,IAAI,UACjD,OAAO,WAAW;IAChB,MAAM,QAAQ,KAAK;GACrB,CAAC,CACH;EACF,CAAC,CACH,EAAE,KACA,OAAO,YAAY,UACjB,OAAO,WAAW;GAChB,MAAM,UAAU,KAAK;EACvB,CAAC,CACH,CACF;EAGA,QADqB,QAAQ,OAAO,EAAE,WACnB;EAGnB,aAAa;GACX,IAAI,UAAU,MAAM;IAClB,eAAe,KAAK;IACpB,QAAQ;GACV;EACF;CACF,CAAC;CAED,OAAO,EACL,IAAI,UAAU;EACZ,OAAO,MAAM;CACf,EACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,eACd,MACA,QAC4B;CAE5B,OAAO,YADSA,kBACS,SAAS;EAChC,MAAM,cAAc,OAAO,SAAS,aAAa,KAAK,IAAI;EAC1D,MAAM,gBAAgB,OAAO,WAAW,aAAa,OAAO,IAAI;EAChE,OAAO,WAAW,OAAO,eAAe,WAAW;CACrD,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,iBACd,MACA,QAC8B;CAC9B,MAAM,UAAUA,kBAAW;CAE3B,MAAM,QAAQ,WAAiB;CAC/B,IAAI,eAAyC;CAE7C,MAAM,YAAY;EAChB,IAAI,iBAAiB,MACnB,eAAe,YAAY;EAG7B,MAAM,cAAc,OAAO,SAAS,aAAa,KAAK,IAAI;EAC1D,MAAM,gBAAgB,OAAO,WAAW,aAAa,OAAO,IAAI;EAEhE,MAAM,aAAa;EAEnB,MAAM,iBAAiB,WAAW,SAAS,eAAe,WAAW;EACrE,MAAM,eAAe,QAAQ,OAAO,EAAE,cAAc;EACpD,eAAe;EAEf,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;CACF;CAEA,cAAc;EAEZ,aAAa;GACX,IAAI,iBAAiB,MAAM;IACzB,eAAe,YAAY;IAC3B,eAAe;GACjB;EACF;CACF,CAAC;CAED,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA;CACF;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,oBAA4D;CAC1E,MAAM,UAAUA,kBAAW;CAC3B,QAAQ,SAAS;EACf,QAAQ,OAAO,EAAE,WAAW,WAAW,IAAI,CAAC;CAC9C;AACF"}
1
+ {"version":3,"file":"reactivity.svelte.js","names":["getRuntime"],"sources":["../src/reactivity.svelte.ts"],"sourcesContent":["import { Effect, Fiber, Stream } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { Reactivity } from \"effect/unstable/reactivity\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { interruptFiber, runFork, runSync, type RuntimeLike } from \"./internal/run.js\";\nimport { makeResult } from \"./internal/result.svelte.js\";\nimport { streamState } from \"./internal/subscribe.js\";\n\nexport interface ReactiveQueryReturn<A, E> {\n /** The current result, re-derived whenever the keys are invalidated. */\n readonly current: AsyncResult.AsyncResult<A, E>;\n}\n\nexport interface ReactiveOptions<R> {\n /**\n * Run against this runtime instead of the ambient context. Must still provide\n * `Reactivity.Reactivity`; when provided, the effect's `R` is otherwise\n * constrained to what the runtime provides.\n */\n readonly runtime?: RuntimeLike<R | Reactivity.Reactivity>;\n}\n\n/**\n * Creates a reactive query that automatically re-runs when the specified keys are invalidated.\n *\n * Note: Make sure your runtime includes `Reactivity.layer` from `effect/unstable/reactivity`.\n *\n * @param keys - Reactivity keys that this query depends on\n * @param effectFn - The Effect to run when keys are invalidated\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { reactiveQuery, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * // Query that depends on a user ID key\n * let userId = $state(1);\n *\n * const user = reactiveQuery(\n * () => ['user', userId],\n * () => Effect.gen(function* () {\n * const data = yield* fetchUser(userId);\n * return data;\n * })\n * );\n * </script>\n *\n * {#if AsyncResult.isSuccess(user.current)}\n * <p>User: {user.current.value.name}</p>\n * {/if}\n * ```\n */\nexport function reactiveQuery<A, E, R>(\n keys: () => ReadonlyArray<unknown>,\n effectFn: () => Effect.Effect<A, E, R>,\n options: ReactiveOptions<R> = {},\n): ReactiveQueryReturn<A, E> {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R | Reactivity.Reactivity>);\n\n const state = makeResult<A, E>();\n let fiber: Fiber.Fiber<void, never> | null = null;\n\n $effect(() => {\n // Get the current keys (tracks Svelte dependencies)\n const currentKeys = keys();\n const effect = effectFn();\n\n // Mark as waiting, preserving any previous value.\n state.startWaiting();\n\n // Run the reactive query inside a scope so that the registered\n // invalidation handlers are cleaned up when the fiber is interrupted.\n const queryEffect = Effect.scoped(\n Effect.gen(function* () {\n const queue = yield* Reactivity.query(effect, currentKeys);\n yield* Stream.runForEach(Stream.fromQueue(queue), (value) =>\n Effect.sync(() => {\n state.succeed(value);\n }),\n );\n }),\n ).pipe(\n Effect.catchCause((cause) =>\n Effect.sync(() => {\n state.failCause(cause);\n }),\n ),\n );\n\n const runningFiber = runFork(runtime)(queryEffect);\n fiber = runningFiber;\n\n // Cleanup: interrupt the fiber when dependencies change or component unmounts\n return () => {\n if (fiber !== null) {\n interruptFiber(fiber);\n fiber = null;\n }\n };\n });\n\n return {\n get current() {\n return state.current;\n },\n };\n}\n\nexport interface ReactiveStreamReturn<A, E> {\n readonly current: AsyncResult.AsyncResult<ReadonlyArray<A>, E>;\n readonly values: ReadonlyArray<A>;\n readonly latest: A | undefined;\n}\n\n/**\n * Creates a reactive stream that emits values whenever the specified keys are invalidated.\n * The effect re-runs automatically when keys change.\n *\n * @param keys - Reactivity keys that this stream depends on\n * @param effect - The Effect to run when keys are invalidated\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { reactiveStream } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const messages = reactiveStream(\n * ['messages'],\n * Effect.gen(function* () {\n * const data = yield* fetchMessages();\n * return data;\n * })\n * );\n * </script>\n *\n * <ul>\n * {#each messages.values as msg}\n * <li>{msg}</li>\n * {/each}\n * </ul>\n * ```\n */\nexport function reactiveStream<A, E, R>(\n keys: ReadonlyArray<unknown> | (() => ReadonlyArray<unknown>),\n effect: Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>),\n options: ReactiveOptions<R> = {},\n): ReactiveStreamReturn<A, E> {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R | Reactivity.Reactivity>);\n return streamState(runtime, () => {\n const currentKeys = typeof keys === \"function\" ? keys() : keys;\n const currentEffect = typeof effect === \"function\" ? effect() : effect;\n return Reactivity.stream(currentEffect, currentKeys);\n });\n}\n\nexport interface ReactiveMutationReturn<A, E> {\n readonly current: AsyncResult.AsyncResult<A, E>;\n run: () => void;\n}\n\n/**\n * Runs a mutation effect and invalidates the specified keys.\n * This will cause any reactive queries depending on these keys to re-run.\n *\n * @param keys - Reactivity keys to invalidate after the mutation\n * @param effect - The mutation effect to run\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { reactiveMutation } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * const updateUser = reactiveMutation(\n * ['user', userId],\n * Effect.gen(function* () {\n * yield* updateUserInDB(userId, data);\n * })\n * );\n * </script>\n *\n * <button onclick={() => updateUser.run()}>Update</button>\n * ```\n */\nexport function reactiveMutation<A, E, R>(\n keys: ReadonlyArray<unknown> | (() => ReadonlyArray<unknown>),\n effect: Effect.Effect<A, E, R> | (() => Effect.Effect<A, E, R>),\n options: ReactiveOptions<R> = {},\n): ReactiveMutationReturn<A, E> {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R | Reactivity.Reactivity>);\n\n const state = makeResult<A, E>();\n let currentFiber: Fiber.Fiber<A, E> | null = null;\n\n const run = () => {\n if (currentFiber !== null) {\n interruptFiber(currentFiber);\n }\n\n const currentKeys = typeof keys === \"function\" ? keys() : keys;\n const currentEffect = typeof effect === \"function\" ? effect() : effect;\n\n state.startWaiting();\n\n const mutationEffect = Reactivity.mutation(currentEffect, currentKeys);\n const runningFiber = runFork(runtime)(mutationEffect);\n currentFiber = runningFiber;\n\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\n $effect(() => {\n // Cleanup: interrupt any running fiber on unmount\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 run,\n };\n}\n\n/**\n * Returns a function that manually invalidates reactivity keys, causing any\n * reactive queries depending on those keys to re-run.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useInvalidateKeys } from '@thomasfosterau/effect-svelte';\n *\n * const invalidate = useInvalidateKeys();\n * </script>\n *\n * <button onclick={() => invalidate(['users'])}>Refresh Users</button>\n * ```\n */\nexport function useInvalidateKeys(\n options: ReactiveOptions<never> = {},\n): (keys: ReadonlyArray<unknown>) => void {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<Reactivity.Reactivity>);\n return (keys) => {\n runSync(runtime)(Reactivity.invalidate(keys));\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAgB,cACd,MACA,UACA,UAA8B,CAAC,GACJ;CAC3B,MAAM,UAAU,QAAQ,WAAYA,kBAAW;CAE/C,MAAM,QAAQ,WAAiB;CAC/B,IAAI,QAAyC;CAE7C,cAAc;EAEZ,MAAM,cAAc,KAAK;EACzB,MAAM,SAAS,SAAS;EAGxB,MAAM,aAAa;EAInB,MAAM,cAAc,OAAO,OACzB,OAAO,IAAI,aAAa;GACtB,MAAM,QAAQ,OAAO,WAAW,MAAM,QAAQ,WAAW;GACzD,OAAO,OAAO,WAAW,OAAO,UAAU,KAAK,IAAI,UACjD,OAAO,WAAW;IAChB,MAAM,QAAQ,KAAK;GACrB,CAAC,CACH;EACF,CAAC,CACH,EAAE,KACA,OAAO,YAAY,UACjB,OAAO,WAAW;GAChB,MAAM,UAAU,KAAK;EACvB,CAAC,CACH,CACF;EAGA,QADqB,QAAQ,OAAO,EAAE,WACnB;EAGnB,aAAa;GACX,IAAI,UAAU,MAAM;IAClB,eAAe,KAAK;IACpB,QAAQ;GACV;EACF;CACF,CAAC;CAED,OAAO,EACL,IAAI,UAAU;EACZ,OAAO,MAAM;CACf,EACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,eACd,MACA,QACA,UAA8B,CAAC,GACH;CAE5B,OAAO,YADS,QAAQ,WAAYA,kBAAW,SACb;EAChC,MAAM,cAAc,OAAO,SAAS,aAAa,KAAK,IAAI;EAC1D,MAAM,gBAAgB,OAAO,WAAW,aAAa,OAAO,IAAI;EAChE,OAAO,WAAW,OAAO,eAAe,WAAW;CACrD,CAAC;AACH;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,iBACd,MACA,QACA,UAA8B,CAAC,GACD;CAC9B,MAAM,UAAU,QAAQ,WAAYA,kBAAW;CAE/C,MAAM,QAAQ,WAAiB;CAC/B,IAAI,eAAyC;CAE7C,MAAM,YAAY;EAChB,IAAI,iBAAiB,MACnB,eAAe,YAAY;EAG7B,MAAM,cAAc,OAAO,SAAS,aAAa,KAAK,IAAI;EAC1D,MAAM,gBAAgB,OAAO,WAAW,aAAa,OAAO,IAAI;EAEhE,MAAM,aAAa;EAEnB,MAAM,iBAAiB,WAAW,SAAS,eAAe,WAAW;EACrE,MAAM,eAAe,QAAQ,OAAO,EAAE,cAAc;EACpD,eAAe;EAEf,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;CACF;CAEA,cAAc;EAEZ,aAAa;GACX,IAAI,iBAAiB,MAAM;IACzB,eAAe,YAAY;IAC3B,eAAe;GACjB;EACF;CACF,CAAC;CAED,OAAO;EACL,IAAI,UAAU;GACZ,OAAO,MAAM;EACf;EACA;CACF;AACF;;;;;;;;;;;;;;;;AAiBA,SAAgB,kBACd,UAAkC,CAAC,GACK;CACxC,MAAM,UAAU,QAAQ,WAAYA,kBAAW;CAC/C,QAAQ,SAAS;EACf,QAAQ,OAAO,EAAE,WAAW,WAAW,IAAI,CAAC;CAC9C;AACF"}
@@ -1,7 +1,15 @@
1
+ import { RuntimeLike } from "./internal/run.js";
1
2
  import { Stream } from "effect";
2
3
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
3
4
 
4
5
  //#region src/stream.svelte.d.ts
6
+ interface UseStreamOptions<R> {
7
+ /**
8
+ * Run against this runtime instead of the ambient context. When provided, the
9
+ * stream's `R` is constrained to what the runtime provides.
10
+ */
11
+ readonly runtime?: RuntimeLike<R>;
12
+ }
5
13
  interface UseStreamReturn<A, E> {
6
14
  /**
7
15
  * The current result state of the stream. `Initial`/`Success` carry a
@@ -42,7 +50,7 @@ interface UseStreamReturn<A, E> {
42
50
  * <p>All values: {counter.values.join(', ')}</p>
43
51
  * ```
44
52
  */
45
- declare function useStream<A, E, R>(stream: Stream.Stream<A, E, R>): UseStreamReturn<A, E>;
53
+ declare function useStream<A, E, R>(stream: Stream.Stream<A, E, R>, options?: UseStreamOptions<R>): UseStreamReturn<A, E>;
46
54
  //#endregion
47
- export { UseStreamReturn, useStream };
55
+ export { UseStreamOptions, UseStreamReturn, useStream };
48
56
  //# sourceMappingURL=stream.svelte.d.ts.map
@@ -25,8 +25,8 @@ import { streamState } from "./internal/subscribe.js";
25
25
  * <p>All values: {counter.values.join(', ')}</p>
26
26
  * ```
27
27
  */
28
- function useStream(stream) {
29
- return streamState(getRuntimeContext(), () => stream);
28
+ function useStream(stream, options = {}) {
29
+ return streamState(options.runtime ?? getRuntimeContext(), () => stream);
30
30
  }
31
31
  //#endregion
32
32
  export { useStream };
@@ -1 +1 @@
1
- {"version":3,"file":"stream.svelte.js","names":["getRuntime"],"sources":["../src/stream.svelte.ts"],"sourcesContent":["import type { Stream } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport type { RuntimeLike } from \"./internal/run.js\";\nimport { streamState } from \"./internal/subscribe.js\";\n\nexport interface UseStreamReturn<A, E> {\n /**\n * The current result state of the stream. `Initial`/`Success` carry a\n * `waiting` flag while the stream is still live (stale-while-revalidate), and\n * settle to a non-waiting `Success`/`Failure` once it completes.\n */\n readonly current: AsyncResult.AsyncResult<ReadonlyArray<A>, E>;\n\n /**\n * All values emitted by the stream so far\n */\n readonly values: ReadonlyArray<A>;\n\n /**\n * The most recent value emitted by the stream (undefined if none)\n */\n readonly latest: A | undefined;\n}\n\n/**\n * Subscribe to a Stream and collect emissions.\n *\n * The subscription starts the first time `current`/`values`/`latest` is read\n * inside an effect or template, and is interrupted automatically when the last\n * reader is destroyed (via Svelte's `createSubscriber`).\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useStream } from '@thomasfosterau/effect-svelte';\n * import { Stream, Schedule } from 'effect';\n *\n * const counter = useStream(\n * Stream.iterate(0, n => n + 1).pipe(\n * Stream.schedule(Schedule.spaced('1 second'))\n * )\n * );\n * </script>\n *\n * <p>Latest: {counter.latest}</p>\n * <p>All values: {counter.values.join(', ')}</p>\n * ```\n */\nexport function useStream<A, E, R>(stream: Stream.Stream<A, E, R>): UseStreamReturn<A, E> {\n const runtime = getRuntime() as RuntimeLike<R>;\n return streamState(runtime, () => stream);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,UAAmB,QAAuD;CAExF,OAAO,YADSA,kBACS,SAAS,MAAM;AAC1C"}
1
+ {"version":3,"file":"stream.svelte.js","names":["getRuntime"],"sources":["../src/stream.svelte.ts"],"sourcesContent":["import type { Stream } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport type { RuntimeLike } from \"./internal/run.js\";\nimport { streamState } from \"./internal/subscribe.js\";\n\nexport interface UseStreamOptions<R> {\n /**\n * Run against this runtime instead of the ambient context. When provided, the\n * stream's `R` is constrained to what the runtime provides.\n */\n readonly runtime?: RuntimeLike<R>;\n}\n\nexport interface UseStreamReturn<A, E> {\n /**\n * The current result state of the stream. `Initial`/`Success` carry a\n * `waiting` flag while the stream is still live (stale-while-revalidate), and\n * settle to a non-waiting `Success`/`Failure` once it completes.\n */\n readonly current: AsyncResult.AsyncResult<ReadonlyArray<A>, E>;\n\n /**\n * All values emitted by the stream so far\n */\n readonly values: ReadonlyArray<A>;\n\n /**\n * The most recent value emitted by the stream (undefined if none)\n */\n readonly latest: A | undefined;\n}\n\n/**\n * Subscribe to a Stream and collect emissions.\n *\n * The subscription starts the first time `current`/`values`/`latest` is read\n * inside an effect or template, and is interrupted automatically when the last\n * reader is destroyed (via Svelte's `createSubscriber`).\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useStream } from '@thomasfosterau/effect-svelte';\n * import { Stream, Schedule } from 'effect';\n *\n * const counter = useStream(\n * Stream.iterate(0, n => n + 1).pipe(\n * Stream.schedule(Schedule.spaced('1 second'))\n * )\n * );\n * </script>\n *\n * <p>Latest: {counter.latest}</p>\n * <p>All values: {counter.values.join(', ')}</p>\n * ```\n */\nexport function useStream<A, E, R>(\n stream: Stream.Stream<A, E, R>,\n options: UseStreamOptions<R> = {},\n): UseStreamReturn<A, E> {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R>);\n return streamState(runtime, () => stream);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,SAAgB,UACd,QACA,UAA+B,CAAC,GACT;CAEvB,OAAO,YADS,QAAQ,WAAYA,kBAAW,SACb,MAAM;AAC1C"}
@@ -1,6 +1,15 @@
1
+ import { RuntimeLike } from "./internal/run.js";
1
2
  import { PubSub, SubscriptionRef } from "effect";
2
3
 
3
4
  //#region src/subscription.svelte.d.ts
5
+ interface UseSubscriptionRefOptions<R> {
6
+ /** Run against this runtime instead of the ambient context. */
7
+ readonly runtime?: RuntimeLike<R>;
8
+ }
9
+ interface UsePubSubOptions<R> {
10
+ /** Run against this runtime instead of the ambient context. */
11
+ readonly runtime?: RuntimeLike<R>;
12
+ }
4
13
  interface UseSubscriptionRefReturn<A> {
5
14
  /**
6
15
  * The current value of the SubscriptionRef
@@ -28,7 +37,7 @@ interface UseSubscriptionRefReturn<A> {
28
37
  * <p>Count: {count.current}</p>
29
38
  * ```
30
39
  */
31
- declare function useSubscriptionRef<A>(ref: SubscriptionRef.SubscriptionRef<A>): UseSubscriptionRefReturn<A>;
40
+ declare function useSubscriptionRef<A, R = never>(ref: SubscriptionRef.SubscriptionRef<A>, options?: UseSubscriptionRefOptions<R>): UseSubscriptionRefReturn<A>;
32
41
  interface UsePubSubReturn<A> {
33
42
  /**
34
43
  * All values received from the PubSub
@@ -63,7 +72,7 @@ interface UsePubSubReturn<A> {
63
72
  * </ul>
64
73
  * ```
65
74
  */
66
- declare function usePubSub<A>(pubsub: PubSub.PubSub<A>): UsePubSubReturn<A>;
75
+ declare function usePubSub<A, R = never>(pubsub: PubSub.PubSub<A>, options?: UsePubSubOptions<R>): UsePubSubReturn<A>;
67
76
  //#endregion
68
- export { UsePubSubReturn, UseSubscriptionRefReturn, usePubSub, useSubscriptionRef };
77
+ export { UsePubSubOptions, UsePubSubReturn, UseSubscriptionRefOptions, UseSubscriptionRefReturn, usePubSub, useSubscriptionRef };
69
78
  //# sourceMappingURL=subscription.svelte.d.ts.map
@@ -24,8 +24,8 @@ import { Stream, SubscriptionRef } from "effect";
24
24
  * <p>Count: {count.current}</p>
25
25
  * ```
26
26
  */
27
- function useSubscriptionRef(ref) {
28
- const runtime = getRuntimeContext();
27
+ function useSubscriptionRef(ref, options = {}) {
28
+ const runtime = options.runtime ?? getRuntimeContext();
29
29
  return valueState(runtime, runSync(runtime)(SubscriptionRef.get(ref)), () => SubscriptionRef.changes(ref));
30
30
  }
31
31
  /**
@@ -52,8 +52,8 @@ function useSubscriptionRef(ref) {
52
52
  * </ul>
53
53
  * ```
54
54
  */
55
- function usePubSub(pubsub) {
56
- const state = streamState(getRuntimeContext(), () => Stream.fromPubSub(pubsub));
55
+ function usePubSub(pubsub, options = {}) {
56
+ const state = streamState(options.runtime ?? getRuntimeContext(), () => Stream.fromPubSub(pubsub));
57
57
  return {
58
58
  get values() {
59
59
  return state.values;
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.svelte.js","names":["getRuntime"],"sources":["../src/subscription.svelte.ts"],"sourcesContent":["import { PubSub, Stream, SubscriptionRef } from \"effect\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { runSync, type RuntimeLike } from \"./internal/run.js\";\nimport { streamState, valueState } from \"./internal/subscribe.js\";\n\nexport interface UseSubscriptionRefReturn<A> {\n /**\n * The current value of the SubscriptionRef\n */\n readonly current: A;\n}\n\n/**\n * Subscribe to an Effect SubscriptionRef and get reactive updates.\n * Returns an object exposing the current value via the `current` property.\n *\n * The initial value is read synchronously; the subscription that keeps it up to\n * date starts when `current` is first read inside an effect or template, and is\n * interrupted when the last reader is destroyed.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useSubscriptionRef } from '@thomasfosterau/effect-svelte';\n * import { SubscriptionRef, Effect } from 'effect';\n *\n * // Assume counterRef is a SubscriptionRef<number>\n * const count = useSubscriptionRef(counterRef);\n * </script>\n *\n * <p>Count: {count.current}</p>\n * ```\n */\nexport function useSubscriptionRef<A>(\n ref: SubscriptionRef.SubscriptionRef<A>,\n): UseSubscriptionRefReturn<A> {\n const runtime = getRuntime() as RuntimeLike<never>;\n\n // Get the initial value synchronously so it is available before (and during\n // SSR, instead of) the subscription. `SubscriptionRef.changes` re-emits the\n // current value on subscribe, so no update is missed.\n const initial = runSync(runtime)(SubscriptionRef.get(ref));\n return valueState(runtime, initial, () => SubscriptionRef.changes(ref));\n}\n\nexport interface UsePubSubReturn<A> {\n /**\n * All values received from the PubSub\n */\n readonly values: ReadonlyArray<A>;\n\n /**\n * The most recent value (undefined if none)\n */\n readonly latest: A | undefined;\n}\n\n/**\n * Subscribe to an Effect PubSub and collect messages.\n *\n * The subscription starts when `values`/`latest` is first read inside an effect\n * or template, and is interrupted when the last reader is destroyed.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { usePubSub } from '@thomasfosterau/effect-svelte';\n * import { PubSub } from 'effect';\n *\n * // Assume messagePubSub is a PubSub<string>\n * const messages = usePubSub(messagePubSub);\n * </script>\n *\n * <p>Latest: {messages.latest}</p>\n * <ul>\n * {#each messages.values as msg}\n * <li>{msg}</li>\n * {/each}\n * </ul>\n * ```\n */\nexport function usePubSub<A>(pubsub: PubSub.PubSub<A>): UsePubSubReturn<A> {\n const runtime = getRuntime() as RuntimeLike<never>;\n const state = streamState(runtime, () => Stream.fromPubSub(pubsub));\n return {\n get values() {\n return state.values;\n },\n get latest() {\n return state.latest;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,mBACd,KAC6B;CAC7B,MAAM,UAAUA,kBAAW;CAM3B,OAAO,WAAW,SADF,QAAQ,OAAO,EAAE,gBAAgB,IAAI,GAAG,CACvB,SAAS,gBAAgB,QAAQ,GAAG,CAAC;AACxE;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,UAAa,QAA8C;CAEzE,MAAM,QAAQ,YADEA,kBACgB,SAAS,OAAO,WAAW,MAAM,CAAC;CAClE,OAAO;EACL,IAAI,SAAS;GACX,OAAO,MAAM;EACf;EACA,IAAI,SAAS;GACX,OAAO,MAAM;EACf;CACF;AACF"}
1
+ {"version":3,"file":"subscription.svelte.js","names":["getRuntime"],"sources":["../src/subscription.svelte.ts"],"sourcesContent":["import { PubSub, Stream, SubscriptionRef } from \"effect\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { runSync, type RuntimeLike } from \"./internal/run.js\";\nimport { streamState, valueState } from \"./internal/subscribe.js\";\n\nexport interface UseSubscriptionRefOptions<R> {\n /** Run against this runtime instead of the ambient context. */\n readonly runtime?: RuntimeLike<R>;\n}\n\nexport interface UsePubSubOptions<R> {\n /** Run against this runtime instead of the ambient context. */\n readonly runtime?: RuntimeLike<R>;\n}\n\nexport interface UseSubscriptionRefReturn<A> {\n /**\n * The current value of the SubscriptionRef\n */\n readonly current: A;\n}\n\n/**\n * Subscribe to an Effect SubscriptionRef and get reactive updates.\n * Returns an object exposing the current value via the `current` property.\n *\n * The initial value is read synchronously; the subscription that keeps it up to\n * date starts when `current` is first read inside an effect or template, and is\n * interrupted when the last reader is destroyed.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useSubscriptionRef } from '@thomasfosterau/effect-svelte';\n * import { SubscriptionRef, Effect } from 'effect';\n *\n * // Assume counterRef is a SubscriptionRef<number>\n * const count = useSubscriptionRef(counterRef);\n * </script>\n *\n * <p>Count: {count.current}</p>\n * ```\n */\nexport function useSubscriptionRef<A, R = never>(\n ref: SubscriptionRef.SubscriptionRef<A>,\n options: UseSubscriptionRefOptions<R> = {},\n): UseSubscriptionRefReturn<A> {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R>);\n\n // Get the initial value synchronously so it is available before (and during\n // SSR, instead of) the subscription. `SubscriptionRef.changes` re-emits the\n // current value on subscribe, so no update is missed.\n const initial = runSync(runtime)(SubscriptionRef.get(ref));\n return valueState(runtime, initial, () => SubscriptionRef.changes(ref));\n}\n\nexport interface UsePubSubReturn<A> {\n /**\n * All values received from the PubSub\n */\n readonly values: ReadonlyArray<A>;\n\n /**\n * The most recent value (undefined if none)\n */\n readonly latest: A | undefined;\n}\n\n/**\n * Subscribe to an Effect PubSub and collect messages.\n *\n * The subscription starts when `values`/`latest` is first read inside an effect\n * or template, and is interrupted when the last reader is destroyed.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { usePubSub } from '@thomasfosterau/effect-svelte';\n * import { PubSub } from 'effect';\n *\n * // Assume messagePubSub is a PubSub<string>\n * const messages = usePubSub(messagePubSub);\n * </script>\n *\n * <p>Latest: {messages.latest}</p>\n * <ul>\n * {#each messages.values as msg}\n * <li>{msg}</li>\n * {/each}\n * </ul>\n * ```\n */\nexport function usePubSub<A, R = never>(\n pubsub: PubSub.PubSub<A>,\n options: UsePubSubOptions<R> = {},\n): UsePubSubReturn<A> {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R>);\n const state = streamState(runtime, () => Stream.fromPubSub(pubsub));\n return {\n get values() {\n return state.values;\n },\n get latest() {\n return state.latest;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,mBACd,KACA,UAAwC,CAAC,GACZ;CAC7B,MAAM,UAAU,QAAQ,WAAYA,kBAAW;CAM/C,OAAO,WAAW,SADF,QAAQ,OAAO,EAAE,gBAAgB,IAAI,GAAG,CACvB,SAAS,gBAAgB,QAAQ,GAAG,CAAC;AACxE;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,UACd,QACA,UAA+B,CAAC,GACZ;CAEpB,MAAM,QAAQ,YADE,QAAQ,WAAYA,kBAAW,SACN,OAAO,WAAW,MAAM,CAAC;CAClE,OAAO;EACL,IAAI,SAAS;GACX,OAAO,MAAM;EACf;EACA,IAAI,SAAS;GACX,OAAO,MAAM;EACf;CACF;AACF"}
@@ -1,6 +1,11 @@
1
+ import { RuntimeLike } from "./internal/run.js";
1
2
  import { SubscriptionRef } from "effect";
2
3
 
3
4
  //#region src/writable-ref.svelte.d.ts
5
+ interface UseWritableRefOptions<R> {
6
+ /** Run against this runtime instead of the ambient context. */
7
+ readonly runtime?: RuntimeLike<R>;
8
+ }
4
9
  interface UseWritableRefReturn<A> {
5
10
  /**
6
11
  * The current value of the `SubscriptionRef`. Reading tracks it reactively;
@@ -42,7 +47,7 @@ interface UseWritableRefReturn<A> {
42
47
  * <button onclick={() => name.set("")}>Clear</button>
43
48
  * ```
44
49
  */
45
- declare function useWritableRef<A>(ref: SubscriptionRef.SubscriptionRef<A>): UseWritableRefReturn<A>;
50
+ declare function useWritableRef<A, R = never>(ref: SubscriptionRef.SubscriptionRef<A>, options?: UseWritableRefOptions<R>): UseWritableRefReturn<A>;
46
51
  //#endregion
47
- export { UseWritableRefReturn, useWritableRef };
52
+ export { UseWritableRefOptions, UseWritableRefReturn, useWritableRef };
48
53
  //# sourceMappingURL=writable-ref.svelte.d.ts.map
@@ -32,8 +32,8 @@ import { Effect, Stream, SubscriptionRef } from "effect";
32
32
  * <button onclick={() => name.set("")}>Clear</button>
33
33
  * ```
34
34
  */
35
- function useWritableRef(ref) {
36
- const runtime = getRuntimeContext();
35
+ function useWritableRef(ref, options = {}) {
36
+ const runtime = options.runtime ?? getRuntimeContext();
37
37
  let value = $state(runSync(runtime)(SubscriptionRef.get(ref)));
38
38
  const writer = refWriter(runtime, ref);
39
39
  const set = (next) => {
@@ -1 +1 @@
1
- {"version":3,"file":"writable-ref.svelte.js","names":["getRuntime"],"sources":["../src/writable-ref.svelte.ts"],"sourcesContent":["import { Effect, Stream, SubscriptionRef } from \"effect\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { interruptFiber, runFork, runSync, type RuntimeLike } from \"./internal/run.js\";\nimport { refWriter } from \"./internal/writable.js\";\n\nexport interface UseWritableRefReturn<A> {\n /**\n * The current value of the `SubscriptionRef`. Reading tracks it reactively;\n * assigning writes back to the ref, so this property is usable directly with\n * Svelte's `bind:` directive (e.g. `bind:value={name.current}`).\n */\n current: A;\n\n /** Replace the ref's value. */\n readonly set: (value: A) => void;\n\n /** Apply a function to the ref's current value. */\n readonly update: (f: (current: A) => A) => void;\n}\n\n/**\n * Two-way binding between an Effect `SubscriptionRef` and Svelte runes.\n *\n * Unlike {@link useSubscriptionRef} (read-only `{ current }`), this hook exposes\n * a writable `current` — reading tracks the ref reactively and assigning writes\n * back — so Effect-managed state can drive `bind:value` for shared form / app\n * state. `set` and `update` are also provided for imperative writes.\n *\n * Local writes update `current` optimistically (so `bind:` does not glitch) and\n * are reconciled by the ref's `changes` stream, which also delivers updates\n * made elsewhere in the application.\n *\n * A `SubscriptionRef` is required (not a plain `Ref`): only a `SubscriptionRef`\n * exposes the `changes` stream needed to observe external updates reactively.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useWritableRef } from '@thomasfosterau/effect-svelte';\n * import { SubscriptionRef } from 'effect';\n *\n * // Assume nameRef is a SubscriptionRef<string> from shared app state\n * const name = useWritableRef(nameRef);\n * </script>\n *\n * <input bind:value={name.current} />\n * <button onclick={() => name.set(\"\")}>Clear</button>\n * ```\n */\nexport function useWritableRef<A>(\n ref: SubscriptionRef.SubscriptionRef<A>,\n): UseWritableRefReturn<A> {\n const runtime = getRuntime() as RuntimeLike<never>;\n\n // Read the initial value synchronously so it is available before (and during\n // SSR, instead of) the subscription.\n let value = $state(runSync(runtime)(SubscriptionRef.get(ref)));\n\n const writer = refWriter(runtime, ref);\n\n const set = (next: A): void => {\n // Optimistic local update so `bind:value` reflects the change immediately,\n // then persist to the ref (whose `changes` stream reconciles the value).\n value = next;\n writer.set(next);\n };\n\n const update = (f: (current: A) => A): void => {\n set(f(value));\n };\n\n // Keep `value` in sync with updates made anywhere else in the app. The\n // `changes` stream re-emits the current value on subscribe, so no update is\n // missed between the synchronous initial read and the subscription starting.\n $effect(() => {\n const fiber = runFork(runtime)(\n Stream.runForEach(SubscriptionRef.changes(ref), (next) =>\n Effect.sync(() => {\n value = next;\n }),\n ),\n );\n return () => interruptFiber(fiber);\n });\n\n return {\n get current() {\n return value;\n },\n set current(next: A) {\n set(next);\n },\n set,\n update,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,eACd,KACyB;CACzB,MAAM,UAAUA,kBAAW;CAI3B,IAAI,QAAQ,OAAO,QAAQ,OAAO,EAAE,gBAAgB,IAAI,GAAG,CAAC,CAAC;CAE7D,MAAM,SAAS,UAAU,SAAS,GAAG;CAErC,MAAM,OAAO,SAAkB;EAG7B,QAAQ;EACR,OAAO,IAAI,IAAI;CACjB;CAEA,MAAM,UAAU,MAA+B;EAC7C,IAAI,EAAE,KAAK,CAAC;CACd;CAKA,cAAc;EACZ,MAAM,QAAQ,QAAQ,OAAO,EAC3B,OAAO,WAAW,gBAAgB,QAAQ,GAAG,IAAI,SAC/C,OAAO,WAAW;GAChB,QAAQ;EACV,CAAC,CACH,CACF;EACA,aAAa,eAAe,KAAK;CACnC,CAAC;CAED,OAAO;EACL,IAAI,UAAU;GACZ,OAAO;EACT;EACA,IAAI,QAAQ,MAAS;GACnB,IAAI,IAAI;EACV;EACA;EACA;CACF;AACF"}
1
+ {"version":3,"file":"writable-ref.svelte.js","names":["getRuntime"],"sources":["../src/writable-ref.svelte.ts"],"sourcesContent":["import { Effect, Stream, SubscriptionRef } from \"effect\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { interruptFiber, runFork, runSync, type RuntimeLike } from \"./internal/run.js\";\nimport { refWriter } from \"./internal/writable.js\";\n\nexport interface UseWritableRefOptions<R> {\n /** Run against this runtime instead of the ambient context. */\n readonly runtime?: RuntimeLike<R>;\n}\n\nexport interface UseWritableRefReturn<A> {\n /**\n * The current value of the `SubscriptionRef`. Reading tracks it reactively;\n * assigning writes back to the ref, so this property is usable directly with\n * Svelte's `bind:` directive (e.g. `bind:value={name.current}`).\n */\n current: A;\n\n /** Replace the ref's value. */\n readonly set: (value: A) => void;\n\n /** Apply a function to the ref's current value. */\n readonly update: (f: (current: A) => A) => void;\n}\n\n/**\n * Two-way binding between an Effect `SubscriptionRef` and Svelte runes.\n *\n * Unlike {@link useSubscriptionRef} (read-only `{ current }`), this hook exposes\n * a writable `current` — reading tracks the ref reactively and assigning writes\n * back — so Effect-managed state can drive `bind:value` for shared form / app\n * state. `set` and `update` are also provided for imperative writes.\n *\n * Local writes update `current` optimistically (so `bind:` does not glitch) and\n * are reconciled by the ref's `changes` stream, which also delivers updates\n * made elsewhere in the application.\n *\n * A `SubscriptionRef` is required (not a plain `Ref`): only a `SubscriptionRef`\n * exposes the `changes` stream needed to observe external updates reactively.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useWritableRef } from '@thomasfosterau/effect-svelte';\n * import { SubscriptionRef } from 'effect';\n *\n * // Assume nameRef is a SubscriptionRef<string> from shared app state\n * const name = useWritableRef(nameRef);\n * </script>\n *\n * <input bind:value={name.current} />\n * <button onclick={() => name.set(\"\")}>Clear</button>\n * ```\n */\nexport function useWritableRef<A, R = never>(\n ref: SubscriptionRef.SubscriptionRef<A>,\n options: UseWritableRefOptions<R> = {},\n): UseWritableRefReturn<A> {\n const runtime = options.runtime ?? (getRuntime() as RuntimeLike<R>);\n\n // Read the initial value synchronously so it is available before (and during\n // SSR, instead of) the subscription.\n let value = $state(runSync(runtime)(SubscriptionRef.get(ref)));\n\n const writer = refWriter(runtime, ref);\n\n const set = (next: A): void => {\n // Optimistic local update so `bind:value` reflects the change immediately,\n // then persist to the ref (whose `changes` stream reconciles the value).\n value = next;\n writer.set(next);\n };\n\n const update = (f: (current: A) => A): void => {\n set(f(value));\n };\n\n // Keep `value` in sync with updates made anywhere else in the app. The\n // `changes` stream re-emits the current value on subscribe, so no update is\n // missed between the synchronous initial read and the subscription starting.\n $effect(() => {\n const fiber = runFork(runtime)(\n Stream.runForEach(SubscriptionRef.changes(ref), (next) =>\n Effect.sync(() => {\n value = next;\n }),\n ),\n );\n return () => interruptFiber(fiber);\n });\n\n return {\n get current() {\n return value;\n },\n set current(next: A) {\n set(next);\n },\n set,\n update,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,SAAgB,eACd,KACA,UAAoC,CAAC,GACZ;CACzB,MAAM,UAAU,QAAQ,WAAYA,kBAAW;CAI/C,IAAI,QAAQ,OAAO,QAAQ,OAAO,EAAE,gBAAgB,IAAI,GAAG,CAAC,CAAC;CAE7D,MAAM,SAAS,UAAU,SAAS,GAAG;CAErC,MAAM,OAAO,SAAkB;EAG7B,QAAQ;EACR,OAAO,IAAI,IAAI;CACjB;CAEA,MAAM,UAAU,MAA+B;EAC7C,IAAI,EAAE,KAAK,CAAC;CACd;CAKA,cAAc;EACZ,MAAM,QAAQ,QAAQ,OAAO,EAC3B,OAAO,WAAW,gBAAgB,QAAQ,GAAG,IAAI,SAC/C,OAAO,WAAW;GAChB,QAAQ;EACV,CAAC,CACH,CACF;EACA,aAAa,eAAe,KAAK;CACnC,CAAC;CAED,OAAO;EACL,IAAI,UAAU;GACZ,OAAO;EACT;EACA,IAAI,QAAQ,MAAS;GACnB,IAAI,IAAI;EACV;EACA;EACA;CACF;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thomasfosterau/effect-svelte",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Effect integration for Svelte 5 runes",
5
5
  "keywords": [
6
6
  "effect",