@thomasfosterau/effect-svelte 0.1.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.
- package/dist/await.svelte.d.ts +53 -0
- package/dist/await.svelte.js +44 -0
- package/dist/await.svelte.js.map +1 -0
- package/dist/context.svelte.d.ts +51 -1
- package/dist/context.svelte.js +43 -1
- package/dist/context.svelte.js.map +1 -1
- package/dist/derived.svelte.d.ts +32 -12
- package/dist/derived.svelte.js +23 -13
- package/dist/derived.svelte.js.map +1 -1
- package/dist/effect.svelte.d.ts +21 -2
- package/dist/effect.svelte.js +13 -3
- package/dist/effect.svelte.js.map +1 -1
- package/dist/index.d.ts +12 -7
- package/dist/index.js +6 -2
- package/dist/internal/await.js +52 -0
- package/dist/internal/await.js.map +1 -0
- package/dist/internal/live-stream.js +43 -0
- package/dist/internal/live-stream.js.map +1 -0
- package/dist/internal/mutation.d.ts +38 -0
- package/dist/internal/mutation.js +61 -0
- package/dist/internal/mutation.js.map +1 -0
- package/dist/internal/result.svelte.d.ts +1 -0
- package/dist/internal/writable.js +21 -0
- package/dist/internal/writable.js.map +1 -0
- package/dist/live-stream.svelte.d.ts +72 -0
- package/dist/live-stream.svelte.js +77 -0
- package/dist/live-stream.svelte.js.map +1 -0
- package/dist/mutation.svelte.d.ts +65 -0
- package/dist/mutation.svelte.js +53 -0
- package/dist/mutation.svelte.js.map +1 -0
- package/dist/query.svelte.d.ts +21 -2
- package/dist/query.svelte.js +10 -3
- package/dist/query.svelte.js.map +1 -1
- package/dist/reactivity.svelte.d.ts +15 -5
- package/dist/reactivity.svelte.js +8 -8
- package/dist/reactivity.svelte.js.map +1 -1
- package/dist/stream.svelte.d.ts +10 -2
- package/dist/stream.svelte.js +2 -2
- package/dist/stream.svelte.js.map +1 -1
- package/dist/subscription.svelte.d.ts +12 -3
- package/dist/subscription.svelte.js +4 -4
- package/dist/subscription.svelte.js.map +1 -1
- package/dist/writable-ref.svelte.d.ts +53 -0
- package/dist/writable-ref.svelte.js +66 -0
- package/dist/writable-ref.svelte.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { RuntimeLike } from "./internal/run.js";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
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
|
+
}
|
|
13
|
+
interface UseQueryPromiseReturn<A, E> {
|
|
14
|
+
/** The underlying query result state (same as {@link useQuery}). */
|
|
15
|
+
readonly current: AsyncResult.AsyncResult<A, E>;
|
|
16
|
+
/**
|
|
17
|
+
* A `Promise` reflecting `current`, for use with Svelte's `{#await}` (or
|
|
18
|
+
* `<svelte:boundary>`). Its identity is stable until the result transitions:
|
|
19
|
+
* pending while loading, resolved with the (possibly stale) value, rejected
|
|
20
|
+
* on failure.
|
|
21
|
+
*/
|
|
22
|
+
readonly promise: Promise<A>;
|
|
23
|
+
/** Re-run the effect. A new `promise` is produced as the result transitions. */
|
|
24
|
+
readonly refetch: () => void;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Run an effect and bridge its result to Svelte 5's native async, so you can
|
|
28
|
+
* render it with an `{#await}` block instead of `AsyncResult.match`.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```svelte
|
|
32
|
+
* <script lang="ts">
|
|
33
|
+
* import { useQueryPromise } from '@thomasfosterau/effect-svelte';
|
|
34
|
+
* import { Effect } from 'effect';
|
|
35
|
+
*
|
|
36
|
+
* const user = useQueryPromise(fetchUser(1));
|
|
37
|
+
* </script>
|
|
38
|
+
*
|
|
39
|
+
* {#await user.promise}
|
|
40
|
+
* <p>Loading…</p>
|
|
41
|
+
* {:then value}
|
|
42
|
+
* <p>{value.name}</p>
|
|
43
|
+
* {:catch error}
|
|
44
|
+
* <p>Failed: {error.message}</p>
|
|
45
|
+
* {/await}
|
|
46
|
+
*
|
|
47
|
+
* <button onclick={user.refetch}>Reload</button>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function useQueryPromise<A, E, R>(effect: Effect.Effect<A, E, R>, options?: UseQueryPromiseOptions<R>): UseQueryPromiseReturn<A, E>;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { UseQueryPromiseOptions, UseQueryPromiseReturn, useQueryPromise };
|
|
53
|
+
//# sourceMappingURL=await.svelte.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { resultToPromise } from "./internal/await.js";
|
|
2
|
+
import { useQuery } from "./query.svelte.js";
|
|
3
|
+
//#region src/await.svelte.ts
|
|
4
|
+
/**
|
|
5
|
+
* Run an effect and bridge its result to Svelte 5's native async, so you can
|
|
6
|
+
* render it with an `{#await}` block instead of `AsyncResult.match`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```svelte
|
|
10
|
+
* <script lang="ts">
|
|
11
|
+
* import { useQueryPromise } from '@thomasfosterau/effect-svelte';
|
|
12
|
+
* import { Effect } from 'effect';
|
|
13
|
+
*
|
|
14
|
+
* const user = useQueryPromise(fetchUser(1));
|
|
15
|
+
* <\/script>
|
|
16
|
+
*
|
|
17
|
+
* {#await user.promise}
|
|
18
|
+
* <p>Loading…</p>
|
|
19
|
+
* {:then value}
|
|
20
|
+
* <p>{value.name}</p>
|
|
21
|
+
* {:catch error}
|
|
22
|
+
* <p>Failed: {error.message}</p>
|
|
23
|
+
* {/await}
|
|
24
|
+
*
|
|
25
|
+
* <button onclick={user.refetch}>Reload</button>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function useQueryPromise(effect, options = {}) {
|
|
29
|
+
const query = useQuery(effect, options);
|
|
30
|
+
const promise = $derived(resultToPromise(query.current));
|
|
31
|
+
return {
|
|
32
|
+
get current() {
|
|
33
|
+
return query.current;
|
|
34
|
+
},
|
|
35
|
+
get promise() {
|
|
36
|
+
return promise;
|
|
37
|
+
},
|
|
38
|
+
refetch: query.refetch
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { useQueryPromise };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=await.svelte.js.map
|
|
@@ -0,0 +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 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"}
|
package/dist/context.svelte.d.ts
CHANGED
|
@@ -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
|
package/dist/context.svelte.js
CHANGED
|
@@ -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"}
|
package/dist/derived.svelte.d.ts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RuntimeLike } from "./internal/run.js";
|
|
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
|
|
6
|
+
interface UseDerivedOptions<R = never> {
|
|
7
|
+
/**
|
|
8
|
+
* Debounce re-runs by this duration: when a dependency changes, wait this
|
|
9
|
+
* long before running, and if another change arrives within the window,
|
|
10
|
+
* restart the wait. The previous value stays visible during the window (the
|
|
11
|
+
* result only flips to `waiting` once the run actually starts).
|
|
12
|
+
*
|
|
13
|
+
* Accepts any Effect `Duration.Input` (e.g. `"300 millis"`, `500`,
|
|
14
|
+
* `Duration.seconds(1)`).
|
|
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>;
|
|
22
|
+
}
|
|
5
23
|
interface DerivedReturn<A, E> {
|
|
6
24
|
/**
|
|
7
25
|
* The current result. While re-running after a dependency change, the
|
|
@@ -14,30 +32,32 @@ interface DerivedReturn<A, E> {
|
|
|
14
32
|
* The function parameter is called inside $effect, so it automatically tracks dependencies.
|
|
15
33
|
* When dependencies change, the previous fiber is interrupted and a new one starts.
|
|
16
34
|
*
|
|
35
|
+
* Pass `{ debounce }` to coalesce rapid dependency changes (e.g. a search box):
|
|
36
|
+
* interrupting the previous fiber cancels its pending debounce sleep, so only
|
|
37
|
+
* the last change in a burst actually runs.
|
|
38
|
+
*
|
|
17
39
|
* @example
|
|
18
40
|
* ```svelte
|
|
19
41
|
* <script lang="ts">
|
|
20
42
|
* import { useDerived, AsyncResult } from '@thomasfosterau/effect-svelte';
|
|
21
43
|
* import { Effect } from 'effect';
|
|
22
44
|
*
|
|
23
|
-
* let
|
|
45
|
+
* let query = $state('');
|
|
24
46
|
*
|
|
25
|
-
* const
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* return response;
|
|
29
|
-
* })
|
|
47
|
+
* const results = useDerived(
|
|
48
|
+
* () => search(query),
|
|
49
|
+
* { debounce: '300 millis' },
|
|
30
50
|
* );
|
|
31
51
|
* </script>
|
|
32
52
|
*
|
|
33
|
-
* <
|
|
53
|
+
* <input bind:value={query} />
|
|
34
54
|
*
|
|
35
|
-
* {#if AsyncResult.isSuccess(
|
|
36
|
-
* <
|
|
55
|
+
* {#if AsyncResult.isSuccess(results.current)}
|
|
56
|
+
* <ul>{#each results.current.value as r}<li>{r}</li>{/each}</ul>
|
|
37
57
|
* {/if}
|
|
38
58
|
* ```
|
|
39
59
|
*/
|
|
40
|
-
declare function useDerived<A, E, R>(fn: () => Effect.Effect<A, E, R>): DerivedReturn<A, E>;
|
|
60
|
+
declare function useDerived<A, E, R>(fn: () => Effect.Effect<A, E, R>, options?: UseDerivedOptions<R>): DerivedReturn<A, E>;
|
|
41
61
|
//#endregion
|
|
42
|
-
export { DerivedReturn, useDerived };
|
|
62
|
+
export { DerivedReturn, UseDerivedOptions, useDerived };
|
|
43
63
|
//# sourceMappingURL=derived.svelte.d.ts.map
|
package/dist/derived.svelte.js
CHANGED
|
@@ -8,38 +8,48 @@ import { Effect, Fiber } from "effect";
|
|
|
8
8
|
* The function parameter is called inside $effect, so it automatically tracks dependencies.
|
|
9
9
|
* When dependencies change, the previous fiber is interrupted and a new one starts.
|
|
10
10
|
*
|
|
11
|
+
* Pass `{ debounce }` to coalesce rapid dependency changes (e.g. a search box):
|
|
12
|
+
* interrupting the previous fiber cancels its pending debounce sleep, so only
|
|
13
|
+
* the last change in a burst actually runs.
|
|
14
|
+
*
|
|
11
15
|
* @example
|
|
12
16
|
* ```svelte
|
|
13
17
|
* <script lang="ts">
|
|
14
18
|
* import { useDerived, AsyncResult } from '@thomasfosterau/effect-svelte';
|
|
15
19
|
* import { Effect } from 'effect';
|
|
16
20
|
*
|
|
17
|
-
* let
|
|
21
|
+
* let query = $state('');
|
|
18
22
|
*
|
|
19
|
-
* const
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* return response;
|
|
23
|
-
* })
|
|
23
|
+
* const results = useDerived(
|
|
24
|
+
* () => search(query),
|
|
25
|
+
* { debounce: '300 millis' },
|
|
24
26
|
* );
|
|
25
27
|
* <\/script>
|
|
26
28
|
*
|
|
27
|
-
* <
|
|
29
|
+
* <input bind:value={query} />
|
|
28
30
|
*
|
|
29
|
-
* {#if AsyncResult.isSuccess(
|
|
30
|
-
* <
|
|
31
|
+
* {#if AsyncResult.isSuccess(results.current)}
|
|
32
|
+
* <ul>{#each results.current.value as r}<li>{r}</li>{/each}</ul>
|
|
31
33
|
* {/if}
|
|
32
34
|
* ```
|
|
33
35
|
*/
|
|
34
|
-
function useDerived(fn) {
|
|
35
|
-
const
|
|
36
|
+
function useDerived(fn, options = {}) {
|
|
37
|
+
const { debounce } = options;
|
|
38
|
+
const runtime = options.runtime ?? getRuntimeContext();
|
|
36
39
|
const state = makeResult();
|
|
37
40
|
let currentFiber = null;
|
|
38
41
|
$effect(() => {
|
|
39
42
|
if (currentFiber !== null) interruptFiber(currentFiber);
|
|
40
43
|
const effect = fn();
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
let runningFiber;
|
|
45
|
+
if (debounce === void 0) {
|
|
46
|
+
state.startWaiting();
|
|
47
|
+
runningFiber = runFork(runtime)(effect);
|
|
48
|
+
} else runningFiber = runFork(runtime)(Effect.gen(function* () {
|
|
49
|
+
yield* Effect.sleep(debounce);
|
|
50
|
+
yield* Effect.sync(() => state.startWaiting());
|
|
51
|
+
return yield* effect;
|
|
52
|
+
}));
|
|
43
53
|
currentFiber = runningFiber;
|
|
44
54
|
runFork(runtime)(Effect.gen(function* () {
|
|
45
55
|
const exit = yield* Fiber.await(runningFiber);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derived.svelte.js","names":["getRuntime"],"sources":["../src/derived.svelte.ts"],"sourcesContent":["import { Effect, Fiber } from \"effect\";\nimport type * as AsyncResult from \"effect/unstable/reactivity/AsyncResult\";\nimport { getRuntime } from \"./context.svelte.js\";\nimport { interruptFiber, runFork, type RuntimeLike } from \"./internal/run.js\";\nimport { makeResult } from \"./internal/result.svelte.js\";\n\nexport interface DerivedReturn<A, E> {\n /**\n * The current result. While re-running after a dependency change, the\n * previous value is kept as a waiting `Success` (stale-while-revalidate).\n */\n readonly current: AsyncResult.AsyncResult<A, E>;\n}\n\n/**\n * Re-run an effect whenever reactive dependencies change.\n * The function parameter is called inside $effect, so it automatically tracks dependencies.\n * When dependencies change, the previous fiber is interrupted and a new one starts.\n *\n * @example\n * ```svelte\n * <script lang=\"ts\">\n * import { useDerived, AsyncResult } from '@thomasfosterau/effect-svelte';\n * import { Effect } from 'effect';\n *\n * let
|
|
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"}
|
package/dist/effect.svelte.d.ts
CHANGED
|
@@ -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
|
/**
|
|
@@ -24,6 +30,19 @@ interface UseEffectReturn<A, E> {
|
|
|
24
30
|
* Interrupt the currently running effect
|
|
25
31
|
*/
|
|
26
32
|
interrupt: () => void;
|
|
33
|
+
/**
|
|
34
|
+
* Optimistically overwrite the current value with a non-waiting `Success`,
|
|
35
|
+
* without running the effect. Accepts a value or an updater function that
|
|
36
|
+
* receives the current value (`undefined` if there is none yet). Useful for
|
|
37
|
+
* local cache writes after a mutation, reconciled by the next `run`.
|
|
38
|
+
*/
|
|
39
|
+
setData: (updater: A | ((previous: A | undefined) => A)) => void;
|
|
40
|
+
/**
|
|
41
|
+
* `true` while a previous value is being shown but a re-run is in flight
|
|
42
|
+
* (stale-while-revalidate) — i.e. the result is `waiting` and still holds a
|
|
43
|
+
* value.
|
|
44
|
+
*/
|
|
45
|
+
readonly isStale: boolean;
|
|
27
46
|
}
|
|
28
47
|
/**
|
|
29
48
|
* Run a single Effect and track its result state.
|
|
@@ -45,7 +64,7 @@ interface UseEffectReturn<A, E> {
|
|
|
45
64
|
* {/if}
|
|
46
65
|
* ```
|
|
47
66
|
*/
|
|
48
|
-
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>;
|
|
49
68
|
//#endregion
|
|
50
69
|
export { UseEffectOptions, UseEffectReturn, useEffect };
|
|
51
70
|
//# sourceMappingURL=effect.svelte.d.ts.map
|
package/dist/effect.svelte.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getRuntimeContext } from "./context.svelte.js";
|
|
2
2
|
import { interruptFiber, runFork } from "./internal/run.js";
|
|
3
3
|
import { makeResult } from "./internal/result.svelte.js";
|
|
4
|
-
import { Effect, Fiber } from "effect";
|
|
4
|
+
import { Effect, Fiber, Option } from "effect";
|
|
5
|
+
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
5
6
|
//#region src/effect.svelte.ts
|
|
6
7
|
/**
|
|
7
8
|
* Run a single Effect and track its result state.
|
|
@@ -25,7 +26,7 @@ import { Effect, Fiber } from "effect";
|
|
|
25
26
|
*/
|
|
26
27
|
function useEffect(effect, options = {}) {
|
|
27
28
|
const { immediate = false } = options;
|
|
28
|
-
const runtime = getRuntimeContext();
|
|
29
|
+
const runtime = options.runtime ?? getRuntimeContext();
|
|
29
30
|
const state = makeResult();
|
|
30
31
|
let currentFiber = null;
|
|
31
32
|
const run = () => {
|
|
@@ -54,12 +55,21 @@ function useEffect(effect, options = {}) {
|
|
|
54
55
|
interrupt();
|
|
55
56
|
};
|
|
56
57
|
});
|
|
58
|
+
const setData = (updater) => {
|
|
59
|
+
const previous = Option.getOrUndefined(AsyncResult.value(state.current));
|
|
60
|
+
const next = typeof updater === "function" ? updater(previous) : updater;
|
|
61
|
+
state.succeed(next);
|
|
62
|
+
};
|
|
57
63
|
return {
|
|
58
64
|
get current() {
|
|
59
65
|
return state.current;
|
|
60
66
|
},
|
|
61
67
|
run,
|
|
62
|
-
interrupt
|
|
68
|
+
interrupt,
|
|
69
|
+
setData,
|
|
70
|
+
get isStale() {
|
|
71
|
+
return AsyncResult.isWaiting(state.current) && Option.isSome(AsyncResult.value(state.current));
|
|
72
|
+
}
|
|
63
73
|
};
|
|
64
74
|
}
|
|
65
75
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effect.svelte.js","names":["getRuntime"],"sources":["../src/effect.svelte.ts"],"sourcesContent":["import { Effect, Fiber } from \"effect\";\nimport
|
|
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,14 +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";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
6
|
+
import { UseStreamOptions, UseStreamReturn, useStream } from "./stream.svelte.js";
|
|
7
|
+
import { UseLiveStreamOptions, UseLiveStreamReturn, useLiveStream } from "./live-stream.svelte.js";
|
|
8
|
+
import { DerivedReturn, UseDerivedOptions, useDerived } from "./derived.svelte.js";
|
|
9
|
+
import { UseQueryOptions, UseQueryReturn, useQuery } from "./query.svelte.js";
|
|
10
|
+
import { MutationCallbacks } from "./internal/mutation.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";
|
|
11
16
|
import { SignalEmitter } from "./emitter.js";
|
|
12
17
|
import { Store_d_exports } from "./Store.js";
|
|
13
18
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
14
|
-
export { AsyncResult, type DerivedReturn, type ReactiveMutationReturn, type ReactiveQueryReturn, type ReactiveStreamReturn, type RuntimeLike, SignalEmitter, Store_d_exports as Store, SvelteRuntime, type UseEffectOptions, type UseEffectReturn, type UsePubSubReturn, type UseQueryReturn, type UseStreamReturn, type UseSubscriptionRefReturn, defaultSvelteRuntime, getRuntimeContext as getRuntime, provideRuntime, reactiveMutation, reactiveQuery, reactiveStream, useDerived, useEffect, useInvalidateKeys, usePubSub, useQuery, useScope, useScopeCallback, useStream, useSubscriptionRef };
|
|
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,13 +1,17 @@
|
|
|
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";
|
|
9
|
+
import { useMutation } from "./mutation.svelte.js";
|
|
10
|
+
import { useQueryPromise } from "./await.svelte.js";
|
|
8
11
|
import { usePubSub, useSubscriptionRef } from "./subscription.svelte.js";
|
|
12
|
+
import { useWritableRef } from "./writable-ref.svelte.js";
|
|
9
13
|
import { reactiveMutation, reactiveQuery, reactiveStream, useInvalidateKeys } from "./reactivity.svelte.js";
|
|
10
14
|
import { SignalEmitter } from "./emitter.js";
|
|
11
15
|
import { Store_exports } from "./Store.js";
|
|
12
16
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
13
|
-
export { AsyncResult, SignalEmitter, Store_exports as Store, SvelteRuntime, defaultSvelteRuntime, getRuntimeContext as getRuntime, provideRuntime, reactiveMutation, reactiveQuery, reactiveStream, useDerived, useEffect, useInvalidateKeys, usePubSub, useQuery, useScope, useScopeCallback, useStream, useSubscriptionRef };
|
|
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 };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Cause, Option } from "effect";
|
|
2
|
+
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
3
|
+
//#region src/internal/await.ts
|
|
4
|
+
/**
|
|
5
|
+
* Convert an {@link AsyncResult.AsyncResult} into a `Promise`, so Effect-backed
|
|
6
|
+
* state can drive Svelte 5's native async (`{#await}` blocks and
|
|
7
|
+
* `<svelte:boundary>` suspense), which both consume a real thenable.
|
|
8
|
+
*
|
|
9
|
+
* Mapping:
|
|
10
|
+
* - **Failure** → a rejected promise (with the typed error `E` when present,
|
|
11
|
+
* otherwise the squashed cause). Checked first, so a failure that carries a
|
|
12
|
+
* previous success still surfaces the error in the `{:catch}` block.
|
|
13
|
+
* - **Holds a value** (a `Success`, including a waiting one during
|
|
14
|
+
* stale-while-revalidate) → a resolved promise, so the UI keeps showing data
|
|
15
|
+
* while a refresh is in flight.
|
|
16
|
+
* - **Initial / waiting with no value** → a perpetually-pending promise, so the
|
|
17
|
+
* `{#await}` loading branch (or the boundary's `pending` snippet) shows.
|
|
18
|
+
*
|
|
19
|
+
* Callers should memoise the result (e.g. via `$derived`) so a fresh pending
|
|
20
|
+
* promise is not produced on unrelated re-renders — a new promise identity is
|
|
21
|
+
* only wanted when the underlying result transitions.
|
|
22
|
+
*/
|
|
23
|
+
function resultToPromise(result) {
|
|
24
|
+
if (AsyncResult.isFailure(result)) {
|
|
25
|
+
const cause = AsyncResult.cause(result);
|
|
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"));
|
|
30
|
+
}
|
|
31
|
+
const value = AsyncResult.value(result);
|
|
32
|
+
if (Option.isSome(value)) return Promise.resolve(value.value);
|
|
33
|
+
return new Promise(() => {});
|
|
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
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { resultToPromise };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=await.js.map
|
|
@@ -0,0 +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 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"}
|