@effect-app/vue 4.0.0-beta.8 → 4.0.0-beta.82
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 +535 -0
- package/dist/{experimental/commander.d.ts → commander.d.ts} +53 -53
- package/dist/commander.d.ts.map +1 -0
- package/dist/commander.js +556 -0
- package/dist/{experimental/confirm.d.ts → confirm.d.ts} +2 -2
- package/dist/confirm.d.ts.map +1 -0
- package/dist/confirm.js +28 -0
- package/dist/form.d.ts +9 -0
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +38 -9
- package/dist/intl.d.ts +15 -0
- package/dist/intl.d.ts.map +1 -0
- package/dist/intl.js +9 -0
- package/dist/makeClient.d.ts +18 -241
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +12 -335
- package/dist/{experimental/makeUseCommand.d.ts → makeUseCommand.d.ts} +1 -1
- package/dist/makeUseCommand.d.ts.map +1 -0
- package/dist/makeUseCommand.js +13 -0
- package/dist/mutate.d.ts +1 -1
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +2 -2
- package/dist/query.d.ts +10 -14
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +23 -23
- package/dist/runtime.d.ts +3 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +15 -3
- package/dist/{experimental/toast.d.ts → toast.d.ts} +9 -10
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +32 -0
- package/dist/{experimental/withToast.d.ts → withToast.d.ts} +3 -3
- package/dist/withToast.d.ts.map +1 -0
- package/dist/withToast.js +45 -0
- package/package.json +43 -43
- package/src/{experimental/commander.ts → commander.ts} +826 -202
- package/src/{experimental/confirm.ts → confirm.ts} +2 -2
- package/src/form.ts +46 -8
- package/src/intl.ts +12 -0
- package/src/makeClient.ts +27 -924
- package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +1 -1
- package/src/mutate.ts +1 -1
- package/src/query.ts +44 -45
- package/src/runtime.ts +25 -2
- package/src/{experimental/toast.ts → toast.ts} +11 -25
- package/src/{experimental/withToast.ts → withToast.ts} +3 -3
- package/test/Mutation.test.ts +77 -7
- package/test/dist/form.test.d.ts.map +1 -1
- package/test/dist/stubs.d.ts +273 -67
- package/test/dist/stubs.d.ts.map +1 -1
- package/test/dist/stubs.js +34 -15
- package/test/form-validation-errors.test.ts +23 -19
- package/test/form.test.ts +20 -2
- package/test/makeClient.test.ts +38 -23
- package/test/stubs.ts +45 -18
- package/tsconfig.json +0 -1
- package/dist/experimental/commander.d.ts.map +0 -1
- package/dist/experimental/commander.js +0 -558
- 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.map +0 -1
- package/dist/experimental/makeUseCommand.js +0 -13
- package/dist/experimental/toast.d.ts.map +0 -1
- package/dist/experimental/toast.js +0 -41
- package/dist/experimental/withToast.d.ts.map +0 -1
- package/dist/experimental/withToast.js +0 -45
- package/src/experimental/intl.ts +0 -9
|
@@ -12,7 +12,7 @@ export interface CommanderResolved<RT, RTHooks>
|
|
|
12
12
|
export const makeUseCommand = Effect.fnUntraced(
|
|
13
13
|
function*<R = never, RTHooks = never>(rtHooks: Layer.Layer<RTHooks, never, R>) {
|
|
14
14
|
const cmndr = yield* Commander
|
|
15
|
-
const runtime = yield* Effect.
|
|
15
|
+
const runtime = yield* Effect.context<R>()
|
|
16
16
|
|
|
17
17
|
const comm = cmndr(runtime, rtHooks)
|
|
18
18
|
|
package/src/mutate.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
3
2
|
import { type InvalidateOptions, type InvalidateQueryFilters, type QueryClient, useQueryClient } from "@tanstack/vue-query"
|
|
4
3
|
import { type Cause, Effect, type Exit, Option } from "effect-app"
|
|
5
4
|
import { type Req } from "effect-app/client"
|
|
6
5
|
import type { ClientForOptions, RequestHandler, RequestHandlerWithInput } from "effect-app/client/clientFor"
|
|
7
6
|
import { tuple } from "effect-app/Function"
|
|
7
|
+
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
8
8
|
import { computed, type ComputedRef, shallowRef } from "vue"
|
|
9
9
|
import { makeQueryKey } from "./lib.js"
|
|
10
10
|
|
package/src/query.ts
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
4
4
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
5
|
-
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
6
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"
|
|
7
|
-
import { Array, Cause,
|
|
6
|
+
import { Array, Cause, type Context, Effect, Option, S } from "effect-app"
|
|
8
7
|
import { type Req } from "effect-app/client"
|
|
9
8
|
import type { RequestHandler, RequestHandlerWithInput } from "effect-app/client/clientFor"
|
|
10
|
-
import { ServiceUnavailableError } from "effect-app/client/errors"
|
|
9
|
+
import { CauseException, ServiceUnavailableError } from "effect-app/client/errors"
|
|
11
10
|
import { type Span } from "effect/Tracer"
|
|
12
11
|
import { isHttpClientError } from "effect/unstable/http/HttpClientError"
|
|
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
14
|
import { makeQueryKey, 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,14 +123,8 @@ 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
130
|
? undefined
|
|
@@ -151,13 +138,24 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
151
138
|
const queryKey = makeQueryKey(q)
|
|
152
139
|
const handler = q.handler
|
|
153
140
|
|
|
154
|
-
const
|
|
141
|
+
const defaultOptions = {
|
|
142
|
+
// we do not want to throw errors, because we turn the success and error responses into a Result type
|
|
143
|
+
// 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.
|
|
144
|
+
// 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.
|
|
145
|
+
// TODO: we might want to rethink the strategy of how to handle errors that happen after the initial load.
|
|
146
|
+
// For suspense, the initial load is captured by the suspense boundary.
|
|
147
|
+
// For subsequent loads (or non suspense use) we currently are required to use the QueryResult component to conditionally render error/loading/etc.
|
|
148
|
+
throwOnError: false
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const r = useTanstackQuery<A, CauseException<E>, TData>(
|
|
155
152
|
Effect.isEffect(handler)
|
|
156
153
|
? {
|
|
154
|
+
...defaultOptions,
|
|
157
155
|
...options,
|
|
158
156
|
retry: (retryCount, error) => {
|
|
159
|
-
if (error instanceof
|
|
160
|
-
if (!isHttpClientError(error.
|
|
157
|
+
if (error instanceof CauseException) {
|
|
158
|
+
if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
|
|
161
159
|
return false
|
|
162
160
|
}
|
|
163
161
|
}
|
|
@@ -177,10 +175,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
177
175
|
)
|
|
178
176
|
}
|
|
179
177
|
: {
|
|
178
|
+
...defaultOptions,
|
|
180
179
|
...options,
|
|
181
180
|
retry: (retryCount, error) => {
|
|
182
|
-
if (error instanceof
|
|
183
|
-
if (!isHttpClientError(error.
|
|
181
|
+
if (error instanceof CauseException) {
|
|
182
|
+
if (!isHttpClientError(error.cause) && !S.is(ServiceUnavailableError)(error.cause)) {
|
|
184
183
|
return false
|
|
185
184
|
}
|
|
186
185
|
}
|
|
@@ -228,13 +227,13 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
228
227
|
}
|
|
229
228
|
|
|
230
229
|
function swrToQuery<E, A>(r: {
|
|
231
|
-
error:
|
|
230
|
+
error: CauseException<E> | undefined
|
|
232
231
|
data: A | undefined
|
|
233
232
|
isValidating: boolean
|
|
234
233
|
}): AsyncResult.AsyncResult<A, E> {
|
|
235
234
|
if (r.error !== undefined) {
|
|
236
235
|
return AsyncResult.failureWithPrevious(
|
|
237
|
-
r.error.
|
|
236
|
+
r.error.originalCause,
|
|
238
237
|
{
|
|
239
238
|
previous: r.data === undefined ? Option.none() : Option.some(AsyncResult.success(r.data)),
|
|
240
239
|
waiting: r.isValidating
|
|
@@ -261,11 +260,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
261
260
|
* Effect results are passed to the caller, including errors.
|
|
262
261
|
*/
|
|
263
262
|
<TData = A>(
|
|
264
|
-
options: CustomDefinedInitialQueryOptions<A,
|
|
263
|
+
options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
265
264
|
): readonly [
|
|
266
265
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
267
266
|
ComputedRef<TData>,
|
|
268
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
267
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
269
268
|
UseQueryReturnType<any, any>
|
|
270
269
|
]
|
|
271
270
|
<TData = A>(
|
|
@@ -273,17 +272,17 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
273
272
|
): readonly [
|
|
274
273
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
275
274
|
ComputedRef<TData>,
|
|
276
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
277
|
-
UseQueryDefinedReturnType<TData,
|
|
275
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>, never, never>,
|
|
276
|
+
UseQueryDefinedReturnType<TData, CauseException<E>>
|
|
278
277
|
]
|
|
279
278
|
// optional options, optional A
|
|
280
279
|
/**
|
|
281
280
|
* Effect results are passed to the caller, including errors.
|
|
282
281
|
*/
|
|
283
|
-
<TData = A>(options?: CustomUndefinedInitialQueryOptions<A,
|
|
282
|
+
<TData = A>(options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>): readonly [
|
|
284
283
|
ComputedRef<AsyncResult.AsyncResult<A, E>>,
|
|
285
284
|
ComputedRef<A | undefined>,
|
|
286
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
285
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
287
286
|
UseQueryReturnType<any, any>
|
|
288
287
|
]
|
|
289
288
|
}
|
|
@@ -300,11 +299,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
300
299
|
*/
|
|
301
300
|
<TData = A>(
|
|
302
301
|
arg: Arg | WatchSource<Arg>,
|
|
303
|
-
options: CustomDefinedInitialQueryOptions<A,
|
|
302
|
+
options: CustomDefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
304
303
|
): readonly [
|
|
305
304
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
306
305
|
ComputedRef<TData>,
|
|
307
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
306
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
308
307
|
UseQueryReturnType<any, any>
|
|
309
308
|
]
|
|
310
309
|
// required options, with placeholderData
|
|
@@ -313,11 +312,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
313
312
|
*/
|
|
314
313
|
<TData = A>(
|
|
315
314
|
arg: Arg | WatchSource<Arg>,
|
|
316
|
-
options: CustomDefinedPlaceholderQueryOptions<A,
|
|
315
|
+
options: CustomDefinedPlaceholderQueryOptions<A, CauseException<E>, TData>
|
|
317
316
|
): readonly [
|
|
318
317
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
319
318
|
ComputedRef<TData>,
|
|
320
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
319
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
321
320
|
UseQueryReturnType<any, any>
|
|
322
321
|
]
|
|
323
322
|
// optional options, optional A
|
|
@@ -326,11 +325,11 @@ export const makeQuery = <R>(getRuntime: () => ServiceMap.ServiceMap<R>) => {
|
|
|
326
325
|
*/
|
|
327
326
|
<TData = A>(
|
|
328
327
|
arg: Arg | WatchSource<Arg>,
|
|
329
|
-
options?: CustomUndefinedInitialQueryOptions<A,
|
|
328
|
+
options?: CustomUndefinedInitialQueryOptions<A, CauseException<E>, TData>
|
|
330
329
|
): readonly [
|
|
331
330
|
ComputedRef<AsyncResult.AsyncResult<TData, E>>,
|
|
332
331
|
ComputedRef<TData | undefined>,
|
|
333
|
-
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData,
|
|
332
|
+
(options?: RefetchOptions) => Effect.Effect<QueryObserverResult<TData, CauseException<E>>>,
|
|
334
333
|
UseQueryReturnType<any, any>
|
|
335
334
|
]
|
|
336
335
|
}
|
package/src/runtime.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
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
6
|
export function makeAppRuntime<A, E>(layer: Layer.Layer<A, E>) {
|
|
5
7
|
return Effect.gen(function*() {
|
|
@@ -7,7 +9,7 @@ export function makeAppRuntime<A, E>(layer: Layer.Layer<A, E>) {
|
|
|
7
9
|
Layer.provide(Logger.layer([Logger.consolePretty()]))
|
|
8
10
|
) as Layer.Layer<A, never>
|
|
9
11
|
const mrt = ManagedRuntime.make(l)
|
|
10
|
-
yield* mrt.
|
|
12
|
+
yield* mrt.contextEffect
|
|
11
13
|
return Object.assign(mrt, {
|
|
12
14
|
[Symbol.dispose]() {
|
|
13
15
|
return Effect.runSync(mrt.disposeEffect)
|
|
@@ -29,3 +31,24 @@ export function initializeAsync<A, E>(layer: Layer.Layer<A, E, never>) {
|
|
|
29
31
|
return Effect
|
|
30
32
|
.runPromise(makeAppRuntime(layer))
|
|
31
33
|
}
|
|
34
|
+
|
|
35
|
+
// we wrap into CauseException because we want to keep the full cause of the failure.
|
|
36
|
+
export const makeRunPromise = <T>(services: Context<T>) =>
|
|
37
|
+
flow(Effect.runPromiseExitWith(services), (_) =>
|
|
38
|
+
_.then(
|
|
39
|
+
Exit.match({
|
|
40
|
+
onFailure: (cause) => Promise.reject(new CauseException(cause, "runPromise")),
|
|
41
|
+
onSuccess: (value) => Promise.resolve(value)
|
|
42
|
+
})
|
|
43
|
+
))
|
|
44
|
+
|
|
45
|
+
export const makeRunSync = <T>(services: Context<T>) =>
|
|
46
|
+
flow(
|
|
47
|
+
Effect.runSyncExitWith(services),
|
|
48
|
+
Exit.match({
|
|
49
|
+
onFailure: (cause) => {
|
|
50
|
+
throw new CauseException(cause, "runSync")
|
|
51
|
+
},
|
|
52
|
+
onSuccess: (value) => value
|
|
53
|
+
})
|
|
54
|
+
)
|
|
@@ -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,4 +1,4 @@
|
|
|
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
|
|
|
@@ -33,10 +33,10 @@ export interface ToastOptions<A, E, Args extends ReadonlyArray<unknown>, WaiR, S
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// @effect-diagnostics-next-line missingEffectServiceDependency:off
|
|
36
|
-
export class WithToast extends
|
|
36
|
+
export class WithToast extends Context.Service<WithToast>()("WithToast", {
|
|
37
37
|
make: Effect.gen(function*() {
|
|
38
38
|
const toast = yield* Toast
|
|
39
|
-
return <A, E, Args extends
|
|
39
|
+
return <A, E, Args extends readonly unknown[], R, WaiR = never, SucR = never, ErrR = never>(
|
|
40
40
|
options: ToastOptions<A, E, Args, WaiR, SucR, ErrR>
|
|
41
41
|
) =>
|
|
42
42
|
Effect.fnUntraced(function*(self: Effect.Effect<A, E, R>, ...args: Args) {
|
package/test/Mutation.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 { CommandContext, DefaultIntl } from "../src/
|
|
4
|
+
import { CommandContext, DefaultIntl } from "../src/commander.js"
|
|
5
5
|
import { AsyncResult } from "../src/lib.js"
|
|
6
6
|
import { useExperimental } from "./stubs.js"
|
|
7
7
|
|
|
@@ -71,7 +71,10 @@ describe("alt2", () => {
|
|
|
71
71
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
72
72
|
})),
|
|
73
73
|
Effect.tap(() =>
|
|
74
|
-
Effect.currentSpan.pipe(
|
|
74
|
+
Effect.currentSpan.pipe(
|
|
75
|
+
Effect.map((_) => _.name),
|
|
76
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
77
|
+
)
|
|
75
78
|
),
|
|
76
79
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
77
80
|
)
|
|
@@ -125,7 +128,10 @@ it.live("works", () =>
|
|
|
125
128
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
126
129
|
})),
|
|
127
130
|
Effect.tap(() =>
|
|
128
|
-
Effect.currentSpan.pipe(
|
|
131
|
+
Effect.currentSpan.pipe(
|
|
132
|
+
Effect.map((_) => _.name),
|
|
133
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
134
|
+
)
|
|
129
135
|
),
|
|
130
136
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
131
137
|
)
|
|
@@ -175,7 +181,10 @@ it.live("works non-gen", () =>
|
|
|
175
181
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
176
182
|
})),
|
|
177
183
|
Effect.tap(() =>
|
|
178
|
-
Effect.currentSpan.pipe(
|
|
184
|
+
Effect.currentSpan.pipe(
|
|
185
|
+
Effect.map((_) => _.name),
|
|
186
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
187
|
+
)
|
|
179
188
|
),
|
|
180
189
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
181
190
|
)
|
|
@@ -317,7 +326,10 @@ it.live("with toasts", () =>
|
|
|
317
326
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
318
327
|
})),
|
|
319
328
|
Effect.tap(() =>
|
|
320
|
-
Effect.currentSpan.pipe(
|
|
329
|
+
Effect.currentSpan.pipe(
|
|
330
|
+
Effect.map((_) => _.name),
|
|
331
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
332
|
+
)
|
|
321
333
|
),
|
|
322
334
|
// WithToast.handle({
|
|
323
335
|
// onFailure: "failed",
|
|
@@ -471,7 +483,10 @@ it.live("works with alt", () =>
|
|
|
471
483
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
472
484
|
})),
|
|
473
485
|
Effect.tap(() =>
|
|
474
|
-
Effect.currentSpan.pipe(
|
|
486
|
+
Effect.currentSpan.pipe(
|
|
487
|
+
Effect.map((_) => _.name),
|
|
488
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
489
|
+
)
|
|
475
490
|
),
|
|
476
491
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
477
492
|
)
|
|
@@ -624,7 +639,10 @@ it.live("with toasts with alt", () =>
|
|
|
624
639
|
expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action")
|
|
625
640
|
})),
|
|
626
641
|
Effect.tap(() =>
|
|
627
|
-
Effect.currentSpan.pipe(
|
|
642
|
+
Effect.currentSpan.pipe(
|
|
643
|
+
Effect.map((_) => _.name),
|
|
644
|
+
Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action")))
|
|
645
|
+
)
|
|
628
646
|
),
|
|
629
647
|
Command.withDefaultToast(),
|
|
630
648
|
Effect.tap(() => Effect.sync(() => executed = true))
|
|
@@ -758,3 +776,55 @@ it.live("defect with alt", () =>
|
|
|
758
776
|
expect(toasts.length).toBe(1) // toast should show error
|
|
759
777
|
expect(toasts[0].message).toBe("Test Action unexpected error, please try again shortly.")
|
|
760
778
|
}))
|
|
779
|
+
|
|
780
|
+
describe("state-in-toast", () => {
|
|
781
|
+
it("works", () => {
|
|
782
|
+
const toasts: any[] = []
|
|
783
|
+
const removeMutation = Object.assign(
|
|
784
|
+
Effect.fn(function*(_item: string) {
|
|
785
|
+
yield* Effect.sleep(1000)
|
|
786
|
+
}),
|
|
787
|
+
{ id: "remove_thing" }
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
const item = "x"
|
|
791
|
+
|
|
792
|
+
const Command = useExperimental({ toasts, messages: DefaultIntl.en })
|
|
793
|
+
|
|
794
|
+
Command.fn(removeMutation, {
|
|
795
|
+
state: () => ({ item }),
|
|
796
|
+
waitKey: (id) => `${id}.${item}`,
|
|
797
|
+
blockKey: () => `modify_thing.${item}`
|
|
798
|
+
// allowed: () => role.value === "admin"
|
|
799
|
+
})(
|
|
800
|
+
function*() {
|
|
801
|
+
// yield* Command.confirmOrInterrupt(yield* I18n.formatMessage({ id: "confirm.remove_item" }, { item }))
|
|
802
|
+
yield* removeMutation(item)
|
|
803
|
+
},
|
|
804
|
+
Command.withDefaultToast({
|
|
805
|
+
onSuccess: (a, b, c, d) => {
|
|
806
|
+
console.log("Success", { a, b, c, d })
|
|
807
|
+
expectTypeOf(d.state).toEqualTypeOf<{ readonly item: "x" }>()
|
|
808
|
+
}
|
|
809
|
+
})
|
|
810
|
+
)
|
|
811
|
+
|
|
812
|
+
Command.fn(removeMutation, {
|
|
813
|
+
state: () => ({ item }),
|
|
814
|
+
waitKey: (id) => `${id}.${item}`,
|
|
815
|
+
blockKey: () => `modify_thing.${item}`
|
|
816
|
+
// allowed: () => role.value === "admin"
|
|
817
|
+
})(
|
|
818
|
+
function*() {
|
|
819
|
+
// yield* Command.confirmOrInterrupt(yield* I18n.formatMessage({ id: "confirm.remove_item" }, { item }))
|
|
820
|
+
yield* removeMutation(item)
|
|
821
|
+
},
|
|
822
|
+
Command.withDefaultToast({
|
|
823
|
+
onSuccess: (a, b, c) => {
|
|
824
|
+
console.log("Success", { a, b, c })
|
|
825
|
+
expectTypeOf(c).toEqualTypeOf<undefined>()
|
|
826
|
+
}
|
|
827
|
+
})
|
|
828
|
+
)
|
|
829
|
+
})
|
|
830
|
+
})
|
|
@@ -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"}
|