|
| 1 | +/** |
| 2 | + * @since 1.0.0 |
| 3 | + */ |
| 4 | +import * as Atom from "@effect-atom/atom/Atom" |
| 5 | +import type * as AtomRef from "@effect-atom/atom/AtomRef" |
| 6 | +import * as Registry from "@effect-atom/atom/Registry" |
| 7 | +import type * as Result from "@effect-atom/atom/Result" |
| 8 | +import * as Cause from "effect/Cause" |
| 9 | +import * as Effect from "effect/Effect" |
| 10 | +import * as Exit from "effect/Exit" |
| 11 | +import { globalValue } from "effect/GlobalValue" |
| 12 | +import type { Accessor } from "solid-js" |
| 13 | +import { createSignal, onCleanup, useContext } from "solid-js" |
| 14 | +import { RegistryContext } from "./RegistryContext.js" |
| 15 | + |
| 16 | +const initialValuesSet = globalValue( |
| 17 | + "@effect-atom/atom-solid/initialValuesSet", |
| 18 | + () => new WeakMap<Registry.Registry, WeakSet<Atom.Atom<any>>>() |
| 19 | +) |
| 20 | + |
| 21 | +/** |
| 22 | + * @since 1.0.0 |
| 23 | + * @category hooks |
| 24 | + */ |
| 25 | +export const useAtomInitialValues = (initialValues: Iterable<readonly [Atom.Atom<any>, any]>): void => { |
| 26 | + const registry = useContext(RegistryContext) |
| 27 | + let set = initialValuesSet.get(registry) |
| 28 | + if (set === undefined) { |
| 29 | + set = new WeakSet() |
| 30 | + initialValuesSet.set(registry, set) |
| 31 | + } |
| 32 | + for (const [atom, value] of initialValues) { |
| 33 | + if (!set.has(atom)) { |
| 34 | + set.add(atom) |
| 35 | + ;(registry as any).ensureNode(atom).setValue(value) |
| 36 | + } |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +/** |
| 41 | + * @since 1.0.0 |
| 42 | + * @category hooks |
| 43 | + */ |
| 44 | +export const useAtomValue: { |
| 45 | + <A>(atom: Atom.Atom<A>): Accessor<A> |
| 46 | + <A, B>(atom: Atom.Atom<A>, f: (_: A) => B): Accessor<B> |
| 47 | +} = <A>(atom: Atom.Atom<A>, f?: (_: A) => A): Accessor<A> => { |
| 48 | + const registry = useContext(RegistryContext) |
| 49 | + return createAtomAccessor(registry, f ? Atom.map(atom, f) : atom) |
| 50 | +} |
| 51 | + |
| 52 | +function createAtomAccessor<A>(registry: Registry.Registry, atom: Atom.Atom<A>): Accessor<A> { |
| 53 | + const [value, setValue] = createSignal<A>(registry.get(atom)) |
| 54 | + onCleanup(registry.subscribe(atom, setValue as any)) |
| 55 | + return value |
| 56 | +} |
| 57 | + |
| 58 | +function mountAtom<A>(registry: Registry.Registry, atom: Atom.Atom<A>): void { |
| 59 | + onCleanup(registry.mount(atom)) |
| 60 | +} |
| 61 | + |
| 62 | +function setAtom<R, W, Mode extends "value" | "promise" | "promiseExit" = never>( |
| 63 | + registry: Registry.Registry, |
| 64 | + atom: Atom.Writable<R, W>, |
| 65 | + options?: { |
| 66 | + readonly mode?: ([R] extends [Result.Result<any, any>] ? Mode : "value") | undefined |
| 67 | + } |
| 68 | +): "promise" extends Mode ? ( |
| 69 | + (value: W) => Promise<Result.Result.Success<R>> |
| 70 | + ) : |
| 71 | + "promiseExit" extends Mode ? ( |
| 72 | + (value: W) => Promise<Exit.Exit<Result.Result.Success<R>, Result.Result.Failure<R>>> |
| 73 | + ) : |
| 74 | + ((value: W | ((value: R) => W)) => void) |
| 75 | +{ |
| 76 | + if (options?.mode === "promise" || options?.mode === "promiseExit") { |
| 77 | + return ((value: W) => { |
| 78 | + registry.set(atom, value) |
| 79 | + const promise = Effect.runPromiseExit( |
| 80 | + Registry.getResult(registry, atom as Atom.Atom<Result.Result<any, any>>, { suspendOnWaiting: true }) |
| 81 | + ) |
| 82 | + return options!.mode === "promise" ? promise.then(flattenExit) : promise |
| 83 | + }) as any |
| 84 | + } |
| 85 | + return ((value: W | ((value: R) => W)) => { |
| 86 | + registry.set(atom, typeof value === "function" ? (value as any)(registry.get(atom)) : value) |
| 87 | + }) as any |
| 88 | +} |
| 89 | + |
| 90 | +const flattenExit = <A, E>(exit: Exit.Exit<A, E>): A => { |
| 91 | + if (Exit.isSuccess(exit)) return exit.value |
| 92 | + throw Cause.squash(exit.cause) |
| 93 | +} |
| 94 | + |
| 95 | +/** |
| 96 | + * @since 1.0.0 |
| 97 | + * @category hooks |
| 98 | + */ |
| 99 | +export const useAtomMount = <A>(atom: Atom.Atom<A>): void => { |
| 100 | + const registry = useContext(RegistryContext) |
| 101 | + mountAtom(registry, atom) |
| 102 | +} |
| 103 | + |
| 104 | +/** |
| 105 | + * @since 1.0.0 |
| 106 | + * @category hooks |
| 107 | + */ |
| 108 | +export const useAtomSet = < |
| 109 | + R, |
| 110 | + W, |
| 111 | + Mode extends "value" | "promise" | "promiseExit" = never |
| 112 | +>( |
| 113 | + atom: Atom.Writable<R, W>, |
| 114 | + options?: { |
| 115 | + readonly mode?: ([R] extends [Result.Result<any, any>] ? Mode : "value") | undefined |
| 116 | + } |
| 117 | +): "promise" extends Mode ? ( |
| 118 | + (value: W) => Promise<Result.Result.Success<R>> |
| 119 | + ) : |
| 120 | + "promiseExit" extends Mode ? ( |
| 121 | + (value: W) => Promise<Exit.Exit<Result.Result.Success<R>, Result.Result.Failure<R>>> |
| 122 | + ) : |
| 123 | + ((value: W | ((value: R) => W)) => void) => |
| 124 | +{ |
| 125 | + const registry = useContext(RegistryContext) |
| 126 | + mountAtom(registry, atom) |
| 127 | + return setAtom(registry, atom, options) |
| 128 | +} |
| 129 | + |
| 130 | +/** |
| 131 | + * @since 1.0.0 |
| 132 | + * @category hooks |
| 133 | + */ |
| 134 | +export const useAtomRefresh = <A>(atom: Atom.Atom<A>): () => void => { |
| 135 | + const registry = useContext(RegistryContext) |
| 136 | + mountAtom(registry, atom) |
| 137 | + return () => registry.refresh(atom) |
| 138 | +} |
| 139 | + |
| 140 | +/** |
| 141 | + * @since 1.0.0 |
| 142 | + * @category hooks |
| 143 | + */ |
| 144 | +export const useAtom = <R, W, const Mode extends "value" | "promise" | "promiseExit" = never>( |
| 145 | + atom: Atom.Writable<R, W>, |
| 146 | + options?: { |
| 147 | + readonly mode?: ([R] extends [Result.Result<any, any>] ? Mode : "value") | undefined |
| 148 | + } |
| 149 | +): readonly [ |
| 150 | + value: Accessor<R>, |
| 151 | + write: "promise" extends Mode ? ( |
| 152 | + (value: W) => Promise<Result.Result.Success<R>> |
| 153 | + ) : |
| 154 | + "promiseExit" extends Mode ? ( |
| 155 | + (value: W) => Promise<Exit.Exit<Result.Result.Success<R>, Result.Result.Failure<R>>> |
| 156 | + ) : |
| 157 | + ((value: W | ((value: R) => W)) => void) |
| 158 | +] => { |
| 159 | + const registry = useContext(RegistryContext) |
| 160 | + return [ |
| 161 | + createAtomAccessor(registry, atom), |
| 162 | + setAtom(registry, atom, options) |
| 163 | + ] as const |
| 164 | +} |
| 165 | + |
| 166 | +/** |
| 167 | + * @since 1.0.0 |
| 168 | + * @category hooks |
| 169 | + */ |
| 170 | +export const useAtomSubscribe = <A>( |
| 171 | + atom: Atom.Atom<A>, |
| 172 | + f: (_: A) => void, |
| 173 | + options?: { readonly immediate?: boolean } |
| 174 | +): void => { |
| 175 | + const registry = useContext(RegistryContext) |
| 176 | + onCleanup(registry.subscribe(atom, f, options)) |
| 177 | +} |
| 178 | + |
| 179 | +/** |
| 180 | + * @since 1.0.0 |
| 181 | + * @category hooks |
| 182 | + */ |
| 183 | +export const useAtomRef = <A>(ref: AtomRef.ReadonlyRef<A>): Accessor<A> => { |
| 184 | + const [value, setValue] = createSignal(ref.value) |
| 185 | + onCleanup(ref.subscribe(setValue)) |
| 186 | + return value |
| 187 | +} |
| 188 | + |
| 189 | +/** |
| 190 | + * @since 1.0.0 |
| 191 | + * @category hooks |
| 192 | + */ |
| 193 | +export const useAtomRefProp = <A, K extends keyof A>(ref: AtomRef.AtomRef<A>, prop: K): AtomRef.AtomRef<A[K]> => |
| 194 | + ref.prop(prop) |
| 195 | + |
| 196 | +/** |
| 197 | + * @since 1.0.0 |
| 198 | + * @category hooks |
| 199 | + */ |
| 200 | +export const useAtomRefPropValue = <A, K extends keyof A>(ref: AtomRef.AtomRef<A>, prop: K): Accessor<A[K]> => |
| 201 | + useAtomRef(useAtomRefProp(ref, prop)) |
| 202 | + |
0 commit comments