1/**
2 * @since 4.0.0
3 */
4import * as Cause from "effect/Cause"
5import * as Effect from "effect/Effect"
6import * as Exit from "effect/Exit"
7import type * as AsyncResult from "effect/reactivity/AsyncResult"
8import type * as Atom from "effect/reactivity/Atom"
9import type * as AtomRef from "effect/reactivity/AtomRef"
10import * as AtomRegistry from "effect/reactivity/AtomRegistry"
11import { 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 */
18export * as AtomRegistry from "effect/reactivity/AtomRegistry"
19
20/**
21 * @stability unstable
22 * @since 4.0.0
23 * @category re-exports
24 */
25export * as AsyncResult from "effect/reactivity/AsyncResult"
26
27/**
28 * @stability unstable
29 * @since 4.0.0
30 * @category re-exports
31 */
32export * as Atom from "effect/reactivity/Atom"
33
34/**
35 * @stability unstable
36 * @since 4.0.0
37 * @category re-exports
38 */
39export * as AtomRef from "effect/reactivity/AtomRef"
40
41/**
42 * @stability unstable
43 * @since 4.0.0
44 * @category re-exports
45 */
46export * as AtomHttpApi from "effect/reactivity/AtomHttpApi"
47
48/**
49 * @stability unstable
50 * @since 4.0.0
51 * @category re-exports
52 */
53export * as AtomRpc from "effect/reactivity/AtomRpc"
54
55/**
56 * @since 4.0.0
57 * @category symbols
58 */
59export const registryKey = Symbol.for("@effect/atom-vue/registryKey") as InjectionKey<AtomRegistry.AtomRegistry>
60
61/**
62 * @since 4.0.0
63 * @category constants
64 */
65export const defaultRegistry: AtomRegistry.AtomRegistry = AtomRegistry.make()
66
67/**
68 * @since 4.0.0
69 * @category accessors
70 */
71export const injectRegistry = (): AtomRegistry.AtomRegistry => {
72 return inject(registryKey, defaultRegistry)
73}
74
75const 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 */
91export 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 */
114export const useAtomValue = <A>(atom: () => Atom.Atom<A>): Readonly<Ref<A>> => useAtomValueRef(atom)[0]
115
116const 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
121function 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 */
168export 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 */
207export 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}