@systemfsoftware/effect-atom 0.5.3

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,1348 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { A as toExit, M as value, N as waiting, O as replacePrevious, P as waitingFrom, S as isSuccess, _ as isFailure, c as failWithPrevious, g as initial, h as getOrThrow, k as success, p as fromExitWithPrevious, u as failureWithPrevious, v as isInitial, w as map$1, x as isResult } from "./Result-D8FNEzuX.mjs";
3
+ import { a as getResult$1, d as toStream$1, f as toStreamResult$1, m as batch$1, t as AtomRegistry, u as mount$1 } from "./Registry-DhP1TSer.mjs";
4
+ import * as Arr from "effect/Array";
5
+ import * as Cause from "effect/Cause";
6
+ import * as Channel from "effect/Channel";
7
+ import * as Context from "effect/Context";
8
+ import * as Duration from "effect/Duration";
9
+ import * as Effect from "effect/Effect";
10
+ import * as Exit from "effect/Exit";
11
+ import * as Fiber from "effect/Fiber";
12
+ import { constTrue, constVoid, constant, dual, pipe } from "effect/Function";
13
+ import * as Layer from "effect/Layer";
14
+ import * as MutableHashMap from "effect/MutableHashMap";
15
+ import * as Option from "effect/Option";
16
+ import * as Pull from "effect/Pull";
17
+ import * as Scheduler$1 from "effect/Scheduler";
18
+ import * as Schema from "effect/Schema";
19
+ import * as Scope$1 from "effect/Scope";
20
+ import * as Stream from "effect/Stream";
21
+ import * as SubscriptionRef from "effect/SubscriptionRef";
22
+ import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore";
23
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity";
24
+ import { pipeArguments } from "effect/Pipeable";
25
+ import { hasProperty } from "effect/Predicate";
26
+ //#region src/internal/core.ts
27
+ /** @internal */
28
+ const PipeInspectableProto = { pipe() {
29
+ return pipeArguments(this, arguments);
30
+ } };
31
+ /**
32
+ * Runtime identifier attached to `Atom` values and used by `isAtom`.
33
+ *
34
+ * @category type IDs
35
+ * @since 4.0.0
36
+ */
37
+ const TypeId = "~effect/reactivity/Atom";
38
+ /**
39
+ * Runtime identifier attached to writable atoms and used by `isWritable`.
40
+ *
41
+ * @category type IDs
42
+ * @since 4.0.0
43
+ */
44
+ const WritableTypeId = "~effect/reactivity/Atom/Writable";
45
+ /**
46
+ * Returns `true` when a value is an `Atom`.
47
+ *
48
+ * @category guards
49
+ * @since 4.0.0
50
+ */
51
+ const isAtom = (u) => hasProperty(u, TypeId);
52
+ /**
53
+ * Returns a copy of an atom with an idle time-to-live: finite durations dispose it after inactivity, while an infinite duration keeps it alive.
54
+ *
55
+ * @category combinators
56
+ * @since 4.0.0
57
+ */
58
+ const setIdleTTL = dual(2, (self, durationInput) => {
59
+ const duration = Duration.fromInputUnsafe(durationInput);
60
+ const isFinite = Duration.isFinite(duration);
61
+ return Object.assign(Object.create(Object.getPrototypeOf(self)), {
62
+ ...self,
63
+ keepAlive: !isFinite,
64
+ idleTTL: isFinite ? Duration.toMillis(duration) : void 0
65
+ });
66
+ });
67
+ /** @internal */
68
+ const removeTtl = setIdleTTL(0);
69
+ /** @internal */
70
+ const AtomProto = {
71
+ [TypeId]: TypeId,
72
+ equals: Object.is,
73
+ ...PipeInspectableProto,
74
+ toJSON() {
75
+ return {
76
+ _id: "Atom",
77
+ keepAlive: this.keepAlive,
78
+ lazy: this.lazy,
79
+ label: this.label
80
+ };
81
+ }
82
+ };
83
+ /** @internal */
84
+ const WritableProto = {
85
+ ...AtomProto,
86
+ [WritableTypeId]: WritableTypeId
87
+ };
88
+ /**
89
+ * Returns `true` when an atom is writable.
90
+ *
91
+ * @category guards
92
+ * @since 4.0.0
93
+ */
94
+ const isWritable = (atom) => WritableTypeId in atom;
95
+ /**
96
+ * Creates a read-only atom from a read function and an optional custom refresh registration callback.
97
+ *
98
+ * @category constructors
99
+ * @since 4.0.0
100
+ */
101
+ const readable = (read, refresh) => {
102
+ const self = Object.create(AtomProto);
103
+ self.keepAlive = false;
104
+ self.lazy = true;
105
+ self.read = read;
106
+ self.refresh = refresh;
107
+ return self;
108
+ };
109
+ /**
110
+ * Creates a writable atom from read and write functions, with an optional custom refresh registration callback.
111
+ *
112
+ * @category constructors
113
+ * @since 4.0.0
114
+ */
115
+ const writable = (read, write, refresh) => {
116
+ const self = Object.create(WritableProto);
117
+ self.keepAlive = false;
118
+ self.lazy = true;
119
+ self.read = read;
120
+ self.write = write;
121
+ self.refresh = refresh;
122
+ return self;
123
+ };
124
+ const getInitialValueTarget = (atom) => {
125
+ let target = atom;
126
+ while (target.initialValueTarget) target = target.initialValueTarget;
127
+ return target;
128
+ };
129
+ /**
130
+ * Creates a derived atom by reading another atom with a custom `AtomContext`
131
+ * function.
132
+ *
133
+ * **Details**
134
+ *
135
+ * If the source is writable, the derived atom keeps the source write input and
136
+ * forwards writes to the source. `initialValueTarget` controls which atom receives
137
+ * preloaded initial values for the derived atom.
138
+ *
139
+ * @category combinators
140
+ * @since 4.0.0
141
+ */
142
+ const transform = dual((args) => isAtom(args[0]), (self, f, options) => {
143
+ const atom = removeTtl(isWritable(self) ? writable((get) => f(get, self), function(ctx, value) {
144
+ ctx.set(self, value);
145
+ }, self.refresh ?? function(refresh) {
146
+ refresh(self);
147
+ }) : readable((get) => f(get, self), self.refresh ?? function(refresh) {
148
+ refresh(self);
149
+ }));
150
+ if (options?.initialValueTarget) atom.initialValueTarget = getInitialValueTarget(options.initialValueTarget);
151
+ return atom;
152
+ });
153
+ //#endregion
154
+ //#region src/browser.ts
155
+ /**
156
+ * Browser-only `Atom` helpers.
157
+ *
158
+ * This module holds the parts of `Atom` that touch `window`, `history`, or
159
+ * `document`: window focus tracking and URL search parameter atoms. All
160
+ * exports are re-exported from `Atom` so consumers keep importing everything
161
+ * from there.
162
+ *
163
+ * @since 4.0.0
164
+ */
165
+ /**
166
+ * Creates a browser-only signal atom that increments when the document becomes visible.
167
+ *
168
+ * **Details**
169
+ *
170
+ * It listens for `visibilitychange` events on `window` and removes the listener
171
+ * when the atom is disposed.
172
+ *
173
+ * @category constants
174
+ * @since 4.0.0
175
+ */
176
+ const windowFocusSignal = readable((get) => {
177
+ let count = 0;
178
+ function update() {
179
+ if (document.visibilityState === "visible") get.setSelf(++count);
180
+ }
181
+ window.addEventListener("visibilitychange", update);
182
+ get.addFinalizer(() => {
183
+ window.removeEventListener("visibilitychange", update);
184
+ });
185
+ return count;
186
+ });
187
+ /**
188
+ * Creates a combinator that refreshes an atom whenever the supplied signal atom
189
+ * changes.
190
+ *
191
+ * **Details**
192
+ *
193
+ * The derived atom also subscribes to the source atom so normal source updates are
194
+ * forwarded to its own value.
195
+ *
196
+ * @category constructors
197
+ * @since 4.0.0
198
+ */
199
+ const makeRefreshOnSignal = (signal) => {
200
+ function refreshOnSignal(self) {
201
+ return transform(self, (get) => {
202
+ get.once(signal);
203
+ get.subscribe(signal, (_) => get.refresh(self));
204
+ get.subscribe(self, (value) => get.setSelf(value));
205
+ return get.once(self);
206
+ }, { initialValueTarget: self });
207
+ }
208
+ return refreshOnSignal;
209
+ };
210
+ /**
211
+ * Refreshes an atom whenever `windowFocusSignal` changes.
212
+ *
213
+ * **Details**
214
+ *
215
+ * This helper is browser-only because `windowFocusSignal` depends on `window` and
216
+ * `document.visibilityState`.
217
+ *
218
+ * @category combinators
219
+ * @since 4.0.0
220
+ */
221
+ const refreshOnWindowFocus = makeRefreshOnSignal(windowFocusSignal);
222
+ function searchParam(name, options) {
223
+ const decode = options?.schema && Schema.decodeExit(options.schema);
224
+ const encode = options?.schema && Schema.encodeExit(options.schema);
225
+ return writable((get) => {
226
+ if (typeof window === "undefined") return decode ? Option.none() : "";
227
+ const handleUpdate = () => {
228
+ if (searchParamState.updating) return;
229
+ const newValue = new URLSearchParams(window.location.search).get(name) || "";
230
+ if (decode) get.setSelf(Exit.getSuccess(decode(newValue)));
231
+ else if (newValue !== Option.getOrUndefined(get.self())) get.setSelf(newValue);
232
+ };
233
+ window.addEventListener("popstate", handleUpdate);
234
+ window.addEventListener("pushstate", handleUpdate);
235
+ get.addFinalizer(() => {
236
+ window.removeEventListener("popstate", handleUpdate);
237
+ window.removeEventListener("pushstate", handleUpdate);
238
+ });
239
+ const value = new URLSearchParams(window.location.search).get(name) || "";
240
+ return decode ? Exit.getSuccess(decode(value)) : value;
241
+ }, (ctx, value) => {
242
+ if (typeof window === "undefined") {
243
+ ctx.setSelf(value);
244
+ return;
245
+ }
246
+ if (encode) {
247
+ const encoded = Option.flatMap(value, (v) => Exit.getSuccess(encode(v)));
248
+ searchParamState.updates.set(name, Option.getOrElse(encoded, () => ""));
249
+ value = Option.zipRight(encoded, value);
250
+ } else searchParamState.updates.set(name, value);
251
+ ctx.setSelf(value);
252
+ if (searchParamState.timeout) clearTimeout(searchParamState.timeout);
253
+ searchParamState.timeout = setTimeout(updateSearchParams, 500);
254
+ });
255
+ }
256
+ const searchParamState = {
257
+ timeout: void 0,
258
+ updates: /* @__PURE__ */ new Map(),
259
+ updating: false
260
+ };
261
+ function updateSearchParams() {
262
+ searchParamState.timeout = void 0;
263
+ searchParamState.updating = true;
264
+ const searchParams = new URLSearchParams(window.location.search);
265
+ for (const [key, value] of searchParamState.updates.entries()) if (value.length > 0) searchParams.set(key, value);
266
+ else searchParams.delete(key);
267
+ searchParamState.updates.clear();
268
+ const newUrl = `${window.location.pathname}?${searchParams.toString()}`;
269
+ window.history.pushState({}, "", newUrl);
270
+ searchParamState.updating = false;
271
+ }
272
+ //#endregion
273
+ //#region src/server.ts
274
+ /**
275
+ * Server-side `Atom` helpers.
276
+ *
277
+ * This module holds the parts of `Atom` that describe how to read atom values
278
+ * on the server: the server-value type id, the read-override combinators, and
279
+ * the registry getter that honors them. All exports are re-exported from
280
+ * `Atom` so consumers keep importing everything from there.
281
+ *
282
+ * @since 4.0.0
283
+ */
284
+ /**
285
+ * The type id used to mark atoms with a server-side read override.
286
+ *
287
+ * @category type IDs
288
+ * @since 4.0.0
289
+ */
290
+ const ServerValueTypeId = "~effect-atom/atom/Atom/ServerValue";
291
+ const isServerValue = (self) => ServerValueTypeId in self;
292
+ /**
293
+ * Sets the value of an Atom when read on the server.
294
+ *
295
+ * @category transforming
296
+ * @since 4.0.0
297
+ */
298
+ const withServerValue = dual(2, (self, read) => Object.assign(Object.create(Object.getPrototypeOf(self)), {
299
+ ...self,
300
+ [ServerValueTypeId]: read
301
+ }));
302
+ /**
303
+ * Sets an `AsyncResult` atom's server-side value to
304
+ * `AsyncResult.initial(true)`.
305
+ *
306
+ * @category transforming
307
+ * @since 4.0.0
308
+ */
309
+ const withServerValueInitial = (self) => Object.assign(Object.create(Object.getPrototypeOf(self)), {
310
+ ...self,
311
+ [ServerValueTypeId]: constant(initial(true))
312
+ });
313
+ /**
314
+ * Reads an atom from a registry, using its server-side read override when one is
315
+ * present.
316
+ *
317
+ * **Details**
318
+ *
319
+ * Nested reads performed by the override are resolved against the same registry.
320
+ *
321
+ * @category getters
322
+ * @since 4.0.0
323
+ */
324
+ const getServerValue = dual(2, (self, registry) => isServerValue(self) ? self[ServerValueTypeId]((atom) => registry.get(atom)) : registry.get(self));
325
+ //#endregion
326
+ //#region src/Atom.ts
327
+ /**
328
+ * Reactive state primitives for values managed by an `AtomRegistry`.
329
+ *
330
+ * An `Atom` describes how to produce or update one piece of reactive state. The
331
+ * registry runs atom reads, remembers current values, tracks dependencies
332
+ * between atoms, starts effects and streams, and cleans up atoms that are no
333
+ * longer used. This module includes the atom constructors and update helpers
334
+ * used for cached values, effect-backed values, streams, browser state, stored
335
+ * values, and server-rendered values.
336
+ *
337
+ * @since 4.0.0
338
+ */
339
+ var Atom_exports = /* @__PURE__ */ __exportAll({
340
+ Interrupt: () => Interrupt,
341
+ Reset: () => Reset,
342
+ SerializableTypeId: () => SerializableTypeId,
343
+ ServerValueTypeId: () => ServerValueTypeId,
344
+ TypeId: () => TypeId,
345
+ WritableTypeId: () => WritableTypeId,
346
+ autoDispose: () => autoDispose,
347
+ batch: () => batch,
348
+ context: () => context,
349
+ debounce: () => debounce,
350
+ family: () => family,
351
+ fn: () => fn,
352
+ fnSync: () => fnSync,
353
+ get: () => get,
354
+ getResult: () => getResult,
355
+ getServerValue: () => getServerValue,
356
+ initialValue: () => initialValue,
357
+ isAtom: () => isAtom,
358
+ isSerializable: () => isSerializable,
359
+ isWritable: () => isWritable,
360
+ keepAlive: () => keepAlive,
361
+ kvs: () => kvs,
362
+ make: () => make,
363
+ makeRefreshOnSignal: () => makeRefreshOnSignal,
364
+ map: () => map,
365
+ mapResult: () => mapResult,
366
+ modify: () => modify,
367
+ mount: () => mount,
368
+ optimistic: () => optimistic,
369
+ optimisticFn: () => optimisticFn,
370
+ pull: () => pull,
371
+ readable: () => readable,
372
+ refresh: () => refresh,
373
+ refreshOnWindowFocus: () => refreshOnWindowFocus,
374
+ runtime: () => runtime,
375
+ searchParam: () => searchParam,
376
+ serializable: () => serializable,
377
+ set: () => set,
378
+ setIdleTTL: () => setIdleTTL,
379
+ setLazy: () => setLazy,
380
+ subscriptionRef: () => subscriptionRef,
381
+ swr: () => swr,
382
+ toStream: () => toStream,
383
+ toStreamResult: () => toStreamResult,
384
+ transform: () => transform,
385
+ update: () => update,
386
+ windowFocusSignal: () => windowFocusSignal,
387
+ withEquality: () => withEquality,
388
+ withFallback: () => withFallback,
389
+ withLabel: () => withLabel,
390
+ withReactivity: () => withReactivity,
391
+ withRefresh: () => withRefresh,
392
+ withServerValue: () => withServerValue,
393
+ withServerValueInitial: () => withServerValueInitial,
394
+ writable: () => writable
395
+ });
396
+ const RuntimeProto = {
397
+ ...AtomProto,
398
+ atom(arg, options) {
399
+ const read = makeRead(arg, options);
400
+ return readable((get) => {
401
+ const previous = get.self();
402
+ const runtimeResult = get(this);
403
+ if (runtimeResult._tag !== "Success") return replacePrevious(runtimeResult, previous);
404
+ return read(get, runtimeResult.value);
405
+ });
406
+ },
407
+ fn(arg, options) {
408
+ if (arguments.length === 0) return (arg, options) => makeFnRuntime(this, arg, options);
409
+ return makeFnRuntime(this, arg, options);
410
+ },
411
+ pull(arg, options) {
412
+ const pullSignal = removeTtl(state(0));
413
+ const pullAtom = readable((get) => {
414
+ const previous = get.self();
415
+ const runtimeResult = get(this);
416
+ if (runtimeResult._tag !== "Success") return replacePrevious(runtimeResult, previous);
417
+ return makeEffect(get, makeStreamPullEffect(get, pullSignal, arg, options), initial(true), runtimeResult.value);
418
+ });
419
+ return makeStreamPull(pullSignal, pullAtom);
420
+ },
421
+ subscriptionRef(ref) {
422
+ return makeSubRef(removeTtl(readable((get) => {
423
+ const previous = get.self();
424
+ const runtimeResult = get(this);
425
+ if (runtimeResult._tag !== "Success") return replacePrevious(runtimeResult, previous);
426
+ const value = typeof ref === "function" ? ref(get) : ref;
427
+ return SubscriptionRef.isSubscriptionRef(value) ? value : makeEffect(get, value, initial(true), runtimeResult.value);
428
+ })), (get, ref) => {
429
+ const runtime = getOrThrow(get(this));
430
+ return readSubscriptionRef(get, ref, runtime);
431
+ });
432
+ }
433
+ };
434
+ const makeFnRuntime = (self, arg, options) => {
435
+ const [read, write, argAtom] = makeResultFn(options?.reactivityKeys ? ((a, get) => {
436
+ const effect = arg(a, get);
437
+ return Effect.isEffect(effect) ? Reactivity.mutation(effect, options.reactivityKeys) : Stream.ensuring(effect, Reactivity.invalidate(options.reactivityKeys));
438
+ }) : arg, options);
439
+ return writable((get) => {
440
+ get.get(argAtom);
441
+ const previous = get.self();
442
+ const runtimeResult = get.get(self);
443
+ if (runtimeResult._tag !== "Success") return replacePrevious(runtimeResult, previous);
444
+ return read(get, runtimeResult.value);
445
+ }, write);
446
+ };
447
+ function constSetSelf(ctx, value) {
448
+ ctx.setSelf(value);
449
+ }
450
+ function make(arg, options) {
451
+ const readOrAtom = makeRead(arg, options);
452
+ if (isAtom(readOrAtom)) return readOrAtom;
453
+ return readable(readOrAtom);
454
+ }
455
+ function makeRead(arg, options) {
456
+ if (typeof arg === "function" && !Effect.isEffect(arg) && !Stream.isStream(arg)) {
457
+ const create = arg;
458
+ return function(get, providedServices) {
459
+ const value = create(get);
460
+ switch (typeof value) {
461
+ case "function":
462
+ case "object":
463
+ if (value === null) return value;
464
+ else if (EffectTypeId in value) return effect(get, value, options, providedServices);
465
+ else if (StreamTypeId in value) return stream(get, value, options, providedServices);
466
+ return value;
467
+ default: return value;
468
+ }
469
+ };
470
+ } else if (Effect.isEffect(arg)) return function(get, providedServices) {
471
+ return effect(get, arg, options, providedServices);
472
+ };
473
+ else if (Stream.isStream(arg)) return function(get, providedServices) {
474
+ return stream(get, arg, options, providedServices);
475
+ };
476
+ return state(arg);
477
+ }
478
+ const EffectTypeId = "~effect/Effect";
479
+ const StreamTypeId = "~effect/Stream";
480
+ const state = (initialValue) => writable(function(_get) {
481
+ return initialValue;
482
+ }, constSetSelf);
483
+ const effect = (get, effect, options, services) => {
484
+ return makeEffect(get, effect, options?.initialValue !== void 0 ? success(options.initialValue) : initial(), services, options?.uninterruptible);
485
+ };
486
+ function makeEffect(ctx, effect, initialValue, services = Context.empty(), uninterruptible = false) {
487
+ const previous = ctx.self();
488
+ const scope = Scope$1.makeUnsafe();
489
+ ctx.addFinalizer(() => {
490
+ Effect.runForkWith(services)(Scope$1.close(scope, Exit.void));
491
+ });
492
+ const servicesMap = new Map(services.mapUnsafe);
493
+ servicesMap.set(Scope$1.Scope.key, scope);
494
+ servicesMap.set(AtomRegistry.key, ctx.registry);
495
+ servicesMap.set(Scheduler$1.Scheduler.key, ctx.registry.scheduler);
496
+ let syncResult;
497
+ let isAsync = false;
498
+ const cancel = runCallbackSync(Context.makeUnsafe(servicesMap), effect, function(exit) {
499
+ syncResult = fromExitWithPrevious(exit, previous);
500
+ if (isAsync) ctx.setSelf(syncResult);
501
+ }, uninterruptible);
502
+ isAsync = true;
503
+ if (cancel !== void 0) ctx.addFinalizer(cancel);
504
+ if (syncResult !== void 0) return syncResult;
505
+ else if (previous._tag === "Some") return waitingFrom(previous);
506
+ return waiting(initialValue);
507
+ }
508
+ function runCallbackSync(services, effect, onExit, uninterruptible = false) {
509
+ if (Exit.isExit(effect)) {
510
+ onExit(effect);
511
+ return;
512
+ }
513
+ const fiber = Effect.runForkWith(services)(effect);
514
+ fiber.currentDispatcher?.flush();
515
+ const result = fiber.pollUnsafe();
516
+ if (result) {
517
+ onExit(result);
518
+ return;
519
+ }
520
+ const remove = fiber.addObserver(onExit);
521
+ function cancel() {
522
+ remove();
523
+ if (!uninterruptible) fiber.interruptUnsafe();
524
+ }
525
+ return cancel;
526
+ }
527
+ function context(options) {
528
+ const memoMap = options?.memoMap ?? removeTtl(make(() => Layer.makeMemoMapUnsafe()));
529
+ const resolveMemoMap = (get) => isAtom(memoMap) ? get(memoMap) : memoMap;
530
+ let globalLayer = Reactivity.layer;
531
+ function factory(create) {
532
+ const self = Object.create(RuntimeProto);
533
+ self.keepAlive = false;
534
+ self.lazy = true;
535
+ self.refresh = void 0;
536
+ self.factory = factory;
537
+ const layerAtom = keepAlive(typeof create === "function" ? readable((get) => Layer.provideMerge(create(get), globalLayer)) : readable(() => Layer.provideMerge(create, globalLayer)));
538
+ self.layer = layerAtom;
539
+ self.read = function read(get) {
540
+ const layer = get(layerAtom);
541
+ const build = Effect.flatMap(Effect.scope, (scope) => Layer.buildWithMemoMap(layer, resolveMemoMap(get), scope));
542
+ return effect(get, build, { uninterruptible: true });
543
+ };
544
+ return self;
545
+ }
546
+ factory.memoMap = memoMap;
547
+ factory.addGlobalLayer = (layer) => {
548
+ globalLayer = Layer.provideMerge(globalLayer, Layer.provide(layer, Reactivity.layer));
549
+ };
550
+ const reactivityAtom = removeTtl(make((get) => Effect.contextWith((services) => Layer.buildWithMemoMap(Reactivity.layer, resolveMemoMap(get), Context.get(services, Scope$1.Scope))).pipe(Effect.map(Context.get(Reactivity.Reactivity)))));
551
+ factory.withReactivity = (keys) => {
552
+ function withReactivityKeys(atom) {
553
+ return transform(atom, (get) => {
554
+ const reactivity = getOrThrow(get(reactivityAtom));
555
+ get.addFinalizer(reactivity.registerUnsafe(keys, () => {
556
+ get.refresh(atom);
557
+ }));
558
+ get.subscribe(atom, (value) => get.setSelf(value));
559
+ return get.once(atom);
560
+ }, { initialValueTarget: atom });
561
+ }
562
+ return withReactivityKeys;
563
+ };
564
+ return factory;
565
+ }
566
+ /**
567
+ * Default registry-scoped `RuntimeFactory`.
568
+ *
569
+ * @category context
570
+ * @since 4.0.0
571
+ */
572
+ const runtime = context();
573
+ /**
574
+ * Returns `Rx.runtime.withReactivity` for refreshing an atom whenever the
575
+ * keys change in the `Reactivity` service.
576
+ *
577
+ * **When to use**
578
+ *
579
+ * Use to refresh an atom whenever one or more invalidation keys change in the
580
+ * default reactivity runtime.
581
+ *
582
+ * @category reactivity
583
+ * @since 4.0.0
584
+ */
585
+ const withReactivity = runtime.withReactivity;
586
+ const stream = (get, stream, options, services) => {
587
+ return makeStream(get, stream, options?.initialValue !== void 0 ? success(options.initialValue) : initial(), services);
588
+ };
589
+ function makeStream(ctx, stream, initialValue, services = Context.empty()) {
590
+ const previous = ctx.self();
591
+ services = Context.add(services, AtomRegistry, ctx.registry);
592
+ const run = Effect.scopedWith((scope) => Effect.flatMap(Channel.toPullScoped(stream.channel, scope), (pull) => Effect.whileLoop({
593
+ while: constTrue,
594
+ body: () => pull,
595
+ step(arr) {
596
+ ctx.setSelf(success(Arr.lastNonEmpty(arr), { waiting: true }));
597
+ }
598
+ }))).pipe(Effect.catchCause((cause) => {
599
+ if (Pull.isDoneCause(cause)) pipe(ctx.self(), Option.flatMap(value), Option.match({
600
+ onNone: () => ctx.setSelf(failWithPrevious(new Cause.NoSuchElementError(), { previous: ctx.self() })),
601
+ onSome: (a) => ctx.setSelf(success(a))
602
+ }));
603
+ else ctx.setSelf(failureWithPrevious(cause, { previous: ctx.self() }));
604
+ return Effect.void;
605
+ }));
606
+ const servicesMap = new Map(services.mapUnsafe);
607
+ servicesMap.set(AtomRegistry.key, ctx.registry);
608
+ servicesMap.set(Scheduler$1.Scheduler.key, ctx.registry.scheduler);
609
+ const cancel = runCallbackSync(Context.makeUnsafe(servicesMap), run, constVoid, false);
610
+ if (cancel !== void 0) ctx.addFinalizer(cancel);
611
+ if (previous._tag === "Some") return waitingFrom(previous);
612
+ return waiting(initialValue);
613
+ }
614
+ /**
615
+ * Creates a writable atom backed by a `SubscriptionRef`, or by an effect that produces one, updating from ref changes and writing atom updates back to the ref.
616
+ *
617
+ * @category constructors
618
+ * @since 4.0.0
619
+ */
620
+ const subscriptionRef = (ref) => makeSubRef(readable((get) => {
621
+ const value = typeof ref === "function" ? ref(get) : ref;
622
+ return SubscriptionRef.isSubscriptionRef(value) ? value : makeEffect(get, value, initial(true));
623
+ }), readSubscriptionRef);
624
+ const readSubscriptionRef = (get, sub, services = Context.empty()) => {
625
+ if (SubscriptionRef.isSubscriptionRef(sub)) {
626
+ get.addFinalizer(SubscriptionRef.changes(sub).pipe(Stream.runForEachArray((arr) => {
627
+ for (let i = 0; i < arr.length; i++) get.setSelf(arr[i]);
628
+ return Effect.void;
629
+ }), Effect.runCallbackWith(services)));
630
+ return Effect.runSyncWith(services)(SubscriptionRef.get(sub));
631
+ } else if (sub._tag !== "Success") return sub;
632
+ return makeStream(get, SubscriptionRef.changes(sub.value), initial(true), services);
633
+ };
634
+ const makeSubRef = (refAtom, read) => {
635
+ function write(ctx, value) {
636
+ const ref = ctx.get(refAtom);
637
+ if (SubscriptionRef.isSubscriptionRef(ref)) Effect.runSync(SubscriptionRef.set(ref, value));
638
+ else if (isSuccess(ref)) Effect.runSync(SubscriptionRef.set(ref.value, value));
639
+ }
640
+ return writable((get) => {
641
+ const ref = get(refAtom);
642
+ if (SubscriptionRef.isSubscriptionRef(ref)) return read(get, ref);
643
+ else if (isSuccess(ref)) return read(get, ref);
644
+ return ref;
645
+ }, write);
646
+ };
647
+ function fnSync(...args) {
648
+ if (args.length === 0) return makeFnSync;
649
+ return makeFnSync(...args);
650
+ }
651
+ const makeFnSync = (f, options) => {
652
+ const argAtom = removeTtl(state([0, void 0]));
653
+ const hasInitialValue = options?.initialValue !== void 0;
654
+ return writable(function(get) {
655
+ get.isFn = true;
656
+ const [counter, arg] = get.get(argAtom);
657
+ if (counter === 0) return hasInitialValue ? options.initialValue : Option.none();
658
+ return hasInitialValue ? f(arg, get) : Option.some(f(arg, get));
659
+ }, function(ctx, arg) {
660
+ batch(() => {
661
+ ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, arg]);
662
+ ctx.refreshSelf();
663
+ });
664
+ });
665
+ };
666
+ /**
667
+ * Defines the control symbol that can be written to an `AtomResultFn` to reset it to its initial state.
668
+ *
669
+ * **When to use**
670
+ *
671
+ * Use when you need an `AtomResultFn` write value that clears the current async
672
+ * result and returns it to the initial state.
673
+ *
674
+ * @category symbols
675
+ * @since 4.0.0
676
+ */
677
+ const Reset = Symbol.for("effect/reactivity/atom/Atom/Reset");
678
+ /**
679
+ * Defines the control symbol that can be written to an `AtomResultFn` to interrupt the current asynchronous computation.
680
+ *
681
+ * **When to use**
682
+ *
683
+ * Use when you need an `AtomResultFn` write value that interrupts the currently
684
+ * running async computation.
685
+ *
686
+ * @category symbols
687
+ * @since 4.0.0
688
+ */
689
+ const Interrupt = Symbol.for("effect/reactivity/atom/Atom/Interrupt");
690
+ function fn(...args) {
691
+ if (args.length === 0) return makeFn;
692
+ return makeFn(...args);
693
+ }
694
+ function makeFn(f, options) {
695
+ const [read, write] = makeResultFn(f, options);
696
+ return writable(read, write);
697
+ }
698
+ function makeResultFn(f, options) {
699
+ const argAtom = removeTtl(state([0, void 0]));
700
+ const initialValue = options?.initialValue !== void 0 ? success(options.initialValue) : initial();
701
+ const fibersAtom = options?.concurrent ? removeTtl(readable((get) => {
702
+ const fibers = /* @__PURE__ */ new Set();
703
+ get.addFinalizer(() => fibers.forEach((f) => f.interruptUnsafe()));
704
+ return fibers;
705
+ })) : void 0;
706
+ function read(get, services) {
707
+ const fibers = fibersAtom ? get(fibersAtom) : void 0;
708
+ get.isFn = true;
709
+ const [counter, arg] = get.get(argAtom);
710
+ if (counter === 0) return initialValue;
711
+ else if (arg === Interrupt) return failureWithPrevious(Cause.interrupt(), { previous: get.self() });
712
+ let value = f(arg, get);
713
+ if (Effect.isEffect(value)) {
714
+ if (fibers) {
715
+ const eff = value;
716
+ value = Effect.flatMap(Effect.forkDetach(eff, { startImmediately: true }), (fiber) => {
717
+ fibers.add(fiber);
718
+ fiber.addObserver(() => fibers.delete(fiber));
719
+ return Effect.map(Fiber.joinAll(fibers), (arr) => arr[0]);
720
+ });
721
+ }
722
+ return makeEffect(get, value, initialValue, services, false);
723
+ }
724
+ return makeStream(get, value, initialValue, services);
725
+ }
726
+ function write(ctx, arg) {
727
+ batch(() => {
728
+ if (arg === Reset) ctx.set(argAtom, [0, void 0]);
729
+ else if (arg === Interrupt) ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, Interrupt]);
730
+ else ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, arg]);
731
+ ctx.refreshSelf();
732
+ });
733
+ }
734
+ return [
735
+ read,
736
+ write,
737
+ argAtom
738
+ ];
739
+ }
740
+ /**
741
+ * Creates a writable atom that pulls an initial chunk from a stream and then pulls the next chunk whenever it is written to, accumulating items unless `disableAccumulation` is enabled.
742
+ *
743
+ * @category constructors
744
+ * @since 4.0.0
745
+ */
746
+ const pull = (create, options) => {
747
+ const pullSignal = removeTtl(state(0));
748
+ const pullAtom = readable(makeRead(function(get) {
749
+ return makeStreamPullEffect(get, pullSignal, create, options);
750
+ }));
751
+ return makeStreamPull(pullSignal, pullAtom);
752
+ };
753
+ const makeStreamPullEffect = (get, pullSignal, create, options) => Effect.flatMap(Stream.toPull(typeof create === "function" ? create(get) : create), (pullChunk) => {
754
+ const services = Fiber.getCurrent().context;
755
+ let acc = Arr.empty();
756
+ const pull = Effect.matchCauseEffect(pullChunk, {
757
+ onFailure(cause) {
758
+ if (Pull.isDoneCause(cause)) {
759
+ if (!Arr.isReadonlyArrayNonEmpty(acc)) return Effect.fail(new Cause.NoSuchElementError(`Atom.pull: no items`));
760
+ return Effect.succeed({
761
+ done: true,
762
+ items: acc
763
+ });
764
+ }
765
+ return Effect.failCause(cause);
766
+ },
767
+ onSuccess(chunk) {
768
+ let items;
769
+ if (options?.disableAccumulation) items = Arr.fromIterable(chunk);
770
+ else {
771
+ items = Arr.appendAll(acc, chunk);
772
+ acc = items;
773
+ }
774
+ if (!Arr.isReadonlyArrayNonEmpty(items)) return pull;
775
+ return Effect.succeed({
776
+ done: false,
777
+ items
778
+ });
779
+ }
780
+ });
781
+ const cancels = /* @__PURE__ */ new Set();
782
+ get.addFinalizer(() => {
783
+ for (const cancel of cancels) cancel();
784
+ });
785
+ get.once(pullSignal);
786
+ get.subscribe(pullSignal, () => {
787
+ get.setSelf(waitingFrom(get.self()));
788
+ let cancel;
789
+ cancel = runCallbackSync(services, pull, (exit) => {
790
+ if (cancel) cancels.delete(cancel);
791
+ const result = fromExitWithPrevious(exit, get.self());
792
+ const pending = cancels.size > 0;
793
+ get.setSelf(pending ? waiting(result) : result);
794
+ });
795
+ if (cancel) cancels.add(cancel);
796
+ });
797
+ return pull;
798
+ });
799
+ const makeStreamPull = (pullSignal, pullAtom) => writable(pullAtom.read, function(ctx, _) {
800
+ ctx.set(pullSignal, ctx.get(pullSignal) + 1);
801
+ });
802
+ /**
803
+ * Creates a memoized atom factory that returns the same object for the same argument, using weak references for cached values when the platform supports them.
804
+ *
805
+ * @category constructors
806
+ * @since 4.0.0
807
+ */
808
+ const family = typeof WeakRef === "undefined" || typeof FinalizationRegistry === "undefined" ? (f) => {
809
+ const atoms = MutableHashMap.empty();
810
+ return function(arg) {
811
+ const atomEntry = MutableHashMap.get(atoms, arg);
812
+ if (atomEntry._tag === "Some") return atomEntry.value;
813
+ const newAtom = f(arg);
814
+ MutableHashMap.set(atoms, arg, newAtom);
815
+ return newAtom;
816
+ };
817
+ } : (f) => {
818
+ const atoms = MutableHashMap.empty();
819
+ const registry = new FinalizationRegistry((arg) => {
820
+ MutableHashMap.remove(atoms, arg);
821
+ });
822
+ return function(arg) {
823
+ const atomEntry = MutableHashMap.get(atoms, arg).pipe(Option.flatMapNullishOr((ref) => ref.deref()));
824
+ if (atomEntry._tag === "Some") return atomEntry.value;
825
+ const newAtom = f(arg);
826
+ MutableHashMap.set(atoms, arg, new WeakRef(newAtom));
827
+ registry.register(newAtom, arg);
828
+ return newAtom;
829
+ };
830
+ };
831
+ /**
832
+ * Uses a fallback `AsyncResult` atom while the primary atom is `Initial`, marking the fallback result as waiting until the primary atom produces a non-initial result.
833
+ *
834
+ * @category combinators
835
+ * @since 4.0.0
836
+ */
837
+ const withFallback = dual(2, (self, fallback) => {
838
+ function withFallback(get) {
839
+ const result = get(self);
840
+ if (result._tag === "Initial") return waiting(get(fallback));
841
+ return result;
842
+ }
843
+ return isWritable(self) ? writable(withFallback, self.write, self.refresh ?? function(refresh) {
844
+ refresh(self);
845
+ }) : readable(withFallback, self.refresh ?? function(refresh) {
846
+ refresh(self);
847
+ });
848
+ });
849
+ /**
850
+ * Returns a copy of an atom that remains cached and mounted even when no subscribers are using it.
851
+ *
852
+ * @category combinators
853
+ * @since 4.0.0
854
+ */
855
+ const keepAlive = (self) => Object.assign(Object.create(Object.getPrototypeOf(self)), {
856
+ ...self,
857
+ keepAlive: true
858
+ });
859
+ /**
860
+ * Allows a reactive value to be disposed of when it is not in use.
861
+ *
862
+ * **Details**
863
+ *
864
+ * Atoms have this behavior by default, so use this to undo `keepAlive` on a copied atom.
865
+ *
866
+ * @category combinators
867
+ * @since 4.0.0
868
+ */
869
+ const autoDispose = (self) => Object.assign(Object.create(Object.getPrototypeOf(self)), {
870
+ ...self,
871
+ keepAlive: false
872
+ });
873
+ /**
874
+ * Sets whether an atom should be lazy.
875
+ *
876
+ * **Details**
877
+ *
878
+ * Lazy atoms defer recomputation while they have no active listeners or active
879
+ * non-lazy dependents, rebuilding the next time their value is observed.
880
+ *
881
+ * @category combinators
882
+ * @since 4.0.0
883
+ */
884
+ const setLazy = dual(2, (self, lazy) => Object.assign(Object.create(Object.getPrototypeOf(self)), {
885
+ ...self,
886
+ lazy
887
+ }));
888
+ /**
889
+ * Returns a copy of an atom that uses a custom equality function to detect
890
+ * value changes.
891
+ *
892
+ * **Details**
893
+ *
894
+ * When an atom's value is rebuilt or written, the registry compares the new
895
+ * value against the current one to decide whether dependents and listeners
896
+ * should be notified. By default the comparison uses `Object.is`, so a
897
+ * structurally equal but referentially distinct value still triggers
898
+ * notifications. Providing an equality function lets the atom skip updates
899
+ * when the new value is equal to the current one.
900
+ *
901
+ * **Example** (Comparing values structurally)
902
+ *
903
+ * ```ts import.meta.vitest
904
+ * import { Atom } from "effect/unstable/reactivity"
905
+ *
906
+ * const point = Atom.make({ x: 0, y: 0 }).pipe(
907
+ * Atom.withEquality<{ x: number; y: number }>((a, b) => a.x === b.x && a.y === b.y)
908
+ * )
909
+ * point.equals({ x: 1, y: 2 }, { x: 1, y: 2 }) // => true
910
+ * ```
911
+ *
912
+ * @category combinators
913
+ * @since 4.0.0
914
+ */
915
+ const withEquality = dual(2, (self, equals) => Object.assign(Object.create(Object.getPrototypeOf(self)), {
916
+ ...self,
917
+ equals
918
+ }));
919
+ /**
920
+ * Attaches a diagnostic label to an atom.
921
+ *
922
+ * **Details**
923
+ *
924
+ * The label is used for inspection and debugging metadata and does not change the
925
+ * atom's read or write behavior.
926
+ *
927
+ * @category combinators
928
+ * @since 4.0.0
929
+ */
930
+ const withLabel = dual(2, (self, name) => Object.assign(Object.create(Object.getPrototypeOf(self)), {
931
+ ...self,
932
+ label: [name, (/* @__PURE__ */ new Error()).stack?.split("\n")[5] ?? ""]
933
+ }));
934
+ /**
935
+ * Pairs an atom with an initial value for registry initialization.
936
+ *
937
+ * **When to use**
938
+ *
939
+ * Use to preload an atom value when constructing or seeding a registry.
940
+ *
941
+ * **Details**
942
+ *
943
+ * The returned tuple can be supplied to `AtomRegistry` initial values so the atom
944
+ * starts with the provided value before it is first rebuilt.
945
+ *
946
+ * @category combinators
947
+ * @since 4.0.0
948
+ */
949
+ const initialValue = dual(2, (self, initialValue) => [self, initialValue]);
950
+ /**
951
+ * Maps the value of an atom by reading the source atom, applying the function,
952
+ *
953
+ * **Details**
954
+ *
955
+ * When the source atom is writable, the returned atom remains writable and keeps
956
+ * the source atom's write input type.
957
+ *
958
+ * @category combinators
959
+ * @since 4.0.0
960
+ */
961
+ const map = dual(2, (self, f) => transform(self, (get) => f(get(self))));
962
+ /**
963
+ * Maps the successful value inside an `AsyncResult` atom.
964
+ *
965
+ * **Details**
966
+ *
967
+ * Initial and failure states are preserved, and writable source atoms keep their
968
+ * original write input type.
969
+ *
970
+ * @category combinators
971
+ * @since 4.0.0
972
+ */
973
+ const mapResult = dual(2, (self, f) => map(self, map$1(f)));
974
+ /**
975
+ * Creates an atom that publishes source changes only after the source has stopped
976
+ * changing for the specified duration.
977
+ *
978
+ * **Details**
979
+ *
980
+ * The current source value is used immediately, and any pending debounce timer is
981
+ * cleared when the derived atom is disposed.
982
+ *
983
+ * @category combinators
984
+ * @since 4.0.0
985
+ */
986
+ const debounce = dual(2, (self, duration) => {
987
+ const millis = Duration.toMillis(Duration.fromInputUnsafe(duration));
988
+ return transform(self, function(get) {
989
+ let timeout;
990
+ let value = get.once(self);
991
+ function update() {
992
+ timeout = void 0;
993
+ get.setSelf(value);
994
+ }
995
+ get.addFinalizer(function() {
996
+ if (timeout) clearTimeout(timeout);
997
+ });
998
+ get.subscribe(self, function(val) {
999
+ value = val;
1000
+ if (timeout) clearTimeout(timeout);
1001
+ timeout = setTimeout(update, millis);
1002
+ });
1003
+ return value;
1004
+ }, { initialValueTarget: self });
1005
+ });
1006
+ /**
1007
+ * Creates a derived atom that reads the source and schedules a refresh after the
1008
+ * specified duration.
1009
+ *
1010
+ * **Details**
1011
+ *
1012
+ * The scheduled refresh is canceled when the derived atom's lifetime is disposed.
1013
+ *
1014
+ * @category combinators
1015
+ * @since 4.0.0
1016
+ */
1017
+ const withRefresh = dual(2, (self, duration) => {
1018
+ const millis = Duration.toMillis(Duration.fromInputUnsafe(duration));
1019
+ return transform(self, function(get) {
1020
+ const fiber = Effect.runFork(Effect.sleep(millis).pipe(Effect.andThen(Effect.sync(() => get.refresh(self)))));
1021
+ get.addFinalizer(() => fiber.interruptUnsafe());
1022
+ return get(self);
1023
+ }, { initialValueTarget: self });
1024
+ });
1025
+ /**
1026
+ * Adds stale-while-revalidate refresh behavior to an async result atom.
1027
+ *
1028
+ * **Details**
1029
+ *
1030
+ * Automatic revalidation during reads is skipped while the current value is
1031
+ * fresh within `staleTime`. Manual `refresh` calls remain forceful and always
1032
+ * forward to the wrapped atom. Use `revalidateOnMount` to control whether stale data should trigger a
1033
+ * background refresh on first mount. Use `revalidateOnFocus` to control
1034
+ * focus behavior. `true` respects `staleTime` and `"always"` forces refetch.
1035
+ *
1036
+ * @category combinators
1037
+ * @since 4.0.0
1038
+ */
1039
+ const swr = dual(2, (self, options) => {
1040
+ const staleTime = Duration.toMillis(Duration.fromInputUnsafe(options.staleTime));
1041
+ return transform(self, (get) => {
1042
+ const current = get.once(self);
1043
+ get.subscribe(self, (value) => {
1044
+ get.setSelf(value);
1045
+ });
1046
+ if (options.revalidateOnFocus && options.focusSignal) {
1047
+ get.once(options.focusSignal);
1048
+ get.subscribe(options.focusSignal, options.revalidateOnFocus === "always" ? () => get.refresh(self) : () => {
1049
+ const current = get.once(self);
1050
+ if (shouldRevalidateSWR(current, staleTime)) get.refresh(self);
1051
+ });
1052
+ }
1053
+ if (Option.isNone(get.self()) && options.revalidateOnMount === false) return current;
1054
+ if (shouldRevalidateSWR(current, staleTime)) get.refresh(self);
1055
+ return current;
1056
+ }, { initialValueTarget: self });
1057
+ });
1058
+ const swrTimestamp = (result) => {
1059
+ if (result._tag === "Success") return Option.some(result.timestamp);
1060
+ if (result._tag === "Failure") return Option.map(result.previousSuccess, (success) => success.timestamp);
1061
+ return Option.none();
1062
+ };
1063
+ const isFreshWithin = (timestamp, staleTime, now) => now - timestamp < staleTime;
1064
+ const shouldRevalidateSWR = (result, staleTime) => {
1065
+ if (result.waiting) return false;
1066
+ const timestamp = Option.getOrUndefined(swrTimestamp(result));
1067
+ if (timestamp === void 0) return result._tag !== "Initial";
1068
+ return !isFreshWithin(timestamp, staleTime, Date.now());
1069
+ };
1070
+ /**
1071
+ * Wraps an atom in a writable optimistic atom.
1072
+ *
1073
+ * **Details**
1074
+ *
1075
+ * Writes accept transition atoms containing `AsyncResult` values. Waiting
1076
+ * successes are shown optimistically while transitions run; when successful
1077
+ * transitions finish, the source atom is refreshed, and failures roll the value
1078
+ * back to the latest source value.
1079
+ *
1080
+ * @category constructors
1081
+ * @since 4.0.0
1082
+ */
1083
+ const optimistic = (self) => {
1084
+ let counter = 0;
1085
+ const writeAtom = removeTtl(state([counter, void 0]));
1086
+ return writable((get) => {
1087
+ let lastValue = get.once(self);
1088
+ let needsRefresh = false;
1089
+ get.subscribe(self, (value) => {
1090
+ lastValue = value;
1091
+ if (transitions.size > 0) return;
1092
+ needsRefresh = false;
1093
+ if (!isResult(value)) return get.setSelf(value);
1094
+ const current = Option.getOrUndefined(get.self());
1095
+ switch (value._tag) {
1096
+ case "Initial":
1097
+ if (isInitial(current)) get.setSelf(value);
1098
+ return;
1099
+ case "Success":
1100
+ if (isSuccess(current)) {
1101
+ if (!value.waiting && value.timestamp >= current.timestamp) get.setSelf(value);
1102
+ } else get.setSelf(value);
1103
+ return;
1104
+ case "Failure": return get.setSelf(value);
1105
+ }
1106
+ });
1107
+ const transitions = /* @__PURE__ */ new Set();
1108
+ const cancels = /* @__PURE__ */ new Set();
1109
+ get.subscribe(writeAtom, ([, atom]) => {
1110
+ if (transitions.has(atom)) return;
1111
+ transitions.add(atom);
1112
+ let cancel;
1113
+ cancel = get.registry.subscribe(atom, (result) => {
1114
+ if (isSuccess(result) && result.waiting) return get.setSelf(result.value);
1115
+ transitions.delete(atom);
1116
+ if (cancel) {
1117
+ cancels.delete(cancel);
1118
+ cancel();
1119
+ }
1120
+ if (!needsRefresh && !isFailure(result)) needsRefresh = true;
1121
+ if (transitions.size === 0) {
1122
+ if (needsRefresh) {
1123
+ needsRefresh = false;
1124
+ get.refresh(self);
1125
+ } else get.setSelf(lastValue);
1126
+ }
1127
+ }, { immediate: true });
1128
+ if (transitions.has(atom)) cancels.add(cancel);
1129
+ else cancel();
1130
+ });
1131
+ get.addFinalizer(() => {
1132
+ for (const cancel of cancels) cancel();
1133
+ transitions.clear();
1134
+ cancels.clear();
1135
+ });
1136
+ return lastValue;
1137
+ }, (ctx, atom) => ctx.set(writeAtom, [++counter, atom]), (refresh) => refresh(self));
1138
+ };
1139
+ /**
1140
+ * Creates an `AtomResultFn` that applies an optimistic update before running the
1141
+ * underlying mutation.
1142
+ *
1143
+ * **Details**
1144
+ *
1145
+ * The reducer computes the provisional value from the current value and mutation
1146
+ * input. The wrapped function result then completes the transition or updates the
1147
+ * optimistic value through the provided setter callback.
1148
+ *
1149
+ * @category combinators
1150
+ * @since 4.0.0
1151
+ */
1152
+ const optimisticFn = dual(2, (self, options) => {
1153
+ const transition = removeTtl(state(initial()));
1154
+ return fn((arg, get) => {
1155
+ let value = options.reducer(get(self), arg);
1156
+ if (isResult(value)) value = waiting(value, { touch: true });
1157
+ get.set(transition, success(value, { waiting: true }));
1158
+ get.set(self, transition);
1159
+ const fn = typeof options.fn === "function" ? autoDispose(options.fn((value) => get.set(transition, success(isResult(value) ? waiting(value) : value, { waiting: true })))) : options.fn;
1160
+ get.set(fn, arg);
1161
+ return Effect.callback((resume) => {
1162
+ get.subscribe(fn, (result) => {
1163
+ if (result._tag === "Initial" || result.waiting) return;
1164
+ get.set(transition, map$1(result, () => value));
1165
+ resume(toExit(result));
1166
+ }, { immediate: true });
1167
+ });
1168
+ });
1169
+ });
1170
+ /**
1171
+ * Runs synchronous atom updates as a batch.
1172
+ *
1173
+ * **Details**
1174
+ *
1175
+ * Stale nodes are rebuilt and listeners are notified after the callback completes,
1176
+ * so dependent updates observe the final batched state.
1177
+ *
1178
+ * @category batching
1179
+ * @since 4.0.0
1180
+ */
1181
+ const batch = batch$1;
1182
+ function kvs(options) {
1183
+ const setAtom = options.runtime.fn((value) => KeyValueStore.KeyValueStore.use((store) => KeyValueStore.toSchemaStore(store, options.schema).set(options.key, value)));
1184
+ const resultAtom = options.runtime.atom(KeyValueStore.KeyValueStore.use((store) => KeyValueStore.toSchemaStore(store, options.schema).get(options.key)));
1185
+ let written = false;
1186
+ return writable(options.mode === "async" ? (get) => {
1187
+ written = false;
1188
+ get.mount(setAtom);
1189
+ const mapper = map$1(Option.getOrElse(() => {
1190
+ const value = options.defaultValue();
1191
+ get.set(setAtom, value);
1192
+ return value;
1193
+ }));
1194
+ get.subscribe(resultAtom, (result) => {
1195
+ if (written) return;
1196
+ get.setSelf(mapper(result));
1197
+ });
1198
+ return mapper(get.once(resultAtom));
1199
+ } : (get) => {
1200
+ written = false;
1201
+ get.mount(setAtom);
1202
+ get.subscribe(resultAtom, (result) => {
1203
+ if (!isSuccess(result)) return;
1204
+ if (written) return;
1205
+ if (Option.isSome(result.value)) get.setSelf(result.value.value);
1206
+ else {
1207
+ const value = Option.getOrElse(get.self(), options.defaultValue);
1208
+ get.setSelf(value);
1209
+ get.set(setAtom, value);
1210
+ }
1211
+ }, { immediate: true });
1212
+ return Option.getOrElse(get.self(), options.defaultValue);
1213
+ }, (ctx, value) => {
1214
+ written = true;
1215
+ ctx.set(setAtom, value);
1216
+ ctx.setSelf(options.mode === "async" ? success(value) : value);
1217
+ });
1218
+ }
1219
+ /**
1220
+ * Converts an atom into a stream using the `AtomRegistry` service.
1221
+ *
1222
+ * **Details**
1223
+ *
1224
+ * The stream emits the atom's current value immediately and then emits subsequent
1225
+ * changes until the stream scope is closed.
1226
+ *
1227
+ * @category converting
1228
+ * @since 4.0.0
1229
+ */
1230
+ const toStream = (self) => Stream.unwrap(AtomRegistry.use((r) => Effect.succeed(toStream$1(r, self))));
1231
+ /**
1232
+ * Converts an `AsyncResult` atom into a stream using the `AtomRegistry` service.
1233
+ *
1234
+ * **Details**
1235
+ *
1236
+ * Initial results are skipped, successes are emitted as stream values, and
1237
+ * failures fail the stream with the result cause.
1238
+ *
1239
+ * @category converting
1240
+ * @since 4.0.0
1241
+ */
1242
+ const toStreamResult = (self) => Stream.unwrap(AtomRegistry.use((r) => Effect.succeed(toStreamResult$1(r, self))));
1243
+ /**
1244
+ * Reads an atom's current value from the `AtomRegistry` service.
1245
+ *
1246
+ * @category converting
1247
+ * @since 4.0.0
1248
+ */
1249
+ const get = (self) => AtomRegistry.use((r) => Effect.succeed(r.get(self)));
1250
+ /**
1251
+ * Reads a writable atom, computes a return value and next write value, writes the
1252
+ * next value, and returns the computed result.
1253
+ *
1254
+ * @category converting
1255
+ * @since 4.0.0
1256
+ */
1257
+ const modify = dual(2, (self, f) => Effect.map(AtomRegistry, (_) => _.modify(self, f)));
1258
+ /**
1259
+ * Writes a value to a writable atom through the `AtomRegistry` service.
1260
+ *
1261
+ * @category converting
1262
+ * @since 4.0.0
1263
+ */
1264
+ const set = dual(2, (self, value) => Effect.map(AtomRegistry, (_) => _.set(self, value)));
1265
+ /**
1266
+ * Updates a writable atom by reading its current value from the registry and
1267
+ * writing the value returned by the update function.
1268
+ *
1269
+ * @category converting
1270
+ * @since 4.0.0
1271
+ */
1272
+ const update = dual(2, (self, f) => Effect.map(AtomRegistry, (_) => _.update(self, f)));
1273
+ /**
1274
+ * Reads an `AsyncResult` atom as an effect through the `AtomRegistry` service.
1275
+ *
1276
+ * **Details**
1277
+ *
1278
+ * The effect waits while the result is `Initial`, and also while it is waiting
1279
+ * when `suspendOnWaiting` is enabled. Successes succeed with the value and
1280
+ * failures fail with the result cause.
1281
+ *
1282
+ * @category converting
1283
+ * @since 4.0.0
1284
+ */
1285
+ const getResult = (self, options) => AtomRegistry.use(getResult$1(self, options));
1286
+ /**
1287
+ * Runs a refresh request for an atom through the `AtomRegistry` service.
1288
+ *
1289
+ * **When to use**
1290
+ *
1291
+ * Use to invalidate and recompute an atom from an Effect that has access to the
1292
+ * active registry.
1293
+ *
1294
+ * @category converting
1295
+ * @since 4.0.0
1296
+ */
1297
+ const refresh = (self) => Effect.map(AtomRegistry, (_) => _.refresh(self));
1298
+ /**
1299
+ * Mounts an atom in the `AtomRegistry` for the lifetime of the current scope.
1300
+ *
1301
+ * **Details**
1302
+ *
1303
+ * Mounting keeps the atom subscribed with a no-op listener until the scope
1304
+ * finalizer releases it.
1305
+ *
1306
+ * @category converting
1307
+ * @since 4.0.0
1308
+ */
1309
+ const mount = (self) => AtomRegistry.use((r) => mount$1(r, self));
1310
+ /**
1311
+ * The type id used to mark atoms that carry serialization metadata.
1312
+ *
1313
+ * @category type IDs
1314
+ * @since 4.0.0
1315
+ */
1316
+ const SerializableTypeId = "~effect-atom/atom/Atom/Serializable";
1317
+ /**
1318
+ * Returns `true` when an atom carries `Serializable` metadata.
1319
+ *
1320
+ * @category guards
1321
+ * @since 4.0.0
1322
+ */
1323
+ const isSerializable = (self) => SerializableTypeId in self;
1324
+ /**
1325
+ * Attaches serialization metadata to an atom using a schema and stable key.
1326
+ *
1327
+ * **Details**
1328
+ *
1329
+ * The schema is converted to a JSON codec for synchronous encode/decode, and the
1330
+ * key is also used as the atom label when the atom does not already have one.
1331
+ *
1332
+ * @category combinators
1333
+ * @since 4.0.0
1334
+ */
1335
+ const serializable = dual(2, (self, options) => {
1336
+ const codecJson = Schema.toCodecJson(options.schema);
1337
+ return Object.assign(Object.create(Object.getPrototypeOf(self)), {
1338
+ ...self,
1339
+ label: self.label ?? [options.key, (/* @__PURE__ */ new Error()).stack?.split("\n")[5] ?? ""],
1340
+ [SerializableTypeId]: {
1341
+ key: options.key,
1342
+ encode: Schema.encodeSync(codecJson),
1343
+ decode: Schema.decodeSync(codecJson)
1344
+ }
1345
+ });
1346
+ });
1347
+ //#endregion
1348
+ export { readable as $, setLazy as A, withRefresh as B, optimistic as C, runtime as D, refresh as E, update as F, makeRefreshOnSignal as G, getServerValue as H, withEquality as I, windowFocusSignal as J, refreshOnWindowFocus as K, withFallback as L, swr as M, toStream as N, serializable as O, toStreamResult as P, isWritable as Q, withLabel as R, mount as S, pull as T, withServerValue as U, ServerValueTypeId as V, withServerValueInitial as W, WritableTypeId as X, TypeId as Y, isAtom as Z, kvs as _, autoDispose as a, mapResult as b, debounce as c, fnSync as d, setIdleTTL as et, get as f, keepAlive as g, isSerializable as h, SerializableTypeId as i, subscriptionRef as j, set as k, family as l, initialValue as m, Interrupt as n, writable as nt, batch as o, getResult as p, searchParam as q, Reset as r, context as s, Atom_exports as t, transform as tt, fn as u, make as v, optimisticFn as w, modify as x, map as y, withReactivity as z };