@effect-app/vue 4.0.0-beta.18 → 4.0.0-beta.180
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 +1224 -0
- package/dist/commander.d.ts +370 -0
- package/dist/commander.d.ts.map +1 -0
- package/dist/commander.js +591 -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 +148 -290
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +205 -361
- 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 +57 -25
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +160 -33
- package/dist/query.d.ts +11 -15
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +19 -27
- 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 +49 -0
- package/eslint.config.mjs +2 -2
- package/examples/streamMutation.ts +83 -0
- package/package.json +48 -48
- package/src/{experimental/commander.ts → commander.ts} +930 -255
- 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 +570 -1038
- package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +3 -3
- package/src/mutate.ts +306 -72
- package/src/query.ts +39 -50
- package/src/runtime.ts +39 -18
- package/src/{experimental/toast.ts → toast.ts} +11 -25
- package/src/{experimental/withToast.ts → withToast.ts} +15 -6
- package/test/Mutation.test.ts +130 -10
- 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/stubs.d.ts +3144 -117
- package/test/dist/stubs.d.ts.map +1 -1
- package/test/dist/stubs.js +132 -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 +241 -38
- package/test/streamFinal.test.ts +110 -0
- package/test/stubs.ts +172 -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/intl.ts +0 -9
package/src/query.ts
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
4
4
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
5
5
|
import { type DefaultError, type Enabled, type InitialDataFunction, type NonUndefinedGuard, type PlaceholderDataFunction, type QueryKey, type QueryObserverOptions, type QueryObserverResult, type RefetchOptions, useQuery as useTanstackQuery, useQueryClient, type UseQueryDefinedReturnType, type UseQueryReturnType } from "@tanstack/vue-query"
|
|
6
|
-
import { Array, Cause,
|
|
7
|
-
import { type Req } from "effect-app/client"
|
|
6
|
+
import { Array, Cause, type Context, Effect, Option, S } from "effect-app"
|
|
7
|
+
import { makeQueryKey, type Req } from "effect-app/client"
|
|
8
8
|
import type { RequestHandler, RequestHandlerWithInput } from "effect-app/client/clientFor"
|
|
9
|
-
import { ServiceUnavailableError } from "effect-app/client/errors"
|
|
9
|
+
import { CauseException, ServiceUnavailableError } from "effect-app/client/errors"
|
|
10
10
|
import { type Span } from "effect/Tracer"
|
|
11
11
|
import { isHttpClientError } from "effect/unstable/http/HttpClientError"
|
|
12
12
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
13
13
|
import { computed, type ComputedRef, type MaybeRefOrGetter, ref, shallowRef, watch, type WatchSource } from "vue"
|
|
14
|
-
import {
|
|
14
|
+
import { reportRuntimeError } from "./lib.js"
|
|
15
|
+
import { makeRunPromise } from "./runtime.js"
|
|
15
16
|
|
|
16
17
|
// 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
18
|
// but because how they are dealing with some extends clause, we loose all properties except initialData
|
|
@@ -74,15 +75,7 @@ export interface CustomDefinedPlaceholderQueryOptions<
|
|
|
74
75
|
| PlaceholderDataFunction<NonFunctionGuard<TQueryData>, TError, NonFunctionGuard<TQueryData>, TQueryKey>
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
export
|
|
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)
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
78
|
+
export const makeQuery = <R>(getRuntime: () => Context.Context<R>) => {
|
|
86
79
|
const useQuery_: {
|
|
87
80
|
<I, A, E, Request extends Req, Name extends string>(
|
|
88
81
|
q:
|
|
@@ -95,8 +88,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
95
88
|
): readonly [
|
|
96
89
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
97
90
|
ComputedRef<TData | undefined>,
|
|
98
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
99
|
-
UseQueryDefinedReturnType<TData,
|
|
91
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
|
|
92
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
100
93
|
]
|
|
101
94
|
|
|
102
95
|
<TData = A>(
|
|
@@ -105,8 +98,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
105
98
|
): readonly [
|
|
106
99
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
107
100
|
ComputedRef<TData>,
|
|
108
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
109
|
-
UseQueryDefinedReturnType<TData,
|
|
101
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
|
|
102
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
110
103
|
]
|
|
111
104
|
|
|
112
105
|
<TData = A>(
|
|
@@ -115,8 +108,8 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
115
108
|
): readonly [
|
|
116
109
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
117
110
|
ComputedRef<TData>,
|
|
118
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
119
|
-
UseQueryDefinedReturnType<TData,
|
|
111
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
|
|
112
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
120
113
|
]
|
|
121
114
|
}
|
|
122
115
|
} = <I, A, E, Request extends Req, Name extends string>(
|
|
@@ -130,25 +123,21 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
130
123
|
options?: any
|
|
131
124
|
// TODO
|
|
132
125
|
) => {
|
|
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
|
-
))
|
|
126
|
+
// we wrap into CauseException because we want to keep the full cause of the failure.
|
|
127
|
+
const runPromise = makeRunPromise(getRuntime())
|
|
141
128
|
const arr = arg
|
|
142
129
|
const req: { value: I } = !arg
|
|
143
|
-
? undefined
|
|
130
|
+
? undefined as any
|
|
144
131
|
: typeof arr === "function"
|
|
145
132
|
? ({
|
|
146
133
|
get value() {
|
|
147
134
|
return (arr as any)()
|
|
148
135
|
}
|
|
149
|
-
}
|
|
136
|
+
})
|
|
150
137
|
: ref(arg)
|
|
151
138
|
const queryKey = makeQueryKey(q)
|
|
139
|
+
const projectionHash = (q as { queryKeyProjectionHash?: string }).queryKeyProjectionHash
|
|
140
|
+
const baseQueryKey = projectionHash === undefined ? queryKey : [...queryKey, projectionHash]
|
|
152
141
|
const handler = q.handler
|
|
153
142
|
|
|
154
143
|
const defaultOptions = {
|
|
@@ -161,21 +150,21 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
161
150
|
throwOnError: false
|
|
162
151
|
}
|
|
163
152
|
|
|
164
|
-
const r = useTanstackQuery<A,
|
|
153
|
+
const r = useTanstackQuery<A, CauseException<E>, TData>(
|
|
165
154
|
Effect.isEffect(handler)
|
|
166
155
|
? {
|
|
167
156
|
...defaultOptions,
|
|
168
157
|
...options,
|
|
169
158
|
retry: (retryCount, error) => {
|
|
170
|
-
if (error instanceof
|
|
171
|
-
if (!isHttpClientError(error.
|
|
159
|
+
if (error instanceof CauseException) {
|
|
160
|
+
if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
|
|
172
161
|
return false
|
|
173
162
|
}
|
|
174
163
|
}
|
|
175
164
|
|
|
176
165
|
return retryCount < 5
|
|
177
166
|
},
|
|
178
|
-
queryKey,
|
|
167
|
+
queryKey: baseQueryKey,
|
|
179
168
|
queryFn: ({ meta, signal }) =>
|
|
180
169
|
runPromise(
|
|
181
170
|
handler
|
|
@@ -191,15 +180,15 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
191
180
|
...defaultOptions,
|
|
192
181
|
...options,
|
|
193
182
|
retry: (retryCount, error) => {
|
|
194
|
-
if (error instanceof
|
|
195
|
-
if (!isHttpClientError(error.
|
|
183
|
+
if (error instanceof CauseException) {
|
|
184
|
+
if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
|
|
196
185
|
return false
|
|
197
186
|
}
|
|
198
187
|
}
|
|
199
188
|
|
|
200
189
|
return retryCount < 5
|
|
201
190
|
},
|
|
202
|
-
queryKey: [...queryKey, req],
|
|
191
|
+
queryKey: projectionHash === undefined ? [...queryKey, req] : [...queryKey, req, projectionHash],
|
|
203
192
|
queryFn: ({ meta, signal }) =>
|
|
204
193
|
runPromise(
|
|
205
194
|
handler(req.value)
|
|
@@ -240,13 +229,13 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
240
229
|
}
|
|
241
230
|
|
|
242
231
|
function swrToQuery<E, A>(r: {
|
|
243
|
-
error:
|
|
232
|
+
error: CauseException<E> | undefined
|
|
244
233
|
data: A | undefined
|
|
245
234
|
isValidating: boolean
|
|
246
235
|
}): AsyncResult.AsyncResult<A, E> {
|
|
247
236
|
if (r.error !== undefined) {
|
|
248
237
|
return AsyncResult.failureWithPrevious(
|
|
249
|
-
r.error.
|
|
238
|
+
r.error.originalCause,
|
|
250
239
|
{
|
|
251
240
|
previous: r.data === undefined ? Option.none() : Option.some(AsyncResult.success(r.data)),
|
|
252
241
|
waiting: r.isValidating
|
|
@@ -273,11 +262,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
273
262
|
* Effect results are passed to the caller, including errors.
|
|
274
263
|
*/
|
|
275
264
|
<TData = A>(
|
|
276
|
-
options: CustomDefinedInitialQueryOptions<A,
|
|
265
|
+
options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
277
266
|
): readonly [
|
|
278
267
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
279
268
|
ComputedRef<TData>,
|
|
280
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
269
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
281
270
|
UseQueryReturnType<any, any>
|
|
282
271
|
]
|
|
283
272
|
<TData = A>(
|
|
@@ -285,17 +274,17 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
285
274
|
): readonly [
|
|
286
275
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
287
276
|
ComputedRef<TData>,
|
|
288
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
289
|
-
UseQueryDefinedReturnType<TData,
|
|
277
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
|
|
278
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
290
279
|
]
|
|
291
280
|
// optional options, optional A
|
|
292
281
|
/**
|
|
293
282
|
* Effect results are passed to the caller, including errors.
|
|
294
283
|
*/
|
|
295
|
-
<TData = A>(options?: CustomUndefinedInitialQueryOptions<A,
|
|
284
|
+
<TData = A>(options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>): readonly [
|
|
296
285
|
ComputedRef<AsyncResult.AsyncResult<A, E>>,
|
|
297
286
|
ComputedRef<A | undefined>,
|
|
298
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
287
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
299
288
|
UseQueryReturnType<any, any>
|
|
300
289
|
]
|
|
301
290
|
}
|
|
@@ -312,11 +301,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
312
301
|
*/
|
|
313
302
|
<TData = A>(
|
|
314
303
|
arg: Arg | WatchSource<Arg>,
|
|
315
|
-
options: CustomDefinedInitialQueryOptions<A,
|
|
304
|
+
options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
316
305
|
): readonly [
|
|
317
306
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
318
307
|
ComputedRef<TData>,
|
|
319
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
308
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
320
309
|
UseQueryReturnType<any, any>
|
|
321
310
|
]
|
|
322
311
|
// required options, with placeholderData
|
|
@@ -325,11 +314,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
325
314
|
*/
|
|
326
315
|
<TData = A>(
|
|
327
316
|
arg: Arg | WatchSource<Arg>,
|
|
328
|
-
options: CustomDefinedPlaceholderQueryOptions<A,
|
|
317
|
+
options: CustomDefinedPlaceholderQueryOptions<A, CauseException<E>, TData>
|
|
329
318
|
): readonly [
|
|
330
319
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
331
320
|
ComputedRef<TData>,
|
|
332
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
321
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
333
322
|
UseQueryReturnType<any, any>
|
|
334
323
|
]
|
|
335
324
|
// optional options, optional A
|
|
@@ -338,11 +327,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
338
327
|
*/
|
|
339
328
|
<TData = A>(
|
|
340
329
|
arg: Arg | WatchSource<Arg>,
|
|
341
|
-
options?: CustomUndefinedInitialQueryOptions<A,
|
|
330
|
+
options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
342
331
|
): readonly [
|
|
343
332
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
344
333
|
ComputedRef<TData | undefined>,
|
|
345
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
334
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
346
335
|
UseQueryReturnType<any, any>
|
|
347
336
|
]
|
|
348
337
|
}
|
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, Layer, type Option } from "effect-app"
|
|
2
2
|
import { wrapEffect } from "effect-app/utils"
|
|
3
3
|
import { CurrentToastId, Toast } 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) {
|
|
@@ -74,15 +75,23 @@ export class WithToast extends ServiceMap.Service<WithToast>()("WithToast", {
|
|
|
74
75
|
return
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
const spanInfo = options.showSpanInfo !== false
|
|
79
|
+
? yield* Effect.currentSpan.pipe(
|
|
80
|
+
Effect.map((span) => `\nTrace: ${span.traceId}\nSpan: ${span.spanId}`),
|
|
81
|
+
Effect.orElseSucceed(() => "")
|
|
82
|
+
)
|
|
83
|
+
: ""
|
|
84
|
+
|
|
77
85
|
const t = yield* wrapEffect(options.onFailure)(Cause.findErrorOption(cause), ...args)
|
|
78
86
|
const opts = { timeout: baseTimeout * 2 }
|
|
79
87
|
|
|
80
88
|
if (typeof t === "object") {
|
|
89
|
+
const message = t.message + spanInfo
|
|
81
90
|
return t.level === "warn"
|
|
82
|
-
? yield* toast.warning(
|
|
83
|
-
: yield* toast.error(
|
|
91
|
+
? yield* toast.warning(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
|
|
92
|
+
: yield* toast.error(message, toastId !== undefined ? { ...opts, id: toastId } : opts)
|
|
84
93
|
}
|
|
85
|
-
yield* toast.error(t, toastId !== undefined ? { ...opts, id: toastId } : opts)
|
|
94
|
+
yield* toast.error(t + spanInfo, toastId !== undefined ? { ...opts, id: toastId } : opts)
|
|
86
95
|
}, Effect.uninterruptible)),
|
|
87
96
|
toastId !== undefined ? Effect.provideService(CurrentToastId, CurrentToastId.of({ toastId })) : (_) => _
|
|
88
97
|
)
|
package/test/Mutation.test.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { it } from "@effect/vitest"
|
|
3
3
|
import { Cause, Effect, Exit, Fiber, Option } from "effect-app"
|
|
4
|
-
import {
|
|
4
|
+
import { OperationFailure } from "effect-app/Operations"
|
|
5
|
+
import { CommandContext, DefaultIntl } from "../src/commander.js"
|
|
5
6
|
import { AsyncResult } from "../src/lib.js"
|
|
6
7
|
import { useExperimental } from "./stubs.js"
|
|
7
8
|
|
|
@@ -71,7 +72,10 @@ describe("alt2", () => {
|
|
|
71
72
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
72
73
|
})),
|
|
73
74
|
Effect.tap(() =>
|
|
74
|
-
Effect.currentSpan.pipe(
|
|
75
|
+
Effect.currentSpan.pipe(
|
|
76
|
+
Effect.map((_) => _.name),
|
|
77
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
78
|
+
)
|
|
75
79
|
),
|
|
76
80
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
77
81
|
)
|
|
@@ -125,7 +129,10 @@ it.live("works", () =>
|
|
|
125
129
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
126
130
|
})),
|
|
127
131
|
Effect.tap(() =>
|
|
128
|
-
Effect.currentSpan.pipe(
|
|
132
|
+
Effect.currentSpan.pipe(
|
|
133
|
+
Effect.map((_) => _.name),
|
|
134
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
135
|
+
)
|
|
129
136
|
),
|
|
130
137
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
131
138
|
)
|
|
@@ -175,7 +182,10 @@ it.live("works non-gen", () =>
|
|
|
175
182
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
176
183
|
})),
|
|
177
184
|
Effect.tap(() =>
|
|
178
|
-
Effect.currentSpan.pipe(
|
|
185
|
+
Effect.currentSpan.pipe(
|
|
186
|
+
Effect.map((_) => _.name),
|
|
187
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
188
|
+
)
|
|
179
189
|
),
|
|
180
190
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
181
191
|
)
|
|
@@ -317,7 +327,10 @@ it.live("with toasts", () =>
|
|
|
317
327
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
318
328
|
})),
|
|
319
329
|
Effect.tap(() =>
|
|
320
|
-
Effect.currentSpan.pipe(
|
|
330
|
+
Effect.currentSpan.pipe(
|
|
331
|
+
Effect.map((_) => _.name),
|
|
332
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
333
|
+
)
|
|
321
334
|
),
|
|
322
335
|
// WithToast.handle({
|
|
323
336
|
// onFailure: "failed",
|
|
@@ -388,9 +401,55 @@ it.live("fail", () =>
|
|
|
388
401
|
expect(command.waiting).toBe(false)
|
|
389
402
|
expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true)
|
|
390
403
|
expect(toasts.length).toBe(1) // toast should show error
|
|
404
|
+
expect(toasts[0].type).toBe("warning")
|
|
405
|
+
expect(toasts[0].message).toContain("Test Action Failed:\nBoom!")
|
|
406
|
+
expect(toasts[0].message).toMatch(/Trace: [a-f0-9]{32}/)
|
|
407
|
+
expect(toasts[0].message).toMatch(/Span: [a-f0-9]{16}/)
|
|
408
|
+
}))
|
|
409
|
+
|
|
410
|
+
it.live("fail with showSpanInfo disabled", () =>
|
|
411
|
+
Effect
|
|
412
|
+
.gen(function*() {
|
|
413
|
+
const toasts: any[] = []
|
|
414
|
+
const Command = useExperimental({ toasts, messages: DefaultIntl.en })
|
|
415
|
+
|
|
416
|
+
const command = Command.fn("Test Action")(
|
|
417
|
+
function*() {
|
|
418
|
+
return yield* Effect.fail({ message: "Boom!" })
|
|
419
|
+
},
|
|
420
|
+
Command.withDefaultToast({ showSpanInfo: false })
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
yield* Fiber.join(command.handle())
|
|
424
|
+
|
|
425
|
+
expect(toasts.length).toBe(1)
|
|
391
426
|
expect(toasts[0].message).toBe("Test Action Failed:\nBoom!")
|
|
392
427
|
}))
|
|
393
428
|
|
|
429
|
+
it.live("fail with custom errorRenderer uses warning toast", () =>
|
|
430
|
+
Effect
|
|
431
|
+
.gen(function*() {
|
|
432
|
+
const toasts: any[] = []
|
|
433
|
+
const Command = useExperimental({ toasts, messages: DefaultIntl.en })
|
|
434
|
+
|
|
435
|
+
const command = Command.fn("Test Action")(
|
|
436
|
+
function*() {
|
|
437
|
+
return yield* Effect.fail(OperationFailure.make({ message: null }))
|
|
438
|
+
},
|
|
439
|
+
Command.withDefaultToast({
|
|
440
|
+
errorRenderer: () => "Rendered Boom!"
|
|
441
|
+
})
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
yield* Fiber.join(command.handle())
|
|
445
|
+
|
|
446
|
+
expect(toasts.length).toBe(1)
|
|
447
|
+
expect(toasts[0].type).toBe("warning")
|
|
448
|
+
expect(toasts[0].message).toContain("Test Action, with warnings\nRendered Boom!")
|
|
449
|
+
expect(toasts[0].message).toMatch(/Trace: [a-f0-9]{32}/)
|
|
450
|
+
expect(toasts[0].message).toMatch(/Span: [a-f0-9]{16}/)
|
|
451
|
+
}))
|
|
452
|
+
|
|
394
453
|
it.live("fail and recover", () =>
|
|
395
454
|
Effect
|
|
396
455
|
.gen(function*() {
|
|
@@ -444,7 +503,8 @@ it.live("defect", () =>
|
|
|
444
503
|
expect(command.waiting).toBe(false)
|
|
445
504
|
expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true)
|
|
446
505
|
expect(toasts.length).toBe(1) // toast should show error
|
|
447
|
-
expect(toasts[0].
|
|
506
|
+
expect(toasts[0].type).toBe("error")
|
|
507
|
+
expect(toasts[0].message).toContain("Test Action unexpected error, please try again shortly.")
|
|
448
508
|
}))
|
|
449
509
|
|
|
450
510
|
it.live("works with alt", () =>
|
|
@@ -471,7 +531,10 @@ it.live("works with alt", () =>
|
|
|
471
531
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
472
532
|
})),
|
|
473
533
|
Effect.tap(() =>
|
|
474
|
-
Effect.currentSpan.pipe(
|
|
534
|
+
Effect.currentSpan.pipe(
|
|
535
|
+
Effect.map((_) => _.name),
|
|
536
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
537
|
+
)
|
|
475
538
|
),
|
|
476
539
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
477
540
|
)
|
|
@@ -624,7 +687,10 @@ it.live("with toasts with alt", () =>
|
|
|
624
687
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
625
688
|
})),
|
|
626
689
|
Effect.tap(() =>
|
|
627
|
-
Effect.currentSpan.pipe(
|
|
690
|
+
Effect.currentSpan.pipe(
|
|
691
|
+
Effect.map((_) => _.name),
|
|
692
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
693
|
+
)
|
|
628
694
|
),
|
|
629
695
|
Command.withDefaultToast(),
|
|
630
696
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
@@ -696,7 +762,8 @@ it.live("fail with alt", () =>
|
|
|
696
762
|
expect(command.waiting).toBe(false)
|
|
697
763
|
expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true)
|
|
698
764
|
expect(toasts.length).toBe(1) // toast should show error
|
|
699
|
-
expect(toasts[0].
|
|
765
|
+
expect(toasts[0].type).toBe("warning")
|
|
766
|
+
expect(toasts[0].message).toContain("Test Action Failed:\nBoom!")
|
|
700
767
|
}))
|
|
701
768
|
|
|
702
769
|
it.live("fail and recover with alt", () =>
|
|
@@ -756,5 +823,58 @@ it.live("defect with alt", () =>
|
|
|
756
823
|
expect(command.waiting).toBe(false)
|
|
757
824
|
expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true)
|
|
758
825
|
expect(toasts.length).toBe(1) // toast should show error
|
|
759
|
-
expect(toasts[0].
|
|
826
|
+
expect(toasts[0].type).toBe("error")
|
|
827
|
+
expect(toasts[0].message).toContain("Test Action unexpected error, please try again shortly.")
|
|
760
828
|
}))
|
|
829
|
+
|
|
830
|
+
describe("state-in-toast", () => {
|
|
831
|
+
it("works", () => {
|
|
832
|
+
const toasts: any[] = []
|
|
833
|
+
const removeMutation = Object.assign(
|
|
834
|
+
Effect.fn(function*(_item: string) {
|
|
835
|
+
yield* Effect.sleep(1000)
|
|
836
|
+
}),
|
|
837
|
+
{ id: "remove_thing" }
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
const item = "x"
|
|
841
|
+
|
|
842
|
+
const Command = useExperimental({ toasts, messages: DefaultIntl.en })
|
|
843
|
+
|
|
844
|
+
Command.fn(removeMutation, {
|
|
845
|
+
state: () => ({ item }),
|
|
846
|
+
waitKey: (id) => `${id}.${item}`,
|
|
847
|
+
blockKey: () => `modify_thing.${item}`
|
|
848
|
+
// allowed: () => role.value === "admin"
|
|
849
|
+
})(
|
|
850
|
+
function*() {
|
|
851
|
+
// yield* Command.confirmOrInterrupt(yield* I18n.formatMessage({ id: "confirm.remove_item" }, { item }))
|
|
852
|
+
yield* removeMutation(item)
|
|
853
|
+
},
|
|
854
|
+
Command.withDefaultToast({
|
|
855
|
+
onSuccess: (a, b, c, d) => {
|
|
856
|
+
console.log("Success", { a, b, c, d })
|
|
857
|
+
expectTypeOf(d.state).toEqualTypeOf<{ readonly item: "x" }>()
|
|
858
|
+
}
|
|
859
|
+
})
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
Command.fn(removeMutation, {
|
|
863
|
+
state: () => ({ item }),
|
|
864
|
+
waitKey: (id) => `${id}.${item}`,
|
|
865
|
+
blockKey: () => `modify_thing.${item}`
|
|
866
|
+
// allowed: () => role.value === "admin"
|
|
867
|
+
})(
|
|
868
|
+
function*() {
|
|
869
|
+
// yield* Command.confirmOrInterrupt(yield* I18n.formatMessage({ id: "confirm.remove_item" }, { item }))
|
|
870
|
+
yield* removeMutation(item)
|
|
871
|
+
},
|
|
872
|
+
Command.withDefaultToast({
|
|
873
|
+
onSuccess: (a, b, c) => {
|
|
874
|
+
console.log("Success", { a, b, c })
|
|
875
|
+
expectTypeOf(c).toEqualTypeOf<undefined>()
|
|
876
|
+
}
|
|
877
|
+
})
|
|
878
|
+
)
|
|
879
|
+
})
|
|
880
|
+
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form.test.d.ts","sourceRoot":"","sources":["../form.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,CAAC,EAAE,MAAM,YAAY,CAAA
|
|
1
|
+
{"version":3,"file":"form.test.d.ts","sourceRoot":"","sources":["../form.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,CAAC,EAAE,MAAM,YAAY,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAGtC,qBAAa,YAAa,SAAQ,iBAahC;CAAG;;;;AAEL,qBAAa,mBAAoB,SAAQ,wBAEvC;CAAG;;;;;;;;;;;;AAEL,qBAAa,WAAY,SAAQ,gBAK/B;CAAG;;;;;;;;;;;;;;;;;;;;;;;;AAEL,cAAM,MAAO,SAAQ,WAEnB;CAAG;;;;;;;;;;;;;;;;;;;;;;;;AAEL,cAAM,MAAO,SAAQ,WAEnB;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEL,cAAM,QAAS,SAAQ,aAGrB;CAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqBL,qBAAa,cAAe,SAAQ,mBAGlC;CAAG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lib.test.d.ts","sourceRoot":"","sources":["../lib.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streamFinal.test.d.ts","sourceRoot":"","sources":["../streamFinal.test.ts"],"names":[],"mappings":""}
|