@effect-app/vue 4.0.0-beta.3 → 4.0.0-beta.300

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