effect-atom-svelte 0.0.0-stage → 0.1.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.
@@ -0,0 +1,127 @@
1
+ <!--
2
+ @component
3
+ Hydrates dehydrated atom state, for example from `Hydration.dehydrate` returned by a remote
4
+ function. Atoms new to the registry are hydrated before children render; in the browser, atoms
5
+ that already exist are updated after render, so current UI does not jump to the incoming data
6
+ mid-render. On the server, where nothing renders again, they are updated at once.
7
+
8
+ A value for an atom nobody reads waits in the registry until something does. When the boundary
9
+ is destroyed, as at the end of a server render, the values it brought that are still unread are
10
+ dropped, so a registry that outlives the boundary doesn't hand them to a later reader.
11
+
12
+ **Example** (Hydrating the state a load function dehydrated)
13
+
14
+ ```svelte
15
+ <script lang="ts">
16
+ import { HydrationBoundary } from "effect-atom-svelte";
17
+
18
+ import TodoList from "./todo-list.svelte";
19
+
20
+ // `data.state` is `Hydration.dehydrate(registry)`, returned by +page.server.ts.
21
+ const { data } = $props();
22
+ </script>
23
+
24
+ <HydrationBoundary state={data.state}>
25
+ <TodoList />
26
+ </HydrationBoundary>
27
+ ```
28
+ -->
29
+ <script lang="ts">
30
+ import { Hydration } from "effect/reactivity";
31
+ import { BROWSER } from "esm-env";
32
+ import { onDestroy } from "svelte";
33
+ import type { Snippet } from "svelte";
34
+
35
+ import { getRegistry } from "./RegistryContext.js";
36
+
37
+ interface Props {
38
+ readonly state?: Iterable<Hydration.DehydratedAtom> | undefined;
39
+ readonly children: Snippet;
40
+ }
41
+
42
+ const { children, state }: Props = $props();
43
+ const registry = getRegistry();
44
+
45
+ // SAFETY: preloadedSerializable is on the registry implementation, not the interface (Effect
46
+ // 4.0.0). It holds the encoded values Hydration.hydrate queued, until a lookup of the key takes one.
47
+ const { preloadedSerializable: preloaded } = registry as unknown as {
48
+ readonly preloadedSerializable?: Map<string, unknown>;
49
+ };
50
+ // The values this boundary queued, by key, so it can drop the ones nobody took.
51
+ const queued = new Map<string, unknown>();
52
+
53
+ const queue = (atoms: readonly Hydration.DehydratedAtomValue[]): void => {
54
+ Hydration.hydrate(registry, atoms);
55
+ for (const { key, value } of atoms) {
56
+ queued.set(key, value);
57
+ }
58
+ };
59
+
60
+ const hydrateNew = (
61
+ incoming: Iterable<Hydration.DehydratedAtom> | undefined
62
+ ): Hydration.DehydratedAtomValue[] => {
63
+ const nodes = registry.getNodes();
64
+ const fresh: Hydration.DehydratedAtomValue[] = [];
65
+ const existing: Hydration.DehydratedAtomValue[] = [];
66
+ for (const atom of Hydration.toValues([...(incoming ?? [])])) {
67
+ (nodes.has(atom.key) ? existing : fresh).push(atom);
68
+ }
69
+ queue(fresh);
70
+ return existing;
71
+ };
72
+
73
+ const hydrateExisting = (
74
+ existing: readonly Hydration.DehydratedAtomValue[]
75
+ ): void => {
76
+ queue(existing);
77
+ // hydrate only queues values for existing nodes; the registry applies a queued value the next
78
+ // time the node is looked up, so look each one up now to update its subscribers.
79
+ const nodes = registry.getNodes();
80
+ for (const { key } of existing) {
81
+ const node = nodes.get(key);
82
+ if (node) {
83
+ registry.get(node.atom);
84
+ }
85
+ }
86
+ };
87
+
88
+ // Read once at init so children render with the new atoms already in place.
89
+ // svelte-ignore state_referenced_locally
90
+ let hydrated = state;
91
+ // svelte-ignore state_referenced_locally
92
+ let deferred = hydrateNew(state);
93
+ // Effects don't run on the server, and nothing there renders again, so the atoms that already
94
+ // exist are updated now, before the children read them.
95
+ if (!BROWSER) {
96
+ hydrateExisting(deferred);
97
+ deferred = [];
98
+ }
99
+
100
+ $effect(() => {
101
+ const incoming = state;
102
+ if (incoming !== hydrated) {
103
+ hydrated = incoming;
104
+ deferred = [...deferred, ...hydrateNew(incoming)];
105
+ }
106
+ if (deferred.length > 0) {
107
+ hydrateExisting(deferred);
108
+ deferred = [];
109
+ }
110
+ });
111
+
112
+ // A queued value stays in the registry until its key is looked up. With a registry that outlives
113
+ // the boundary, such as one the caller passes to the server's provider, it would reach a later
114
+ // request. Drop the ones still waiting, unless something queued another value since.
115
+ onDestroy(() => {
116
+ if (!preloaded) {
117
+ return;
118
+ }
119
+ for (const [key, value] of queued) {
120
+ if (preloaded.get(key) === value) {
121
+ preloaded.delete(key);
122
+ }
123
+ }
124
+ });
125
+ </script>
126
+
127
+ {@render children()}
@@ -0,0 +1,36 @@
1
+ import { Hydration } from "effect/reactivity";
2
+ import type { Snippet } from "svelte";
3
+ interface Props {
4
+ readonly state?: Iterable<Hydration.DehydratedAtom> | undefined;
5
+ readonly children: Snippet;
6
+ }
7
+ /**
8
+ * Hydrates dehydrated atom state, for example from `Hydration.dehydrate` returned by a remote
9
+ * function. Atoms new to the registry are hydrated before children render; in the browser, atoms
10
+ * that already exist are updated after render, so current UI does not jump to the incoming data
11
+ * mid-render. On the server, where nothing renders again, they are updated at once.
12
+ *
13
+ * A value for an atom nobody reads waits in the registry until something does. When the boundary
14
+ * is destroyed, as at the end of a server render, the values it brought that are still unread are
15
+ * dropped, so a registry that outlives the boundary doesn't hand them to a later reader.
16
+ *
17
+ * **Example** (Hydrating the state a load function dehydrated)
18
+ *
19
+ * ```svelte
20
+ * <script lang="ts">
21
+ * import { HydrationBoundary } from "effect-atom-svelte";
22
+ *
23
+ * import TodoList from "./todo-list.svelte";
24
+ *
25
+ * // `data.state` is `Hydration.dehydrate(registry)`, returned by +page.server.ts.
26
+ * const { data } = $props();
27
+ * </script>
28
+ *
29
+ * <HydrationBoundary state={data.state}>
30
+ * <TodoList />
31
+ * </HydrationBoundary>
32
+ * ```
33
+ */
34
+ declare const HydrationBoundary: import("svelte").Component<Props, {}, "">;
35
+ type HydrationBoundary = ReturnType<typeof HydrationBoundary>;
36
+ export default HydrationBoundary;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The registry that hooks read from, and how a component tree gets one.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ import { AtomRegistry } from "effect/reactivity";
7
+ /**
8
+ * Options for a registry created by `provideRegistry` or `RegistryProvider`: the options of
9
+ * Effect's `AtomRegistry.make`, all optional.
10
+ *
11
+ * - `initialValues`: pairs of an atom and its starting value, such as `[[tokenAtom, token]]`.
12
+ * - `scheduleTask`: schedules the registry's deferred work, such as disposing of atoms nobody
13
+ * reads. It takes a task and returns a function that cancels it.
14
+ * - `defaultIdleTTL`: in milliseconds, how long an atom nobody reads is kept before it is disposed
15
+ * of, for atoms without an idle TTL of their own. Without it, such atoms are disposed of shortly
16
+ * after their last reader goes.
17
+ * - `timeoutResolution`: in milliseconds, how finely idle timeouts are grouped. Defaults to half
18
+ * of `defaultIdleTTL`, or 1000.
19
+ *
20
+ * @stability unstable
21
+ * @since 0.1.0
22
+ * @category models
23
+ */
24
+ export type RegistryOptions = NonNullable<Parameters<typeof AtomRegistry.make>[0]>;
25
+ /**
26
+ * Returns the registry from the nearest `provideRegistry` or `RegistryProvider`.
27
+ *
28
+ * In the browser a shared default registry is used when none is provided. On the server there is
29
+ * no default: one module-level registry would share atom state between concurrent requests.
30
+ *
31
+ * **Example** (Writing an atom from code that is not reactive)
32
+ *
33
+ * ```ts
34
+ * import { getRegistry } from "effect-atom-svelte";
35
+ * import { countAtom } from "./atoms.ts";
36
+ *
37
+ * const registry = getRegistry(); // while the component initializes
38
+ * const reset = () => registry.set(countAtom, 0);
39
+ * ```
40
+ *
41
+ * @stability unstable
42
+ * @since 0.1.0
43
+ * @category registry
44
+ */
45
+ export declare const getRegistry: () => AtomRegistry.AtomRegistry;
46
+ /**
47
+ * The options `provideRegistry` and `RegistryProvider` take whether they create a registry or are
48
+ * given one.
49
+ *
50
+ * @stability unstable
51
+ * @since 0.1.0
52
+ * @category models
53
+ */
54
+ export interface ProvideRegistryCommon {
55
+ /**
56
+ * Run server-rendered async atoms again once the page has hydrated. Defaults to `false`: the
57
+ * server's value is milliseconds old. Children inherit it; the async hooks' own option overrides
58
+ * it. `@effect/atom-react` runs atoms wrapped by `withReactivity` and similar again.
59
+ */
60
+ readonly revalidateOnHydrate?: boolean | undefined;
61
+ }
62
+ /**
63
+ * Provides a registry made elsewhere. The `AtomRegistry.make` options apply only to a registry the
64
+ * provider creates, so they are not accepted here.
65
+ *
66
+ * @stability unstable
67
+ * @since 0.1.0
68
+ * @category models
69
+ */
70
+ export interface ProvideExistingRegistry extends ProvideRegistryCommon {
71
+ /** An existing registry to provide instead of creating one. The caller disposes of it. */
72
+ readonly registry: AtomRegistry.AtomRegistry;
73
+ readonly initialValues?: undefined;
74
+ readonly scheduleTask?: undefined;
75
+ readonly timeoutResolution?: undefined;
76
+ readonly defaultIdleTTL?: undefined;
77
+ }
78
+ /**
79
+ * Creates a registry with the `AtomRegistry.make` options, owned by the component.
80
+ *
81
+ * @stability unstable
82
+ * @since 0.1.0
83
+ * @category models
84
+ */
85
+ export interface ProvideNewRegistry extends RegistryOptions, ProvideRegistryCommon {
86
+ readonly registry?: undefined;
87
+ }
88
+ /**
89
+ * Options for `provideRegistry` and `RegistryProvider`: an existing `registry`, or the options for
90
+ * a new one, not both (JND-61).
91
+ *
92
+ * @stability unstable
93
+ * @since 0.1.0
94
+ * @category models
95
+ */
96
+ export type ProvideRegistryOptions = ProvideExistingRegistry | ProvideNewRegistry;
97
+ /**
98
+ * Puts a registry in context for this component and its children.
99
+ *
100
+ * A registry created here is owned by the component and disposed of with it, which on the server
101
+ * means at the end of the request. A registry passed in is left for the caller to dispose of.
102
+ *
103
+ * **Example** (Giving the app a registry from the root layout)
104
+ *
105
+ * ```ts
106
+ * import { provideRegistry } from "effect-atom-svelte";
107
+ *
108
+ * // In src/routes/+layout.svelte: one registry per server request,
109
+ * // disposed of when the request ends
110
+ * provideRegistry();
111
+ * ```
112
+ *
113
+ * @stability unstable
114
+ * @since 0.1.0
115
+ * @category registry
116
+ */
117
+ export declare const provideRegistry: (options?: ProvideRegistryOptions) => AtomRegistry.AtomRegistry;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * The registry that hooks read from, and how a component tree gets one.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ import { AtomRegistry } from "effect/reactivity";
7
+ import { BROWSER } from "esm-env";
8
+ import { createContext, onDestroy } from "svelte";
9
+ import { setRevalidateOnHydrate } from "./internal/hydration.js";
10
+ const [getContextRegistry, setContextRegistry, hasContextRegistry] = createContext();
11
+ let browserRegistry;
12
+ /**
13
+ * Returns the registry from the nearest `provideRegistry` or `RegistryProvider`.
14
+ *
15
+ * In the browser a shared default registry is used when none is provided. On the server there is
16
+ * no default: one module-level registry would share atom state between concurrent requests.
17
+ *
18
+ * **Example** (Writing an atom from code that is not reactive)
19
+ *
20
+ * ```ts
21
+ * import { getRegistry } from "effect-atom-svelte";
22
+ * import { countAtom } from "./atoms.ts";
23
+ *
24
+ * const registry = getRegistry(); // while the component initializes
25
+ * const reset = () => registry.set(countAtom, 0);
26
+ * ```
27
+ *
28
+ * @stability unstable
29
+ * @since 0.1.0
30
+ * @category registry
31
+ */
32
+ export const getRegistry = () => {
33
+ if (hasContextRegistry()) {
34
+ return getContextRegistry();
35
+ }
36
+ if (!BROWSER) {
37
+ throw new Error("No AtomRegistry in context. Wrap the app in <RegistryProvider> so each server request gets its own registry.");
38
+ }
39
+ browserRegistry ??= AtomRegistry.make();
40
+ return browserRegistry;
41
+ };
42
+ /**
43
+ * Puts a registry in context for this component and its children.
44
+ *
45
+ * A registry created here is owned by the component and disposed of with it, which on the server
46
+ * means at the end of the request. A registry passed in is left for the caller to dispose of.
47
+ *
48
+ * **Example** (Giving the app a registry from the root layout)
49
+ *
50
+ * ```ts
51
+ * import { provideRegistry } from "effect-atom-svelte";
52
+ *
53
+ * // In src/routes/+layout.svelte: one registry per server request,
54
+ * // disposed of when the request ends
55
+ * provideRegistry();
56
+ * ```
57
+ *
58
+ * @stability unstable
59
+ * @since 0.1.0
60
+ * @category registry
61
+ */
62
+ export const provideRegistry = (options = {}) => {
63
+ const { defaultIdleTTL, initialValues, registry: provided, revalidateOnHydrate, scheduleTask, timeoutResolution, } = options;
64
+ // Picked one by one, as RegistryProvider passes its props here, children included.
65
+ const registryOptions = {
66
+ defaultIdleTTL,
67
+ initialValues,
68
+ scheduleTask,
69
+ timeoutResolution,
70
+ };
71
+ if (provided &&
72
+ Object.values(registryOptions).some((value) => value !== undefined)) {
73
+ // The types rule this out; a caller without them would otherwise lose the options silently.
74
+ throw new Error("provideRegistry takes an existing registry or options for a new one, not both. Apply initialValues to the existing registry yourself.");
75
+ }
76
+ const registry = provided ?? AtomRegistry.make(registryOptions);
77
+ setContextRegistry(registry);
78
+ if (revalidateOnHydrate !== undefined) {
79
+ setRevalidateOnHydrate(revalidateOnHydrate);
80
+ }
81
+ if (!provided) {
82
+ onDestroy(() => registry.dispose());
83
+ }
84
+ return registry;
85
+ };
@@ -0,0 +1,60 @@
1
+ <!--
2
+ @component
3
+ Puts an atom registry in context for its children: one per request on the server, and in the
4
+ browser one per mount of the provider, disposed of when it unmounts. A root layout's provider
5
+ stays mounted, so its registry lasts the whole visit. Takes the AtomRegistry.make options, or an
6
+ existing `registry`, and `revalidateOnHydrate` to run server-rendered async atoms again once the
7
+ page has hydrated.
8
+
9
+ The props are read once, when the provider sets up: changing them later doesn't change the
10
+ registry or create a new one. Development builds warn when `registry` or `revalidateOnHydrate`
11
+ changes. To start over with other options, remount the provider, for example with `{#key}`.
12
+
13
+ **Example** (Providing a registry from the root layout)
14
+
15
+ ```svelte
16
+ <script lang="ts">
17
+ import { RegistryProvider } from "effect-atom-svelte";
18
+
19
+ const { children } = $props();
20
+ </script>
21
+
22
+ <RegistryProvider>{@render children()}</RegistryProvider>
23
+ ```
24
+ -->
25
+ <script lang="ts">
26
+ import { DEV } from "esm-env";
27
+ import type { Snippet } from "svelte";
28
+
29
+ import { provideRegistry } from "./RegistryContext.js";
30
+ import type { ProvideRegistryOptions } from "./RegistryContext.js";
31
+
32
+ type Props = ProvideRegistryOptions & { readonly children: Snippet };
33
+
34
+ const props: Props = $props();
35
+ // Props are read once: a registry is created for the provider's lifetime, not per prop change.
36
+ // provideRegistry passes on only the options it knows, so `children` is ignored there.
37
+ // svelte-ignore state_referenced_locally
38
+ provideRegistry(props);
39
+
40
+ if (DEV) {
41
+ // Only the props compared by identity: an option written inline, such as `initialValues`, can
42
+ // be a new array on every read without having changed.
43
+ // svelte-ignore state_referenced_locally
44
+ const { registry, revalidateOnHydrate } = props;
45
+ let warned = false;
46
+ $effect(() => {
47
+ const changed =
48
+ props.registry !== registry ||
49
+ props.revalidateOnHydrate !== revalidateOnHydrate;
50
+ if (changed && !warned) {
51
+ warned = true;
52
+ console.warn(
53
+ "effect-atom-svelte: RegistryProvider reads its props once, so the new `registry` or `revalidateOnHydrate` is ignored. Remount the provider, for example with {#key}, to apply it."
54
+ );
55
+ }
56
+ });
57
+ }
58
+ </script>
59
+
60
+ {@render props.children()}
@@ -0,0 +1,31 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { ProvideRegistryOptions } from "./RegistryContext.ts";
3
+ type Props = ProvideRegistryOptions & {
4
+ readonly children: Snippet;
5
+ };
6
+ /**
7
+ * Puts an atom registry in context for its children: one per request on the server, and in the
8
+ * browser one per mount of the provider, disposed of when it unmounts. A root layout's provider
9
+ * stays mounted, so its registry lasts the whole visit. Takes the AtomRegistry.make options, or an
10
+ * existing `registry`, and `revalidateOnHydrate` to run server-rendered async atoms again once the
11
+ * page has hydrated.
12
+ *
13
+ * The props are read once, when the provider sets up: changing them later doesn't change the
14
+ * registry or create a new one. Development builds warn when `registry` or `revalidateOnHydrate`
15
+ * changes. To start over with other options, remount the provider, for example with `{#key}`.
16
+ *
17
+ * **Example** (Providing a registry from the root layout)
18
+ *
19
+ * ```svelte
20
+ * <script lang="ts">
21
+ * import { RegistryProvider } from "effect-atom-svelte";
22
+ *
23
+ * const { children } = $props();
24
+ * </script>
25
+ *
26
+ * <RegistryProvider>{@render children()}</RegistryProvider>
27
+ * ```
28
+ */
29
+ declare const RegistryProvider: import("svelte").Component<Props, {}, "">;
30
+ type RegistryProvider = ReturnType<typeof RegistryProvider>;
31
+ export default RegistryProvider;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Atoms scoped to a component subtree.
3
+ *
4
+ * @since 0.1.0
5
+ */
6
+ import type { Atom } from "effect/reactivity";
7
+ /**
8
+ * Marks a value as a `ScopedAtom`: every scoped atom has this key, set to this same string, as
9
+ * Effect's own types carry a type id.
10
+ *
11
+ * @stability unstable
12
+ * @since 0.1.0
13
+ * @category type ids
14
+ */
15
+ export declare const TypeId: "~effect-atom-svelte/ScopedAtom";
16
+ /**
17
+ * The type of `TypeId`.
18
+ *
19
+ * @stability unstable
20
+ * @since 0.1.0
21
+ * @category type ids
22
+ */
23
+ export type TypeId = typeof TypeId;
24
+ /**
25
+ * An atom created per component subtree. `provide` builds it once in a parent and puts it in
26
+ * context; `use` reads it in descendants. `provide` takes the factory's input, which may be left
27
+ * out when the factory's input is optional.
28
+ *
29
+ * @stability unstable
30
+ * @since 0.1.0
31
+ * @category models
32
+ */
33
+ export interface ScopedAtom<A extends Atom.Atom<unknown>, Input = never> {
34
+ readonly [TypeId]: TypeId;
35
+ readonly provide: [Input] extends [never] ? () => A : undefined extends Input ? (input?: Input) => A : (input: Input) => A;
36
+ readonly use: () => A;
37
+ }
38
+ /**
39
+ * Options for `ScopedAtom.make`.
40
+ *
41
+ * @stability unstable
42
+ * @since 0.1.0
43
+ * @category models
44
+ */
45
+ export interface MakeOptions {
46
+ /** A name for error messages, such as the one thrown when `use` finds no provider. */
47
+ readonly name?: string | undefined;
48
+ }
49
+ /**
50
+ * Creates a scoped atom from a factory, which runs once per providing component.
51
+ *
52
+ * Each provider gets its own atom, so a factory that adds `Atom.serializable` with a fixed key
53
+ * gives every copy the same key: once two providers are on a page, reading them with
54
+ * `useAtomResult` or `useAtomSuspense` throws, as two different atoms share the serialization
55
+ * key. Put the input in the key, or leave scoped atoms unserialized.
56
+ *
57
+ * **Example** (A counter per subtree, started from an input)
58
+ *
59
+ * ```ts
60
+ * import { ScopedAtom } from "effect-atom-svelte";
61
+ * import { Atom } from "effect/reactivity";
62
+ *
63
+ * export const Counter = ScopedAtom.make((start: number) => Atom.make(start), {
64
+ * name: "Counter",
65
+ * });
66
+ * // A parent's script calls Counter.provide(0); descendants call Counter.use()
67
+ * ```
68
+ *
69
+ * @stability unstable
70
+ * @since 0.1.0
71
+ * @category constructors
72
+ */
73
+ export declare const make: <A extends Atom.Atom<unknown>, Input = never>(f: (() => A) | ((input: Input) => A), options?: MakeOptions) => ScopedAtom<A, Input>;
@@ -0,0 +1,55 @@
1
+ import { createContext } from "svelte";
2
+ // A const and a type of the same name, as in Effect's modules.
3
+ /* oxlint-disable eslint/no-redeclare */
4
+ /**
5
+ * Marks a value as a `ScopedAtom`: every scoped atom has this key, set to this same string, as
6
+ * Effect's own types carry a type id.
7
+ *
8
+ * @stability unstable
9
+ * @since 0.1.0
10
+ * @category type ids
11
+ */
12
+ export const TypeId = "~effect-atom-svelte/ScopedAtom";
13
+ /**
14
+ * Creates a scoped atom from a factory, which runs once per providing component.
15
+ *
16
+ * Each provider gets its own atom, so a factory that adds `Atom.serializable` with a fixed key
17
+ * gives every copy the same key: once two providers are on a page, reading them with
18
+ * `useAtomResult` or `useAtomSuspense` throws, as two different atoms share the serialization
19
+ * key. Put the input in the key, or leave scoped atoms unserialized.
20
+ *
21
+ * **Example** (A counter per subtree, started from an input)
22
+ *
23
+ * ```ts
24
+ * import { ScopedAtom } from "effect-atom-svelte";
25
+ * import { Atom } from "effect/reactivity";
26
+ *
27
+ * export const Counter = ScopedAtom.make((start: number) => Atom.make(start), {
28
+ * name: "Counter",
29
+ * });
30
+ * // A parent's script calls Counter.provide(0); descendants call Counter.use()
31
+ * ```
32
+ *
33
+ * @stability unstable
34
+ * @since 0.1.0
35
+ * @category constructors
36
+ */
37
+ export const make = (f, options) => {
38
+ const [get, set, has] = createContext();
39
+ const provide = (...args) => set(args.length === 0
40
+ ? f()
41
+ : f(args[0]));
42
+ return {
43
+ [TypeId]: TypeId,
44
+ provide: provide,
45
+ use: () => {
46
+ if (!has()) {
47
+ const name = options?.name;
48
+ const which = name ? `ScopedAtom "${name}"` : "ScopedAtom";
49
+ const call = name ? `${name}.provide()` : "its provide()";
50
+ throw new Error(`${which} used outside of the component that provides it. Call ${call} in a parent component's script.`);
51
+ }
52
+ return get();
53
+ },
54
+ };
55
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * SvelteKit `handleError` hooks that keep an Effect error's `_tag`, so a `<svelte:boundary>`
3
+ * `failed` snippet can tell typed errors apart. Import them from `effect-atom-svelte/sveltekit`.
4
+ *
5
+ * The module does not import SvelteKit: the hooks take the fields of SvelteKit's input they read
6
+ * and return the fields they set, so they type-check against `HandleClientError` and
7
+ * `HandleServerError` once `App.Error` declares `tag?: string`.
8
+ *
9
+ * @since 0.1.0
10
+ */
11
+ /**
12
+ * The part of SvelteKit's `handleError` input that the hooks read. In SvelteKit 3, `kind` is
13
+ * `"unknown"` for errors thrown by your code, `"app"` for `error(...)`, `"framework"` for
14
+ * SvelteKit's own errors (such as 404s) and, on the server, `"validation"` for invalid remote
15
+ * function arguments. SvelteKit 2 passes no `kind`, so the hooks treat every error as `"unknown"`,
16
+ * and use the `message` it passes instead when they set none.
17
+ *
18
+ * @stability unstable
19
+ * @since 0.1.0
20
+ * @category models
21
+ */
22
+ export interface CaughtError {
23
+ readonly kind?: string | undefined;
24
+ readonly error: unknown;
25
+ readonly issues?: unknown;
26
+ /** SvelteKit 2's message for the error, read only when there is no `kind`. */
27
+ readonly message?: string | undefined;
28
+ }
29
+ /**
30
+ * The `App.Error` fields the hooks set. SvelteKit keeps its own `status` and `message` for any
31
+ * field left out.
32
+ *
33
+ * @stability unstable
34
+ * @since 0.1.0
35
+ * @category models
36
+ */
37
+ export interface EffectErrorBody {
38
+ readonly message?: string;
39
+ readonly tag?: string;
40
+ }
41
+ /**
42
+ * A client `handleError` hook (`src/hooks.client.ts`) that keeps the message and `_tag` of errors
43
+ * thrown by your code, so a boundary's `failed` snippet shows them instead of
44
+ * `{ status: 500, message: "Internal Error" }`. It logs those errors like SvelteKit's default
45
+ * hook, and leaves `error(...)` and SvelteKit's own errors as they are. Made for SvelteKit 3:
46
+ * SvelteKit 2 doesn't say which errors are its own, so there every error is kept and logged.
47
+ *
48
+ * **Example** (Using it as the client hook)
49
+ *
50
+ * ```ts
51
+ * // src/hooks.client.ts
52
+ * export { handleClientError as handleError } from "effect-atom-svelte/sveltekit";
53
+ * ```
54
+ *
55
+ * @stability unstable
56
+ * @since 0.1.0
57
+ * @category hooks
58
+ */
59
+ export declare const handleClientError: (input: CaughtError) => EffectErrorBody | undefined;
60
+ /**
61
+ * A server `handleError` hook (`src/hooks.server.ts`) that keeps the `_tag` of errors thrown by
62
+ * your code but not their message, which could expose details of the server to users: the
63
+ * message stays SvelteKit's `"Internal Error"`. It logs errors with `console.error`.
64
+ * Server errors reach a boundary's `failed` snippet when it renders on the server. Made for
65
+ * SvelteKit 3: SvelteKit 2 doesn't say which errors are its own, so there every error is logged.
66
+ *
67
+ * **Example** (Using it as the server hook)
68
+ *
69
+ * ```ts
70
+ * // src/hooks.server.ts
71
+ * export { handleServerError as handleError } from "effect-atom-svelte/sveltekit";
72
+ * ```
73
+ *
74
+ * @stability unstable
75
+ * @since 0.1.0
76
+ * @category hooks
77
+ */
78
+ export declare const handleServerError: (input: CaughtError) => EffectErrorBody | undefined;