@effect-app/vue 4.0.0-beta.29 → 4.0.0-beta.290

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.
Files changed (131) hide show
  1. package/CHANGELOG.md +2157 -0
  2. package/dist/atomQuery.d.ts +107 -0
  3. package/dist/atomQuery.d.ts.map +1 -0
  4. package/dist/atomQuery.js +345 -0
  5. package/dist/commander.d.ts +634 -0
  6. package/dist/commander.d.ts.map +1 -0
  7. package/dist/commander.js +1070 -0
  8. package/dist/{experimental/confirm.d.ts → confirm.d.ts} +6 -4
  9. package/dist/confirm.d.ts.map +1 -0
  10. package/dist/confirm.js +26 -0
  11. package/dist/dependencyMetadata.d.ts +10 -0
  12. package/dist/dependencyMetadata.d.ts.map +1 -0
  13. package/dist/dependencyMetadata.js +29 -0
  14. package/dist/errorReporter.d.ts +6 -4
  15. package/dist/errorReporter.d.ts.map +1 -1
  16. package/dist/errorReporter.js +14 -19
  17. package/dist/form.d.ts +14 -5
  18. package/dist/form.d.ts.map +1 -1
  19. package/dist/form.js +46 -13
  20. package/dist/internal/tanstackQuery.d.ts +9 -0
  21. package/dist/internal/tanstackQuery.d.ts.map +1 -0
  22. package/dist/internal/tanstackQuery.js +194 -0
  23. package/dist/intl.d.ts +15 -0
  24. package/dist/intl.d.ts.map +1 -0
  25. package/dist/intl.js +9 -0
  26. package/dist/lib.d.ts +7 -9
  27. package/dist/lib.d.ts.map +1 -1
  28. package/dist/lib.js +35 -10
  29. package/dist/makeClient.d.ts +212 -336
  30. package/dist/makeClient.d.ts.map +1 -1
  31. package/dist/makeClient.js +399 -411
  32. package/dist/makeContext.d.ts.map +1 -1
  33. package/dist/makeIntl.d.ts.map +1 -1
  34. package/dist/{experimental/makeUseCommand.d.ts → makeUseCommand.d.ts} +4 -3
  35. package/dist/makeUseCommand.d.ts.map +1 -0
  36. package/dist/makeUseCommand.js +13 -0
  37. package/dist/mutate.d.ts +108 -40
  38. package/dist/mutate.d.ts.map +1 -1
  39. package/dist/mutate.js +211 -58
  40. package/dist/query.d.ts +139 -45
  41. package/dist/query.d.ts.map +1 -1
  42. package/dist/query.js +330 -122
  43. package/dist/routeParams.d.ts +4 -4
  44. package/dist/routeParams.d.ts.map +1 -1
  45. package/dist/routeParams.js +4 -3
  46. package/dist/runtime.d.ts +1 -14
  47. package/dist/runtime.d.ts.map +1 -1
  48. package/dist/runtime.js +2 -26
  49. package/dist/suspense.d.ts +5 -0
  50. package/dist/suspense.d.ts.map +1 -0
  51. package/dist/suspense.js +12 -0
  52. package/dist/toast.d.ts +2 -0
  53. package/dist/toast.d.ts.map +1 -0
  54. package/dist/toast.js +2 -0
  55. package/dist/withToast.d.ts +2 -0
  56. package/dist/withToast.d.ts.map +1 -0
  57. package/dist/withToast.js +2 -0
  58. package/docs/atom-query-api-redesign.md +207 -0
  59. package/docs/query-key-invalidation.md +106 -0
  60. package/examples/streamMutation.ts +72 -0
  61. package/package.json +30 -94
  62. package/src/atomQuery.ts +466 -0
  63. package/src/commander.ts +3413 -0
  64. package/src/{experimental/confirm.ts → confirm.ts} +13 -15
  65. package/src/dependencyMetadata.ts +38 -0
  66. package/src/errorReporter.ts +65 -75
  67. package/src/form.ts +61 -18
  68. package/src/index.ts +7 -7
  69. package/src/internal/tanstackQuery.ts +289 -0
  70. package/src/intl.ts +12 -0
  71. package/src/lib.ts +49 -21
  72. package/src/makeClient.ts +941 -1159
  73. package/src/makeIntl.ts +2 -2
  74. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +9 -6
  75. package/src/mutate.ts +372 -152
  76. package/src/query.ts +688 -260
  77. package/src/routeParams.ts +7 -7
  78. package/src/runtime.ts +1 -31
  79. package/src/suspense.ts +17 -0
  80. package/src/toast.ts +1 -0
  81. package/src/withToast.ts +1 -0
  82. package/test/Mutation.test.ts +181 -24
  83. package/test/dependencyInvalidation.test.ts +352 -0
  84. package/test/dist/dependencyInvalidation.test.d.ts.map +1 -0
  85. package/test/dist/form.test.d.ts.map +1 -1
  86. package/test/dist/lib.test.d.ts.map +1 -0
  87. package/test/dist/query-span.test.d.ts.map +1 -0
  88. package/test/dist/streamFinal.test.d.ts.map +1 -0
  89. package/test/dist/streamFn.test.d.ts.map +1 -0
  90. package/test/dist/stubs.d.ts +3756 -122
  91. package/test/dist/stubs.d.ts.map +1 -1
  92. package/test/dist/stubs.js +192 -32
  93. package/test/dist/suspense-regression.test.d.ts.map +1 -0
  94. package/test/dist/suspense.test.d.ts.map +1 -0
  95. package/test/form-validation-errors.test.ts +25 -20
  96. package/test/form.test.ts +22 -3
  97. package/test/lib.test.ts +240 -0
  98. package/test/makeClient.test.ts +437 -38
  99. package/test/query-span.test.ts +92 -0
  100. package/test/streamFinal.test.ts +64 -0
  101. package/test/streamFn.test.ts +457 -0
  102. package/test/stubs.ts +228 -43
  103. package/test/suspense-regression.test.ts +115 -0
  104. package/test/suspense.test.ts +160 -0
  105. package/tsconfig.examples.json +20 -0
  106. package/tsconfig.json +3 -1
  107. package/tsconfig.src.json +34 -34
  108. package/tsconfig.test.json +2 -2
  109. package/vitest.config.ts +5 -5
  110. package/dist/experimental/commander.d.ts +0 -359
  111. package/dist/experimental/commander.d.ts.map +0 -1
  112. package/dist/experimental/commander.js +0 -557
  113. package/dist/experimental/confirm.d.ts.map +0 -1
  114. package/dist/experimental/confirm.js +0 -28
  115. package/dist/experimental/intl.d.ts +0 -16
  116. package/dist/experimental/intl.d.ts.map +0 -1
  117. package/dist/experimental/intl.js +0 -5
  118. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  119. package/dist/experimental/makeUseCommand.js +0 -13
  120. package/dist/experimental/toast.d.ts +0 -47
  121. package/dist/experimental/toast.d.ts.map +0 -1
  122. package/dist/experimental/toast.js +0 -41
  123. package/dist/experimental/withToast.d.ts +0 -25
  124. package/dist/experimental/withToast.d.ts.map +0 -1
  125. package/dist/experimental/withToast.js +0 -45
  126. package/eslint.config.mjs +0 -24
  127. package/src/experimental/commander.ts +0 -1835
  128. package/src/experimental/intl.ts +0 -9
  129. package/src/experimental/toast.ts +0 -66
  130. package/src/experimental/withToast.ts +0 -99
  131. package/tsconfig.json.bak +0 -12
package/src/query.ts CHANGED
@@ -2,33 +2,199 @@
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-call */
3
3
  /* eslint-disable @typescript-eslint/no-unsafe-return */
4
4
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
5
- import { type DefaultError, type Enabled, type InitialDataFunction, type NonUndefinedGuard, type PlaceholderDataFunction, type QueryKey, type QueryObserverOptions, type QueryObserverResult, type RefetchOptions, useQuery as useTanstackQuery, useQueryClient, type UseQueryDefinedReturnType, type UseQueryReturnType } from "@tanstack/vue-query"
6
- import { Array, Cause, Effect, Exit, flow, Option, S, type ServiceMap } from "effect-app"
7
- import { type Req } from "effect-app/client"
8
- import type { RequestHandler, RequestHandlerWithInput } from "effect-app/client/clientFor"
9
- import { ServiceUnavailableError } from "effect-app/client/errors"
10
- import { type Span } from "effect/Tracer"
11
- import { isHttpClientError } from "effect/unstable/http/HttpClientError"
5
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
6
+ import { injectRegistry, useAtomValue } from "@effect/atom-vue"
7
+ import * as Array from "effect-app/Array"
8
+ import { makeQueryKey, type Req } from "effect-app/client"
9
+ import type { ClientForOptions, RequestHandlerWithInput, RequestStreamHandlerWithInput } from "effect-app/client/clientFor"
10
+ import { type CauseException } from "effect-app/client/errors"
11
+ import type * as Context from "effect-app/Context"
12
+ import * as Effect from "effect-app/Effect"
13
+ import * as Option from "effect-app/Option"
14
+ import type * as Cause from "effect/Cause"
15
+ import * as Exit from "effect/Exit"
16
+ import * as Stream from "effect/Stream"
12
17
  import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
13
- import { computed, type ComputedRef, type MaybeRefOrGetter, ref, shallowRef, watch, type WatchSource } from "vue"
14
- import { makeQueryKey, reportRuntimeError } from "./lib.js"
18
+ import * as Atom from "effect/unstable/reactivity/Atom"
19
+ import { computed, type ComputedRef, effectScope, type MaybeRefOrGetter, onBeforeUnmount, onMounted, onScopeDispose, ref, toValue, type WatchSource } from "vue"
20
+ import { type AtomClientRuntime, type AtomQueryOptions, awaitAtomResult, buildQueryFamily, buildStreamQueryFamily, disabledQueryAtom, isStaleResult, refreshAtomWithCurrentSpan, staleTimeMsOf, withQueryOptions } from "./atomQuery.ts"
15
21
 
16
- // we must use interface extends, or we get the dreaded typescript error of isn't portable blabla @tanstack/vue-query/build/modern/types.js
17
- // but because how they are dealing with some extends clause, we loose all properties except initialData
18
- // so we actually reconstruct the interfaces here from the ground up :/
22
+ // --- minimal local types (replacing the former @tanstack/vue-query type imports) ---
23
+ type DefaultError = Error
24
+ type QueryKey = ReadonlyArray<unknown>
25
+ /** Options accepted by `refetch()` — kept for source compatibility; the atom path ignores them. */
26
+ export interface RefetchOptions {
27
+ readonly cancelRefetch?: boolean
28
+ readonly throwOnError?: boolean
29
+ }
30
+ /** The 4th tuple element: private atom/registry handle for client helpers. */
31
+ export interface QueryHandle<A = unknown, E = unknown> {
32
+ readonly awaitResult: () => Effect.Effect<A, E, never>
33
+ readonly refetch: () => Effect.Effect<A, E, never>
34
+ readonly refresh: () => void
35
+ readonly registry: ReturnType<typeof injectRegistry>
36
+ readonly atom: ComputedRef<Atom.Atom<AsyncResult.AsyncResult<A, E>>>
37
+ }
38
+ export interface QueryView<A, E> extends QueryHandle<A, E> {
39
+ readonly result: ComputedRef<AsyncResult.AsyncResult<A, E>>
40
+ readonly data: ComputedRef<A | undefined>
41
+ }
42
+
43
+ const useScopedSuspenseSetup = <A>(setup: () => A) => {
44
+ const scope = effectScope()
45
+ const controller = new AbortController()
46
+ const value = scope.run(setup)
47
+ if (value === undefined) {
48
+ throw new Error("Internal Error: atom suspense setup scope did not initialize")
49
+ }
50
+
51
+ const isMounted = ref(true)
52
+ let stopped = false
53
+ const stop = () => {
54
+ if (stopped) return
55
+ stopped = true
56
+ isMounted.value = false
57
+ controller.abort()
58
+ scope.stop()
59
+ }
60
+ onBeforeUnmount(stop)
61
+ onScopeDispose(stop)
62
+
63
+ return [value, isMounted, controller.signal] as const
64
+ }
65
+
66
+ export interface QueryCacheUpdater {
67
+ readonly update: <I, A, E, R, Request extends Req, Name extends string>(
68
+ registry: ReturnType<typeof injectRegistry>,
69
+ query: RequestHandlerWithInput<I, A, E, R, Request, Name>,
70
+ input: I,
71
+ updater: (data: NoInfer<A>) => NoInfer<A>
72
+ ) => void
73
+ }
74
+
75
+ // retained generic aliases so the exported option-interface arity is unchanged for consumers
76
+ export type UseQueryReturnType<A = any, E = any> = QueryHandle<A, E>
77
+ export type UseQueryDefinedReturnType<A = any, E = any> = QueryHandle<A, E>
78
+ export type QueryObserverResult<A = any, _E = any> = AsyncResult.AsyncResult<A, any>
79
+ export type SuspenseQueryTuple<A, E> = readonly [
80
+ ComputedRef<AsyncResult.AsyncResult<A, E>>,
81
+ ComputedRef<A>,
82
+ (options?: RefetchOptions) => Effect.Effect<A, E, never>,
83
+ QueryHandle<A, E>
84
+ ]
85
+
86
+ export type SuspenseQueryView<A, E> =
87
+ & Omit<QueryView<A, E>, "data">
88
+ & {
89
+ readonly data: ComputedRef<A>
90
+ }
91
+ & SuspenseQueryTuple<A, E>
92
+
93
+ export type QueryAtomFamily<I, A, E> = (input: I) => Atom.Atom<AsyncResult.AsyncResult<A, E>>
94
+ export type StreamQueryAtomFamily<I, A, E> = (input: I) => Atom.Writable<Atom.PullResult<A, E>, void>
95
+
96
+ interface QueryFamilyDescriptor<I, A, E> {
97
+ readonly id: string
98
+ readonly handler: (i: I) => Effect.Effect<A, E, any>
99
+ readonly options?: ClientForOptions
100
+ readonly queryKeyProjectionHash?: string
101
+ }
102
+
103
+ interface StreamQueryFamilyDescriptor<I, A, E> {
104
+ readonly id: string
105
+ readonly handler: (i: I) => Stream.Stream<A, E, any>
106
+ readonly options?: ClientForOptions
107
+ readonly queryKeyProjectionHash?: string
108
+ }
109
+
110
+ const queryFamilyCacheKey = (
111
+ q: { readonly id: string; readonly options?: ClientForOptions; readonly queryKeyProjectionHash?: string }
112
+ ) => `${makeQueryKey(q).join("/")}:${q.queryKeyProjectionHash ?? ""}`
113
+
114
+ // One atom family per request shape, keyed by the stable query key + projection hash (not the
115
+ // handler object — `clientFor` returns a fresh proxy per call, so the object isn't shareable).
116
+ // Module-level + key-indexed => the family is process-global, so the same request+input read
117
+ // the same atom across components/pages => cross-page caching via the global registry.
118
+ const queryFamilyByKey = new Map<string, any>()
119
+ const getQueryFamily = <I, A, E>(
120
+ rt: AtomClientRuntime,
121
+ q: QueryFamilyDescriptor<I, A, E>
122
+ ): QueryAtomFamily<I, A, E> => {
123
+ const key = queryFamilyCacheKey(q)
124
+ let f = queryFamilyByKey.get(key)
125
+ if (!f) {
126
+ f = buildQueryFamily(rt, q)
127
+ queryFamilyByKey.set(key, f)
128
+ }
129
+ return f
130
+ }
131
+
132
+ const streamQueryFamilyByKey = new Map<string, any>()
133
+ const getStreamQueryFamily = <I, A, E>(
134
+ rt: AtomClientRuntime,
135
+ q: StreamQueryFamilyDescriptor<I, A, E>
136
+ ): StreamQueryAtomFamily<I, A, E> => {
137
+ const key = queryFamilyCacheKey(q)
138
+ let f = streamQueryFamilyByKey.get(key)
139
+ if (!f) {
140
+ f = buildStreamQueryFamily(rt, q)
141
+ streamQueryFamilyByKey.set(key, f)
142
+ }
143
+ return f
144
+ }
145
+
146
+ const makeAtomQueryCacheUpdater = (warnIfMissing: boolean): QueryCacheUpdater => ({
147
+ update: (registry, query, input) => {
148
+ const family = queryFamilyByKey.get(queryFamilyCacheKey(query))
149
+ if (!family) {
150
+ if (warnIfMissing) {
151
+ console.warn(`Query ${query.id} has not been used yet; nothing to update`)
152
+ }
153
+ return
154
+ }
155
+ registry.refresh(family(input))
156
+ }
157
+ })
158
+
159
+ export const atomQueryCacheUpdater = makeAtomQueryCacheUpdater(true)
160
+ export const optionalAtomQueryCacheUpdater = makeAtomQueryCacheUpdater(false)
161
+
162
+ export const combineQueryCacheUpdaters = (first: QueryCacheUpdater, second: QueryCacheUpdater): QueryCacheUpdater => ({
163
+ update: (registry, query, input, updater) => {
164
+ first.update(registry, query, input, updater)
165
+ second.update(registry, query, input, updater)
166
+ }
167
+ })
168
+
169
+ let activeQueryCacheUpdater = atomQueryCacheUpdater
170
+
171
+ export const setQueryCacheUpdater = (updater: QueryCacheUpdater) => {
172
+ activeQueryCacheUpdater = updater
173
+ }
174
+
175
+ // Atom-engine query options (formerly reconstructed from @tanstack/vue-query types).
176
+ // The generic arity is kept so the exported interface signatures are unchanged for consumers.
19
177
  export interface CustomUseQueryOptions<
20
178
  TQueryFnData = unknown,
21
179
  TError = DefaultError,
22
180
  TData = TQueryFnData,
23
181
  TQueryData = TQueryFnData,
24
182
  TQueryKey extends QueryKey = QueryKey
25
- > extends
26
- Omit<
27
- QueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>,
28
- "queryKey" | "queryFn" | "initialData" | "enabled" | "placeholderData"
29
- >
30
- {
31
- enabled?: MaybeRefOrGetter<boolean | undefined> | (() => Enabled<TQueryFnData, TError, TQueryData, TQueryKey>)
183
+ > {
184
+ readonly enabled?: MaybeRefOrGetter<boolean | undefined>
185
+ /** stale threshold in ms (or a Duration input) */
186
+ readonly staleTime?: number
187
+ /** garbage-collect after idle, ms (or "infinity") */
188
+ readonly gcTime?: number | "infinity"
189
+ readonly refetchOnWindowFocus?: boolean
190
+ readonly structuralSharing?: boolean
191
+ /** poll: re-fetch every N ms (tanstack refetchInterval) */
192
+ readonly refetchInterval?: number
193
+ readonly select?: (data: TQueryFnData) => TData
194
+ /** accepted for source compatibility; not used by the atom engine */
195
+ readonly retry?: boolean | number
196
+ readonly meta?: Record<string, unknown>
197
+ readonly _phantom?: [TQueryData, TQueryKey, TError]
32
198
  }
33
199
 
34
200
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
@@ -41,11 +207,8 @@ export interface CustomUndefinedInitialQueryOptions<
41
207
  TQueryData = TQueryFnData,
42
208
  TQueryKey extends QueryKey = QueryKey
43
209
  > extends CustomUseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey> {
44
- initialData?: undefined | InitialDataFunction<NonUndefinedGuard<TQueryFnData>> | NonUndefinedGuard<TQueryFnData>
45
- placeholderData?:
46
- | undefined
47
- | NonFunctionGuard<TQueryData>
48
- | PlaceholderDataFunction<NonFunctionGuard<TQueryData>, TError, NonFunctionGuard<TQueryData>, TQueryKey>
210
+ readonly initialData?: TQueryFnData | (() => TQueryFnData) | undefined
211
+ readonly placeholderData?: NonFunctionGuard<TQueryData> | ((prev: TQueryData | undefined) => TQueryData) | undefined
49
212
  }
50
213
  export interface CustomDefinedInitialQueryOptions<
51
214
  TQueryFnData = unknown,
@@ -54,11 +217,8 @@ export interface CustomDefinedInitialQueryOptions<
54
217
  TQueryData = TQueryFnData,
55
218
  TQueryKey extends QueryKey = QueryKey
56
219
  > extends CustomUseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey> {
57
- initialData: NonUndefinedGuard<TQueryFnData> | (() => NonUndefinedGuard<TQueryFnData>)
58
- placeholderData?:
59
- | undefined
60
- | NonFunctionGuard<TQueryData>
61
- | PlaceholderDataFunction<NonFunctionGuard<TQueryData>, TError, NonFunctionGuard<TQueryData>, TQueryKey>
220
+ readonly initialData: TQueryFnData | (() => TQueryFnData)
221
+ readonly placeholderData?: NonFunctionGuard<TQueryData> | ((prev: TQueryData | undefined) => TQueryData) | undefined
62
222
  }
63
223
 
64
224
  export interface CustomDefinedPlaceholderQueryOptions<
@@ -68,35 +228,393 @@ export interface CustomDefinedPlaceholderQueryOptions<
68
228
  TQueryData = TQueryFnData,
69
229
  TQueryKey extends QueryKey = QueryKey
70
230
  > extends CustomUseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey> {
71
- initialData?: NonUndefinedGuard<TQueryFnData> | (() => NonUndefinedGuard<TQueryFnData>) | undefined
72
- placeholderData:
73
- | NonFunctionGuard<TQueryData>
74
- | PlaceholderDataFunction<NonFunctionGuard<TQueryData>, TError, NonFunctionGuard<TQueryData>, TQueryKey>
231
+ readonly initialData?: TQueryFnData | (() => TQueryFnData) | undefined
232
+ readonly placeholderData: NonFunctionGuard<TQueryData> | ((prev: TQueryData | undefined) => TQueryData)
233
+ }
234
+
235
+ export interface AtomQueryNewOptions<TQueryFnData = unknown, TData = TQueryFnData> {
236
+ readonly enabled?: MaybeRefOrGetter<boolean | undefined>
237
+ readonly staleTime?: number
238
+ readonly idleTTL?: number | "infinity"
239
+ readonly gcTime?: number | "infinity"
240
+ readonly revalidateOnFocus?: boolean
241
+ readonly refetchOnWindowFocus?: boolean
242
+ readonly structuralSharing?: boolean
243
+ readonly refreshEvery?: number
244
+ readonly refetchInterval?: number
245
+ readonly select?: (data: TQueryFnData) => TData
246
+ }
247
+
248
+ export interface AtomStreamQueryOptions {
249
+ readonly staleTime?: number
250
+ readonly idleTTL?: number | "infinity"
251
+ readonly gcTime?: number | "infinity"
252
+ readonly revalidateOnFocus?: boolean
253
+ readonly refetchOnWindowFocus?: boolean
254
+ readonly refreshEvery?: number
255
+ readonly refetchInterval?: number
256
+ }
257
+
258
+ const normalizeQueryOptions = (options?: {
259
+ readonly staleTime?: number
260
+ readonly gcTime?: number | "infinity"
261
+ readonly idleTTL?: number | "infinity"
262
+ readonly refetchOnWindowFocus?: boolean
263
+ readonly revalidateOnFocus?: boolean
264
+ readonly structuralSharing?: boolean
265
+ readonly refetchInterval?: number
266
+ readonly refreshEvery?: number
267
+ }): AtomQueryOptions => {
268
+ const out: {
269
+ staleTime?: number
270
+ gcTime?: number | "infinity"
271
+ revalidateOnFocus?: boolean
272
+ structuralSharing?: boolean
273
+ refetchInterval?: number
274
+ } = {}
275
+ if (options?.staleTime !== undefined) out.staleTime = options.staleTime
276
+ const gcTime = options?.idleTTL ?? options?.gcTime
277
+ if (gcTime !== undefined) out.gcTime = gcTime
278
+ const revalidateOnFocus = options?.revalidateOnFocus ?? options?.refetchOnWindowFocus
279
+ if (revalidateOnFocus !== undefined) out.revalidateOnFocus = revalidateOnFocus
280
+ if (options?.structuralSharing !== undefined) out.structuralSharing = options.structuralSharing
281
+ const refetchInterval = options?.refreshEvery ?? options?.refetchInterval
282
+ if (refetchInterval !== undefined) out.refetchInterval = refetchInterval
283
+ return out
284
+ }
285
+
286
+ export const useAtomQuery = <A, E>(
287
+ atom: () => Atom.Atom<AsyncResult.AsyncResult<A, E>>
288
+ ): QueryView<A, E> => {
289
+ const registry = injectRegistry()
290
+ const atomRef = computed(atom)
291
+ const atomResult = useAtomValue(() => atomRef.value)
292
+ const result = computed(() => atomResult.value)
293
+ const refresh = () => registry.refresh(atomRef.value)
294
+ const awaitResult = () => awaitAtomResult(registry, atomRef.value)
295
+ const refetch = () =>
296
+ Effect.gen(function*() {
297
+ yield* refreshAtomWithCurrentSpan(registry, atomRef.value)
298
+ return yield* awaitResult()
299
+ })
300
+ const data = computed(() => Option.getOrUndefined(AsyncResult.value(result.value)))
301
+
302
+ return {
303
+ result,
304
+ data,
305
+ awaitResult,
306
+ refetch,
307
+ refresh,
308
+ registry,
309
+ atom: atomRef
310
+ }
311
+ }
312
+
313
+ export const useAtomSuspense = <A, E>(
314
+ atom: () => Atom.Atom<AsyncResult.AsyncResult<A, E>>
315
+ ): Promise<SuspenseQueryView<A, E>> => {
316
+ const [{ view, data }, isMounted, signal] = useScopedSuspenseSetup(() => {
317
+ const view = useAtomQuery(atom)
318
+ const data = computed<A>(() => {
319
+ const latest = view.data.value
320
+ if (latest === undefined) {
321
+ throw new Error("Internal Error: atom suspense resolved without a latest value")
322
+ }
323
+ return latest
324
+ })
325
+ return { view, data }
326
+ })
327
+
328
+ const eff = Effect.gen(function*() {
329
+ const exit = yield* view.awaitResult().pipe(Effect.exit)
330
+ if (!isMounted.value) {
331
+ return yield* Effect.interrupt
332
+ }
333
+ if (Exit.isFailure(exit)) {
334
+ return yield* Exit.failCause(exit.cause)
335
+ }
336
+
337
+ const fetch = (_options?: RefetchOptions) => view.refetch()
338
+ const handle = {
339
+ awaitResult: view.awaitResult,
340
+ refetch: view.refetch,
341
+ refresh: view.refresh,
342
+ registry: view.registry,
343
+ atom: view.atom
344
+ }
345
+ return Object.assign(
346
+ [
347
+ view.result,
348
+ data,
349
+ fetch,
350
+ handle
351
+ ] as const,
352
+ {
353
+ ...view,
354
+ data
355
+ }
356
+ )
357
+ })
358
+
359
+ return Effect.runPromise(eff, { signal })
360
+ }
361
+
362
+ export type StreamQueryPullValue<A> = {
363
+ readonly done: boolean
364
+ readonly items: ReadonlyArray<A>
365
+ }
366
+
367
+ export interface StreamQueryView<A, E> {
368
+ readonly result: ComputedRef<Atom.PullResult<A, E>>
369
+ readonly items: ComputedRef<ReadonlyArray<A>>
370
+ readonly latest: ComputedRef<A | undefined>
371
+ readonly done: ComputedRef<boolean>
372
+ readonly awaitResult: () => Effect.Effect<StreamQueryPullValue<A>, E | Cause.NoSuchElementError, never>
373
+ readonly pull: () => void
374
+ readonly pullAndAwait: () => Effect.Effect<StreamQueryPullValue<A>, E | Cause.NoSuchElementError, never>
375
+ readonly refresh: () => void
376
+ readonly registry: ReturnType<typeof injectRegistry>
377
+ readonly atom: ComputedRef<Atom.Writable<Atom.PullResult<A, E>, void>>
378
+ }
379
+
380
+ export const useAtomStreamQuery = <A, E>(
381
+ atom: () => Atom.Writable<Atom.PullResult<A, E>, void>
382
+ ): StreamQueryView<A, E> => {
383
+ const registry = injectRegistry()
384
+ const atomRef = computed(atom)
385
+ const atomResult = useAtomValue(() => atomRef.value)
386
+ const result = computed(() => atomResult.value)
387
+ const pull = () => registry.set(atomRef.value, void 0)
388
+ const refresh = () => registry.refresh(atomRef.value)
389
+ const awaitResult = () => awaitAtomResult(registry, atomRef.value)
390
+ const pullAndAwait = () =>
391
+ Effect.gen(function*() {
392
+ pull()
393
+ return yield* awaitResult()
394
+ })
395
+ const items = computed(() =>
396
+ Option.getOrElse(
397
+ Option.map(AsyncResult.value(result.value), (_) => _.items),
398
+ () => []
399
+ )
400
+ )
401
+ const latest = computed(() => items.value.at(-1))
402
+ const done = computed(() =>
403
+ Option.getOrElse(
404
+ Option.map(AsyncResult.value(result.value), (_) => _.done),
405
+ () => false
406
+ )
407
+ )
408
+
409
+ return {
410
+ result,
411
+ items,
412
+ latest,
413
+ done,
414
+ awaitResult,
415
+ pull,
416
+ pullAndAwait,
417
+ refresh,
418
+ registry,
419
+ atom: atomRef
420
+ }
75
421
  }
76
422
 
77
- export class KnownFiberFailure<E> extends Error {
78
- readonly error: unknown
79
- constructor(public effectCause: Cause.Cause<E>) {
80
- super("Query failed with cause: " + Cause.squash(effectCause))
81
- this.error = Cause.squash(effectCause)
423
+ const optionValue = <I>(
424
+ arr: I | WatchSource<I> | undefined | WatchSource<Option.Option<I>>,
425
+ options?: { readonly mode?: "optional"; readonly enabled?: MaybeRefOrGetter<boolean | undefined> }
426
+ ): readonly [{ readonly value: I }, ComputedRef<boolean>] => {
427
+ if (options?.mode === "optional") {
428
+ const getOption: () => Option.Option<I> = typeof arr === "function"
429
+ ? arr as () => Option.Option<I>
430
+ : () => (arr as { value: Option.Option<I> }).value
431
+ return [
432
+ {
433
+ get value() {
434
+ return Option.getOrUndefined(getOption()) as I
435
+ }
436
+ },
437
+ computed(() => Option.isSome(getOption()))
438
+ ] as const
82
439
  }
440
+ const req = !arr
441
+ ? ({ value: undefined as I })
442
+ : typeof arr === "function"
443
+ ? ({
444
+ get value() {
445
+ return (arr as any)()
446
+ }
447
+ })
448
+ : (ref(arr) as any)
449
+ const enabled = options?.enabled
450
+ return [req, computed(() => enabled === undefined ? true : !!toValue(enabled))] as const
83
451
  }
84
452
 
85
- export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
453
+ const observedAtom = <A, E>(
454
+ atom: Atom.Atom<AsyncResult.AsyncResult<A, E>>,
455
+ options?: {
456
+ readonly staleTime?: number
457
+ readonly gcTime?: number | "infinity"
458
+ readonly idleTTL?: number | "infinity"
459
+ readonly refetchOnWindowFocus?: boolean
460
+ readonly revalidateOnFocus?: boolean
461
+ readonly structuralSharing?: boolean
462
+ readonly refetchInterval?: number
463
+ readonly refreshEvery?: number
464
+ }
465
+ ): Atom.Atom<AsyncResult.AsyncResult<A, E>> => withQueryOptions(atom, normalizeQueryOptions(options))
466
+
467
+ const observedStreamAtom = <A, E>(
468
+ atom: Atom.Writable<Atom.PullResult<A, E>, void>,
469
+ options?: AtomStreamQueryOptions
470
+ ): Atom.Writable<Atom.PullResult<A, E>, void> => {
471
+ let next = atom
472
+ const refetchInterval = options?.refreshEvery ?? options?.refetchInterval
473
+ if (refetchInterval !== undefined) next = Atom.withRefresh(refetchInterval)(next)
474
+ const gcTime = options?.idleTTL ?? options?.gcTime
475
+ if (gcTime === "infinity") return Atom.keepAlive(next)
476
+ if (gcTime !== undefined) return Atom.setIdleTTL(next, gcTime)
477
+ return next
478
+ }
479
+
480
+ const queryAtomFor = <I, A, E, TData>(
481
+ rt: AtomClientRuntime,
482
+ q: QueryFamilyDescriptor<I, A, E>,
483
+ arg: I,
484
+ options?: Omit<AtomQueryNewOptions<A, TData>, "select"> | Omit<CustomUseQueryOptions<A, E, TData>, "select">
485
+ ): Atom.Atom<AsyncResult.AsyncResult<A, E>> => {
486
+ const family = getQueryFamily(rt, q)
487
+ return observedAtom(family(arg), options)
488
+ }
489
+
490
+ const makeQueryView = <I, A, E, TData>(
491
+ getAtomRt: () => AtomClientRuntime,
492
+ q: QueryFamilyDescriptor<I, A, E>,
493
+ arg: I | WatchSource<I> | undefined | WatchSource<Option.Option<I>>,
494
+ options?: (AtomQueryNewOptions<A, TData> | CustomUseQueryOptions<A, E, TData>) & {
495
+ readonly mode?: "optional"
496
+ }
497
+ ): QueryView<TData, E> => {
498
+ const atomRt = getAtomRt()
499
+ const registry = injectRegistry()
500
+ const [req, enabledRef] = optionValue<I>(arg, options)
501
+ const family = getQueryFamily(atomRt, q)
502
+ const atomRef = computed(() => enabledRef.value ? observedAtom(family(req.value), options) : disabledQueryAtom)
503
+ const rawResult = useAtomValue(() => atomRef.value) as ComputedRef<AsyncResult.AsyncResult<A, E>>
504
+ const select = options?.select
505
+ const result = (select
506
+ ? computed(() => AsyncResult.map(rawResult.value, select))
507
+ : rawResult) as ComputedRef<AsyncResult.AsyncResult<TData, E>>
508
+ const refresh = () => registry.refresh(atomRef.value)
509
+ const awaitResult = () =>
510
+ select
511
+ ? awaitAtomResult(registry, atomRef.value).pipe(Effect.map(select))
512
+ : awaitAtomResult(registry, atomRef.value)
513
+ const refetch = () =>
514
+ Effect.gen(function*() {
515
+ yield* refreshAtomWithCurrentSpan(registry, atomRef.value)
516
+ return yield* awaitResult()
517
+ })
518
+ const staleMs = staleTimeMsOf(normalizeQueryOptions(options))
519
+ onMounted(() => {
520
+ if (!enabledRef.value) return
521
+ if (isStaleResult(registry.get(atomRef.value), staleMs)) refresh()
522
+ })
523
+ const data = computed(() => Option.getOrUndefined(AsyncResult.value(result.value)))
524
+
525
+ return {
526
+ result,
527
+ data,
528
+ awaitResult,
529
+ refetch,
530
+ refresh,
531
+ registry,
532
+ atom: atomRef
533
+ }
534
+ }
535
+
536
+ export const makeQueryFamily = <R>(_getRuntime: () => Context.Context<R>, getAtomRt: () => AtomClientRuntime) => {
537
+ const useQueryFamily: {
538
+ <I, E, A, Request extends Req, Name extends string>(
539
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
540
+ ): QueryAtomFamily<I, A, E>
541
+ } = <I, E, A, Request extends Req, Name extends string>(
542
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
543
+ ) => getQueryFamily(getAtomRt(), q)
544
+
545
+ return useQueryFamily
546
+ }
547
+
548
+ export const makeQueryAtom = <R>(_getRuntime: () => Context.Context<R>, getAtomRt: () => AtomClientRuntime) => {
549
+ const useQueryAtom: {
550
+ <I, E, A, Request extends Req, Name extends string>(
551
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
552
+ ): {
553
+ <TData = A>(
554
+ arg: I,
555
+ options?: Omit<AtomQueryNewOptions<A, TData>, "select">
556
+ ): Atom.Atom<AsyncResult.AsyncResult<A, E>>
557
+ }
558
+ } = <I, E, A, Request extends Req, Name extends string>(
559
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
560
+ ) =>
561
+ <TData = A>(
562
+ arg: I,
563
+ options?: Omit<AtomQueryNewOptions<A, TData>, "select">
564
+ ) => queryAtomFor(getAtomRt(), q, arg, options)
565
+
566
+ return useQueryAtom
567
+ }
568
+
569
+ export const makeQueryNew = <R>(_getRuntime: () => Context.Context<R>, getAtomRt: () => AtomClientRuntime) => {
570
+ const useQueryNew: {
571
+ <I, E, A, Request extends Req, Name extends string>(
572
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
573
+ ): {
574
+ <TData = A>(
575
+ arg: WatchSource<Option.Option<I>>,
576
+ options: Omit<AtomQueryNewOptions<A, TData>, "enabled"> & { mode: "optional" }
577
+ ): QueryView<TData, E>
578
+
579
+ <TData = A>(
580
+ arg: I | WatchSource<I> | undefined,
581
+ options?: AtomQueryNewOptions<A, TData>
582
+ ): QueryView<TData, E>
583
+ }
584
+ } = <I, E, A, Request extends Req, Name extends string>(
585
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
586
+ ) =>
587
+ <TData = A>(
588
+ arg: I | WatchSource<I> | undefined | WatchSource<Option.Option<I>>,
589
+ options?: AtomQueryNewOptions<A, TData> & { readonly mode?: "optional" }
590
+ ) => makeQueryView<I, A, E, TData>(getAtomRt, q, arg, options)
591
+
592
+ return useQueryNew
593
+ }
594
+
595
+ export const makeQuery = <R>(_getRuntime: () => Context.Context<R>, getAtomRt: () => AtomClientRuntime) => {
86
596
  const useQuery_: {
87
597
  <I, A, E, Request extends Req, Name extends string>(
88
- q:
89
- | RequestHandlerWithInput<I, A, E, R, Request, Name>
90
- | RequestHandler<A, E, R, Request, Name>
598
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
91
599
  ): {
600
+ <TData = A>(
601
+ arg: WatchSource<Option.Option<I>>,
602
+ options: Omit<CustomUndefinedInitialQueryOptions<A, E, TData>, "enabled"> & { mode: "optional" }
603
+ ): readonly [
604
+ ComputedRef<AsyncResult.AsyncResult<TData, E>>,
605
+ ComputedRef<TData | undefined>,
606
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
607
+ UseQueryDefinedReturnType<TData, CauseException<E>>
608
+ ]
609
+
92
610
  <TData = A>(
93
611
  arg: I | WatchSource<I> | undefined,
94
612
  options?: CustomUndefinedInitialQueryOptions<A, E, TData>
95
613
  ): readonly [
96
614
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
97
615
  ComputedRef<TData | undefined>,
98
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
99
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
616
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
617
+ UseQueryDefinedReturnType<TData, CauseException<E>>
100
618
  ]
101
619
 
102
620
  <TData = A>(
@@ -105,8 +623,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
105
623
  ): readonly [
106
624
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
107
625
  ComputedRef<TData>,
108
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
109
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
626
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
627
+ UseQueryDefinedReturnType<TData, CauseException<E>>
110
628
  ]
111
629
 
112
630
  <TData = A>(
@@ -115,253 +633,178 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
115
633
  ): readonly [
116
634
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
117
635
  ComputedRef<TData>,
118
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
119
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
636
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
637
+ UseQueryDefinedReturnType<TData, CauseException<E>>
120
638
  ]
121
639
  }
122
640
  } = <I, A, E, Request extends Req, Name extends string>(
123
- q:
124
- | RequestHandlerWithInput<I, A, E, R, Request, Name>
125
- | RequestHandler<A, E, R, Request, Name>
641
+ q: RequestHandlerWithInput<I, A, E, R, Request, Name>
126
642
  ) =>
127
643
  <TData = A>(
128
- arg: I | WatchSource<I> | undefined,
129
- // todo QueryKey type would be [string, ...string[]], but with I it would be [string, ...string[], I]
644
+ arg: I | WatchSource<I> | undefined | WatchSource<Option.Option<I>>,
130
645
  options?: any
131
- // TODO
132
646
  ) => {
133
- // we wrap into KnownFiberFailure because we want to keep the full cause of the failure.
134
- const runPromise = flow(Effect.runPromiseExitWith(getRuntime()), (_) =>
135
- _.then(
136
- Exit.match({
137
- onFailure: (cause) => Promise.reject(new KnownFiberFailure(cause)),
138
- onSuccess: (value) => Promise.resolve(value)
139
- })
140
- ))
141
- const arr = arg
142
- const req: { value: I } = !arg
143
- ? undefined
144
- : typeof arr === "function"
145
- ? ({
146
- get value() {
147
- return (arr as any)()
148
- }
149
- } as any)
150
- : ref(arg)
151
- const queryKey = makeQueryKey(q)
152
- const handler = q.handler
153
-
154
- const defaultOptions = {
155
- // we do not want to throw errors, because we turn the success and error responses into a Result type
156
- // why don't we turn the error/success response into a Result type before returning to tanstack query? because we want to leverage tanstack query's retry and caching mechanism, which relies on throwing errors to trigger retries, and we don't want to interfere with that by catching the errors too early.
157
- // but if we allow tanstack query to throw, it will trigger the error boundary in Vue - via a "watcher callback" error - which we currently report and log, which is not what we want.
158
- // TODO: we might want to rethink the strategy of how to handle errors that happen after the initial load.
159
- // For suspense, the initial load is captured by the suspense boundary.
160
- // For subsequent loads (or non suspense use) we currently are required to use the QueryResult component to conditionally render error/loading/etc.
161
- throwOnError: false
162
- }
647
+ const view = makeQueryView<I, A, E, TData>(getAtomRt, q, arg, options)
163
648
 
164
- const r = useTanstackQuery<A, KnownFiberFailure<E>, TData>(
165
- Effect.isEffect(handler)
166
- ? {
167
- ...defaultOptions,
168
- ...options,
169
- retry: (retryCount, error) => {
170
- if (error instanceof KnownFiberFailure) {
171
- if (!isHttpClientError(error.error) && !S.is(ServiceUnavailableError)(error.error)) {
172
- return false
173
- }
174
- }
175
-
176
- return retryCount < 5
177
- },
178
- queryKey,
179
- queryFn: ({ meta, signal }) =>
180
- runPromise(
181
- handler
182
- .pipe(
183
- Effect.tapCauseIf(Cause.hasDies, (cause) => reportRuntimeError(cause)),
184
- Effect.withSpan(`query ${q.id}`, {}, { captureStackTrace: false }),
185
- meta?.["span"] ? Effect.withParentSpan(meta["span"] as Span) : (_) => _
186
- ),
187
- { signal }
188
- )
189
- }
190
- : {
191
- ...defaultOptions,
192
- ...options,
193
- retry: (retryCount, error) => {
194
- if (error instanceof KnownFiberFailure) {
195
- if (!isHttpClientError(error.error) && !S.is(ServiceUnavailableError)(error.error)) {
196
- return false
197
- }
198
- }
199
-
200
- return retryCount < 5
201
- },
202
- queryKey: [...queryKey, req],
203
- queryFn: ({ meta, signal }) =>
204
- runPromise(
205
- handler(req.value)
206
- .pipe(
207
- Effect.tapCauseIf(Cause.hasDies, (cause) => reportRuntimeError(cause)),
208
- Effect.withSpan(`query ${q.id}`, {}, { captureStackTrace: false }),
209
- meta?.["span"] ? Effect.withParentSpan(meta["span"] as Span) : (_) => _
210
- ),
211
- { signal }
212
- )
213
- }
214
- )
215
-
216
- const latestSuccess = shallowRef<TData>()
217
- const result = computed((): AsyncResult.AsyncResult<TData, E> =>
218
- swrToQuery({
219
- error: r.error.value ?? undefined,
220
- data: r.data.value === undefined ? latestSuccess.value : r.data.value, // we fall back to existing data, as tanstack query might loose it when the key changes
221
- isValidating: r.isFetching.value
222
- })
223
- )
224
- // not using `computed` here as we have a circular dependency
225
- watch(result, (value) => latestSuccess.value = Option.getOrUndefined(AsyncResult.value(value)), { immediate: true })
649
+ // 4th element is internal-only; the public `.suspense()` Promise boundary lives in makeClient.
650
+ const handle = {
651
+ awaitResult: view.awaitResult,
652
+ refetch: view.refetch,
653
+ refresh: view.refresh,
654
+ registry: view.registry,
655
+ atom: view.atom
656
+ }
226
657
 
227
658
  return [
228
- result,
229
- computed(() => latestSuccess.value),
230
- // one thing to keep in mind is that span will be disconnected as Context does not pass from outside.
231
- // TODO: consider how we should handle the Result here which is `QueryObserverResult<A, E>`
232
- // and always ends up in the success channel, even when error..
233
- (options?: RefetchOptions) =>
234
- Effect.currentSpan.pipe(
235
- Effect.orElseSucceed(() => null),
236
- Effect.flatMap((span) => Effect.promise(() => r.refetch({ ...options, updateMeta: { span } })))
237
- ),
238
- r
659
+ view.result,
660
+ view.data,
661
+ (_options?: RefetchOptions) => view.refetch(),
662
+ handle
239
663
  ] as any
240
664
  }
241
665
 
242
- function swrToQuery<E, A>(r: {
243
- error: KnownFiberFailure<E> | undefined
244
- data: A | undefined
245
- isValidating: boolean
246
- }): AsyncResult.AsyncResult<A, E> {
247
- if (r.error !== undefined) {
248
- return AsyncResult.failureWithPrevious(
249
- r.error.effectCause,
250
- {
251
- previous: r.data === undefined ? Option.none() : Option.some(AsyncResult.success(r.data)),
252
- waiting: r.isValidating
253
- }
254
- )
255
- }
256
- if (r.data !== undefined) {
257
- return AsyncResult.success<A, E>(r.data, { waiting: r.isValidating })
258
- }
259
-
260
- return AsyncResult.initial(r.isValidating)
261
- }
262
-
263
666
  const useQuery: {
264
667
  /**
265
668
  * Effect results are passed to the caller, including errors.
669
+ * When `I = void` the input argument may be omitted.
266
670
  * @deprecated use client helpers instead (.query())
267
671
  */
268
- <E, A, Request extends Req, Name extends string>(
269
- self: RequestHandler<A, E, R, Request, Name>
672
+ <I, E, A, Request extends Req, Name extends string>(
673
+ self: RequestHandlerWithInput<I, A, E, R, Request, Name>
270
674
  ): {
271
- // required options, with initialData
272
- /**
273
- * Effect results are passed to the caller, including errors.
274
- */
275
675
  <TData = A>(
276
- options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
676
+ arg: WatchSource<Option.Option<I>>,
677
+ options: Omit<CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>, "enabled"> & { mode: "optional" }
277
678
  ): readonly [
278
679
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
279
- ComputedRef<TData>,
280
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
680
+ ComputedRef<TData | undefined>,
681
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
281
682
  UseQueryReturnType<any, any>
282
683
  ]
283
684
  <TData = A>(
284
- options: CustomDefinedPlaceholderQueryOptions<A, E, TData>
685
+ arg: I | WatchSource<I>,
686
+ options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
285
687
  ): readonly [
286
688
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
287
689
  ComputedRef<TData>,
288
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>, never, never>,
289
- UseQueryDefinedReturnType<TData, KnownFiberFailure<E>>
290
- ]
291
- // optional options, optional A
292
- /**
293
- * Effect results are passed to the caller, including errors.
294
- */
295
- <TData = A>(options?: CustomUndefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>): readonly [
296
- ComputedRef<AsyncResult.AsyncResult<A, E>>,
297
- ComputedRef<A | undefined>,
298
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
690
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
299
691
  UseQueryReturnType<any, any>
300
692
  ]
301
- }
302
- /**
303
- * Effect results are passed to the caller, including errors.
304
- * @deprecated use client helpers instead (.query())
305
- */
306
- <Arg, E, A, Request extends Req, Name extends string>(
307
- self: RequestHandlerWithInput<Arg, A, E, R, Request, Name>
308
- ): {
309
- // required options, with initialData
310
- /**
311
- * Effect results are passed to the caller, including errors.
312
- */
313
693
  <TData = A>(
314
- arg: Arg | WatchSource<Arg>,
315
- options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
694
+ arg: I | WatchSource<I>,
695
+ options: CustomDefinedPlaceholderQueryOptions<A, CauseException<E>, TData>
316
696
  ): readonly [
317
697
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
318
698
  ComputedRef<TData>,
319
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
699
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
320
700
  UseQueryReturnType<any, any>
321
701
  ]
322
- // required options, with placeholderData
323
- /**
324
- * Effect results are passed to the caller, including errors.
325
- */
326
702
  <TData = A>(
327
- arg: Arg | WatchSource<Arg>,
328
- options: CustomDefinedPlaceholderQueryOptions<A, KnownFiberFailure<E>, TData>
329
- ): readonly [
330
- ComputedRef<AsyncResult.AsyncResult<TData, E>>,
331
- ComputedRef<TData>,
332
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
333
- UseQueryReturnType<any, any>
334
- ]
335
- // optional options, optional A
336
- /**
337
- * Effect results are passed to the caller, including errors.
338
- */
339
- <TData = A>(
340
- arg: Arg | WatchSource<Arg>,
341
- options?: CustomUndefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
703
+ arg: I | WatchSource<I>,
704
+ options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
342
705
  ): readonly [
343
706
  ComputedRef<AsyncResult.AsyncResult<TData, E>>,
344
707
  ComputedRef<TData | undefined>,
345
- (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
708
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
346
709
  UseQueryReturnType<any, any>
347
710
  ]
348
711
  }
349
- } = (
712
+ } = ((
350
713
  self: any
351
714
  ) => {
352
715
  const q = useQuery_(self)
353
-
354
- return (argOrOptions?: any, options?: any) =>
355
- Effect.isEffect(self.handler)
356
- ? q(undefined, argOrOptions)
357
- : q(argOrOptions, options)
358
- }
716
+ return (arg?: any, options?: any) => q(arg, options)
717
+ }) as any
359
718
  return useQuery
360
719
  }
361
720
 
362
721
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
363
722
  export interface MakeQuery2<R> extends ReturnType<typeof makeQuery<R>> {}
364
723
 
724
+ const streamQueryAtomFor = <I, A, E>(
725
+ rt: AtomClientRuntime,
726
+ q: StreamQueryFamilyDescriptor<I, A, E>,
727
+ arg: I,
728
+ options?: AtomStreamQueryOptions
729
+ ): Atom.Writable<Atom.PullResult<A, E>, void> => {
730
+ const family = getStreamQueryFamily(rt, q)
731
+ return observedStreamAtom(family(arg), options)
732
+ }
733
+
734
+ export const makeStreamQueryFamily = <R>(_getRuntime: () => Context.Context<R>, getAtomRt: () => AtomClientRuntime) => {
735
+ const useStreamQueryFamily: {
736
+ <I, E, A, Request extends Req, Name extends string>(
737
+ q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
738
+ ): StreamQueryAtomFamily<I, A, E>
739
+ } = <I, E, A, Request extends Req, Name extends string>(
740
+ q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
741
+ ) => getStreamQueryFamily(getAtomRt(), q)
742
+
743
+ return useStreamQueryFamily
744
+ }
745
+
746
+ export const makeStreamQueryAtom = <R>(_getRuntime: () => Context.Context<R>, getAtomRt: () => AtomClientRuntime) => {
747
+ const useStreamQueryAtom: {
748
+ <I, E, A, Request extends Req, Name extends string>(
749
+ q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
750
+ ): (arg: I, options?: AtomStreamQueryOptions) => Atom.Writable<Atom.PullResult<A, E>, void>
751
+ } = <I, E, A, Request extends Req, Name extends string>(
752
+ q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
753
+ ) =>
754
+ (arg: I, options?: AtomStreamQueryOptions) => streamQueryAtomFor(getAtomRt(), q, arg, options)
755
+
756
+ return useStreamQueryAtom
757
+ }
758
+
759
+ export const makeStreamQueryNew = <R>(_getRuntime: () => Context.Context<R>, getAtomRt: () => AtomClientRuntime) => {
760
+ const useStreamQueryNew: {
761
+ <I, E, A, Request extends Req, Name extends string>(
762
+ q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
763
+ ): (arg: MaybeRefOrGetter<I>, options?: AtomStreamQueryOptions) => StreamQueryView<A, E>
764
+ } = <I, E, A, Request extends Req, Name extends string>(
765
+ q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
766
+ ) =>
767
+ (arg: MaybeRefOrGetter<I>, options?: AtomStreamQueryOptions) =>
768
+ useAtomStreamQuery(() => streamQueryAtomFor(getAtomRt(), q, toValue(arg), options))
769
+
770
+ return useStreamQueryNew
771
+ }
772
+
773
+ type StreamQueryResult<A, E> = readonly [
774
+ ComputedRef<AsyncResult.AsyncResult<A[], E>>,
775
+ ComputedRef<A[] | undefined>,
776
+ (options?: RefetchOptions) => Effect.Effect<QueryObserverResult<A[], CauseException<E>>>,
777
+ UseQueryReturnType<any, any>
778
+ ]
779
+
780
+ export const makeStreamQuery = <R>(
781
+ getRuntime: () => Context.Context<R>,
782
+ getAtomRt: () => AtomClientRuntime
783
+ ) => {
784
+ const query = makeQuery(getRuntime, getAtomRt)
785
+ // A stream query is an ordinary atom query over an effect that collects the whole stream
786
+ // into an array (`Stream.runCollect`). It reuses all the atom machinery (family cache, swr,
787
+ // invalidation, structural sharing). Note: unlike the old tanstack `streamedQuery`, the result
788
+ // appears once the stream completes, not incrementally (stream queries are not used today).
789
+ const streamQuery_: {
790
+ <I, E, A, Request extends Req, Name extends string>(
791
+ q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
792
+ ): (arg: I | WatchSource<I>) => StreamQueryResult<A, E>
793
+ } = (q: any) => {
794
+ const hook = query({
795
+ id: q.id,
796
+ options: q.options,
797
+ handler: (i: any) => Stream.runCollect(q.handler(i)).pipe(Effect.map((chunk) => [...chunk]))
798
+ } as any)
799
+ return (arg?: any) => hook(arg) as any
800
+ }
801
+
802
+ return streamQuery_
803
+ }
804
+
805
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
806
+ export interface MakeStreamQuery2<R> extends ReturnType<typeof makeStreamQuery<R>> {}
807
+
365
808
  function orPrevious<E, A>(result: AsyncResult.AsyncResult<A, E>) {
366
809
  return AsyncResult.isFailure(result) && Option.isSome(result.previousSuccess)
367
810
  ? AsyncResult.success(result.previousSuccess.value, { waiting: result.waiting })
@@ -409,29 +852,14 @@ export function composeQueries<
409
852
  }
410
853
 
411
854
  export const useUpdateQuery = () => {
412
- const queryClient = useQueryClient()
855
+ const registry = injectRegistry()
413
856
 
414
857
  const f: {
415
- <A>(
416
- query: RequestHandler<A, any, any, any, any>,
417
- updater: (data: NoInfer<A>) => NoInfer<A>
418
- ): void
419
- <I, A>(
420
- query: RequestHandlerWithInput<I, A, any, any, any, any>,
858
+ <I, A, E, R, Request extends Req, Name extends string>(
859
+ query: RequestHandlerWithInput<I, A, E, R, Request, Name>,
421
860
  input: I,
422
861
  updater: (data: NoInfer<A>) => NoInfer<A>
423
862
  ): void
424
- } = (query: any, updateOrInput: any, updaterMaybe?: any) => {
425
- const updater = updaterMaybe !== undefined ? updaterMaybe : updateOrInput
426
- const key = updaterMaybe !== undefined
427
- ? [...makeQueryKey(query), updateOrInput]
428
- : makeQueryKey(query)
429
- const data = queryClient.getQueryData(key)
430
- if (data) {
431
- queryClient.setQueryData(key, updater)
432
- } else {
433
- console.warn(`Query data for key ${key} not found`, key)
434
- }
435
- }
863
+ } = (query, input, updater) => activeQueryCacheUpdater.update(registry, query, input, updater)
436
864
  return f
437
865
  }