@effect-app/vue 4.0.0-beta.19 → 4.0.0-beta.190
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 +1383 -0
- package/dist/commander.d.ts +620 -0
- package/dist/commander.d.ts.map +1 -0
- package/dist/commander.js +1056 -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 +13 -4
- 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 -8
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +34 -7
- package/dist/makeClient.d.ts +191 -292
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +217 -369
- 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 +56 -25
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +132 -33
- package/dist/query.d.ts +24 -16
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +119 -37
- package/dist/routeParams.d.ts +1 -1
- package/dist/runtime.d.ts +5 -2
- 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/eslint.config.mjs +2 -2
- package/examples/streamMutation.ts +70 -0
- package/package.json +48 -48
- package/src/commander.ts +3378 -0
- package/src/{experimental/confirm.ts → confirm.ts} +10 -14
- package/src/errorReporter.ts +62 -74
- package/src/form.ts +55 -16
- package/src/intl.ts +12 -0
- package/src/lib.ts +46 -13
- package/src/makeClient.ts +623 -1043
- package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +6 -4
- package/src/mutate.ts +273 -72
- package/src/query.ts +181 -68
- package/src/runtime.ts +39 -18
- package/src/{experimental/toast.ts → toast.ts} +11 -25
- package/src/{experimental/withToast.ts → withToast.ts} +28 -10
- package/test/Mutation.test.ts +105 -11
- 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 +3289 -114
- package/test/dist/stubs.d.ts.map +1 -1
- package/test/dist/stubs.js +152 -25
- 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 +286 -38
- package/test/streamFinal.test.ts +63 -0
- package/test/streamFn.test.ts +436 -0
- package/test/stubs.ts +192 -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 -557
- 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/src/experimental/commander.ts +0 -1835
- 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 {
|
|
7
|
-
import { type
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
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 { RequestHandler, RequestHandlerWithInput, RequestStreamHandler, 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"
|
|
10
15
|
import { type Span } from "effect/Tracer"
|
|
11
16
|
import { isHttpClientError } from "effect/unstable/http/HttpClientError"
|
|
12
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,15 +80,65 @@ 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
|
+
)
|
|
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)
|
|
102
|
+
}
|
|
103
|
+
|
|
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
|
+
}
|
|
82
138
|
}
|
|
83
139
|
}
|
|
84
140
|
|
|
85
|
-
export const makeQuery = <R>(getRuntime: () =>
|
|
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
144
|
q:
|
|
@@ -95,8 +151,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
95
151
|
): readonly [
|
|
96
152
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
97
153
|
ComputedRef<TData | undefined>,
|
|
98
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
99
|
-
UseQueryDefinedReturnType<TData,
|
|
154
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
|
|
155
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
100
156
|
]
|
|
101
157
|
|
|
102
158
|
<TData = A>(
|
|
@@ -105,8 +161,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
105
161
|
): readonly [
|
|
106
162
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
107
163
|
ComputedRef<TData>,
|
|
108
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
109
|
-
UseQueryDefinedReturnType<TData,
|
|
164
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
|
|
165
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
110
166
|
]
|
|
111
167
|
|
|
112
168
|
<TData = A>(
|
|
@@ -115,8 +171,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
115
171
|
): readonly [
|
|
116
172
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
117
173
|
ComputedRef<TData>,
|
|
118
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
119
|
-
UseQueryDefinedReturnType<TData,
|
|
174
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
|
|
175
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
120
176
|
]
|
|
121
177
|
}
|
|
122
178
|
} = <I, A, E, Request extends Req, Name extends string>(
|
|
@@ -130,25 +186,21 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
130
186
|
options?: any
|
|
131
187
|
// TODO
|
|
132
188
|
) => {
|
|
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
|
-
))
|
|
189
|
+
// we wrap into CauseException because we want to keep the full cause of the failure.
|
|
190
|
+
const runPromise = makeRunPromise(getRuntime())
|
|
141
191
|
const arr = arg
|
|
142
192
|
const req: { value: I } = !arg
|
|
143
|
-
? undefined
|
|
193
|
+
? undefined as any
|
|
144
194
|
: typeof arr === "function"
|
|
145
195
|
? ({
|
|
146
196
|
get value() {
|
|
147
197
|
return (arr as any)()
|
|
148
198
|
}
|
|
149
|
-
}
|
|
199
|
+
})
|
|
150
200
|
: ref(arg)
|
|
151
201
|
const queryKey = makeQueryKey(q)
|
|
202
|
+
const projectionHash = (q as { queryKeyProjectionHash?: string }).queryKeyProjectionHash
|
|
203
|
+
const baseQueryKey = projectionHash === undefined ? queryKey : [...queryKey, projectionHash]
|
|
152
204
|
const handler = q.handler
|
|
153
205
|
|
|
154
206
|
const defaultOptions = {
|
|
@@ -161,21 +213,21 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
161
213
|
throwOnError: false
|
|
162
214
|
}
|
|
163
215
|
|
|
164
|
-
const r = useTanstackQuery<A,
|
|
216
|
+
const r = useTanstackQuery<A, CauseException<E>, TData>(
|
|
165
217
|
Effect.isEffect(handler)
|
|
166
218
|
? {
|
|
167
219
|
...defaultOptions,
|
|
168
220
|
...options,
|
|
169
221
|
retry: (retryCount, error) => {
|
|
170
|
-
if (error instanceof
|
|
171
|
-
if (!isHttpClientError(error.
|
|
222
|
+
if (error instanceof CauseException) {
|
|
223
|
+
if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
|
|
172
224
|
return false
|
|
173
225
|
}
|
|
174
226
|
}
|
|
175
227
|
|
|
176
228
|
return retryCount < 5
|
|
177
229
|
},
|
|
178
|
-
queryKey,
|
|
230
|
+
queryKey: baseQueryKey,
|
|
179
231
|
queryFn: ({ meta, signal }) =>
|
|
180
232
|
runPromise(
|
|
181
233
|
handler
|
|
@@ -191,15 +243,15 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
191
243
|
...defaultOptions,
|
|
192
244
|
...options,
|
|
193
245
|
retry: (retryCount, error) => {
|
|
194
|
-
if (error instanceof
|
|
195
|
-
if (!isHttpClientError(error.
|
|
246
|
+
if (error instanceof CauseException) {
|
|
247
|
+
if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
|
|
196
248
|
return false
|
|
197
249
|
}
|
|
198
250
|
}
|
|
199
251
|
|
|
200
252
|
return retryCount < 5
|
|
201
253
|
},
|
|
202
|
-
queryKey: [...queryKey, req],
|
|
254
|
+
queryKey: projectionHash === undefined ? [...queryKey, req] : [...queryKey, req, projectionHash],
|
|
203
255
|
queryFn: ({ meta, signal }) =>
|
|
204
256
|
runPromise(
|
|
205
257
|
handler(req.value)
|
|
@@ -239,27 +291,6 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
239
291
|
] as any
|
|
240
292
|
}
|
|
241
293
|
|
|
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
294
|
const useQuery: {
|
|
264
295
|
/**
|
|
265
296
|
* Effect results are passed to the caller, including errors.
|
|
@@ -273,11 +304,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
273
304
|
* Effect results are passed to the caller, including errors.
|
|
274
305
|
*/
|
|
275
306
|
<TData = A>(
|
|
276
|
-
options: CustomDefinedInitialQueryOptions<A,
|
|
307
|
+
options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
277
308
|
): readonly [
|
|
278
309
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
279
310
|
ComputedRef<TData>,
|
|
280
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
311
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
281
312
|
UseQueryReturnType<any, any>
|
|
282
313
|
]
|
|
283
314
|
<TData = A>(
|
|
@@ -285,17 +316,17 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
285
316
|
): readonly [
|
|
286
317
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
287
318
|
ComputedRef<TData>,
|
|
288
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
289
|
-
UseQueryDefinedReturnType<TData,
|
|
319
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
|
|
320
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
290
321
|
]
|
|
291
322
|
// optional options, optional A
|
|
292
323
|
/**
|
|
293
324
|
* Effect results are passed to the caller, including errors.
|
|
294
325
|
*/
|
|
295
|
-
<TData = A>(options?: CustomUndefinedInitialQueryOptions<A,
|
|
326
|
+
<TData = A>(options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>): readonly [
|
|
296
327
|
ComputedRef<AsyncResult.AsyncResult<A, E>>,
|
|
297
328
|
ComputedRef<A | undefined>,
|
|
298
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
329
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
299
330
|
UseQueryReturnType<any, any>
|
|
300
331
|
]
|
|
301
332
|
}
|
|
@@ -312,11 +343,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
312
343
|
*/
|
|
313
344
|
<TData = A>(
|
|
314
345
|
arg: Arg | WatchSource<Arg>,
|
|
315
|
-
options: CustomDefinedInitialQueryOptions<A,
|
|
346
|
+
options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
316
347
|
): readonly [
|
|
317
348
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
318
349
|
ComputedRef<TData>,
|
|
319
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
350
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
320
351
|
UseQueryReturnType<any, any>
|
|
321
352
|
]
|
|
322
353
|
// required options, with placeholderData
|
|
@@ -325,11 +356,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
325
356
|
*/
|
|
326
357
|
<TData = A>(
|
|
327
358
|
arg: Arg | WatchSource<Arg>,
|
|
328
|
-
options: CustomDefinedPlaceholderQueryOptions<A,
|
|
359
|
+
options: CustomDefinedPlaceholderQueryOptions<A, CauseException<E>, TData>
|
|
329
360
|
): readonly [
|
|
330
361
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
331
362
|
ComputedRef<TData>,
|
|
332
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
363
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
333
364
|
UseQueryReturnType<any, any>
|
|
334
365
|
]
|
|
335
366
|
// optional options, optional A
|
|
@@ -338,11 +369,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
338
369
|
*/
|
|
339
370
|
<TData = A>(
|
|
340
371
|
arg: Arg | WatchSource<Arg>,
|
|
341
|
-
options?: CustomUndefinedInitialQueryOptions<A,
|
|
372
|
+
options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
342
373
|
): readonly [
|
|
343
374
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
344
375
|
ComputedRef<TData | undefined>,
|
|
345
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
376
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
346
377
|
UseQueryReturnType<any, any>
|
|
347
378
|
]
|
|
348
379
|
}
|
|
@@ -362,6 +393,88 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
362
393
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
363
394
|
export interface MakeQuery2<R> extends ReturnType<typeof makeQuery<R>> {}
|
|
364
395
|
|
|
396
|
+
type StreamQueryResult<A, E> = readonly [
|
|
397
|
+
ComputedRef<AsyncResult.AsyncResult<A[], E>>,
|
|
398
|
+
ComputedRef<A[] | undefined>,
|
|
399
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<A[], CauseException<E>>, never, never>,
|
|
400
|
+
UseQueryReturnType<any, any>
|
|
401
|
+
]
|
|
402
|
+
|
|
403
|
+
export const makeStreamQuery = <R>(getRuntime: () => Context.Context<R>) => {
|
|
404
|
+
const streamQuery_: {
|
|
405
|
+
<E, A, Request extends Req, Name extends string>(
|
|
406
|
+
q: RequestStreamHandler<A, E, R, Request, Name>
|
|
407
|
+
): () => StreamQueryResult<A, E>
|
|
408
|
+
<Arg, E, A, Request extends Req, Name extends string>(
|
|
409
|
+
q: RequestStreamHandlerWithInput<Arg, A, E, R, Request, Name>
|
|
410
|
+
): (arg: Arg | WatchSource<Arg>) => StreamQueryResult<A, E>
|
|
411
|
+
} = (q: any) => (arg?: any) => {
|
|
412
|
+
const context = getRuntime()
|
|
413
|
+
const arr = arg
|
|
414
|
+
const req: { value: any } = !arg
|
|
415
|
+
? undefined as any
|
|
416
|
+
: typeof arr === "function"
|
|
417
|
+
? ({
|
|
418
|
+
get value() {
|
|
419
|
+
return arr()
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
: ref(arg)
|
|
423
|
+
const queryKey = makeQueryKey(q)
|
|
424
|
+
const handler = q.handler
|
|
425
|
+
const isWithInput = typeof handler === "function"
|
|
426
|
+
|
|
427
|
+
const r = useTanstackQuery<any[], CauseException<any>, any[]>(
|
|
428
|
+
{
|
|
429
|
+
throwOnError: false,
|
|
430
|
+
retry: (retryCount: number, error: unknown) => {
|
|
431
|
+
if (error instanceof CauseException) {
|
|
432
|
+
if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
|
|
433
|
+
return false
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return retryCount < 5
|
|
437
|
+
},
|
|
438
|
+
queryKey: isWithInput ? [...queryKey, req] : queryKey,
|
|
439
|
+
queryFn: streamedQuery({
|
|
440
|
+
streamFn: () => {
|
|
441
|
+
const stream = isWithInput
|
|
442
|
+
? handler(req.value)
|
|
443
|
+
: handler
|
|
444
|
+
return streamToAsyncIterableWithCauseException(stream, context, q.id)
|
|
445
|
+
}
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
const latestSuccess = shallowRef<any[]>()
|
|
451
|
+
const result = computed((): AsyncResult.AsyncResult<any[], any> =>
|
|
452
|
+
swrToQuery({
|
|
453
|
+
error: r.error.value ?? undefined,
|
|
454
|
+
data: r.data.value === undefined ? latestSuccess.value : r.data.value,
|
|
455
|
+
isValidating: r.isFetching.value
|
|
456
|
+
})
|
|
457
|
+
)
|
|
458
|
+
watch(result, (value) => latestSuccess.value = Option.getOrUndefined(AsyncResult.value(value)), { immediate: true })
|
|
459
|
+
|
|
460
|
+
return [
|
|
461
|
+
result,
|
|
462
|
+
computed(() => latestSuccess.value),
|
|
463
|
+
(options?: RefetchOptions) =>
|
|
464
|
+
Effect.currentSpan.pipe(
|
|
465
|
+
Effect.orElseSucceed(() => null),
|
|
466
|
+
Effect.flatMap((span) => Effect.promise(() => r.refetch({ ...options, updateMeta: { span } })))
|
|
467
|
+
),
|
|
468
|
+
r
|
|
469
|
+
] as any
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return streamQuery_
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
476
|
+
export interface MakeStreamQuery2<R> extends ReturnType<typeof makeStreamQuery<R>> {}
|
|
477
|
+
|
|
365
478
|
function orPrevious<E, A>(result: AsyncResult.AsyncResult<A, E>) {
|
|
366
479
|
return AsyncResult.isFailure(result) && Option.isSome(result.previousSuccess)
|
|
367
480
|
? AsyncResult.success(result.previousSuccess.value, { waiting: result.waiting })
|
package/src/runtime.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
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, never>
|
|
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
23
|
export function initializeSync<A, E>(layer: Layer.Layer<A, E, never>) {
|
|
24
24
|
const runtime = Effect.runSync(makeAppRuntime(layer))
|
|
@@ -29,3 +29,24 @@ export function initializeAsync<A, E>(layer: Layer.Layer<A, E, never>) {
|
|
|
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
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Cause, Effect, Layer, type Option
|
|
1
|
+
import { Cause, Context, Effect, Fiber, Layer, type Option } from "effect-app"
|
|
2
2
|
import { wrapEffect } from "effect-app/utils"
|
|
3
|
-
import { CurrentToastId, Toast } from "./toast.js"
|
|
3
|
+
import { CurrentToastId, Toast, type ToastId } from "./toast.js"
|
|
4
4
|
|
|
5
5
|
export interface ToastOptions<A, E, Args extends ReadonlyArray<unknown>, WaiR, SucR, ErrR> {
|
|
6
6
|
stableToastId?: undefined | string | ((...args: Args) => string | undefined)
|
|
7
7
|
timeout?: number
|
|
8
|
+
showSpanInfo?: false
|
|
8
9
|
onWaiting:
|
|
9
10
|
| string
|
|
10
11
|
| ((...args: Args) => string | null)
|
|
@@ -33,10 +34,10 @@ export interface ToastOptions<A, E, Args extends ReadonlyArray<unknown>, WaiR, S
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
// @effect-diagnostics-next-line missingEffectServiceDependency:off
|
|
36
|
-
export class WithToast extends
|
|
37
|
+
export class WithToast extends Context.Service<WithToast>()("WithToast", {
|
|
37
38
|
make: Effect.gen(function*() {
|
|
38
39
|
const toast = yield* Toast
|
|
39
|
-
return <A, E, Args extends
|
|
40
|
+
return <A, E, Args extends readonly unknown[], R, WaiR = never, SucR = never, ErrR = never>(
|
|
40
41
|
options: ToastOptions<A, E, Args, WaiR, SucR, ErrR>
|
|
41
42
|
) =>
|
|
42
43
|
Effect.fnUntraced(function*(self: Effect.Effect<A, E, R>, ...args: Args) {
|
|
@@ -47,12 +48,20 @@ export class WithToast extends ServiceMap.Service<WithToast>()("WithToast", {
|
|
|
47
48
|
: options.stableToastId
|
|
48
49
|
|
|
49
50
|
const t = yield* wrapEffect(options.onWaiting)(...args)
|
|
50
|
-
const toastId = t === null
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const toastId: ToastId | undefined = t === null
|
|
52
|
+
? stableToastId
|
|
53
|
+
: stableToastId ?? `wait-${Math.random().toString(36).slice(2)}`
|
|
54
|
+
|
|
55
|
+
const waitingFiber = t === null ? undefined : yield* Effect.forkChild(
|
|
56
|
+
Effect.sleep("1 seconds").pipe(
|
|
57
|
+
Effect.andThen(toast.info(t, { id: toastId!, timeout: Infinity }))
|
|
58
|
+
)
|
|
53
59
|
)
|
|
60
|
+
const interruptWaiting = waitingFiber ? Fiber.interrupt(waitingFiber) : Effect.void
|
|
61
|
+
|
|
54
62
|
return yield* self.pipe(
|
|
55
63
|
Effect.tap(Effect.fnUntraced(function*(a) {
|
|
64
|
+
yield* interruptWaiting
|
|
56
65
|
const t = yield* wrapEffect(options.onSuccess)(a, ...args)
|
|
57
66
|
if (t === null) {
|
|
58
67
|
return
|
|
@@ -63,6 +72,7 @@ export class WithToast extends ServiceMap.Service<WithToast>()("WithToast", {
|
|
|
63
72
|
)
|
|
64
73
|
})),
|
|
65
74
|
Effect.tapCause(Effect.fnUntraced(function*(cause) {
|
|
75
|
+
yield* interruptWaiting
|
|
66
76
|
yield* Effect.logDebug(
|
|
67
77
|
"WithToast - caught error cause: " + Cause.squash(cause),
|
|
68
78
|
Cause.hasInterruptsOnly(cause),
|
|
@@ -74,15 +84,23 @@ export class WithToast extends ServiceMap.Service<WithToast>()("WithToast", {
|
|
|
74
84
|
return
|
|
75
85
|
}
|
|
76
86
|
|
|
87
|
+
const spanInfo = options.showSpanInfo !== false
|
|
88
|
+
? yield* Effect.currentSpan.pipe(
|
|
89
|
+
Effect.map((span) => `\nTrace: ${span.traceId}\nSpan: ${span.spanId}`),
|
|
90
|
+
Effect.orElseSucceed(() => "")
|
|
91
|
+
)
|
|
92
|
+
: ""
|
|
93
|
+
|
|
77
94
|
const t = yield* wrapEffect(options.onFailure)(Cause.findErrorOption(cause), ...args)
|
|
78
95
|
const opts = { timeout: baseTimeout * 2 }
|
|
79
96
|
|
|
80
97
|
if (typeof t === "object") {
|
|
98
|
+
const message = t.message + spanInfo
|
|
81
99
|
return t.level === "warn"
|
|
82
|
-
? yield* toast.warning(
|
|
83
|
-
: yield* toast.error(
|
|
100
|
+
? yield* toast.warning(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
|
|
101
|
+
: yield* toast.error(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
|
|
84
102
|
}
|
|
85
|
-
yield* toast.error(t, toastId !== undefined ? { ...opts, id: toastId } : opts)
|
|
103
|
+
yield* toast.error(t + spanInfo, toastId !== undefined ? { ...opts, id: toastId } : opts)
|
|
86
104
|
}, Effect.uninterruptible)),
|
|
87
105
|
toastId !== undefined ? Effect.provideService(CurrentToastId, CurrentToastId.of({ toastId })) : (_) => _
|
|
88
106
|
)
|