effect-atom-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.
- package/README.md +1 -1
- package/dist/Hooks.svelte.d.ts +16 -10
- package/dist/Hooks.svelte.js +432 -140
- package/dist/HydrationBoundary.svelte +130 -12
- package/dist/HydrationBoundary.svelte.d.ts +13 -4
- package/dist/Inspector.d.ts +7 -3
- package/dist/Inspector.js +79 -12
- package/dist/RegistryContext.d.ts +2 -0
- package/dist/RegistryContext.js +11 -5
- package/dist/ScopedAtom.d.ts +5 -4
- package/dist/ScopedAtom.js +5 -4
- package/dist/SvelteKit.d.ts +28 -3
- package/dist/SvelteKit.js +9 -42
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/boundaries.d.ts +9 -0
- package/dist/internal/boundaries.js +8 -0
- package/dist/internal/disposal.d.ts +13 -0
- package/dist/internal/disposal.js +43 -0
- package/dist/internal/hydration.d.ts +11 -2
- package/dist/internal/hydration.js +23 -3
- package/dist/internal/nodeInternals.d.ts +6 -0
- package/dist/internal/nodeInternals.js +25 -1
- package/dist/internal/renderEnd.d.ts +13 -0
- package/dist/internal/renderEnd.js +34 -0
- package/dist/internal/teardown.svelte.d.ts +17 -0
- package/dist/internal/teardown.svelte.js +44 -0
- package/dist/internal/troubleshooting.d.ts +2 -0
- package/dist/internal/troubleshooting.js +3 -0
- package/package.json +1 -1
package/dist/SvelteKit.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The module does not import SvelteKit: the hooks take the fields of SvelteKit's input they read
|
|
6
6
|
* and return the fields they set, so they type-check against `HandleClientError` and
|
|
7
|
-
* `HandleServerError` once `App.Error` declares `tag?: string`.
|
|
7
|
+
* `HandleServerError` once `App.Error` declares `tag?: string`. In SvelteKit 2, whose `App.Error`
|
|
8
|
+
* requires a message, they are typed to return one when there is no `kind`, as they then always
|
|
9
|
+
* set one.
|
|
8
10
|
*
|
|
9
11
|
* @since 0.1.0
|
|
10
12
|
*/
|
|
@@ -31,52 +33,17 @@ const body = (message, tag) => {
|
|
|
31
33
|
}
|
|
32
34
|
return { ...(message ? { message } : {}), ...(tag ? { tag } : {}) };
|
|
33
35
|
};
|
|
34
|
-
|
|
35
|
-
* A client `handleError` hook (`src/hooks.client.ts`) that keeps the message and `_tag` of errors
|
|
36
|
-
* thrown by your code, so a boundary's `failed` snippet shows them instead of
|
|
37
|
-
* `{ status: 500, message: "Internal Error" }`. It logs those errors like SvelteKit's default
|
|
38
|
-
* hook, and leaves `error(...)` and SvelteKit's own errors as they are. Made for SvelteKit 3:
|
|
39
|
-
* SvelteKit 2 doesn't say which errors are its own, so there every error is kept and logged.
|
|
40
|
-
*
|
|
41
|
-
* **Example** (Using it as the client hook)
|
|
42
|
-
*
|
|
43
|
-
* ```ts
|
|
44
|
-
* // src/hooks.client.ts
|
|
45
|
-
* export { handleClientError as handleError } from "effect-atom-svelte/sveltekit";
|
|
46
|
-
* ```
|
|
47
|
-
*
|
|
48
|
-
* @stability unstable
|
|
49
|
-
* @since 0.1.0
|
|
50
|
-
* @category hooks
|
|
51
|
-
*/
|
|
52
|
-
export const handleClientError = (input) => {
|
|
36
|
+
export function handleClientError(input) {
|
|
53
37
|
// Destructured here, not in the parameter list, so the API reference shows a named parameter.
|
|
54
38
|
const { error, kind = "unknown" } = input;
|
|
55
39
|
if (kind !== "unknown") {
|
|
56
40
|
return undefined;
|
|
57
41
|
}
|
|
58
42
|
console.error(error);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* your code but not their message, which could expose details of the server to users: the
|
|
64
|
-
* message stays SvelteKit's `"Internal Error"`. It logs errors with `console.error`.
|
|
65
|
-
* Server errors reach a boundary's `failed` snippet when it renders on the server. Made for
|
|
66
|
-
* SvelteKit 3: SvelteKit 2 doesn't say which errors are its own, so there every error is logged.
|
|
67
|
-
*
|
|
68
|
-
* **Example** (Using it as the server hook)
|
|
69
|
-
*
|
|
70
|
-
* ```ts
|
|
71
|
-
* // src/hooks.server.ts
|
|
72
|
-
* export { handleServerError as handleError } from "effect-atom-svelte/sveltekit";
|
|
73
|
-
* ```
|
|
74
|
-
*
|
|
75
|
-
* @stability unstable
|
|
76
|
-
* @since 0.1.0
|
|
77
|
-
* @category hooks
|
|
78
|
-
*/
|
|
79
|
-
export const handleServerError = (input) => {
|
|
43
|
+
// `||`, not `??`: an error with an empty message gets SvelteKit 2's instead.
|
|
44
|
+
return body(messageOf(error) || fallbackMessage(input), tagOf(error));
|
|
45
|
+
}
|
|
46
|
+
export function handleServerError(input) {
|
|
80
47
|
const { error, issues, kind = "unknown" } = input;
|
|
81
48
|
if (kind === "validation") {
|
|
82
49
|
console.error("Remote function schema validation failed:", issues);
|
|
@@ -87,4 +54,4 @@ export const handleServerError = (input) => {
|
|
|
87
54
|
}
|
|
88
55
|
console.error(error);
|
|
89
56
|
return body(fallbackMessage(input), tagOf(error));
|
|
90
|
-
}
|
|
57
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -47,4 +47,4 @@ export * as ScopedAtom from "./ScopedAtom.ts";
|
|
|
47
47
|
* @since 0.1.0
|
|
48
48
|
* @category re-exports
|
|
49
49
|
*/
|
|
50
|
-
export { AsyncResult, Atom, AtomHttpApi, AtomRef, AtomRegistry, AtomRpc, Hydration, } from "effect/reactivity";
|
|
50
|
+
export { AsyncResult, Atom, AtomHttpApi, AtomRef, AtomRegistry, AtomRpc, Hydration, Reactivity, } from "effect/reactivity";
|
package/dist/index.js
CHANGED
|
@@ -47,4 +47,4 @@ export * as ScopedAtom from "./ScopedAtom.js";
|
|
|
47
47
|
* @since 0.1.0
|
|
48
48
|
* @category re-exports
|
|
49
49
|
*/
|
|
50
|
-
export { AsyncResult, Atom, AtomHttpApi, AtomRef, AtomRegistry, AtomRpc, Hydration, } from "effect/reactivity";
|
|
50
|
+
export { AsyncResult, Atom, AtomHttpApi, AtomRef, AtomRegistry, AtomRpc, Hydration, Reactivity, } from "effect/reactivity";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AtomRegistry } from "effect/reactivity";
|
|
2
|
+
/**
|
|
3
|
+
* Per registry, key and queued value, how many live boundaries queued it. Boundaries given the
|
|
4
|
+
* same state, as a layout and its page might be, queue the same values, and one destroyed first
|
|
5
|
+
* must leave them for the other's children.
|
|
6
|
+
*/
|
|
7
|
+
export declare const queuedBy: WeakMap<AtomRegistry.AtomRegistry, Map<string, Map<unknown, number>>>;
|
|
8
|
+
/** Whether a live boundary brought a value for the key: it sends that value to the browser itself. */
|
|
9
|
+
export declare const queuedByBoundary: (registry: AtomRegistry.AtomRegistry, key: string) => boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per registry, key and queued value, how many live boundaries queued it. Boundaries given the
|
|
3
|
+
* same state, as a layout and its page might be, queue the same values, and one destroyed first
|
|
4
|
+
* must leave them for the other's children.
|
|
5
|
+
*/
|
|
6
|
+
export const queuedBy = new WeakMap();
|
|
7
|
+
/** Whether a live boundary brought a value for the key: it sends that value to the browser itself. */
|
|
8
|
+
export const queuedByBoundary = (registry, key) => queuedBy.get(registry)?.has(key) === true;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AtomRegistry } from "effect/reactivity";
|
|
2
|
+
/** Calls `f` once if `registry` is disposed of through `disposeRegistry`; returns the canceler. */
|
|
3
|
+
export declare const onDispose: (registry: AtomRegistry.AtomRegistry, f: () => void) => (() => void);
|
|
4
|
+
/**
|
|
5
|
+
* Disposes of `registry`, then runs what waited on it, even when a finalizer the disposal runs
|
|
6
|
+
* throws.
|
|
7
|
+
*
|
|
8
|
+
* In the browser that error is rethrown on a microtask, so it is still reported as uncaught but
|
|
9
|
+
* stays out of Svelte's teardown: thrown from the `onDestroy` of a component being destroyed, it
|
|
10
|
+
* escapes Svelte's flush and leaves every `$derived` on the page running again on every read. On
|
|
11
|
+
* the server it is thrown here.
|
|
12
|
+
*/
|
|
13
|
+
export declare const disposeRegistry: (registry: AtomRegistry.AtomRegistry) => void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BROWSER } from "esm-env";
|
|
2
|
+
const callbacks = new WeakMap();
|
|
3
|
+
/** Calls `f` once if `registry` is disposed of through `disposeRegistry`; returns the canceler. */
|
|
4
|
+
export const onDispose = (registry, f) => {
|
|
5
|
+
let set = callbacks.get(registry);
|
|
6
|
+
if (set === undefined) {
|
|
7
|
+
set = new Set();
|
|
8
|
+
callbacks.set(registry, set);
|
|
9
|
+
}
|
|
10
|
+
set.add(f);
|
|
11
|
+
return () => {
|
|
12
|
+
set.delete(f);
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Disposes of `registry`, then runs what waited on it, even when a finalizer the disposal runs
|
|
17
|
+
* throws.
|
|
18
|
+
*
|
|
19
|
+
* In the browser that error is rethrown on a microtask, so it is still reported as uncaught but
|
|
20
|
+
* stays out of Svelte's teardown: thrown from the `onDestroy` of a component being destroyed, it
|
|
21
|
+
* escapes Svelte's flush and leaves every `$derived` on the page running again on every read. On
|
|
22
|
+
* the server it is thrown here.
|
|
23
|
+
*/
|
|
24
|
+
export const disposeRegistry = (registry) => {
|
|
25
|
+
try {
|
|
26
|
+
registry.dispose();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (!BROWSER) {
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
queueMicrotask(() => {
|
|
33
|
+
throw error;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
const set = callbacks.get(registry);
|
|
38
|
+
callbacks.delete(registry);
|
|
39
|
+
for (const f of set ?? []) {
|
|
40
|
+
f();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
@@ -6,6 +6,11 @@ export declare const setRevalidateOnHydrate: (value: boolean) => void;
|
|
|
6
6
|
* provider's, else no (JND-19). Call during component init.
|
|
7
7
|
*/
|
|
8
8
|
export declare const revalidatesOnHydrate: (option: boolean | undefined) => boolean;
|
|
9
|
+
/**
|
|
10
|
+
* What the server sends for a result it doesn't pass on, so the browser computes the atom itself.
|
|
11
|
+
* An encoded `AsyncResult` is an object, never `null`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const noSeed: null;
|
|
9
14
|
/**
|
|
10
15
|
* Encodes the server's result for the page, or returns the no-seed marker for a result that must
|
|
11
16
|
* not or cannot travel:
|
|
@@ -17,8 +22,12 @@ export declare const revalidatesOnHydrate: (option: boolean | undefined) => bool
|
|
|
17
22
|
* failing the whole render. Development builds warn.
|
|
18
23
|
*/
|
|
19
24
|
export declare const encodeSeed: (key: string, encode: (value: AsyncResult.AsyncResult<unknown, unknown>) => unknown, result: AsyncResult.AsyncResult<unknown, unknown>) => unknown;
|
|
20
|
-
/**
|
|
21
|
-
|
|
25
|
+
/**
|
|
26
|
+
* The server's result, decoded, or undefined when the server sent none or it doesn't decode with
|
|
27
|
+
* the browser's schema: the browser then computes the atom itself, as for a result the server
|
|
28
|
+
* couldn't encode. Development builds warn.
|
|
29
|
+
*/
|
|
30
|
+
export declare const decodeSeed: (key: string, value: unknown, decode: (value: unknown) => unknown) => unknown;
|
|
22
31
|
/**
|
|
23
32
|
* Whether a seed is still waiting, as a stream's result is between its values. The browser runs
|
|
24
33
|
* the atom again from it, as the server's run ended with the render.
|
|
@@ -19,7 +19,7 @@ export const revalidatesOnHydrate = (option) => option ?? (hasDefault() ? getDef
|
|
|
19
19
|
* What the server sends for a result it doesn't pass on, so the browser computes the atom itself.
|
|
20
20
|
* An encoded `AsyncResult` is an object, never `null`.
|
|
21
21
|
*/
|
|
22
|
-
const noSeed = null;
|
|
22
|
+
export const noSeed = null;
|
|
23
23
|
/**
|
|
24
24
|
* Encodes the server's result for the page, or returns the no-seed marker for a result that must
|
|
25
25
|
* not or cannot travel:
|
|
@@ -48,8 +48,28 @@ export const encodeSeed = (key, encode, result) => {
|
|
|
48
48
|
return noSeed;
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
-
/**
|
|
52
|
-
|
|
51
|
+
/**
|
|
52
|
+
* The server's result, decoded, or undefined when the server sent none or it doesn't decode with
|
|
53
|
+
* the browser's schema: the browser then computes the atom itself, as for a result the server
|
|
54
|
+
* couldn't encode. Development builds warn.
|
|
55
|
+
*/
|
|
56
|
+
export const decodeSeed = (key, value, decode) => {
|
|
57
|
+
if (value === noSeed) {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
return decode(value);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (!Schema.isSchemaError(error)) {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
if (DEV) {
|
|
68
|
+
console.warn(`effect-atom-svelte: the server's result for the serializable atom "${key}" doesn't decode with its schema, so the browser computes the atom again.`, error);
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
53
73
|
/**
|
|
54
74
|
* Whether a seed is still waiting, as a stream's result is between its values. The browser runs
|
|
55
75
|
* the atom again from it, as the server's run ended with the render.
|
|
@@ -14,6 +14,8 @@ export interface NodeHooks {
|
|
|
14
14
|
readonly dispose: (finalizers: number, value: unknown) => void;
|
|
15
15
|
/** A listener was added or removed; the node's `listeners` hold the new set. */
|
|
16
16
|
readonly readers: () => void;
|
|
17
|
+
/** The node's remove() has returned. */
|
|
18
|
+
readonly removed: () => void;
|
|
17
19
|
}
|
|
18
20
|
/** Reports what happens to a node, or returns false if it isn't the implementation this expects. */
|
|
19
21
|
export declare const instrumentNode: (node: AtomRegistry.Node<unknown>, hooks: NodeHooks) => boolean;
|
|
@@ -22,3 +24,7 @@ export declare const instrumentNode: (node: AtomRegistry.Node<unknown>, hooks: N
|
|
|
22
24
|
* which the implementation keeps but the interface doesn't show.
|
|
23
25
|
*/
|
|
24
26
|
export declare const defaultIdleTTL: (registry: AtomRegistry.AtomRegistry) => number | undefined;
|
|
27
|
+
/** Whether the node holds an initial value it hasn't computed (setInitialValue on a new node). */
|
|
28
|
+
export declare const holdsInitialValue: (node: AtomRegistry.Node<unknown>) => boolean;
|
|
29
|
+
/** Whether the node is building and its running build has not read `parent` yet. */
|
|
30
|
+
export declare const buildWillRead: (node: AtomRegistry.Node<unknown>, parent: AtomRegistry.Node<unknown>) => boolean;
|
|
@@ -20,6 +20,13 @@ class Listeners extends Set {
|
|
|
20
20
|
}
|
|
21
21
|
return this;
|
|
22
22
|
}
|
|
23
|
+
clear() {
|
|
24
|
+
const had = this.size > 0;
|
|
25
|
+
super.clear();
|
|
26
|
+
if (had) {
|
|
27
|
+
this.#changed();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
23
30
|
delete(listener) {
|
|
24
31
|
const deleted = super.delete(listener);
|
|
25
32
|
if (deleted) {
|
|
@@ -33,7 +40,15 @@ export const instrumentNode = (node, hooks) => {
|
|
|
33
40
|
if (!isNodeImpl(node)) {
|
|
34
41
|
return false;
|
|
35
42
|
}
|
|
36
|
-
const { build: compute } = node;
|
|
43
|
+
const { build: compute, remove: removeNode } = node;
|
|
44
|
+
node.remove = function remove() {
|
|
45
|
+
try {
|
|
46
|
+
removeNode.call(this);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
hooks.removed();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
37
52
|
let value = node._value;
|
|
38
53
|
let { lifetime } = node;
|
|
39
54
|
node.build = function build() {
|
|
@@ -81,3 +96,12 @@ export const defaultIdleTTL = (registry) => {
|
|
|
81
96
|
.defaultIdleTTL;
|
|
82
97
|
return typeof ttl === "number" ? ttl : undefined;
|
|
83
98
|
};
|
|
99
|
+
/** Whether the node holds an initial value it hasn't computed (setInitialValue on a new node). */
|
|
100
|
+
export const holdsInitialValue = (node) => node
|
|
101
|
+
.preserveInitialValueOnBuild === true;
|
|
102
|
+
/** Whether the node is building and its running build has not read `parent` yet. */
|
|
103
|
+
export const buildWillRead = (node, parent) => {
|
|
104
|
+
const reads = node.lifetime
|
|
105
|
+
?.reads;
|
|
106
|
+
return reads !== undefined && !reads.has(parent);
|
|
107
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `f` once when the server render ends: in `onDestroy`, and failing that when the render's
|
|
3
|
+
* abort signal fires. A `<svelte:boundary>` with a `failed` snippet discards the content of children
|
|
4
|
+
* that throw while setting up, and their `onDestroy` callbacks with it, so a component set up before
|
|
5
|
+
* the throw would otherwise keep its mounts and holds on a caller-owned registry, and the next
|
|
6
|
+
* request would render this one's initial values (JND-17). On the server `getAbortSignal` returns the
|
|
7
|
+
* render's signal, which Svelte aborts once every `onDestroy` has run. A dropped component whose
|
|
8
|
+
* script awaits before calling this resumes after the render has ended and the signal has aborted,
|
|
9
|
+
* where a listener would never fire, so `f` then runs in a microtask: callers take what `f` releases
|
|
10
|
+
* synchronously after calling this. Call it during component init. Svelte bug workaround, to be
|
|
11
|
+
* removed with the fix (https://github.com/jarrednorrisdev/effect-atom-svelte/issues/35).
|
|
12
|
+
*/
|
|
13
|
+
export declare const onRenderEnd: (f: () => void) => void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// When a server render ends, for the hooks that release what they hold in a registry the caller may
|
|
2
|
+
// keep across requests. Internal: not exported from the package.
|
|
3
|
+
import { getAbortSignal, onDestroy } from "svelte";
|
|
4
|
+
/**
|
|
5
|
+
* Runs `f` once when the server render ends: in `onDestroy`, and failing that when the render's
|
|
6
|
+
* abort signal fires. A `<svelte:boundary>` with a `failed` snippet discards the content of children
|
|
7
|
+
* that throw while setting up, and their `onDestroy` callbacks with it, so a component set up before
|
|
8
|
+
* the throw would otherwise keep its mounts and holds on a caller-owned registry, and the next
|
|
9
|
+
* request would render this one's initial values (JND-17). On the server `getAbortSignal` returns the
|
|
10
|
+
* render's signal, which Svelte aborts once every `onDestroy` has run. A dropped component whose
|
|
11
|
+
* script awaits before calling this resumes after the render has ended and the signal has aborted,
|
|
12
|
+
* where a listener would never fire, so `f` then runs in a microtask: callers take what `f` releases
|
|
13
|
+
* synchronously after calling this. Call it during component init. Svelte bug workaround, to be
|
|
14
|
+
* removed with the fix (https://github.com/jarrednorrisdev/effect-atom-svelte/issues/35).
|
|
15
|
+
*/
|
|
16
|
+
export const onRenderEnd = (f) => {
|
|
17
|
+
let done = false;
|
|
18
|
+
const once = () => {
|
|
19
|
+
if (!done) {
|
|
20
|
+
done = true;
|
|
21
|
+
f();
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
onDestroy(once);
|
|
25
|
+
const signal = getAbortSignal();
|
|
26
|
+
if (signal.aborted) {
|
|
27
|
+
// The render already ended, as for a component a failed boundary dropped whose script resumed
|
|
28
|
+
// after an await. Deferred, as callers register before taking what `f` releases.
|
|
29
|
+
queueMicrotask(once);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
signal.addEventListener("abort", once, { once: true });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `f` when the component is destroyed, even while its script is still awaiting. In the browser
|
|
3
|
+
* `onDestroy` only takes effect once the component mounts, so a component removed while pending
|
|
4
|
+
* would never call it. A pre effect runs during init and is torn down with the component (JND-16).
|
|
5
|
+
* On the server, `f` runs when the render ends, even for a component a failed boundary dropped.
|
|
6
|
+
*/
|
|
7
|
+
export declare const onTeardown: (f: () => void) => void;
|
|
8
|
+
/**
|
|
9
|
+
* As `onTeardown`, but for a mounted component `f` runs after its children are destroyed, as with
|
|
10
|
+
* `onDestroy`: a pre effect is torn down before the children, whose own teardown may still use what
|
|
11
|
+
* `f` ends, such as a provider's registry. The pre effect only covers a component destroyed before
|
|
12
|
+
* it mounted. On the server `f` runs through `onRenderEnd`, so `provideRegistry` disposes its
|
|
13
|
+
* registry even when a failed boundary dropped the provider. A successful render keeps the
|
|
14
|
+
* children-first order through `onDestroy`; when the render ends without it, `f` may run before
|
|
15
|
+
* the children's releases, which are then no-ops on the disposed registry.
|
|
16
|
+
*/
|
|
17
|
+
export declare const onTeardownAfterChildren: (f: () => void) => void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// When a component is destroyed, for the hooks and providers. Internal: not exported from the
|
|
2
|
+
// package.
|
|
3
|
+
import { BROWSER } from "esm-env";
|
|
4
|
+
import { onDestroy, onMount } from "svelte";
|
|
5
|
+
import { onRenderEnd } from "./renderEnd.js";
|
|
6
|
+
/**
|
|
7
|
+
* Runs `f` when the component is destroyed, even while its script is still awaiting. In the browser
|
|
8
|
+
* `onDestroy` only takes effect once the component mounts, so a component removed while pending
|
|
9
|
+
* would never call it. A pre effect runs during init and is torn down with the component (JND-16).
|
|
10
|
+
* On the server, `f` runs when the render ends, even for a component a failed boundary dropped.
|
|
11
|
+
*/
|
|
12
|
+
export const onTeardown = (f) => {
|
|
13
|
+
if (BROWSER) {
|
|
14
|
+
$effect.pre(() => f);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
onRenderEnd(f);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* As `onTeardown`, but for a mounted component `f` runs after its children are destroyed, as with
|
|
22
|
+
* `onDestroy`: a pre effect is torn down before the children, whose own teardown may still use what
|
|
23
|
+
* `f` ends, such as a provider's registry. The pre effect only covers a component destroyed before
|
|
24
|
+
* it mounted. On the server `f` runs through `onRenderEnd`, so `provideRegistry` disposes its
|
|
25
|
+
* registry even when a failed boundary dropped the provider. A successful render keeps the
|
|
26
|
+
* children-first order through `onDestroy`; when the render ends without it, `f` may run before
|
|
27
|
+
* the children's releases, which are then no-ops on the disposed registry.
|
|
28
|
+
*/
|
|
29
|
+
export const onTeardownAfterChildren = (f) => {
|
|
30
|
+
if (!BROWSER) {
|
|
31
|
+
onRenderEnd(f);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
let mounted = false;
|
|
35
|
+
$effect.pre(() => () => {
|
|
36
|
+
if (!mounted) {
|
|
37
|
+
f();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
onMount(() => {
|
|
41
|
+
mounted = true;
|
|
42
|
+
});
|
|
43
|
+
onDestroy(f);
|
|
44
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Where error messages point for more. Internal: not exported from the package.
|
|
2
|
+
/** The sentence that ends an error message: a link to its entry on the troubleshooting page. */
|
|
3
|
+
export const troubleshooting = (entry) => `See https://atom.jarrednorris.dev/troubleshooting#${entry}`;
|