effect-atom-svelte 0.1.2 → 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.
@@ -1,13 +1,22 @@
1
1
  <!--
2
2
  @component
3
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.
4
+ function. Atoms new to the registry, or holding only an initial value nobody has read yet, are
5
+ hydrated before children render; in the browser, atoms that already exist are updated after
6
+ render, so current UI does not jump to the incoming data mid-render. On the server, where nothing
7
+ renders again, and in the browser while it hydrates the server's markup, they are updated at once.
8
+ That needs Svelte's `experimental.async`: without it, the browser can't tell that it is hydrating,
9
+ and atoms that already exist, as one read above the boundary, are updated after the first render.
10
+
11
+ With `experimental.async` on, custom server rendering must `await render(...)`, as SvelteKit does,
12
+ so the boundary's `hydratable` entry is written into the page. Otherwise a production build of
13
+ Svelte logs `hydratable_missing_but_expected` once per boundary as the page hydrates.
7
14
 
8
15
  A value for an atom nobody reads waits in the registry until something does. When the boundary
9
16
  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.
17
+ dropped, as is a promise-encoded value that lands afterwards, so a registry that outlives the
18
+ boundary doesn't hand them to a later reader. In the browser, a reader that already held the atom
19
+ when the boundary ended and still holds it, as a layout's reader above it, gets the late value.
11
20
 
12
21
  **Example** (Hydrating the state a load function dehydrated)
13
22
 
@@ -28,10 +37,15 @@
28
37
  -->
29
38
  <script lang="ts">
30
39
  import { Hydration } from "effect/reactivity";
40
+ import type { AtomRegistry } from "effect/reactivity";
31
41
  import { BROWSER } from "esm-env";
32
- import { onDestroy } from "svelte";
42
+ import { hydratable } from "svelte";
33
43
  import type { Snippet } from "svelte";
34
44
 
45
+ import { queuedBy } from "./internal/boundaries.js";
46
+ import { holdsInitialValue } from "./internal/nodeInternals.js";
47
+ import { onRenderEnd } from "./internal/renderEnd.js";
48
+ import { onTeardownAfterChildren } from "./internal/teardown.svelte.js";
35
49
  import { getRegistry } from "./RegistryContext.js";
36
50
 
37
51
  interface Props {
@@ -49,14 +63,93 @@
49
63
  };
50
64
  // The values this boundary queued, by key, so it can drop the ones nobody took.
51
65
  const queued = new Map<string, unknown>();
66
+ // Whether the boundary has ended, as at the end of the server render or once the browser destroys
67
+ // it, after which a late promise-encoded value is dropped.
68
+ let ended = false;
69
+
70
+ const counts =
71
+ queuedBy.get(registry) ?? new Map<string, Map<unknown, number>>();
72
+ queuedBy.set(registry, counts);
73
+ /** Adds one to or takes one from the boundaries queueing a value, and returns the new count. */
74
+ const count = (key: string, value: unknown, delta: 1 | -1): number => {
75
+ const byValue = counts.get(key) ?? new Map<unknown, number>();
76
+ counts.set(key, byValue);
77
+ const next = (byValue.get(value) ?? 0) + delta;
78
+ if (next > 0) {
79
+ byValue.set(value, next);
80
+ } else {
81
+ byValue.delete(value);
82
+ if (byValue.size === 0) {
83
+ counts.delete(key);
84
+ }
85
+ }
86
+ return next;
87
+ };
88
+
89
+ // SAFETY: Hydration.hydrate ignores a promise-encoded value that resolves to this marker, which
90
+ // Hydration.ts registers with Symbol.for (Effect 4.0.1) but doesn't export.
91
+ const skipped = Symbol.for("effect/reactivity/Hydration/Skipped");
92
+ // The keys of the promise-encoded values the boundary is waiting for.
93
+ const waiting = new Set<string>();
94
+ // In the browser, the node that held each of those keys when the boundary ended. That node, as a
95
+ // layout's reader above the boundary that took its waiting value, still needs the result, or it
96
+ // waits forever. A node a later reader creates doesn't get it, nor does one that replaced a node
97
+ // swept since, as a reader inside the boundary's is just after it ends.
98
+ const nodesAtEnd = new Map<string, AtomRegistry.Node<unknown>>();
99
+ /** A promise-encoded value as the boundary waits for it; one landing after the boundary ends is skipped. */
100
+ const lateDropped = async (
101
+ key: string,
102
+ late: Promise<unknown>
103
+ ): Promise<unknown> => {
104
+ waiting.add(key);
105
+ const value = await late;
106
+ if (!ended) {
107
+ return value;
108
+ }
109
+ const held = BROWSER ? nodesAtEnd.get(key) : undefined;
110
+ return held !== undefined && registry.getNodes().get(key) === held
111
+ ? value
112
+ : skipped;
113
+ };
52
114
 
53
115
  const queue = (atoms: readonly Hydration.DehydratedAtomValue[]): void => {
54
- Hydration.hydrate(registry, atoms);
116
+ // A promise-encoded value landing after the boundary has ended, after the server render or once
117
+ // the browser has navigated away, would stay queued in a registry that outlives it for a later
118
+ // reader, however late: it is ignored once the boundary ends, unless, in the browser, the node
119
+ // that held its key then still does.
120
+ Hydration.hydrate(
121
+ registry,
122
+ atoms.map((atom) =>
123
+ atom.resultPromise === undefined
124
+ ? atom
125
+ : {
126
+ ...atom,
127
+ resultPromise: lateDropped(atom.key, atom.resultPromise),
128
+ }
129
+ )
130
+ );
55
131
  for (const { key, value } of atoms) {
132
+ if (queued.has(key)) {
133
+ if (queued.get(key) === value) {
134
+ continue;
135
+ }
136
+ count(key, queued.get(key), -1);
137
+ }
56
138
  queued.set(key, value);
139
+ count(key, value, 1);
57
140
  }
58
141
  };
59
142
 
143
+ /**
144
+ * Whether a node's value may be on the page already. A node holding only an initial value nobody
145
+ * has read, as RegistryProvider's initialValues leave it, shows nowhere yet: it takes the incoming
146
+ * value before children render, as on the server, so the browser's first render matches the
147
+ * server's markup.
148
+ */
149
+ const shown = (node: AtomRegistry.Node<unknown> | undefined): boolean =>
150
+ node !== undefined &&
151
+ !(holdsInitialValue(node) && node.currentState() === "stale");
152
+
60
153
  const hydrateNew = (
61
154
  incoming: Iterable<Hydration.DehydratedAtom> | undefined
62
155
  ): Hydration.DehydratedAtomValue[] => {
@@ -64,7 +157,7 @@
64
157
  const fresh: Hydration.DehydratedAtomValue[] = [];
65
158
  const existing: Hydration.DehydratedAtomValue[] = [];
66
159
  for (const atom of Hydration.toValues([...(incoming ?? [])])) {
67
- (nodes.has(atom.key) ? existing : fresh).push(atom);
160
+ (shown(nodes.get(atom.key)) ? existing : fresh).push(atom);
68
161
  }
69
162
  queue(fresh);
70
163
  return existing;
@@ -91,8 +184,18 @@
91
184
  // svelte-ignore state_referenced_locally
92
185
  let deferred = hydrateNew(state);
93
186
  // 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) {
187
+ // exist are updated now, before the children read them. The browser does the same while it
188
+ // hydrates the server's markup, so its first render matches the server's. The server writes true;
189
+ // the browser reads it only while hydrating, and false otherwise. hydratable throws in development
190
+ // for a key the server didn't write, as for a boundary the server didn't render: that is false too.
191
+ const hydratingServerMarkup = (() => {
192
+ try {
193
+ return hydratable("effect-atom-svelte/HydrationBoundary", () => !BROWSER);
194
+ } catch {
195
+ return false;
196
+ }
197
+ })();
198
+ if (!BROWSER || hydratingServerMarkup) {
96
199
  hydrateExisting(deferred);
97
200
  deferred = [];
98
201
  }
@@ -111,13 +214,28 @@
111
214
 
112
215
  // A queued value stays in the registry until its key is looked up. With a registry that outlives
113
216
  // 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(() => {
217
+ // request. Drop the ones still waiting, unless something queued another value since or another
218
+ // live boundary queued the same one. On the server they're dropped when the render ends, even if
219
+ // a failed boundary discarded this one; in the browser, also when it is destroyed while pending.
220
+ (BROWSER ? onTeardownAfterChildren : onRenderEnd)(() => {
221
+ if (ended) {
222
+ return;
223
+ }
224
+ ended = true;
225
+ if (BROWSER) {
226
+ const nodes = registry.getNodes();
227
+ for (const key of waiting) {
228
+ const node = nodes.get(key);
229
+ if (node) {
230
+ nodesAtEnd.set(key, node);
231
+ }
232
+ }
233
+ }
116
234
  if (!preloaded) {
117
235
  return;
118
236
  }
119
237
  for (const [key, value] of queued) {
120
- if (preloaded.get(key) === value) {
238
+ if (count(key, value, -1) <= 0 && preloaded.get(key) === value) {
121
239
  preloaded.delete(key);
122
240
  }
123
241
  }
@@ -6,13 +6,22 @@ interface Props {
6
6
  }
7
7
  /**
8
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.
9
+ * function. Atoms new to the registry, or holding only an initial value nobody has read yet, are
10
+ * hydrated before children render; in the browser, atoms that already exist are updated after
11
+ * render, so current UI does not jump to the incoming data mid-render. On the server, where nothing
12
+ * renders again, and in the browser while it hydrates the server's markup, they are updated at once.
13
+ * That needs Svelte's `experimental.async`: without it, the browser can't tell that it is hydrating,
14
+ * and atoms that already exist, as one read above the boundary, are updated after the first render.
15
+ *
16
+ * With `experimental.async` on, custom server rendering must `await render(...)`, as SvelteKit does,
17
+ * so the boundary's `hydratable` entry is written into the page. Otherwise a production build of
18
+ * Svelte logs `hydratable_missing_but_expected` once per boundary as the page hydrates.
12
19
  *
13
20
  * A value for an atom nobody reads waits in the registry until something does. When the boundary
14
21
  * 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.
22
+ * dropped, as is a promise-encoded value that lands afterwards, so a registry that outlives the
23
+ * boundary doesn't hand them to a later reader. In the browser, a reader that already held the atom
24
+ * when the boundary ended and still holds it, as a layout's reader above it, gets the late value.
16
25
  *
17
26
  * **Example** (Hydrating the state a load function dehydrated)
18
27
  *
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Watches what an atom registry does, for developer tools: every node added and removed, every
3
+ * computation and why it ran, every value, reader, interruption and finalizer.
4
+ *
5
+ * Nothing is watched until `inspect` is called. In development, the registries an app provides
6
+ * (and the default browser registry) are listed by `registries`, so a tool can find them without
7
+ * being handed one.
8
+ *
9
+ * @since 0.2.0
10
+ */
11
+ import { Atom } from "effect/reactivity";
12
+ import type { AtomRegistry } from "effect/reactivity";
13
+ import type { ReadKind } from "./internal/scope.svelte.ts";
14
+ /**
15
+ * Why a node computed its value.
16
+ *
17
+ * - `FirstRead`: its first computation.
18
+ * - `ParentChanged`: atoms it read changed since its last computation; `parents` are those nodes.
19
+ * - `Refreshed`: `registry.refresh` (or `useAtomRefresh`) asked for it, or for an atom it wraps.
20
+ * A signal that refreshes the atom (`refreshOnWindowFocus`, `makeRefreshOnSignal`, `swr`) calls
21
+ * `registry.refresh` too, so it is reported the same way.
22
+ * - `Invalidated`: something else made it stale, such as the atom refreshing itself (`refreshSelf`)
23
+ * or a reactivity key.
24
+ *
25
+ * @stability unstable
26
+ * @since 0.2.0
27
+ * @category models
28
+ */
29
+ export type BuildCause = {
30
+ readonly _tag: "FirstRead";
31
+ } | {
32
+ readonly _tag: "ParentChanged";
33
+ readonly parents: readonly AtomRegistry.Node<unknown>[];
34
+ } | {
35
+ readonly _tag: "Refreshed";
36
+ } | {
37
+ readonly _tag: "Invalidated";
38
+ };
39
+ /**
40
+ * Where a node's new value came from.
41
+ *
42
+ * - `build`: its computation returned it.
43
+ * - `write`: `registry.set`, `update` or `modify` (which the write hooks use), on it or on a
44
+ * writable atom that wrote it, and another atom's `get.set`, which goes through `registry.set`.
45
+ * - `async`: anything else, such as an effect settling or a stream emitting after the computation
46
+ * returned.
47
+ *
48
+ * @stability unstable
49
+ * @since 0.2.0
50
+ * @category models
51
+ */
52
+ export type UpdateSource = "build" | "write" | "async";
53
+ /**
54
+ * Something a registry did to one of its nodes. `time` is from `performance.now()`.
55
+ *
56
+ * - `NodeAdded`: an atom was read, mounted or written for the first time, or again after it was
57
+ * removed, and the registry made a node for it.
58
+ * - `Built`: the node is about to compute its value, for the `cause` given.
59
+ * - `Updated`: its value changed. `first` marks its first value, which has no `previous`.
60
+ * - `ReadersChanged`: a listener was added or removed; `readers` is how many it has now. Hooks,
61
+ * `registry.subscribe` and `mount`, and atoms that subscribe to others all count.
62
+ * - `Interrupted`: its effect hadn't finished when what its computation started was torn down, so
63
+ * the fiber was interrupted. Followed by `Finalized`. Not reported for an `Atom.runtime` atom,
64
+ * whose layer build isn't interrupted. An effect atom made with `{ uninterruptible: true }` is
65
+ * reported all the same, though its fiber keeps running: the inspector can't tell it apart.
66
+ * - `Finalized`: what its last computation started was torn down, before it computes again or as
67
+ * it is removed; `finalizers` ran.
68
+ * - `NodeRemoved`: the registry disposed of the node, as nothing read it any more.
69
+ *
70
+ * @stability unstable
71
+ * @since 0.2.0
72
+ * @category models
73
+ */
74
+ export type Event = {
75
+ readonly node: AtomRegistry.Node<unknown>;
76
+ readonly time: number;
77
+ } & ({
78
+ readonly _tag: "NodeAdded";
79
+ } | {
80
+ readonly _tag: "Built";
81
+ readonly cause: BuildCause;
82
+ } | {
83
+ readonly _tag: "Updated";
84
+ readonly first: boolean;
85
+ readonly previous: unknown;
86
+ readonly value: unknown;
87
+ readonly source: UpdateSource;
88
+ } | {
89
+ readonly _tag: "ReadersChanged";
90
+ readonly readers: number;
91
+ } | {
92
+ readonly _tag: "Interrupted";
93
+ } | {
94
+ readonly _tag: "Finalized";
95
+ readonly finalizers: number;
96
+ } | {
97
+ readonly _tag: "NodeRemoved";
98
+ });
99
+ /**
100
+ * What `inspect` returns for a registry.
101
+ *
102
+ * @stability unstable
103
+ * @since 0.2.0
104
+ * @category models
105
+ */
106
+ export interface Inspector {
107
+ readonly registry: AtomRegistry.AtomRegistry;
108
+ /**
109
+ * Calls `listener` with each event as it happens; returns the function that stops it. Events
110
+ * arrive in the middle of the registry's work, so a listener must not read or write atoms, nor
111
+ * write Svelte state: queue the event and handle it later, for example on the next animation
112
+ * frame.
113
+ */
114
+ readonly subscribe: (listener: (event: Event) => void) => () => void;
115
+ /**
116
+ * How long an atom nobody reads is kept before it is removed, in milliseconds: its own idle TTL,
117
+ * or the registry's default. `undefined` for an atom kept alive, and for one removed as soon as
118
+ * its last reader goes.
119
+ */
120
+ readonly idleTTL: (atom: Atom.Atom<unknown>) => number | undefined;
121
+ /**
122
+ * `false` when the registry's nodes aren't the implementation the inspector was written against
123
+ * (a later Effect, say). Then only `NodeAdded` and `NodeRemoved` are reported.
124
+ */
125
+ readonly complete: boolean;
126
+ }
127
+ /**
128
+ * Starts watching a registry, and returns its inspector: the same one each time it is called for
129
+ * the same registry.
130
+ *
131
+ * Until then a registry runs as it always does. From then on, in a registry made by Effect 4.0,
132
+ * each node reports what happens to it, which costs a little on every read and write, so call it
133
+ * from developer tools rather than from the app.
134
+ *
135
+ * Nodes the registry already holds are watched from now on; what happened to them before isn't
136
+ * known. Read them with `registry.getNodes()`.
137
+ *
138
+ * **Example** (Logging every computation)
139
+ *
140
+ * ```ts
141
+ * import { getRegistry } from "effect-atom-svelte";
142
+ * import { inspect } from "effect-atom-svelte/inspector";
143
+ *
144
+ * const events = [];
145
+ * inspect(getRegistry()).subscribe((event) => events.push(event));
146
+ * setInterval(() => {
147
+ * for (const event of events.splice(0)) {
148
+ * if (event._tag === "Built") {
149
+ * console.log(event.node.atom.label?.[0], event.cause._tag);
150
+ * }
151
+ * }
152
+ * }, 1000);
153
+ * ```
154
+ *
155
+ * @stability unstable
156
+ * @since 0.2.0
157
+ * @category inspecting
158
+ */
159
+ export declare const inspect: (registry: AtomRegistry.AtomRegistry) => Inspector;
160
+ /**
161
+ * The registries the app has in the browser during development: each one a `RegistryProvider` or
162
+ * `provideRegistry` gives its children, while it is mounted, and the default browser registry once
163
+ * something has used it. Empty on the server and in production builds.
164
+ *
165
+ * @stability unstable
166
+ * @since 0.2.0
167
+ * @category inspecting
168
+ */
169
+ export declare const registries: () => readonly AtomRegistry.AtomRegistry[];
170
+ /**
171
+ * Calls `f` with `registries()` now and whenever it changes, as providers mount and unmount;
172
+ * returns the function that stops it.
173
+ *
174
+ * @stability unstable
175
+ * @since 0.2.0
176
+ * @category inspecting
177
+ */
178
+ export declare const watchRegistries: (f: (registries: readonly AtomRegistry.AtomRegistry[]) => void) => (() => void);
179
+ /**
180
+ * How a component uses an atom it reports to a scope: `read` (`useAtomValue`, `useAtom`,
181
+ * `useAtomResult`, `useAtomSuspense`), `mount` (`useAtomMount`), `subscribe`
182
+ * (`useAtomSubscribe`) or `write` (`useAtomSet`, which holds the atom mounted to write it).
183
+ *
184
+ * @stability unstable
185
+ * @since 0.2.0
186
+ * @category models
187
+ */
188
+ export type ReaderKind = ReadKind;
189
+ /**
190
+ * A hook in the scope's part of the component tree that uses an atom. `component` and `file` name
191
+ * the component it is in, when the atomLabels plugin of effect-atom-svelte-devtools (or
192
+ * `nameComponent`) names components; `instance` tells two instances of one component apart.
193
+ *
194
+ * @stability unstable
195
+ * @since 0.2.0
196
+ * @category models
197
+ */
198
+ export interface ScopeReader {
199
+ readonly id: number;
200
+ readonly kind: ReaderKind;
201
+ /** The `id` of the atom's node in the snapshot. */
202
+ readonly atom: number;
203
+ readonly component: string | undefined;
204
+ readonly file: string | undefined;
205
+ readonly instance: number | undefined;
206
+ }
207
+ /**
208
+ * An atom in a scope's snapshot.
209
+ *
210
+ * @stability unstable
211
+ * @since 0.2.0
212
+ * @category models
213
+ */
214
+ export interface ScopeNode {
215
+ /** Stable for the atom while the scope lives. */
216
+ readonly id: number;
217
+ readonly atom: Atom.Atom<unknown>;
218
+ readonly node: AtomRegistry.Node<unknown>;
219
+ /** The atom's label (its variable's name, with the atomLabels plugin), if it has one. */
220
+ readonly label: string | undefined;
221
+ /** Used by a hook in the scope, rather than only upstream of one. */
222
+ readonly read: boolean;
223
+ /** Has no label while other atoms in the scope do: made by a runtime, mutation or store. */
224
+ readonly plumbing: boolean;
225
+ /** Every listener the node has: the scope's hooks, and anything else that subscribes. */
226
+ readonly listeners: number;
227
+ /** The tag of its value when that is an `AsyncResult`, otherwise `Value`. */
228
+ readonly state: "Value" | "Initial" | "Success" | "Failure";
229
+ readonly waiting: boolean;
230
+ }
231
+ /**
232
+ * An edge from an atom to one that reads it. With plumbing left out, an atom that reads plumbing is
233
+ * linked to the nearest atoms upstream of it that are shown.
234
+ *
235
+ * @stability unstable
236
+ * @since 0.2.0
237
+ * @category models
238
+ */
239
+ export interface ScopeEdge {
240
+ readonly from: number;
241
+ readonly to: number;
242
+ }
243
+ /**
244
+ * What a scope's part of the component tree reads, at one moment: the atoms its hooks use, the
245
+ * atoms upstream of them, the edges between, and the hooks.
246
+ *
247
+ * @stability unstable
248
+ * @since 0.2.0
249
+ * @category models
250
+ */
251
+ export interface ScopeSnapshot {
252
+ readonly nodes: readonly ScopeNode[];
253
+ readonly edges: readonly ScopeEdge[];
254
+ readonly readers: readonly ScopeReader[];
255
+ }
256
+ /**
257
+ * An event a scope passes on: the inspector's `Event` for a node in the scope, with the node's
258
+ * `id`, or `ScopeChanged` once its atoms, edges or hooks have changed, after the registry's work,
259
+ * to take a new `snapshot()`. A node's `NodeAdded` arrives before the node is linked into the
260
+ * scope, so it isn't passed on; the `ScopeChanged` that follows is.
261
+ *
262
+ * @stability unstable
263
+ * @since 0.2.0
264
+ * @category models
265
+ */
266
+ export type ScopeEvent = (Event & {
267
+ readonly id: number;
268
+ }) | {
269
+ readonly _tag: "ScopeChanged";
270
+ };
271
+ /**
272
+ * A part of the component tree, as `provideInspectorScope` returns it.
273
+ *
274
+ * @stability unstable
275
+ * @since 0.2.0
276
+ * @category models
277
+ */
278
+ export interface InspectorScope {
279
+ /** The atoms the scope reads now. `plumbing: true` includes those otherwise left out. */
280
+ readonly snapshot: (options?: {
281
+ readonly plumbing?: boolean;
282
+ }) => ScopeSnapshot;
283
+ /**
284
+ * Calls `listener` with each event for the scope's atoms; returns the function that stops it. The
285
+ * first listener starts the inspector on the registries the scope reads from. As with
286
+ * `Inspector.subscribe`, a listener must not read or write atoms or write Svelte state.
287
+ */
288
+ readonly subscribe: (listener: (event: ScopeEvent) => void) => () => void;
289
+ }
290
+ /**
291
+ * Makes the component being set up, and everything below it, an inspector scope, and returns it.
292
+ * The library's hooks below report the atoms they use to the nearest scope, which shows those
293
+ * atoms, everything upstream of them, and the hooks that use them. Call it while the component
294
+ * sets up, as context requires.
295
+ *
296
+ * Only in the browser, in development and production alike (the effect-atom-svelte docs draw each
297
+ * example's atoms from one); on the server it returns a scope that is always empty. Until the
298
+ * scope has a listener it only keeps a list of its hooks: the registry's inspector starts with the
299
+ * first listener.
300
+ *
301
+ * **Example** (A graph of what an example reads)
302
+ *
303
+ * ```ts
304
+ * import { provideInspectorScope } from "effect-atom-svelte/inspector";
305
+ *
306
+ * // In the component that frames the example, around its content
307
+ * const scope = provideInspectorScope();
308
+ * $effect(() =>
309
+ * scope.subscribe((event) => {
310
+ * if (event._tag === "ScopeChanged") {
311
+ * draw(scope.snapshot());
312
+ * } else if (event._tag === "Updated") {
313
+ * pulse(event.id);
314
+ * }
315
+ * })
316
+ * );
317
+ * ```
318
+ *
319
+ * @stability unstable
320
+ * @since 0.2.0
321
+ * @category inspecting
322
+ */
323
+ export declare const provideInspectorScope: () => InspectorScope;
324
+ /**
325
+ * Names the component being set up, for the scopes its hooks report to. The atomLabels plugin of
326
+ * effect-atom-svelte-devtools calls it at the top of each component's script; without the plugin,
327
+ * call it yourself. Only in the browser.
328
+ *
329
+ * @stability unstable
330
+ * @since 0.2.0
331
+ * @category inspecting
332
+ */
333
+ export declare const nameComponent: (name: string, file?: string) => void;