@effect-app/vue 4.0.0-beta.2 → 4.0.0-beta.200
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.
- package/CHANGELOG.md +1611 -0
- package/dist/commander.d.ts +628 -0
- package/dist/commander.d.ts.map +1 -0
- package/dist/commander.js +1055 -0
- package/dist/confirm.d.ts +19 -0
- package/dist/confirm.d.ts.map +1 -0
- package/dist/confirm.js +24 -0
- package/dist/errorReporter.d.ts +4 -4
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +12 -18
- package/dist/form.d.ts +14 -5
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +41 -12
- package/dist/index.d.ts +1 -1
- package/dist/intl.d.ts +15 -0
- package/dist/intl.d.ts.map +1 -0
- package/dist/intl.js +9 -0
- package/dist/lib.d.ts +6 -9
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +35 -10
- package/dist/makeClient.d.ts +153 -340
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +222 -377
- package/dist/makeContext.d.ts +1 -1
- package/dist/makeContext.d.ts.map +1 -1
- package/dist/makeIntl.d.ts +1 -1
- package/dist/makeIntl.d.ts.map +1 -1
- package/dist/makeUseCommand.d.ts +8 -0
- package/dist/makeUseCommand.d.ts.map +1 -0
- package/dist/makeUseCommand.js +13 -0
- package/dist/mutate.d.ts +53 -35
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +138 -47
- package/dist/query.d.ts +20 -40
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +138 -71
- package/dist/routeParams.d.ts +1 -1
- package/dist/runtime.d.ts +7 -4
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +27 -17
- package/dist/toast.d.ts +46 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +32 -0
- package/dist/withToast.d.ts +26 -0
- package/dist/withToast.d.ts.map +1 -0
- package/dist/withToast.js +54 -0
- package/examples/streamMutation.ts +70 -0
- package/package.json +48 -50
- package/src/commander.ts +3384 -0
- package/src/{experimental/confirm.ts → confirm.ts} +10 -14
- package/src/errorReporter.ts +62 -74
- package/src/form.ts +56 -17
- package/src/intl.ts +12 -0
- package/src/lib.ts +47 -20
- package/src/makeClient.ts +569 -1135
- package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +6 -4
- package/src/mutate.ts +266 -128
- package/src/query.ts +207 -181
- package/src/runtime.ts +41 -20
- package/src/{experimental/toast.ts → toast.ts} +11 -25
- package/src/{experimental/withToast.ts → withToast.ts} +28 -10
- package/test/Mutation.test.ts +176 -23
- package/test/dist/form.test.d.ts.map +1 -1
- package/test/dist/lib.test.d.ts.map +1 -0
- package/test/dist/streamFinal.test.d.ts.map +1 -0
- package/test/dist/streamFn.test.d.ts.map +1 -0
- package/test/dist/stubs.d.ts +3274 -122
- package/test/dist/stubs.d.ts.map +1 -1
- package/test/dist/stubs.js +178 -31
- package/test/form-validation-errors.test.ts +23 -19
- package/test/form.test.ts +20 -2
- package/test/lib.test.ts +240 -0
- package/test/makeClient.test.ts +292 -38
- package/test/streamFinal.test.ts +63 -0
- package/test/streamFn.test.ts +455 -0
- package/test/stubs.ts +214 -42
- package/tsconfig.examples.json +20 -0
- package/tsconfig.json +0 -1
- package/tsconfig.json.bak +5 -2
- package/tsconfig.src.json +34 -34
- package/tsconfig.test.json +2 -2
- package/vitest.config.ts +5 -5
- package/dist/experimental/commander.d.ts +0 -359
- package/dist/experimental/commander.d.ts.map +0 -1
- package/dist/experimental/commander.js +0 -558
- package/dist/experimental/confirm.d.ts +0 -19
- package/dist/experimental/confirm.d.ts.map +0 -1
- package/dist/experimental/confirm.js +0 -28
- package/dist/experimental/intl.d.ts +0 -16
- package/dist/experimental/intl.d.ts.map +0 -1
- package/dist/experimental/intl.js +0 -5
- package/dist/experimental/makeUseCommand.d.ts +0 -8
- package/dist/experimental/makeUseCommand.d.ts.map +0 -1
- package/dist/experimental/makeUseCommand.js +0 -13
- package/dist/experimental/toast.d.ts +0 -47
- package/dist/experimental/toast.d.ts.map +0 -1
- package/dist/experimental/toast.js +0 -41
- package/dist/experimental/withToast.d.ts +0 -25
- package/dist/experimental/withToast.d.ts.map +0 -1
- package/dist/experimental/withToast.js +0 -45
- package/eslint.config.mjs +0 -24
- package/src/experimental/commander.ts +0 -1836
- package/src/experimental/intl.ts +0 -9
package/src/query.ts
CHANGED
|
@@ -2,16 +2,22 @@
|
|
|
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
|
-
|
|
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,
|
|
8
|
-
import { type Req } from "effect-app/client"
|
|
9
|
-
import type {
|
|
10
|
-
import { ServiceUnavailableError } from "effect-app/client/errors"
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
6
|
+
import { type DefaultError, type Enabled, experimental_streamedQuery as streamedQuery, 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, type Context, Effect, Exit, Option, S } from "effect-app"
|
|
8
|
+
import { makeQueryKey, type Req } from "effect-app/client"
|
|
9
|
+
import type { RequestHandlerWithInput, RequestStreamHandlerWithInput } from "effect-app/client/clientFor"
|
|
10
|
+
import { CauseException, ServiceUnavailableError } from "effect-app/client/errors"
|
|
11
|
+
import * as Channel from "effect/Channel"
|
|
12
|
+
import * as Pull from "effect/Pull"
|
|
13
|
+
import * as Scope from "effect/Scope"
|
|
14
|
+
import type * as Stream from "effect/Stream"
|
|
11
15
|
import { type Span } from "effect/Tracer"
|
|
12
16
|
import { isHttpClientError } from "effect/unstable/http/HttpClientError"
|
|
17
|
+
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
13
18
|
import { computed, type ComputedRef, type MaybeRefOrGetter, ref, shallowRef, watch, type WatchSource } from "vue"
|
|
14
|
-
import {
|
|
19
|
+
import { reportRuntimeError } from "./lib.js"
|
|
20
|
+
import { makeRunPromise } from "./runtime.js"
|
|
15
21
|
|
|
16
22
|
// 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
23
|
// but because how they are dealing with some extends clause, we loose all properties except initialData
|
|
@@ -74,20 +80,68 @@ export interface CustomDefinedPlaceholderQueryOptions<
|
|
|
74
80
|
| PlaceholderDataFunction<NonFunctionGuard<TQueryData>, TError, NonFunctionGuard<TQueryData>, TQueryKey>
|
|
75
81
|
}
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
function swrToQuery<E, A>(r: {
|
|
84
|
+
error: CauseException<E> | undefined
|
|
85
|
+
data: A | undefined
|
|
86
|
+
isValidating: boolean
|
|
87
|
+
}): AsyncResult.AsyncResult<A, E> {
|
|
88
|
+
if (r.error !== undefined) {
|
|
89
|
+
return AsyncResult.failureWithPrevious(
|
|
90
|
+
r.error.originalCause,
|
|
91
|
+
{
|
|
92
|
+
previous: r.data === undefined ? Option.none() : Option.some(AsyncResult.success(r.data)),
|
|
93
|
+
waiting: r.isValidating
|
|
94
|
+
}
|
|
95
|
+
)
|
|
82
96
|
}
|
|
97
|
+
if (r.data !== undefined) {
|
|
98
|
+
return AsyncResult.success<A, E>(r.data, { waiting: r.isValidating })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return AsyncResult.initial(r.isValidating)
|
|
83
102
|
}
|
|
84
103
|
|
|
85
|
-
|
|
104
|
+
function streamToAsyncIterableWithCauseException<A, E, R>(
|
|
105
|
+
self: Stream.Stream<A, E, R>,
|
|
106
|
+
context: Context.Context<R>,
|
|
107
|
+
id: string
|
|
108
|
+
): AsyncIterable<A> {
|
|
109
|
+
return {
|
|
110
|
+
[Symbol.asyncIterator]() {
|
|
111
|
+
const runPromise = Effect.runPromiseWith(context)
|
|
112
|
+
const runPromiseExit = Effect.runPromiseExitWith(context)
|
|
113
|
+
const scope = Scope.makeUnsafe()
|
|
114
|
+
let pull: any
|
|
115
|
+
let currentIter: Iterator<A> | undefined
|
|
116
|
+
return {
|
|
117
|
+
async next(): Promise<IteratorResult<A>> {
|
|
118
|
+
if (currentIter) {
|
|
119
|
+
const next = currentIter.next()
|
|
120
|
+
if (!next.done) return next
|
|
121
|
+
currentIter = undefined
|
|
122
|
+
}
|
|
123
|
+
pull ??= await runPromise(Channel.toPullScoped((self as any).channel, scope))
|
|
124
|
+
const exit = await runPromiseExit(pull)
|
|
125
|
+
if (Exit.isSuccess(exit)) {
|
|
126
|
+
currentIter = (exit.value as any)[Symbol.iterator]()
|
|
127
|
+
return currentIter!.next()
|
|
128
|
+
} else if (Pull.isDoneCause((exit as any).cause)) {
|
|
129
|
+
return { done: true, value: undefined }
|
|
130
|
+
}
|
|
131
|
+
throw new CauseException((exit as any).cause, id)
|
|
132
|
+
},
|
|
133
|
+
return(_) {
|
|
134
|
+
return runPromise(Effect.as(Scope.close(scope, Exit.void), { done: true, value: undefined }) as any)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export const makeQuery = <R>(getRuntime: () => Context.Context<R>) => {
|
|
86
142
|
const useQuery_: {
|
|
87
143
|
<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>
|
|
144
|
+
q: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
91
145
|
): {
|
|
92
146
|
<TData = A>(
|
|
93
147
|
arg: I | WatchSource<I> | undefined,
|
|
@@ -95,8 +149,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
95
149
|
): readonly [
|
|
96
150
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
97
151
|
ComputedRef<TData | undefined>,
|
|
98
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
99
|
-
UseQueryDefinedReturnType<TData,
|
|
152
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
153
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
100
154
|
]
|
|
101
155
|
|
|
102
156
|
<TData = A>(
|
|
@@ -105,8 +159,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
105
159
|
): readonly [
|
|
106
160
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
107
161
|
ComputedRef<TData>,
|
|
108
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
109
|
-
UseQueryDefinedReturnType<TData,
|
|
162
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
163
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
110
164
|
]
|
|
111
165
|
|
|
112
166
|
<TData = A>(
|
|
@@ -115,14 +169,12 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
115
169
|
): readonly [
|
|
116
170
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
117
171
|
ComputedRef<TData>,
|
|
118
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
119
|
-
UseQueryDefinedReturnType<TData,
|
|
172
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
173
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
120
174
|
]
|
|
121
175
|
}
|
|
122
176
|
} = <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>
|
|
177
|
+
q: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
126
178
|
) =>
|
|
127
179
|
<TData = A>(
|
|
128
180
|
arg: I | WatchSource<I> | undefined,
|
|
@@ -130,76 +182,56 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
130
182
|
options?: any
|
|
131
183
|
// TODO
|
|
132
184
|
) => {
|
|
133
|
-
// we wrap into
|
|
134
|
-
const runPromise =
|
|
135
|
-
_.then(
|
|
136
|
-
Exit.match({
|
|
137
|
-
onFailure: (cause) => Promise.reject(new KnownFiberFailure(cause)),
|
|
138
|
-
onSuccess: (value) => Promise.resolve(value)
|
|
139
|
-
})
|
|
140
|
-
))
|
|
185
|
+
// we wrap into CauseException because we want to keep the full cause of the failure.
|
|
186
|
+
const runPromise = makeRunPromise(getRuntime())
|
|
141
187
|
const arr = arg
|
|
142
|
-
const req: { value: I } = !arg
|
|
188
|
+
const req: { value: I } | undefined = !arg
|
|
143
189
|
? undefined
|
|
144
190
|
: typeof arr === "function"
|
|
145
191
|
? ({
|
|
146
192
|
get value() {
|
|
147
193
|
return (arr as any)()
|
|
148
194
|
}
|
|
149
|
-
}
|
|
150
|
-
: ref(arg)
|
|
195
|
+
})
|
|
196
|
+
: ref(arg) as any
|
|
151
197
|
const queryKey = makeQueryKey(q)
|
|
152
|
-
const
|
|
198
|
+
const projectionHash = (q as { queryKeyProjectionHash?: string }).queryKeyProjectionHash
|
|
153
199
|
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
200
|
+
const defaultOptions = {
|
|
201
|
+
// we do not want to throw errors, because we turn the success and error responses into a Result type
|
|
202
|
+
// 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.
|
|
203
|
+
// 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.
|
|
204
|
+
// TODO: we might want to rethink the strategy of how to handle errors that happen after the initial load.
|
|
205
|
+
// For suspense, the initial load is captured by the suspense boundary.
|
|
206
|
+
// For subsequent loads (or non suspense use) we currently are required to use the QueryResult component to conditionally render error/loading/etc.
|
|
207
|
+
throwOnError: false
|
|
208
|
+
}
|
|
164
209
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Effect.withSpan(`query ${q.id}`, {}, { captureStackTrace: false }),
|
|
174
|
-
meta?.["span"] ? Effect.withParentSpan(meta["span"] as Span) : (_) => _
|
|
175
|
-
),
|
|
176
|
-
{ signal }
|
|
177
|
-
)
|
|
210
|
+
const r = useTanstackQuery<A, CauseException<E>, TData>({
|
|
211
|
+
...defaultOptions,
|
|
212
|
+
...options,
|
|
213
|
+
retry: (retryCount, error) => {
|
|
214
|
+
if (error instanceof CauseException) {
|
|
215
|
+
if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
178
218
|
}
|
|
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
219
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
220
|
+
return retryCount < 5
|
|
221
|
+
},
|
|
222
|
+
queryKey: projectionHash === undefined ? [...queryKey, req] : [...queryKey, req, projectionHash],
|
|
223
|
+
queryFn: ({ meta, signal }) =>
|
|
224
|
+
runPromise(
|
|
225
|
+
q
|
|
226
|
+
.handler(req?.value as I)
|
|
227
|
+
.pipe(
|
|
228
|
+
Effect.tapCauseIf(Cause.hasDies, (cause) => reportRuntimeError(cause)),
|
|
229
|
+
Effect.withSpan(`query ${q.id}`, {}, { captureStackTrace: false }),
|
|
230
|
+
meta?.["span"] ? Effect.withParentSpan(meta["span"] as Span) : (_) => _
|
|
231
|
+
),
|
|
232
|
+
{ signal }
|
|
233
|
+
)
|
|
234
|
+
})
|
|
203
235
|
|
|
204
236
|
const latestSuccess = shallowRef<TData>()
|
|
205
237
|
const result = computed((): AsyncResult.AsyncResult<TData, E> =>
|
|
@@ -227,129 +259,130 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
227
259
|
] as any
|
|
228
260
|
}
|
|
229
261
|
|
|
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
262
|
const useQuery: {
|
|
252
263
|
/**
|
|
253
264
|
* Effect results are passed to the caller, including errors.
|
|
265
|
+
* When `I = void` the input argument may be omitted.
|
|
254
266
|
* @deprecated use client helpers instead (.query())
|
|
255
267
|
*/
|
|
256
|
-
<E, A, Request extends Req, Name extends string>(
|
|
257
|
-
self:
|
|
258
|
-
): {
|
|
259
|
-
// required options, with initialData
|
|
260
|
-
/**
|
|
261
|
-
* Effect results are passed to the caller, including errors.
|
|
262
|
-
*/
|
|
263
|
-
<TData = A>(
|
|
264
|
-
options: CustomDefinedInitialQueryOptions<A, KnownFiberFailure<E>, TData>
|
|
265
|
-
): readonly [
|
|
266
|
-
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
267
|
-
ComputedRef<TData>,
|
|
268
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, KnownFiberFailure<E>>>,
|
|
269
|
-
UseQueryReturnType<any, any>
|
|
270
|
-
]
|
|
271
|
-
<TData = A>(
|
|
272
|
-
options: CustomDefinedPlaceholderQueryOptions<A, E, TData>
|
|
273
|
-
): readonly [
|
|
274
|
-
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
275
|
-
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>>>,
|
|
287
|
-
UseQueryReturnType<any, any>
|
|
288
|
-
]
|
|
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>
|
|
268
|
+
<I, E, A, Request extends Req, Name extends string>(
|
|
269
|
+
self: RequestHandlerWithInput<I, A, E, R, Request, Name>
|
|
296
270
|
): {
|
|
297
|
-
// required options, with initialData
|
|
298
|
-
/**
|
|
299
|
-
* Effect results are passed to the caller, including errors.
|
|
300
|
-
*/
|
|
301
271
|
<TData = A>(
|
|
302
|
-
arg:
|
|
303
|
-
options: CustomDefinedInitialQueryOptions<A,
|
|
272
|
+
arg: I | WatchSource<I>,
|
|
273
|
+
options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
304
274
|
): readonly [
|
|
305
275
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
306
276
|
ComputedRef<TData>,
|
|
307
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
277
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
308
278
|
UseQueryReturnType<any, any>
|
|
309
279
|
]
|
|
310
|
-
// required options, with placeholderData
|
|
311
|
-
/**
|
|
312
|
-
* Effect results are passed to the caller, including errors.
|
|
313
|
-
*/
|
|
314
280
|
<TData = A>(
|
|
315
|
-
arg:
|
|
316
|
-
options: CustomDefinedPlaceholderQueryOptions<A,
|
|
281
|
+
arg: I | WatchSource<I>,
|
|
282
|
+
options: CustomDefinedPlaceholderQueryOptions<A, CauseException<E>, TData>
|
|
317
283
|
): readonly [
|
|
318
284
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
319
285
|
ComputedRef<TData>,
|
|
320
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
286
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
321
287
|
UseQueryReturnType<any, any>
|
|
322
288
|
]
|
|
323
|
-
// optional options, optional A
|
|
324
|
-
/**
|
|
325
|
-
* Effect results are passed to the caller, including errors.
|
|
326
|
-
*/
|
|
327
289
|
<TData = A>(
|
|
328
|
-
arg:
|
|
329
|
-
options?: CustomUndefinedInitialQueryOptions<A,
|
|
290
|
+
arg: I | WatchSource<I>,
|
|
291
|
+
options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
330
292
|
): readonly [
|
|
331
293
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
332
294
|
ComputedRef<TData | undefined>,
|
|
333
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
295
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
334
296
|
UseQueryReturnType<any, any>
|
|
335
297
|
]
|
|
336
298
|
}
|
|
337
|
-
} = (
|
|
299
|
+
} = ((
|
|
338
300
|
self: any
|
|
339
301
|
) => {
|
|
340
302
|
const q = useQuery_(self)
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
Effect.isEffect(self.handler)
|
|
344
|
-
? q(undefined, argOrOptions)
|
|
345
|
-
: q(argOrOptions, options)
|
|
346
|
-
}
|
|
303
|
+
return (arg?: any, options?: any) => q(arg, options)
|
|
304
|
+
}) as any
|
|
347
305
|
return useQuery
|
|
348
306
|
}
|
|
349
307
|
|
|
350
308
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
351
309
|
export interface MakeQuery2<R> extends ReturnType<typeof makeQuery<R>> {}
|
|
352
310
|
|
|
311
|
+
type StreamQueryResult<A, E> = readonly [
|
|
312
|
+
ComputedRef<AsyncResult.AsyncResult<A[], E>>,
|
|
313
|
+
ComputedRef<A[] | undefined>,
|
|
314
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<A[], CauseException<E>>>,
|
|
315
|
+
UseQueryReturnType<any, any>
|
|
316
|
+
]
|
|
317
|
+
|
|
318
|
+
export const makeStreamQuery = <R>(getRuntime: () => Context.Context<R>) => {
|
|
319
|
+
const streamQuery_: {
|
|
320
|
+
<I, E, A, Request extends Req, Name extends string>(
|
|
321
|
+
q: RequestStreamHandlerWithInput<I, A, E, R, Request, Name>
|
|
322
|
+
): (arg: I | WatchSource<I>) => StreamQueryResult<A, E>
|
|
323
|
+
} = (q: any) => (arg?: any) => {
|
|
324
|
+
const context = getRuntime()
|
|
325
|
+
const arr = arg
|
|
326
|
+
const req: { value: any } | undefined = !arg
|
|
327
|
+
? undefined
|
|
328
|
+
: typeof arr === "function"
|
|
329
|
+
? ({
|
|
330
|
+
get value() {
|
|
331
|
+
return arr()
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
: ref(arg)
|
|
335
|
+
const queryKey = makeQueryKey(q)
|
|
336
|
+
|
|
337
|
+
const r = useTanstackQuery<any[], CauseException<any>, any[]>(
|
|
338
|
+
{
|
|
339
|
+
throwOnError: false,
|
|
340
|
+
retry: (retryCount: number, error: unknown) => {
|
|
341
|
+
if (error instanceof CauseException) {
|
|
342
|
+
if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
|
|
343
|
+
return false
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return retryCount < 5
|
|
347
|
+
},
|
|
348
|
+
queryKey: [...queryKey, req],
|
|
349
|
+
queryFn: streamedQuery({
|
|
350
|
+
streamFn: () => {
|
|
351
|
+
const stream = q.handler(req?.value)
|
|
352
|
+
return streamToAsyncIterableWithCauseException(stream, context, q.id)
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
const latestSuccess = shallowRef<any[]>()
|
|
359
|
+
const result = computed((): AsyncResult.AsyncResult<any[], any> =>
|
|
360
|
+
swrToQuery({
|
|
361
|
+
error: r.error.value ?? undefined,
|
|
362
|
+
data: r.data.value === undefined ? latestSuccess.value : r.data.value,
|
|
363
|
+
isValidating: r.isFetching.value
|
|
364
|
+
})
|
|
365
|
+
)
|
|
366
|
+
watch(result, (value) => latestSuccess.value = Option.getOrUndefined(AsyncResult.value(value)), { immediate: true })
|
|
367
|
+
|
|
368
|
+
return [
|
|
369
|
+
result,
|
|
370
|
+
computed(() => latestSuccess.value),
|
|
371
|
+
(options?: RefetchOptions) =>
|
|
372
|
+
Effect.currentSpan.pipe(
|
|
373
|
+
Effect.orElseSucceed(() => null),
|
|
374
|
+
Effect.flatMap((span) => Effect.promise(() => r.refetch({ ...options, updateMeta: { span } })))
|
|
375
|
+
),
|
|
376
|
+
r
|
|
377
|
+
] as any
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return streamQuery_
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
384
|
+
export interface MakeStreamQuery2<R> extends ReturnType<typeof makeStreamQuery<R>> {}
|
|
385
|
+
|
|
353
386
|
function orPrevious<E, A>(result: AsyncResult.AsyncResult<A, E>) {
|
|
354
387
|
return AsyncResult.isFailure(result) && Option.isSome(result.previousSuccess)
|
|
355
388
|
? AsyncResult.success(result.previousSuccess.value, { waiting: result.waiting })
|
|
@@ -400,20 +433,13 @@ export const useUpdateQuery = () => {
|
|
|
400
433
|
const queryClient = useQueryClient()
|
|
401
434
|
|
|
402
435
|
const f: {
|
|
403
|
-
<A>(
|
|
404
|
-
query: RequestHandler<A, any, any, any, any>,
|
|
405
|
-
updater: (data: NoInfer<A>) => NoInfer<A>
|
|
406
|
-
): void
|
|
407
436
|
<I, A>(
|
|
408
437
|
query: RequestHandlerWithInput<I, A, any, any, any, any>,
|
|
409
438
|
input: I,
|
|
410
439
|
updater: (data: NoInfer<A>) => NoInfer<A>
|
|
411
440
|
): void
|
|
412
|
-
} = (query: any,
|
|
413
|
-
const
|
|
414
|
-
const key = updaterMaybe !== undefined
|
|
415
|
-
? [...makeQueryKey(query), updateOrInput]
|
|
416
|
-
: makeQueryKey(query)
|
|
441
|
+
} = (query: any, input: any, updater: any) => {
|
|
442
|
+
const key = [...makeQueryKey(query), input]
|
|
417
443
|
const data = queryClient.getQueryData(key)
|
|
418
444
|
if (data) {
|
|
419
445
|
queryClient.setQueryData(key, updater)
|
package/src/runtime.ts
CHANGED
|
@@ -1,31 +1,52 @@
|
|
|
1
|
-
import { ManagedRuntime } from "effect"
|
|
1
|
+
import { Exit, flow, ManagedRuntime } from "effect"
|
|
2
2
|
import { Effect, Layer, Logger } from "effect-app"
|
|
3
|
+
import { CauseException } from "effect-app/client/errors"
|
|
4
|
+
import { type Context } from "effect-app/Context"
|
|
3
5
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
},
|
|
6
|
+
export const makeAppRuntime = Effect.fnUntraced(function*<A, E>(layer: Layer.Layer<A, E>) {
|
|
7
|
+
const l = layer.pipe(
|
|
8
|
+
Layer.provide(Logger.layer([Logger.consolePretty()]))
|
|
9
|
+
) as Layer.Layer<A>
|
|
10
|
+
const mrt = ManagedRuntime.make(l)
|
|
11
|
+
yield* mrt.contextEffect
|
|
12
|
+
return Object.assign(mrt, {
|
|
13
|
+
[Symbol.dispose]() {
|
|
14
|
+
return Effect.runSync(mrt.disposeEffect)
|
|
15
|
+
},
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
17
|
+
[Symbol.asyncDispose]() {
|
|
18
|
+
return mrt.dispose()
|
|
19
|
+
}
|
|
20
|
+
}) // as we initialise here, there is no more error left.
|
|
21
|
+
})
|
|
22
22
|
|
|
23
|
-
export function initializeSync<A, E>(layer: Layer.Layer<A, E
|
|
23
|
+
export function initializeSync<A, E>(layer: Layer.Layer<A, E>) {
|
|
24
24
|
const runtime = Effect.runSync(makeAppRuntime(layer))
|
|
25
25
|
return runtime
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function initializeAsync<A, E>(layer: Layer.Layer<A, E
|
|
28
|
+
export function initializeAsync<A, E>(layer: Layer.Layer<A, E>) {
|
|
29
29
|
return Effect
|
|
30
30
|
.runPromise(makeAppRuntime(layer))
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
// we wrap into CauseException because we want to keep the full cause of the failure.
|
|
34
|
+
export const makeRunPromise = <T>(services: Context<T>) =>
|
|
35
|
+
flow(Effect.runPromiseExitWith(services), (_) =>
|
|
36
|
+
_.then(
|
|
37
|
+
Exit.match({
|
|
38
|
+
onFailure: (cause) => Promise.reject(new CauseException(cause, "runPromise")),
|
|
39
|
+
onSuccess: (value) => Promise.resolve(value)
|
|
40
|
+
})
|
|
41
|
+
))
|
|
42
|
+
|
|
43
|
+
export const makeRunSync = <T>(services: Context<T>) =>
|
|
44
|
+
flow(
|
|
45
|
+
Effect.runSyncExitWith(services),
|
|
46
|
+
Exit.match({
|
|
47
|
+
onFailure: (cause) => {
|
|
48
|
+
throw new CauseException(cause, "runSync")
|
|
49
|
+
},
|
|
50
|
+
onSuccess: (value) => value
|
|
51
|
+
})
|
|
52
|
+
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Effect, Option
|
|
2
|
-
import {
|
|
1
|
+
import { Context, Effect, Option } from "effect-app"
|
|
2
|
+
import { accessEffectFn } from "effect-app/Context"
|
|
3
3
|
|
|
4
4
|
export type ToastId = string | number
|
|
5
5
|
export type ToastOpts = { id?: ToastId; timeout?: number }
|
|
@@ -13,7 +13,7 @@ export type UseToast = () => {
|
|
|
13
13
|
dismiss: (this: void, id: ToastId) => void
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export class CurrentToastId extends
|
|
16
|
+
export class CurrentToastId extends Context.Opaque<CurrentToastId, { toastId: ToastId }>()("CurrentToastId") {}
|
|
17
17
|
|
|
18
18
|
/** fallback to CurrentToastId when available unless id is explicitly set to a value or null */
|
|
19
19
|
export const wrap = (toast: ReturnType<UseToast>) => {
|
|
@@ -41,26 +41,12 @@ export const wrap = (toast: ReturnType<UseToast>) => {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
extends proxify(ServiceMap.Opaque<Toast, ReturnType<typeof wrap>>()("Toast"))<Toast, ReturnType<typeof wrap>>()
|
|
46
|
-
{
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// const a = Layer.effect(Toast, Effect.sync(() => Toast.of(null as any)))
|
|
50
|
-
|
|
51
|
-
// const A = Toast.of({
|
|
52
|
-
// error: () => Effect.succeed(null as any),
|
|
53
|
-
// info: () => Effect.succeed(null as any),
|
|
54
|
-
// success: () => Effect.succeed(null as any),
|
|
55
|
-
// warning: () => Effect.succeed(null as any),
|
|
56
|
-
// dismiss: () => Effect.succeed(null as any)
|
|
57
|
-
// })
|
|
44
|
+
type ToastShape = ReturnType<typeof wrap>
|
|
58
45
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// })
|
|
46
|
+
export class Toast extends Context.Opaque<Toast, ToastShape>()("Toast") {
|
|
47
|
+
static readonly error = accessEffectFn(this, "error")
|
|
48
|
+
static readonly info = accessEffectFn(this, "info")
|
|
49
|
+
static readonly success = accessEffectFn(this, "success")
|
|
50
|
+
static readonly warning = accessEffectFn(this, "warning")
|
|
51
|
+
static readonly dismiss = accessEffectFn(this, "dismiss")
|
|
52
|
+
}
|