| /** | |
| * Reactive state primitives for values managed by an `AtomRegistry`. | |
| * | |
| * An `Atom` describes how to produce or update one piece of reactive state. The | |
| * registry runs atom reads, remembers current values, tracks dependencies | |
| * between atoms, starts effects and streams, and cleans up atoms that are no | |
| * longer used. This module includes the atom constructors and update helpers | |
| * used for cached values, effect-backed values, streams, browser state, stored | |
| * values, and server-rendered values. | |
| * | |
| * @since 4.0.0 | |
| */ | |
| import * as Arr from "../../Array.js"; | |
| import * as Cause from "../../Cause.js"; | |
| import * as Channel from "../../Channel.js"; | |
| import * as Context from "../../Context.js"; | |
| import * as Duration from "../../Duration.js"; | |
| import * as Effect from "../../Effect.js"; | |
| import * as Exit from "../../Exit.js"; | |
| import * as Fiber from "../../Fiber.js"; | |
| import { constant, constTrue, constVoid, dual, pipe } from "../../Function.js"; | |
| import { PipeInspectableProto } from "../../internal/core.js"; | |
| import * as Layer from "../../Layer.js"; | |
| import * as MutableHashMap from "../../MutableHashMap.js"; | |
| import * as Option from "../../Option.js"; | |
| import { hasProperty } from "../../Predicate.js"; | |
| import * as Pull from "../../Pull.js"; | |
| import * as Scheduler from "../../Scheduler.js"; | |
| import * as Schema from "../../Schema.js"; | |
| import * as Scope from "../../Scope.js"; | |
| import * as Stream from "../../Stream.js"; | |
| import * as SubscriptionRef from "../../SubscriptionRef.js"; | |
| import * as KeyValueStore from "../persistence/KeyValueStore.js"; | |
| import * as AsyncResult from "./AsyncResult.js"; | |
| import { AtomRegistry } from "./AtomRegistry.js"; | |
| import * as Registry from "./AtomRegistry.js"; | |
| import * as Reactivity from "./Reactivity.js"; | |
| /** | |
| * Runtime identifier attached to `Atom` values and used by `isAtom`. | |
| * | |
| * @category type IDs | |
| * @since 4.0.0 | |
| */ | |
| export const TypeId = "~effect/reactivity/Atom"; | |
| /** | |
| * Returns `true` when a value is an `Atom`. | |
| * | |
| * @category guards | |
| * @since 4.0.0 | |
| */ | |
| export const isAtom = u => hasProperty(u, TypeId); | |
| /** | |
| * Runtime identifier attached to writable atoms and used by `isWritable`. | |
| * | |
| * @category type IDs | |
| * @since 4.0.0 | |
| */ | |
| export const WritableTypeId = "~effect/reactivity/Atom/Writable"; | |
| /** | |
| * 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. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setIdleTTL = /*#__PURE__*/dual(2, (self, durationInput) => { | |
| const duration = Duration.fromInputUnsafe(durationInput); | |
| const isFinite = Duration.isFinite(duration); | |
| return Object.assign(Object.create(Object.getPrototypeOf(self)), { | |
| ...self, | |
| keepAlive: !isFinite, | |
| idleTTL: isFinite ? Duration.toMillis(duration) : undefined | |
| }); | |
| }); | |
| const removeTtl = /*#__PURE__*/setIdleTTL(0); | |
| const AtomProto = { | |
| [TypeId]: TypeId, | |
| ...PipeInspectableProto, | |
| toJSON() { | |
| return { | |
| _id: "Atom", | |
| keepAlive: this.keepAlive, | |
| lazy: this.lazy, | |
| label: this.label | |
| }; | |
| } | |
| }; | |
| const RuntimeProto = { | |
| ...AtomProto, | |
| atom(arg, options) { | |
| const read = makeRead(arg, options); | |
| return readable(get => { | |
| const previous = get.self(); | |
| const runtimeResult = get(this); | |
| if (runtimeResult._tag !== "Success") { | |
| return AsyncResult.replacePrevious(runtimeResult, previous); | |
| } | |
| return read(get, runtimeResult.value); | |
| }); | |
| }, | |
| fn(arg, options) { | |
| if (arguments.length === 0) { | |
| return (arg, options) => makeFnRuntime(this, arg, options); | |
| } | |
| return makeFnRuntime(this, arg, options); | |
| }, | |
| pull(arg, options) { | |
| const pullSignal = removeTtl(state(0)); | |
| const pullAtom = readable(get => { | |
| const previous = get.self(); | |
| const runtimeResult = get(this); | |
| if (runtimeResult._tag !== "Success") { | |
| return AsyncResult.replacePrevious(runtimeResult, previous); | |
| } | |
| return makeEffect(get, makeStreamPullEffect(get, pullSignal, arg, options), AsyncResult.initial(true), runtimeResult.value); | |
| }); | |
| return makeStreamPull(pullSignal, pullAtom); | |
| }, | |
| subscriptionRef(ref) { | |
| return makeSubRef(removeTtl(readable(get => { | |
| const previous = get.self(); | |
| const runtimeResult = get(this); | |
| if (runtimeResult._tag !== "Success") { | |
| return AsyncResult.replacePrevious(runtimeResult, previous); | |
| } | |
| const value = typeof ref === "function" ? ref(get) : ref; | |
| return SubscriptionRef.isSubscriptionRef(value) ? value : makeEffect(get, value, AsyncResult.initial(true), runtimeResult.value); | |
| })), (get, ref) => { | |
| const runtime = AsyncResult.getOrThrow(get(this)); | |
| return readSubscriptionRef(get, ref, runtime); | |
| }); | |
| } | |
| }; | |
| const makeFnRuntime = (self, arg, options) => { | |
| const [read, write, argAtom] = makeResultFn(options?.reactivityKeys ? (a, get) => { | |
| const effect = arg(a, get); | |
| return Effect.isEffect(effect) ? Reactivity.mutation(effect, options.reactivityKeys) : Stream.ensuring(effect, Reactivity.invalidate(options.reactivityKeys)); | |
| } : arg, options); | |
| return writable(get => { | |
| get.get(argAtom); | |
| const previous = get.self(); | |
| const runtimeResult = get.get(self); | |
| if (runtimeResult._tag !== "Success") { | |
| return AsyncResult.replacePrevious(runtimeResult, previous); | |
| } | |
| return read(get, runtimeResult.value); | |
| }, write); | |
| }; | |
| const WritableProto = { | |
| ...AtomProto, | |
| [WritableTypeId]: WritableTypeId | |
| }; | |
| /** | |
| * Returns `true` when an atom is writable. | |
| * | |
| * @category refinements | |
| * @since 4.0.0 | |
| */ | |
| export const isWritable = atom => WritableTypeId in atom; | |
| /** | |
| * Creates a read-only atom from a read function and an optional custom refresh registration callback. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const readable = (read, refresh) => { | |
| const self = Object.create(AtomProto); | |
| self.keepAlive = false; | |
| self.lazy = true; | |
| self.read = read; | |
| self.refresh = refresh; | |
| return self; | |
| }; | |
| /** | |
| * Creates a writable atom from read and write functions, with an optional custom refresh registration callback. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const writable = (read, write, refresh) => { | |
| const self = Object.create(WritableProto); | |
| self.keepAlive = false; | |
| self.lazy = true; | |
| self.read = read; | |
| self.write = write; | |
| self.refresh = refresh; | |
| return self; | |
| }; | |
| function constSetSelf(ctx, value) { | |
| ctx.setSelf(value); | |
| } | |
| // ----------------------------------------------------------------------------- | |
| // constructors | |
| // ----------------------------------------------------------------------------- | |
| /** | |
| * Creates an atom from a synchronous value or read function, or from an `Effect` or `Stream` whose state is exposed as an `AsyncResult`; plain values create writable state atoms. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const make = (arg, options) => { | |
| const readOrAtom = makeRead(arg, options); | |
| if (TypeId in readOrAtom) { | |
| return readOrAtom; | |
| } | |
| return readable(readOrAtom); | |
| }; | |
| // ----------------------------------------------------------------------------- | |
| // constructors - effect | |
| // ----------------------------------------------------------------------------- | |
| const makeRead = (arg, options) => { | |
| if (typeof arg === "function" && !Effect.isEffect(arg) && !Stream.isStream(arg)) { | |
| const create = arg; | |
| return function (get, providedServices) { | |
| const value = create(get); | |
| switch (typeof value) { | |
| case "function": | |
| case "object": | |
| { | |
| if (value === null) return value;else if (EffectTypeId in value) { | |
| return effect(get, value, options, providedServices); | |
| } else if (StreamTypeId in value) { | |
| return stream(get, value, options, providedServices); | |
| } | |
| return value; | |
| } | |
| default: | |
| return value; | |
| } | |
| }; | |
| } else if (Effect.isEffect(arg)) { | |
| return function (get, providedServices) { | |
| return effect(get, arg, options, providedServices); | |
| }; | |
| } else if (Stream.isStream(arg)) { | |
| return function (get, providedServices) { | |
| return stream(get, arg, options, providedServices); | |
| }; | |
| } | |
| return state(arg); | |
| }; | |
| const EffectTypeId = "~effect/Effect"; | |
| const StreamTypeId = "~effect/Stream"; | |
| const state = initialValue => writable(function (_get) { | |
| return initialValue; | |
| }, constSetSelf); | |
| const effect = (get, effect, options, services) => { | |
| const initialValue = options?.initialValue !== undefined ? AsyncResult.success(options.initialValue) : AsyncResult.initial(); | |
| return makeEffect(get, effect, initialValue, services, options?.uninterruptible); | |
| }; | |
| function makeEffect(ctx, effect, initialValue, services = Context.empty(), uninterruptible = false) { | |
| const previous = ctx.self(); | |
| const scope = Scope.makeUnsafe(); | |
| ctx.addFinalizer(() => { | |
| Effect.runForkWith(services)(Scope.close(scope, Exit.void)); | |
| }); | |
| const servicesMap = new Map(services.mapUnsafe); | |
| servicesMap.set(Scope.Scope.key, scope); | |
| servicesMap.set(AtomRegistry.key, ctx.registry); | |
| servicesMap.set(Scheduler.Scheduler.key, ctx.registry.scheduler); | |
| let syncResult; | |
| let isAsync = false; | |
| const cancel = runCallbackSync(Context.makeUnsafe(servicesMap), effect, function (exit) { | |
| syncResult = AsyncResult.fromExitWithPrevious(exit, previous); | |
| if (isAsync) { | |
| ctx.setSelf(syncResult); | |
| } | |
| }, uninterruptible); | |
| isAsync = true; | |
| if (cancel !== undefined) { | |
| ctx.addFinalizer(cancel); | |
| } | |
| if (syncResult !== undefined) { | |
| return syncResult; | |
| } else if (previous._tag === "Some") { | |
| return AsyncResult.waitingFrom(previous); | |
| } | |
| return AsyncResult.waiting(initialValue); | |
| } | |
| function runCallbackSync(services, effect, onExit, uninterruptible = false) { | |
| if (Exit.isExit(effect)) { | |
| onExit(effect); | |
| return undefined; | |
| } | |
| const runFork = Effect.runForkWith(services); | |
| const fiber = runFork(effect); | |
| fiber.currentDispatcher?.flush(); | |
| const result = fiber.pollUnsafe(); | |
| if (result) { | |
| onExit(result); | |
| return undefined; | |
| } | |
| const remove = fiber.addObserver(onExit); | |
| function cancel() { | |
| remove(); | |
| if (!uninterruptible) { | |
| fiber.interruptUnsafe(); | |
| } | |
| } | |
| return cancel; | |
| } | |
| /** | |
| * Creates a `RuntimeFactory` backed by the supplied `Layer.MemoMap`. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const context = options => { | |
| let globalLayer = Reactivity.layer; | |
| function factory(create) { | |
| const self = Object.create(RuntimeProto); | |
| self.keepAlive = false; | |
| self.lazy = true; | |
| self.refresh = undefined; | |
| self.factory = factory; | |
| const layerAtom = keepAlive(typeof create === "function" ? readable(get => Layer.provideMerge(create(get), globalLayer)) : readable(() => Layer.provideMerge(create, globalLayer))); | |
| self.layer = layerAtom; | |
| self.read = function read(get) { | |
| const layer = get(layerAtom); | |
| const build = Effect.flatMap(Effect.scope, scope => Layer.buildWithMemoMap(layer, options.memoMap, scope)); | |
| return effect(get, build, { | |
| uninterruptible: true | |
| }); | |
| }; | |
| return self; | |
| } | |
| factory.memoMap = options.memoMap; | |
| factory.addGlobalLayer = layer => { | |
| globalLayer = Layer.provideMerge(globalLayer, Layer.provide(layer, Reactivity.layer)); | |
| }; | |
| const reactivityAtom = removeTtl(make(Effect.contextWith(services => Layer.buildWithMemoMap(Reactivity.layer, options.memoMap, Context.get(services, Scope.Scope))).pipe(Effect.map(Context.get(Reactivity.Reactivity))))); | |
| factory.withReactivity = keys => atom => transform(atom, get => { | |
| const reactivity = AsyncResult.getOrThrow(get(reactivityAtom)); | |
| get.addFinalizer(reactivity.registerUnsafe(keys, () => { | |
| get.refresh(atom); | |
| })); | |
| get.subscribe(atom, value => get.setSelf(value)); | |
| return get.once(atom); | |
| }, { | |
| initialValueTarget: atom | |
| }); | |
| return factory; | |
| }; | |
| /** | |
| * Default `Layer.MemoMap` used by the module-level `runtime` factory. | |
| * | |
| * @category context | |
| * @since 4.0.0 | |
| */ | |
| export const defaultMemoMap = /*#__PURE__*/Layer.makeMemoMapUnsafe(); | |
| /** | |
| * Default `RuntimeFactory` created with `defaultMemoMap`. | |
| * | |
| * @category context | |
| * @since 4.0.0 | |
| */ | |
| export const runtime = /*#__PURE__*/context({ | |
| memoMap: defaultMemoMap | |
| }); | |
| /** | |
| * Returns `Rx.runtime.withReactivity` for refreshing an atom whenever the | |
| * keys change in the `Reactivity` service. | |
| * | |
| * **When to use** | |
| * | |
| * Use to refresh an atom whenever one or more invalidation keys change in the | |
| * default reactivity runtime. | |
| * | |
| * @category reactivity | |
| * @since 4.0.0 | |
| */ | |
| export const withReactivity = runtime.withReactivity; | |
| // ----------------------------------------------------------------------------- | |
| // constructors - stream | |
| // ----------------------------------------------------------------------------- | |
| const stream = (get, stream, options, services) => { | |
| const initialValue = options?.initialValue !== undefined ? AsyncResult.success(options.initialValue) : AsyncResult.initial(); | |
| return makeStream(get, stream, initialValue, services); | |
| }; | |
| function makeStream(ctx, stream, initialValue, services = Context.empty()) { | |
| const previous = ctx.self(); | |
| services = Context.add(services, AtomRegistry, ctx.registry); | |
| const run = Effect.scopedWith(scope => Effect.flatMap(Channel.toPullScoped(stream.channel, scope), pull => Effect.whileLoop({ | |
| while: constTrue, | |
| body: () => pull, | |
| step(arr) { | |
| ctx.setSelf(AsyncResult.success(Arr.lastNonEmpty(arr), { | |
| waiting: true | |
| })); | |
| } | |
| }))).pipe(Effect.catchCause(cause => { | |
| if (Pull.isDoneCause(cause)) { | |
| pipe(ctx.self(), Option.flatMap(AsyncResult.value), Option.match({ | |
| onNone: () => ctx.setSelf(AsyncResult.failWithPrevious(new Cause.NoSuchElementError(), { | |
| previous: ctx.self() | |
| })), | |
| onSome: a => ctx.setSelf(AsyncResult.success(a)) | |
| })); | |
| } else { | |
| ctx.setSelf(AsyncResult.failureWithPrevious(cause, { | |
| previous: ctx.self() | |
| })); | |
| } | |
| return Effect.void; | |
| })); | |
| const servicesMap = new Map(services.mapUnsafe); | |
| servicesMap.set(AtomRegistry.key, ctx.registry); | |
| servicesMap.set(Scheduler.Scheduler.key, ctx.registry.scheduler); | |
| const cancel = runCallbackSync(Context.makeUnsafe(servicesMap), run, constVoid, false); | |
| if (cancel !== undefined) { | |
| ctx.addFinalizer(cancel); | |
| } | |
| if (previous._tag === "Some") { | |
| return AsyncResult.waitingFrom(previous); | |
| } | |
| return AsyncResult.waiting(initialValue); | |
| } | |
| // ----------------------------------------------------------------------------- | |
| // constructors - subscription ref | |
| // ----------------------------------------------------------------------------- | |
| /** | |
| * 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. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const subscriptionRef = ref => makeSubRef(readable(get => { | |
| const value = typeof ref === "function" ? ref(get) : ref; | |
| return SubscriptionRef.isSubscriptionRef(value) ? value : makeEffect(get, value, AsyncResult.initial(true)); | |
| }), readSubscriptionRef); | |
| const readSubscriptionRef = (get, sub, services = Context.empty()) => { | |
| if (SubscriptionRef.isSubscriptionRef(sub)) { | |
| get.addFinalizer(SubscriptionRef.changes(sub).pipe(Stream.runForEachArray(arr => { | |
| for (let i = 0; i < arr.length; i++) { | |
| get.setSelf(arr[i]); | |
| } | |
| return Effect.void; | |
| }), Effect.runCallbackWith(services))); | |
| return Effect.runSyncWith(services)(SubscriptionRef.get(sub)); | |
| } else if (sub._tag !== "Success") { | |
| return sub; | |
| } | |
| return makeStream(get, SubscriptionRef.changes(sub.value), AsyncResult.initial(true), services); | |
| }; | |
| const makeSubRef = (refAtom, read) => { | |
| function write(ctx, value) { | |
| const ref = ctx.get(refAtom); | |
| if (SubscriptionRef.isSubscriptionRef(ref)) { | |
| Effect.runSync(SubscriptionRef.set(ref, value)); | |
| } else if (AsyncResult.isSuccess(ref)) { | |
| Effect.runSync(SubscriptionRef.set(ref.value, value)); | |
| } | |
| } | |
| return writable(get => { | |
| const ref = get(refAtom); | |
| if (SubscriptionRef.isSubscriptionRef(ref)) { | |
| return read(get, ref); | |
| } else if (AsyncResult.isSuccess(ref)) { | |
| return read(get, ref); | |
| } | |
| return ref; | |
| }, write); | |
| }; | |
| /** | |
| * Creates a writable atom for a synchronous function; writing an argument re-runs the function, returning `Option.none` before the first call unless an initial value is supplied. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const fnSync = function (...args) { | |
| if (args.length === 0) { | |
| return makeFnSync; | |
| } | |
| return makeFnSync(...args); | |
| }; | |
| const makeFnSync = (f, options) => { | |
| const argAtom = removeTtl(state([0, undefined])); | |
| const hasInitialValue = options?.initialValue !== undefined; | |
| return writable(function (get) { | |
| ; | |
| get.isFn = true; | |
| const [counter, arg] = get.get(argAtom); | |
| if (counter === 0) { | |
| return hasInitialValue ? options.initialValue : Option.none(); | |
| } | |
| return hasInitialValue ? f(arg, get) : Option.some(f(arg, get)); | |
| }, function (ctx, arg) { | |
| batch(() => { | |
| ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, arg]); | |
| ctx.refreshSelf(); | |
| }); | |
| }); | |
| }; | |
| /** | |
| * Defines the control symbol that can be written to an `AtomResultFn` to reset it to its initial state. | |
| * | |
| * **When to use** | |
| * | |
| * Use when you need an `AtomResultFn` write value that clears the current async | |
| * result and returns it to the initial state. | |
| * | |
| * @category symbols | |
| * @since 4.0.0 | |
| */ | |
| export const Reset = /*#__PURE__*/Symbol.for("effect/reactivity/atom/Atom/Reset"); | |
| /** | |
| * Defines the control symbol that can be written to an `AtomResultFn` to interrupt the current asynchronous computation. | |
| * | |
| * **When to use** | |
| * | |
| * Use when you need an `AtomResultFn` write value that interrupts the currently | |
| * running async computation. | |
| * | |
| * @category symbols | |
| * @since 4.0.0 | |
| */ | |
| export const Interrupt = /*#__PURE__*/Symbol.for("effect/reactivity/atom/Atom/Interrupt"); | |
| /** | |
| * Creates a writable atom for an `Effect` or `Stream` function; writing an argument starts the computation and exposes its state as an `AsyncResult`. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const fn = function (...args) { | |
| if (args.length === 0) { | |
| return makeFn; | |
| } | |
| return makeFn(...args); | |
| }; | |
| const makeFn = (f, options) => { | |
| const [read, write] = makeResultFn(f, options); | |
| return writable(read, write); | |
| }; | |
| function makeResultFn(f, options) { | |
| const argAtom = removeTtl(state([0, undefined])); | |
| const initialValue = options?.initialValue !== undefined ? AsyncResult.success(options.initialValue) : AsyncResult.initial(); | |
| const fibersAtom = options?.concurrent ? removeTtl(readable(get => { | |
| const fibers = new Set(); | |
| get.addFinalizer(() => fibers.forEach(f => f.interruptUnsafe())); | |
| return fibers; | |
| })) : undefined; | |
| function read(get, services) { | |
| const fibers = fibersAtom ? get(fibersAtom) : undefined; | |
| get.isFn = true; | |
| const [counter, arg] = get.get(argAtom); | |
| if (counter === 0) { | |
| return initialValue; | |
| } else if (arg === Interrupt) { | |
| return AsyncResult.failureWithPrevious(Cause.interrupt(), { | |
| previous: get.self() | |
| }); | |
| } | |
| let value = f(arg, get); | |
| if (EffectTypeId in value) { | |
| if (fibers) { | |
| const eff = value; | |
| value = Effect.flatMap(Effect.forkDetach(eff, { | |
| startImmediately: true | |
| }), fiber => { | |
| fibers.add(fiber); | |
| fiber.addObserver(() => fibers.delete(fiber)); | |
| return Effect.map(Fiber.joinAll(fibers), arr => arr[0]); | |
| }); | |
| } | |
| return makeEffect(get, value, initialValue, services, false); | |
| } | |
| return makeStream(get, value, initialValue, services); | |
| } | |
| function write(ctx, arg) { | |
| batch(() => { | |
| if (arg === Reset) { | |
| ctx.set(argAtom, [0, undefined]); | |
| } else if (arg === Interrupt) { | |
| ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, Interrupt]); | |
| } else { | |
| ctx.set(argAtom, [ctx.get(argAtom)[0] + 1, arg]); | |
| } | |
| ctx.refreshSelf(); | |
| }); | |
| } | |
| return [read, write, argAtom]; | |
| } | |
| /** | |
| * 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. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const pull = (create, options) => { | |
| const pullSignal = removeTtl(state(0)); | |
| const pullAtom = readable(makeRead(function (get) { | |
| return makeStreamPullEffect(get, pullSignal, create, options); | |
| })); | |
| return makeStreamPull(pullSignal, pullAtom); | |
| }; | |
| const makeStreamPullEffect = (get, pullSignal, create, options) => Effect.flatMap(Stream.toPull(typeof create === "function" ? create(get) : create), pullChunk => { | |
| const fiber = Fiber.getCurrent(); | |
| const services = fiber.context; | |
| let acc = Arr.empty(); | |
| const pull = Effect.matchCauseEffect(pullChunk, { | |
| onFailure(cause) { | |
| if (Pull.isDoneCause(cause)) { | |
| if (!Arr.isReadonlyArrayNonEmpty(acc)) { | |
| return Effect.fail(new Cause.NoSuchElementError(`Atom.pull: no items`)); | |
| } | |
| return Effect.succeed({ | |
| done: true, | |
| items: acc | |
| }); | |
| } | |
| return Effect.failCause(cause); | |
| }, | |
| onSuccess(chunk) { | |
| let items; | |
| if (options?.disableAccumulation) { | |
| items = chunk; | |
| } else { | |
| items = Arr.appendAll(acc, chunk); | |
| acc = items; | |
| } | |
| return Effect.succeed({ | |
| done: false, | |
| items | |
| }); | |
| } | |
| }); | |
| const cancels = new Set(); | |
| get.addFinalizer(() => { | |
| for (const cancel of cancels) cancel(); | |
| }); | |
| get.once(pullSignal); | |
| get.subscribe(pullSignal, () => { | |
| get.setSelf(AsyncResult.waitingFrom(get.self())); | |
| let cancel; | |
| // eslint-disable-next-line prefer-const | |
| cancel = runCallbackSync(services, pull, exit => { | |
| if (cancel) cancels.delete(cancel); | |
| const result = AsyncResult.fromExitWithPrevious(exit, get.self()); | |
| const pending = cancels.size > 0; | |
| get.setSelf(pending ? AsyncResult.waiting(result) : result); | |
| }); | |
| if (cancel) cancels.add(cancel); | |
| }); | |
| return pull; | |
| }); | |
| const makeStreamPull = (pullSignal, pullAtom) => writable(pullAtom.read, function (ctx, _) { | |
| ctx.set(pullSignal, ctx.get(pullSignal) + 1); | |
| }); | |
| /** | |
| * 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. | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const family = typeof WeakRef === "undefined" || typeof FinalizationRegistry === "undefined" ? f => { | |
| const atoms = MutableHashMap.empty(); | |
| return function (arg) { | |
| const atomEntry = MutableHashMap.get(atoms, arg); | |
| if (atomEntry._tag === "Some") { | |
| return atomEntry.value; | |
| } | |
| const newAtom = f(arg); | |
| MutableHashMap.set(atoms, arg, newAtom); | |
| return newAtom; | |
| }; | |
| } : f => { | |
| const atoms = MutableHashMap.empty(); | |
| const registry = new FinalizationRegistry(arg => { | |
| MutableHashMap.remove(atoms, arg); | |
| }); | |
| return function (arg) { | |
| const atomEntry = MutableHashMap.get(atoms, arg).pipe(Option.flatMapNullishOr(ref => ref.deref())); | |
| if (atomEntry._tag === "Some") { | |
| return atomEntry.value; | |
| } | |
| const newAtom = f(arg); | |
| MutableHashMap.set(atoms, arg, new WeakRef(newAtom)); | |
| registry.register(newAtom, arg); | |
| return newAtom; | |
| }; | |
| }; | |
| /** | |
| * 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. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const withFallback = /*#__PURE__*/dual(2, (self, fallback) => { | |
| function withFallback(get) { | |
| const result = get(self); | |
| if (result._tag === "Initial") { | |
| return AsyncResult.waiting(get(fallback)); | |
| } | |
| return result; | |
| } | |
| return isWritable(self) ? writable(withFallback, self.write, self.refresh ?? function (refresh) { | |
| refresh(self); | |
| }) : readable(withFallback, self.refresh ?? function (refresh) { | |
| refresh(self); | |
| }); | |
| }); | |
| /** | |
| * Returns a copy of an atom that remains cached and mounted even when no subscribers are using it. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const keepAlive = self => Object.assign(Object.create(Object.getPrototypeOf(self)), { | |
| ...self, | |
| keepAlive: true | |
| }); | |
| /** | |
| * Allows a reactive value to be disposed of when it is not in use. | |
| * | |
| * **Details** | |
| * | |
| * Atoms have this behavior by default, so use this to undo `keepAlive` on a copied atom. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const autoDispose = self => Object.assign(Object.create(Object.getPrototypeOf(self)), { | |
| ...self, | |
| keepAlive: false | |
| }); | |
| /** | |
| * Sets whether an atom should be lazy. | |
| * | |
| * **Details** | |
| * | |
| * Lazy atoms defer recomputation while they have no active listeners or active | |
| * non-lazy dependents, rebuilding the next time their value is observed. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const setLazy = /*#__PURE__*/dual(2, (self, lazy) => Object.assign(Object.create(Object.getPrototypeOf(self)), { | |
| ...self, | |
| lazy | |
| })); | |
| /** | |
| * Attaches a diagnostic label to an atom. | |
| * | |
| * **Details** | |
| * | |
| * The label is used for inspection and debugging metadata and does not change the | |
| * atom's read or write behavior. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const withLabel = /*#__PURE__*/dual(2, (self, name) => Object.assign(Object.create(Object.getPrototypeOf(self)), { | |
| ...self, | |
| label: [name, new Error().stack?.split("\n")[5] ?? ""] | |
| })); | |
| /** | |
| * Pairs an atom with an initial value for registry initialization. | |
| * | |
| * **When to use** | |
| * | |
| * Use to preload an atom value when constructing or seeding a registry. | |
| * | |
| * **Details** | |
| * | |
| * The returned tuple can be supplied to `AtomRegistry` initial values so the atom | |
| * starts with the provided value before it is first rebuilt. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const initialValue = /*#__PURE__*/dual(2, (self, initialValue) => [self, initialValue]); | |
| /** | |
| * Creates a derived atom by reading another atom with a custom `AtomContext` | |
| * function. | |
| * | |
| * **Details** | |
| * | |
| * If the source is writable, the derived atom keeps the source write input and | |
| * forwards writes to the source. `initialValueTarget` controls which atom receives | |
| * preloaded initial values for the derived atom. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const transform = /*#__PURE__*/dual(args => isAtom(args[0]), (self, f, options) => { | |
| const atom = removeTtl(isWritable(self) ? writable(get => f(get, self), function (ctx, value) { | |
| ctx.set(self, value); | |
| }, self.refresh ?? function (refresh) { | |
| refresh(self); | |
| }) : readable(get => f(get, self), self.refresh ?? function (refresh) { | |
| refresh(self); | |
| })); | |
| if (options?.initialValueTarget) { | |
| ; | |
| atom.initialValueTarget = getInitialValueTarget(options.initialValueTarget); | |
| } | |
| return atom; | |
| }); | |
| const getInitialValueTarget = atom => { | |
| let target = atom; | |
| while (target.initialValueTarget) { | |
| target = target.initialValueTarget; | |
| } | |
| return target; | |
| }; | |
| /** | |
| * Maps the current value of an atom with a pure function. | |
| * | |
| * **Details** | |
| * | |
| * When the source atom is writable, the returned atom remains writable and keeps | |
| * the source atom's write input type. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const map = /*#__PURE__*/dual(2, (self, f) => transform(self, get => f(get(self)))); | |
| /** | |
| * Maps the successful value inside an `AsyncResult` atom. | |
| * | |
| * **Details** | |
| * | |
| * Initial and failure states are preserved, and writable source atoms keep their | |
| * original write input type. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const mapResult = /*#__PURE__*/dual(2, (self, f) => map(self, AsyncResult.map(f))); | |
| /** | |
| * Creates an atom that publishes source changes only after the source has stopped | |
| * changing for the specified duration. | |
| * | |
| * **Details** | |
| * | |
| * The current source value is used immediately, and any pending debounce timer is | |
| * cleared when the derived atom is disposed. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const debounce = /*#__PURE__*/dual(2, (self, duration) => { | |
| const millis = Duration.toMillis(Duration.fromInputUnsafe(duration)); | |
| return transform(self, function (get) { | |
| let timeout; | |
| let value = get.once(self); | |
| function update() { | |
| timeout = undefined; | |
| get.setSelf(value); | |
| } | |
| get.addFinalizer(function () { | |
| if (timeout) clearTimeout(timeout); | |
| }); | |
| get.subscribe(self, function (val) { | |
| value = val; | |
| if (timeout) clearTimeout(timeout); | |
| timeout = setTimeout(update, millis); | |
| }); | |
| return value; | |
| }, { | |
| initialValueTarget: self | |
| }); | |
| }); | |
| /** | |
| * Creates a derived atom that reads the source and schedules a refresh after the | |
| * specified duration. | |
| * | |
| * **Details** | |
| * | |
| * The scheduled refresh is canceled when the derived atom's lifetime is disposed. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const withRefresh = /*#__PURE__*/dual(2, (self, duration) => { | |
| const millis = Duration.toMillis(Duration.fromInputUnsafe(duration)); | |
| return transform(self, function (get) { | |
| const handle = setTimeout(() => get.refresh(self), millis); | |
| get.addFinalizer(() => clearTimeout(handle)); | |
| return get(self); | |
| }, { | |
| initialValueTarget: self | |
| }); | |
| }); | |
| /** | |
| * Adds stale-while-revalidate refresh behavior to an async result atom. | |
| * | |
| * **Details** | |
| * | |
| * Automatic revalidation during reads is skipped while the current value is | |
| * fresh within `staleTime`. Manual `refresh` calls remain forceful and always | |
| * forward to the wrapped atom. Use `revalidateOnMount` to control whether stale data should trigger a | |
| * background refresh on first mount. Use `revalidateOnFocus` to control | |
| * focus behavior. `true` respects `staleTime` and `"always"` forces refetch. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const swr = /*#__PURE__*/dual(2, (self, options) => { | |
| const staleTime = Duration.toMillis(Duration.fromInputUnsafe(options.staleTime)); | |
| return transform(self, get => { | |
| const current = get.once(self); | |
| get.subscribe(self, value => { | |
| get.setSelf(value); | |
| }); | |
| if (options.revalidateOnFocus && options.focusSignal) { | |
| get.once(options.focusSignal); | |
| get.subscribe(options.focusSignal, options.revalidateOnFocus === "always" ? () => get.refresh(self) : () => { | |
| const current = get.once(self); | |
| if (shouldRevalidateSWR(current, staleTime)) { | |
| get.refresh(self); | |
| } | |
| }); | |
| } | |
| const firstRead = Option.isNone(get.self()); | |
| if (firstRead && options.revalidateOnMount === false) { | |
| return current; | |
| } | |
| if (shouldRevalidateSWR(current, staleTime)) { | |
| get.refresh(self); | |
| } | |
| return current; | |
| }, { | |
| initialValueTarget: self | |
| }); | |
| }); | |
| const swrTimestamp = result => { | |
| if (result._tag === "Success") { | |
| return Option.some(result.timestamp); | |
| } | |
| if (result._tag === "Failure") { | |
| return Option.map(result.previousSuccess, success => success.timestamp); | |
| } | |
| return Option.none(); | |
| }; | |
| const isFreshWithin = (timestamp, staleTime, now) => now - timestamp < staleTime; | |
| const shouldRevalidateSWR = (result, staleTime) => { | |
| if (result.waiting) { | |
| return false; | |
| } | |
| const timestamp = Option.getOrUndefined(swrTimestamp(result)); | |
| if (timestamp === undefined) { | |
| return result._tag !== "Initial"; | |
| } | |
| return !isFreshWithin(timestamp, staleTime, Date.now()); | |
| }; | |
| /** | |
| * Wraps an atom in a writable optimistic atom. | |
| * | |
| * **Details** | |
| * | |
| * Writes accept transition atoms containing `AsyncResult` values. Waiting | |
| * successes are shown optimistically while transitions run; when successful | |
| * transitions finish, the source atom is refreshed, and failures roll the value | |
| * back to the latest source value. | |
| * | |
| * @category Optimistic | |
| * @since 4.0.0 | |
| */ | |
| export const optimistic = self => { | |
| let counter = 0; | |
| const writeAtom = removeTtl(state([counter, undefined])); | |
| return writable(get => { | |
| let lastValue = get.once(self); | |
| let needsRefresh = false; | |
| get.subscribe(self, value => { | |
| lastValue = value; | |
| if (transitions.size > 0) { | |
| return; | |
| } | |
| needsRefresh = false; | |
| if (!AsyncResult.isAsyncResult(value)) { | |
| return get.setSelf(value); | |
| } | |
| const current = Option.getOrUndefined(get.self()); | |
| switch (value._tag) { | |
| case "Initial": | |
| { | |
| if (AsyncResult.isInitial(current)) { | |
| get.setSelf(value); | |
| } | |
| return; | |
| } | |
| case "Success": | |
| { | |
| if (AsyncResult.isSuccess(current)) { | |
| if (!value.waiting && value.timestamp >= current.timestamp) { | |
| get.setSelf(value); | |
| } | |
| } else { | |
| get.setSelf(value); | |
| } | |
| return; | |
| } | |
| case "Failure": | |
| { | |
| return get.setSelf(value); | |
| } | |
| } | |
| }); | |
| const transitions = new Set(); | |
| const cancels = new Set(); | |
| get.subscribe(writeAtom, ([, atom]) => { | |
| if (transitions.has(atom)) return; | |
| transitions.add(atom); | |
| let cancel; | |
| // eslint-disable-next-line prefer-const | |
| cancel = get.registry.subscribe(atom, result => { | |
| if (AsyncResult.isSuccess(result) && result.waiting) { | |
| return get.setSelf(result.value); | |
| } | |
| transitions.delete(atom); | |
| if (cancel) { | |
| cancels.delete(cancel); | |
| cancel(); | |
| } | |
| if (!needsRefresh && !AsyncResult.isFailure(result)) { | |
| needsRefresh = true; | |
| } | |
| if (transitions.size === 0) { | |
| if (needsRefresh) { | |
| needsRefresh = false; | |
| get.refresh(self); | |
| } else { | |
| get.setSelf(lastValue); | |
| } | |
| } | |
| }, { | |
| immediate: true | |
| }); | |
| if (transitions.has(atom)) { | |
| cancels.add(cancel); | |
| } else { | |
| cancel(); | |
| } | |
| }); | |
| get.addFinalizer(() => { | |
| for (const cancel of cancels) cancel(); | |
| transitions.clear(); | |
| cancels.clear(); | |
| }); | |
| return lastValue; | |
| }, (ctx, atom) => ctx.set(writeAtom, [++counter, atom]), refresh => refresh(self)); | |
| }; | |
| /** | |
| * Creates an `AtomResultFn` that applies an optimistic update before running the | |
| * underlying mutation. | |
| * | |
| * **Details** | |
| * | |
| * The reducer computes the provisional value from the current value and mutation | |
| * input. The wrapped function result then completes the transition or updates the | |
| * optimistic value through the provided setter callback. | |
| * | |
| * @category Optimistic | |
| * @since 4.0.0 | |
| */ | |
| export const optimisticFn = /*#__PURE__*/dual(2, (self, options) => { | |
| const transition = removeTtl(state(AsyncResult.initial())); | |
| return fn((arg, get) => { | |
| let value = options.reducer(get(self), arg); | |
| if (AsyncResult.isAsyncResult(value)) { | |
| value = AsyncResult.waiting(value, { | |
| touch: true | |
| }); | |
| } | |
| get.set(transition, AsyncResult.success(value, { | |
| waiting: true | |
| })); | |
| get.set(self, transition); | |
| const fn = typeof options.fn === "function" ? autoDispose(options.fn(value => get.set(transition, AsyncResult.success(AsyncResult.isAsyncResult(value) ? AsyncResult.waiting(value) : value, { | |
| waiting: true | |
| })))) : options.fn; | |
| get.set(fn, arg); | |
| return Effect.callback(resume => { | |
| get.subscribe(fn, result => { | |
| if (result._tag === "Initial" || result.waiting) return; | |
| get.set(transition, AsyncResult.map(result, () => value)); | |
| resume(AsyncResult.toExit(result)); | |
| }, { | |
| immediate: true | |
| }); | |
| }); | |
| }); | |
| }); | |
| /** | |
| * Runs synchronous atom updates as a batch. | |
| * | |
| * **Details** | |
| * | |
| * Stale nodes are rebuilt and listeners are notified after the callback completes, | |
| * so dependent updates observe the final batched state. | |
| * | |
| * @category batching | |
| * @since 4.0.0 | |
| */ | |
| export const batch = Registry.batch; | |
| // ----------------------------------------------------------------------------- | |
| // Focus | |
| // ----------------------------------------------------------------------------- | |
| /** | |
| * Creates a browser-only signal atom that increments when the document becomes visible. | |
| * | |
| * **Details** | |
| * | |
| * It listens for `visibilitychange` events on `window` and removes the listener | |
| * when the atom is disposed. | |
| * | |
| * @category Focus | |
| * @since 4.0.0 | |
| */ | |
| export const windowFocusSignal = /*#__PURE__*/readable(get => { | |
| let count = 0; | |
| function update() { | |
| if (document.visibilityState === "visible") { | |
| get.setSelf(++count); | |
| } | |
| } | |
| window.addEventListener("visibilitychange", update); | |
| get.addFinalizer(() => { | |
| window.removeEventListener("visibilitychange", update); | |
| }); | |
| return count; | |
| }); | |
| /** | |
| * Creates a combinator that refreshes an atom whenever the supplied signal atom | |
| * changes. | |
| * | |
| * **Details** | |
| * | |
| * The derived atom also subscribes to the source atom so normal source updates are | |
| * forwarded to its own value. | |
| * | |
| * @category Focus | |
| * @since 4.0.0 | |
| */ | |
| export const makeRefreshOnSignal = signal => self => transform(self, get => { | |
| get.once(signal); | |
| get.subscribe(signal, _ => get.refresh(self)); | |
| get.subscribe(self, value => get.setSelf(value)); | |
| return get.once(self); | |
| }, { | |
| initialValueTarget: self | |
| }); | |
| /** | |
| * Refreshes an atom whenever `windowFocusSignal` changes. | |
| * | |
| * **Details** | |
| * | |
| * This helper is browser-only because `windowFocusSignal` depends on `window` and | |
| * `document.visibilityState`. | |
| * | |
| * @category Focus | |
| * @since 4.0.0 | |
| */ | |
| export const refreshOnWindowFocus = /*#__PURE__*/makeRefreshOnSignal(windowFocusSignal); | |
| // ----------------------------------------------------------------------------- | |
| // KeyValueStore | |
| // ----------------------------------------------------------------------------- | |
| /** | |
| * Creates a writable atom backed by a `KeyValueStore` entry. | |
| * | |
| * **Details** | |
| * | |
| * Values are encoded and decoded with the supplied schema. In sync mode the atom | |
| * exposes the decoded value and writes the default value when the key is missing; | |
| * in async mode it exposes an `AsyncResult` of the decoded value. | |
| * | |
| * @category KeyValueStore | |
| * @since 4.0.0 | |
| */ | |
| export const kvs = options => { | |
| const setAtom = options.runtime.fn(value => KeyValueStore.KeyValueStore.use(store => KeyValueStore.toSchemaStore(store, options.schema).set(options.key, value))); | |
| const resultAtom = options.runtime.atom(KeyValueStore.KeyValueStore.use(store => KeyValueStore.toSchemaStore(store, options.schema).get(options.key))); | |
| return writable(options.mode === "async" ? get => { | |
| get.mount(setAtom); | |
| const mapper = AsyncResult.map(Option.getOrElse(() => { | |
| const value = options.defaultValue(); | |
| get.set(setAtom, value); | |
| return value; | |
| })); | |
| get.subscribe(resultAtom, result => get.setSelf(mapper(result))); | |
| return mapper(get.once(resultAtom)); | |
| } : get => { | |
| get.mount(setAtom); | |
| get.subscribe(resultAtom, result => { | |
| if (!AsyncResult.isSuccess(result)) return; | |
| if (Option.isSome(result.value)) { | |
| get.setSelf(result.value.value); | |
| } else { | |
| const value = Option.getOrElse(get.self(), options.defaultValue); | |
| get.setSelf(value); | |
| get.set(setAtom, value); | |
| } | |
| }, { | |
| immediate: true | |
| }); | |
| return Option.getOrElse(get.self(), options.defaultValue); | |
| }, (ctx, value) => { | |
| ctx.set(setAtom, value); | |
| ctx.setSelf(value); | |
| }); | |
| }; | |
| // ----------------------------------------------------------------------------- | |
| // URL search params | |
| // ----------------------------------------------------------------------------- | |
| /** | |
| * Creates an atom that reads and writes a URL search parameter. | |
| * | |
| * **Gotchas** | |
| * | |
| * If you pass a schema, it has to be synchronous and have no context. | |
| * | |
| * @category search params | |
| * @since 4.0.0 | |
| */ | |
| export const searchParam = (name, options) => { | |
| const decode = options?.schema && Schema.decodeExit(options.schema); | |
| const encode = options?.schema && Schema.encodeExit(options.schema); | |
| return writable(get => { | |
| if (typeof window === "undefined") { | |
| return decode ? Option.none() : ""; | |
| } | |
| const handleUpdate = () => { | |
| if (searchParamState.updating) return; | |
| const searchParams = new URLSearchParams(window.location.search); | |
| const newValue = searchParams.get(name) || ""; | |
| if (decode) { | |
| get.setSelf(Exit.getSuccess(decode(newValue))); | |
| } else if (newValue !== Option.getOrUndefined(get.self())) { | |
| get.setSelf(newValue); | |
| } | |
| }; | |
| window.addEventListener("popstate", handleUpdate); | |
| window.addEventListener("pushstate", handleUpdate); | |
| get.addFinalizer(() => { | |
| window.removeEventListener("popstate", handleUpdate); | |
| window.removeEventListener("pushstate", handleUpdate); | |
| }); | |
| const value = new URLSearchParams(window.location.search).get(name) || ""; | |
| return decode ? Exit.getSuccess(decode(value)) : value; | |
| }, (ctx, value) => { | |
| if (typeof window === "undefined") { | |
| ctx.setSelf(value); | |
| return; | |
| } | |
| if (encode) { | |
| const encoded = Option.flatMap(value, v => Exit.getSuccess(encode(v))); | |
| searchParamState.updates.set(name, Option.getOrElse(encoded, () => "")); | |
| value = Option.zipRight(encoded, value); | |
| } else { | |
| searchParamState.updates.set(name, value); | |
| } | |
| ctx.setSelf(value); | |
| if (searchParamState.timeout) { | |
| clearTimeout(searchParamState.timeout); | |
| } | |
| searchParamState.timeout = setTimeout(updateSearchParams, 500); | |
| }); | |
| }; | |
| const searchParamState = { | |
| timeout: undefined, | |
| updates: /*#__PURE__*/new Map(), | |
| updating: false | |
| }; | |
| function updateSearchParams() { | |
| searchParamState.timeout = undefined; | |
| searchParamState.updating = true; | |
| const searchParams = new URLSearchParams(window.location.search); | |
| for (const [key, value] of searchParamState.updates.entries()) { | |
| if (value.length > 0) { | |
| searchParams.set(key, value); | |
| } else { | |
| searchParams.delete(key); | |
| } | |
| } | |
| searchParamState.updates.clear(); | |
| const newUrl = `${window.location.pathname}?${searchParams.toString()}`; | |
| window.history.pushState({}, "", newUrl); | |
| searchParamState.updating = false; | |
| } | |
| // ----------------------------------------------------------------------------- | |
| // conversions | |
| // ----------------------------------------------------------------------------- | |
| /** | |
| * Converts an atom into a stream using the `AtomRegistry` service. | |
| * | |
| * **Details** | |
| * | |
| * The stream emits the atom's current value immediately and then emits subsequent | |
| * changes until the stream scope is closed. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const toStream = self => Stream.unwrap(AtomRegistry.use(r => Effect.succeed(Registry.toStream(r, self)))); | |
| /** | |
| * Converts an `AsyncResult` atom into a stream using the `AtomRegistry` service. | |
| * | |
| * **Details** | |
| * | |
| * Initial results are skipped, successes are emitted as stream values, and | |
| * failures fail the stream with the result cause. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const toStreamResult = self => Stream.unwrap(AtomRegistry.use(r => Effect.succeed(Registry.toStreamResult(r, self)))); | |
| /** | |
| * Reads an atom's current value from the `AtomRegistry` service. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const get = self => AtomRegistry.use(r => Effect.succeed(r.get(self))); | |
| /** | |
| * Reads a writable atom, computes a return value and next write value, writes the | |
| * next value, and returns the computed result. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const modify = /*#__PURE__*/dual(2, (self, f) => Effect.map(AtomRegistry, _ => _.modify(self, f))); | |
| /** | |
| * Writes a value to a writable atom through the `AtomRegistry` service. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const set = /*#__PURE__*/dual(2, (self, value) => Effect.map(AtomRegistry, _ => _.set(self, value))); | |
| /** | |
| * Updates a writable atom by reading its current value from the registry and | |
| * writing the value returned by the update function. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const update = /*#__PURE__*/dual(2, (self, f) => Effect.map(AtomRegistry, _ => _.update(self, f))); | |
| /** | |
| * Reads an `AsyncResult` atom as an effect through the `AtomRegistry` service. | |
| * | |
| * **Details** | |
| * | |
| * The effect waits while the result is `Initial`, and also while it is waiting | |
| * when `suspendOnWaiting` is enabled. Successes succeed with the value and | |
| * failures fail with the result cause. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const getResult = (self, options) => AtomRegistry.use(Registry.getResult(self, options)); | |
| /** | |
| * Runs a refresh request for an atom through the `AtomRegistry` service. | |
| * | |
| * **When to use** | |
| * | |
| * Use to invalidate and recompute an atom from an Effect that has access to the | |
| * active registry. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const refresh = self => Effect.map(AtomRegistry, _ => _.refresh(self)); | |
| /** | |
| * Mounts an atom in the `AtomRegistry` for the lifetime of the current scope. | |
| * | |
| * **Details** | |
| * | |
| * Mounting keeps the atom subscribed with a no-op listener until the scope | |
| * finalizer releases it. | |
| * | |
| * @category converting | |
| * @since 4.0.0 | |
| */ | |
| export const mount = self => AtomRegistry.use(r => Registry.mount(r, self)); | |
| // ----------------------------------------------------------------------------- | |
| // Serializable | |
| // ----------------------------------------------------------------------------- | |
| /** | |
| * The type id used to mark atoms that carry serialization metadata. | |
| * | |
| * @category type IDs | |
| * @since 4.0.0 | |
| */ | |
| export const SerializableTypeId = "~effect-atom/atom/Atom/Serializable"; | |
| /** | |
| * Returns `true` when an atom carries `Serializable` metadata. | |
| * | |
| * @category Serializable | |
| * @since 4.0.0 | |
| */ | |
| export const isSerializable = self => SerializableTypeId in self; | |
| /** | |
| * Attaches serialization metadata to an atom using a schema and stable key. | |
| * | |
| * **Details** | |
| * | |
| * The schema is converted to a JSON codec for synchronous encode/decode, and the | |
| * key is also used as the atom label when the atom does not already have one. | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const serializable = /*#__PURE__*/dual(2, (self, options) => { | |
| const codecJson = Schema.toCodecJson(options.schema); | |
| return Object.assign(Object.create(Object.getPrototypeOf(self)), { | |
| ...self, | |
| label: self.label ?? [options.key, new Error().stack?.split("\n")[5] ?? ""], | |
| [SerializableTypeId]: { | |
| key: options.key, | |
| encode: Schema.encodeSync(codecJson), | |
| decode: Schema.decodeSync(codecJson) | |
| } | |
| }); | |
| }); | |
| /** | |
| * The type id used to mark atoms with a server-side read override. | |
| * | |
| * @category type IDs | |
| * @since 4.0.0 | |
| */ | |
| export const ServerValueTypeId = "~effect-atom/atom/Atom/ServerValue"; | |
| /** | |
| * Sets the value of an Atom when read on the server. | |
| * | |
| * @category ServerValue | |
| * @since 4.0.0 | |
| */ | |
| export const withServerValue = /*#__PURE__*/dual(2, (self, read) => Object.assign(Object.create(Object.getPrototypeOf(self)), { | |
| ...self, | |
| [ServerValueTypeId]: read | |
| })); | |
| /** | |
| * Sets an `AsyncResult` atom's server-side value to | |
| * `AsyncResult.initial(true)`. | |
| * | |
| * @category ServerValue | |
| * @since 4.0.0 | |
| */ | |
| export const withServerValueInitial = self => withServerValue(self, constant(AsyncResult.initial(true))); | |
| /** | |
| * Reads an atom from a registry, using its server-side read override when one is | |
| * present. | |
| * | |
| * **Details** | |
| * | |
| * Nested reads performed by the override are resolved against the same registry. | |
| * | |
| * @category ServerValue | |
| * @since 4.0.0 | |
| */ | |
| export const getServerValue = /*#__PURE__*/dual(2, (self, registry) => ServerValueTypeId in self ? self[ServerValueTypeId](atom => registry.get(atom)) : registry.get(self)); | |
| //# sourceMappingURL=Atom.js.map |
Xet Storage Details
- Size:
- 47.8 kB
- Xet hash:
- c2c7b5d7319adb489c54b90bfa2d88ec63628db3947eeece24ce3b688999021f
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.