| 1 | /** |
| 2 | * @since 4.0.0 |
| 3 | */ |
| 4 | import * as Cause from "effect/Cause" |
| 5 | import * as Effect from "effect/Effect" |
| 6 | import * as Exit from "effect/Exit" |
| 7 | import type * as AsyncResult from "effect/reactivity/AsyncResult" |
| 8 | import type * as Atom from "effect/reactivity/Atom" |
| 9 | import type * as AtomRef from "effect/reactivity/AtomRef" |
| 10 | import * as AtomRegistry from "effect/reactivity/AtomRegistry" |
| 11 | import { computed, type ComputedRef, inject, type InjectionKey, type Ref, shallowRef, watchEffect } from "vue" |
| 12 | |
| 13 | /** |
| 14 | * @stability unstable |
| 15 | * @since 4.0.0 |
| 16 | * @category re-exports |
| 17 | */ |
| 18 | export * as AtomRegistry from "effect/reactivity/AtomRegistry" |
| 19 | |
| 20 | /** |
| 21 | * @stability unstable |
| 22 | * @since 4.0.0 |
| 23 | * @category re-exports |
| 24 | */ |
| 25 | export * as AsyncResult from "effect/reactivity/AsyncResult" |
| 26 | |
| 27 | /** |
| 28 | * @stability unstable |
| 29 | * @since 4.0.0 |
| 30 | * @category re-exports |
| 31 | */ |
| 32 | export * as Atom from "effect/reactivity/Atom" |
| 33 | |
| 34 | /** |
| 35 | * @stability unstable |
| 36 | * @since 4.0.0 |
| 37 | * @category re-exports |
| 38 | */ |
| 39 | export * as AtomRef from "effect/reactivity/AtomRef" |
| 40 | |
| 41 | /** |
| 42 | * @stability unstable |
| 43 | * @since 4.0.0 |
| 44 | * @category re-exports |
| 45 | */ |
| 46 | export * as AtomHttpApi from "effect/reactivity/AtomHttpApi" |
| 47 | |
| 48 | /** |
| 49 | * @stability unstable |
| 50 | * @since 4.0.0 |
| 51 | * @category re-exports |
| 52 | */ |
| 53 | export * as AtomRpc from "effect/reactivity/AtomRpc" |
| 54 | |
| 55 | /** |
| 56 | * @since 4.0.0 |
| 57 | * @category symbols |
| 58 | */ |
| 59 | export const registryKey = Symbol.for("@effect/atom-vue/registryKey") as InjectionKey<AtomRegistry.AtomRegistry> |
| 60 | |
| 61 | /** |
| 62 | * @since 4.0.0 |
| 63 | * @category constants |
| 64 | */ |
| 65 | export const defaultRegistry: AtomRegistry.AtomRegistry = AtomRegistry.make() |
| 66 | |
| 67 | /** |
| 68 | * @since 4.0.0 |
| 69 | * @category accessors |
| 70 | */ |
| 71 | export const injectRegistry = (): AtomRegistry.AtomRegistry => { |
| 72 | return inject(registryKey, defaultRegistry) |
| 73 | } |
| 74 | |
| 75 | const useAtomValueRef = <A extends Atom.Atom<any>>(atom: () => A) => { |
| 76 | const registry = injectRegistry() |
| 77 | const atomRef = computed(atom) |
| 78 | const value = shallowRef(undefined as any as A) |
| 79 | watchEffect((onCleanup) => { |
| 80 | onCleanup(registry.subscribe(atomRef.value, (nextValue: Atom.Type<A>) => { |
| 81 | value.value = nextValue |
| 82 | }, { immediate: true })) |
| 83 | }) |
| 84 | return [value as Readonly<Ref<Atom.Type<A>>>, atomRef, registry] as const |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * @since 4.0.0 |
| 89 | * @category composables |
| 90 | */ |
| 91 | export const useAtom = <R, W, Mode extends "value" | "promise" | "promiseExit" = never>( |
| 92 | atom: () => Atom.Writable<R, W>, |
| 93 | options?: { |
| 94 | readonly mode?: ([R] extends [AsyncResult.AsyncResult<any, any>] ? Mode : "value") | undefined |
| 95 | } |
| 96 | ): readonly [ |
| 97 | Readonly<Ref<R>>, |
| 98 | write: "promise" extends Mode ? ( |
| 99 | (value: W) => Promise<AsyncResult.AsyncResult.Success<R>> |
| 100 | ) : |
| 101 | "promiseExit" extends Mode ? ( |
| 102 | (value: W) => Promise<Exit.Exit<AsyncResult.AsyncResult.Success<R>, AsyncResult.AsyncResult.Failure<R>>> |
| 103 | ) : |
| 104 | ((value: W | ((value: R) => W)) => void) |
| 105 | ] => { |
| 106 | const [value, atomRef, registry] = useAtomValueRef(atom) |
| 107 | return [value as Readonly<Ref<R>>, setAtom(registry, atomRef, options)] |
| 108 | } |
| 109 | |
| 110 | /** |
| 111 | * @since 4.0.0 |
| 112 | * @category composables |
| 113 | */ |
| 114 | export const useAtomValue = <A>(atom: () => Atom.Atom<A>): Readonly<Ref<A>> => useAtomValueRef(atom)[0] |
| 115 | |
| 116 | const flattenExit = <A, E>(exit: Exit.Exit<A, E>): A => { |
| 117 | if (Exit.isSuccess(exit)) return exit.value |
| 118 | throw Cause.squash(exit.cause) |
| 119 | } |
| 120 | |
| 121 | function setAtom<R, W, Mode extends "value" | "promise" | "promiseExit" = never>( |
| 122 | registry: AtomRegistry.AtomRegistry, |
| 123 | atomRef: ComputedRef<Atom.Writable<R, W>>, |
| 124 | options?: { |
| 125 | readonly mode?: ([R] extends [AsyncResult.AsyncResult<any, any>] ? Mode : "value") | undefined |
| 126 | } |
| 127 | ): "promise" extends Mode ? ( |
| 128 | ( |
| 129 | value: W, |
| 130 | options?: { |
| 131 | readonly signal?: AbortSignal | undefined |
| 132 | } | undefined |
| 133 | ) => Promise<AsyncResult.AsyncResult.Success<R>> |
| 134 | ) : |
| 135 | "promiseExit" extends Mode ? ( |
| 136 | ( |
| 137 | value: W, |
| 138 | options?: { |
| 139 | readonly signal?: AbortSignal | undefined |
| 140 | } | undefined |
| 141 | ) => Promise<Exit.Exit<AsyncResult.AsyncResult.Success<R>, AsyncResult.AsyncResult.Failure<R>>> |
| 142 | ) : |
| 143 | ((value: W | ((value: R) => W)) => void) |
| 144 | { |
| 145 | if (options?.mode === "promise" || options?.mode === "promiseExit") { |
| 146 | return ((value: W, opts?: any) => { |
| 147 | registry.set(atomRef.value, value) |
| 148 | const promise = Effect.runPromiseExit( |
| 149 | AtomRegistry.getResult( |
| 150 | registry, |
| 151 | atomRef.value as Atom.Atom<AsyncResult.AsyncResult<any, any>>, |
| 152 | { suspendOnWaiting: true } |
| 153 | ), |
| 154 | opts |
| 155 | ) |
| 156 | return options!.mode === "promise" ? promise.then(flattenExit) : promise |
| 157 | }) as any |
| 158 | } |
| 159 | return ((value: W | ((value: R) => W)) => { |
| 160 | registry.set(atomRef.value, typeof value === "function" ? (value as any)(registry.get(atomRef.value)) : value) |
| 161 | }) as any |
| 162 | } |
| 163 | |
| 164 | /** |
| 165 | * @since 4.0.0 |
| 166 | * @category composables |
| 167 | */ |
| 168 | export const useAtomSet = < |
| 169 | R, |
| 170 | W, |
| 171 | Mode extends "value" | "promise" | "promiseExit" = never |
| 172 | >( |
| 173 | atom: () => Atom.Writable<R, W>, |
| 174 | options?: { |
| 175 | readonly mode?: ([R] extends [AsyncResult.AsyncResult<any, any>] ? Mode : "value") | undefined |
| 176 | } |
| 177 | ): "promise" extends Mode ? ( |
| 178 | ( |
| 179 | value: W, |
| 180 | options?: { |
| 181 | readonly signal?: AbortSignal | undefined |
| 182 | } | undefined |
| 183 | ) => Promise<AsyncResult.AsyncResult.Success<R>> |
| 184 | ) : |
| 185 | "promiseExit" extends Mode ? ( |
| 186 | ( |
| 187 | value: W, |
| 188 | options?: { |
| 189 | readonly signal?: AbortSignal | undefined |
| 190 | } | undefined |
| 191 | ) => Promise<Exit.Exit<AsyncResult.AsyncResult.Success<R>, AsyncResult.AsyncResult.Failure<R>>> |
| 192 | ) : |
| 193 | ((value: W | ((value: R) => W)) => void) => |
| 194 | { |
| 195 | const registry = injectRegistry() |
| 196 | const atomRef = computed(atom) |
| 197 | watchEffect((onCleanup) => { |
| 198 | onCleanup(registry.mount(atomRef.value)) |
| 199 | }) |
| 200 | return setAtom(registry, atomRef, options) |
| 201 | } |
| 202 | |
| 203 | /** |
| 204 | * @since 4.0.0 |
| 205 | * @category composables |
| 206 | */ |
| 207 | export const useAtomRef = <A>(atomRef: () => AtomRef.ReadonlyRef<A>): Readonly<Ref<A>> => { |
| 208 | const atomRefRef = computed(atomRef) |
| 209 | const value = shallowRef<A>(atomRefRef.value.value) |
| 210 | watchEffect((onCleanup) => { |
| 211 | const ref = atomRefRef.value |
| 212 | onCleanup(ref.subscribe((next: A) => { |
| 213 | value.value = next |
| 214 | })) |
| 215 | value.value = ref.value |
| 216 | }) |
| 217 | return value as Readonly<Ref<A>> |
| 218 | } |