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