@effect-app/vue 4.0.0-beta.187 → 4.0.0-beta.189
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 +22 -0
- package/dist/commander.d.ts +24 -83
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +42 -115
- package/dist/makeClient.d.ts +5 -59
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +4 -59
- package/dist/makeUseCommand.d.ts +1 -1
- package/dist/mutate.d.ts +5 -11
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +5 -53
- package/dist/withToast.d.ts.map +1 -1
- package/dist/withToast.js +9 -4
- package/examples/streamMutation.ts +4 -23
- package/package.json +3 -3
- package/src/commander.ts +167 -230
- package/src/makeClient.ts +6 -167
- package/src/makeUseCommand.ts +1 -1
- package/src/mutate.ts +4 -80
- package/src/withToast.ts +14 -5
- package/test/Mutation.test.ts +0 -26
- package/test/dist/stubs.d.ts +2 -151
- package/test/dist/stubs.d.ts.map +1 -1
- package/test/makeClient.test.ts +2 -24
- package/test/streamFinal.test.ts +2 -49
- package/test/streamFn.test.ts +219 -1
package/src/makeClient.ts
CHANGED
|
@@ -9,11 +9,11 @@ import type * as ExitResult from "effect/Exit"
|
|
|
9
9
|
import { type Fiber } from "effect/Fiber"
|
|
10
10
|
import * as Stream from "effect/Stream"
|
|
11
11
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
12
|
-
import {
|
|
12
|
+
import { type ComputedRef, onBeforeUnmount, ref, type WatchSource } from "vue"
|
|
13
13
|
import { type Commander, CommanderStatic, type Progress } from "./commander.js"
|
|
14
14
|
import { type I18n } from "./intl.js"
|
|
15
15
|
import { type CommanderResolved, makeUseCommand } from "./makeUseCommand.js"
|
|
16
|
-
import { makeMutation,
|
|
16
|
+
import { makeMutation, makeStreamMutation2, type MutationOptionsBase, useMakeMutation } from "./mutate.js"
|
|
17
17
|
import { type CustomUndefinedInitialQueryOptions, makeQuery, makeStreamQuery } from "./query.js"
|
|
18
18
|
import { makeRunPromise } from "./runtime.js"
|
|
19
19
|
import { type Toast } from "./toast.js"
|
|
@@ -227,74 +227,6 @@ export type MutationWithExtensions<RT, Req> = Req extends
|
|
|
227
227
|
? MutationExt<RT, Id, A, E, R, S.Codec.Encoded<Request["success"]>>
|
|
228
228
|
: never
|
|
229
229
|
|
|
230
|
-
/**
|
|
231
|
-
* Options for invoking a `mutateToResult` factory. Supplying `progress` produces
|
|
232
|
-
* a tuple-with-id that carries `running` (the live AsyncResult ref) and
|
|
233
|
-
* `progress` (a `ComputedRef<Progress | undefined>` formatted from each value),
|
|
234
|
-
* which `Command.fn` / `Command.wrapStream` surface as the command's `running`
|
|
235
|
-
* and `progress`. When omitted, the resulting command exposes neither.
|
|
236
|
-
*/
|
|
237
|
-
export type MutateStreamCallOptions<A, E> = {
|
|
238
|
-
progress?: (result: AsyncResult.AsyncResult<A, E>) => Progress | undefined
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* The `mutateToResult` factory for a stream-type request handler. Always invoke
|
|
243
|
-
* (optionally with `{ progress }`) to get a fresh callable `execute` — each call
|
|
244
|
-
* produces a new state + execute pair so independent invocations don't share
|
|
245
|
-
* state. The callable updates its underlying ref live with each emitted value
|
|
246
|
-
* and carries `id`, plus `running` and `progress` when the factory was called
|
|
247
|
-
* with a `progress` formatter. When the request declares a `final` schema,
|
|
248
|
-
* the callable resolves with the last emitted value typed as `Final`; otherwise
|
|
249
|
-
* it resolves with the success type. The factory itself carries the request
|
|
250
|
-
* `id` so it can be passed to `Command.fn` / `Command.wrapStream` directly.
|
|
251
|
-
*/
|
|
252
|
-
export type StreamMutationWithExtensions<Req> = Req extends
|
|
253
|
-
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
|
|
254
|
-
& ((options?: MutateStreamCallOptions<A, E>) =>
|
|
255
|
-
& ((input: I) => Effect.Effect<Final, E, R>)
|
|
256
|
-
& {
|
|
257
|
-
readonly id: Id
|
|
258
|
-
readonly _streamCallable: true
|
|
259
|
-
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
260
|
-
readonly progress?: ComputedRef<Progress | undefined>
|
|
261
|
-
})
|
|
262
|
-
& { readonly id: Id; readonly _streamFactory: true }
|
|
263
|
-
: Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
|
|
264
|
-
& ((options?: MutateStreamCallOptions<A, E>) =>
|
|
265
|
-
& Effect.Effect<Final, E, R>
|
|
266
|
-
& {
|
|
267
|
-
readonly id: Id
|
|
268
|
-
readonly _streamCallable: true
|
|
269
|
-
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
270
|
-
readonly progress?: ComputedRef<Progress | undefined>
|
|
271
|
-
})
|
|
272
|
-
& { readonly id: Id; readonly _streamFactory: true }
|
|
273
|
-
: never
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* The pre-built `wrapStream` CommanderWrap for a stream-type request handler.
|
|
277
|
-
* The command's `result` and `running` are the live stream ref.
|
|
278
|
-
* Callable like `wrap`: `client.myExport.wrapStream()` returns the CommandOut.
|
|
279
|
-
*/
|
|
280
|
-
export type StreamCommandWithExtensions<RT, Req> = Req extends
|
|
281
|
-
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer _Final>
|
|
282
|
-
? Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>
|
|
283
|
-
: Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer _Final>
|
|
284
|
-
? Commander.CommanderWrap<RT, Id, Id, undefined, void, A, E, R>
|
|
285
|
-
: never
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* The `fn` builder for a stream-type request handler — identical to calling
|
|
289
|
-
* `Command.fn(id)` where `id` comes from the request.
|
|
290
|
-
*/
|
|
291
|
-
export type StreamFnExtension<RT, Req> = Req extends
|
|
292
|
-
RequestStreamHandlerWithInput<infer _I, infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
|
|
293
|
-
? Commander.CommanderFn<RT, Id, Id, undefined>
|
|
294
|
-
: Req extends RequestStreamHandler<infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
|
|
295
|
-
? Commander.CommanderFn<RT, Id, Id, undefined>
|
|
296
|
-
: never
|
|
297
|
-
|
|
298
230
|
/**
|
|
299
231
|
* The `streamFn` builder for a stream-type request handler, using the stream-specific overloads.
|
|
300
232
|
*/
|
|
@@ -306,7 +238,7 @@ export type StreamFnStreamExtension<RT, Req> = Req extends
|
|
|
306
238
|
: never
|
|
307
239
|
|
|
308
240
|
/**
|
|
309
|
-
* `mutate` factory —
|
|
241
|
+
* `mutate` factory — wraps per-invocation invalidation scaffolding
|
|
310
242
|
* into the stream itself (via `Stream.unwrap`) for use with `streamFn` combinators.
|
|
311
243
|
*/
|
|
312
244
|
export type StreamMutation2WithExtensions<RT, Req> = Req extends
|
|
@@ -314,13 +246,13 @@ export type StreamMutation2WithExtensions<RT, Req> = Req extends
|
|
|
314
246
|
& ((input: I) => Stream.Stream<A, E, R>)
|
|
315
247
|
& {
|
|
316
248
|
readonly id: Id
|
|
317
|
-
readonly
|
|
249
|
+
readonly wrap: Commander.StreamerWrap<RT, Id, Id, undefined, I, A, E, R>
|
|
318
250
|
}
|
|
319
251
|
: Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer _Final> ?
|
|
320
252
|
& Stream.Stream<A, E, R>
|
|
321
253
|
& {
|
|
322
254
|
readonly id: Id
|
|
323
|
-
readonly
|
|
255
|
+
readonly wrap: Commander.StreamerWrap<RT, Id, Id, undefined, void, A, E, R>
|
|
324
256
|
}
|
|
325
257
|
: never
|
|
326
258
|
|
|
@@ -713,9 +645,6 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
713
645
|
let m: ReturnType<typeof useMutationInt>
|
|
714
646
|
const useMutation = () => m ??= useMutationInt()
|
|
715
647
|
|
|
716
|
-
let sm: ReturnType<typeof makeStreamMutation>
|
|
717
|
-
const useStreamMutation = () => sm ??= makeStreamMutation()
|
|
718
|
-
|
|
719
648
|
let sm2: ReturnType<typeof makeStreamMutation2>
|
|
720
649
|
const useStreamMutation2 = () => sm2 ??= makeStreamMutation2()
|
|
721
650
|
|
|
@@ -898,66 +827,6 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
898
827
|
return mutations
|
|
899
828
|
}
|
|
900
829
|
|
|
901
|
-
const mapStreamMutation = <M extends RequestsAny>(
|
|
902
|
-
client: ClientFrom<M>,
|
|
903
|
-
queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
|
|
904
|
-
invalidationResources?: InvalidationResourcesFor<M>
|
|
905
|
-
) => {
|
|
906
|
-
const Command = useCommand()
|
|
907
|
-
const streamMutation = useStreamMutation()
|
|
908
|
-
const invalidation = queryInvalidation?.(client)
|
|
909
|
-
const queryResources = makeQueryResources(invalidationResources)
|
|
910
|
-
const streams = Struct.keys(client).reduce(
|
|
911
|
-
(acc, key) => {
|
|
912
|
-
if (client[key].Request.type !== "stream") {
|
|
913
|
-
return acc
|
|
914
|
-
}
|
|
915
|
-
const fromRequestConfig = client[key].Request.config?.["invalidatesQueries"] as
|
|
916
|
-
| InvalidationCallback<InvalidationResourcesFor<M>>
|
|
917
|
-
| undefined
|
|
918
|
-
const fromRequest = fromRequestConfig
|
|
919
|
-
? ((defaultKey: string[], _name: string, input?: unknown, output?: unknown) =>
|
|
920
|
-
fromRequestConfig(defaultKey, queryResources as never, input as never, output as never).map((entry) => ({
|
|
921
|
-
filters: entry.filters,
|
|
922
|
-
options: entry.options
|
|
923
|
-
})))
|
|
924
|
-
: undefined
|
|
925
|
-
const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
|
|
926
|
-
const smFactory = Object.assign(
|
|
927
|
-
(opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
|
|
928
|
-
const [resultRef, execute] = streamMutation(client[key] as any, mergedInvalidation)
|
|
929
|
-
const extras: {
|
|
930
|
-
id: string
|
|
931
|
-
_streamCallable: true
|
|
932
|
-
running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
|
|
933
|
-
progress?: ComputedRef<Progress | undefined>
|
|
934
|
-
} = { id: client[key].id, _streamCallable: true }
|
|
935
|
-
if (opts?.progress) {
|
|
936
|
-
const fmt = opts.progress
|
|
937
|
-
extras.running = resultRef
|
|
938
|
-
extras.progress = computed(() => fmt(resultRef.value))
|
|
939
|
-
}
|
|
940
|
-
return Object.assign(execute, extras)
|
|
941
|
-
},
|
|
942
|
-
{ id: client[key].id, _streamFactory: true as const }
|
|
943
|
-
)
|
|
944
|
-
;(acc as any)[camelCase(key) + "Stream"] = Object.assign(smFactory, {
|
|
945
|
-
fn: Command.fn(client[key].id)
|
|
946
|
-
})
|
|
947
|
-
return acc
|
|
948
|
-
},
|
|
949
|
-
{} as {
|
|
950
|
-
[
|
|
951
|
-
Key in keyof typeof client as StreamHandler<typeof client[Key]> extends never ? never
|
|
952
|
-
: `${ToCamel<string & Key>}Stream`
|
|
953
|
-
]:
|
|
954
|
-
& StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
|
|
955
|
-
& { fn: StreamFnExtension<RT | RTHooks, StreamHandler<typeof client[Key]>> }
|
|
956
|
-
}
|
|
957
|
-
)
|
|
958
|
-
return streams
|
|
959
|
-
}
|
|
960
|
-
|
|
961
830
|
// make available .query, .suspense and .mutate for each operation
|
|
962
831
|
// and a .helpers with all mutations and queries
|
|
963
832
|
const mapClient = <M extends RequestsAny>(
|
|
@@ -969,7 +838,6 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
969
838
|
) => {
|
|
970
839
|
const Command = useCommand()
|
|
971
840
|
const mutation = useMutation()
|
|
972
|
-
const streamMutation = useStreamMutation()
|
|
973
841
|
const invalidation = queryInvalidation?.(client)
|
|
974
842
|
const queryResources = makeQueryResources(invalidationResources)
|
|
975
843
|
const extended = Struct.keys(client).reduce(
|
|
@@ -1021,31 +889,10 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1021
889
|
})))
|
|
1022
890
|
: undefined
|
|
1023
891
|
const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
|
|
1024
|
-
const streamMutFactory = Object.assign(
|
|
1025
|
-
(opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
|
|
1026
|
-
const [resultRef, execute] = streamMutation(client[key] as any, mergedInvalidation)
|
|
1027
|
-
const extras: {
|
|
1028
|
-
id: string
|
|
1029
|
-
_streamCallable: true
|
|
1030
|
-
running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
|
|
1031
|
-
progress?: ComputedRef<Progress | undefined>
|
|
1032
|
-
} = { id: client[key].id, _streamCallable: true }
|
|
1033
|
-
if (opts?.progress) {
|
|
1034
|
-
const fmt = opts.progress
|
|
1035
|
-
extras.running = resultRef
|
|
1036
|
-
extras.progress = computed(() => fmt(resultRef.value))
|
|
1037
|
-
}
|
|
1038
|
-
return Object.assign(execute, extras)
|
|
1039
|
-
},
|
|
1040
|
-
{ id: client[key].id, _streamFactory: true as const }
|
|
1041
|
-
)
|
|
1042
892
|
return {
|
|
1043
893
|
...client[key],
|
|
1044
894
|
request: h_,
|
|
1045
895
|
streamQuery: useStreamQuery(client[key] as any),
|
|
1046
|
-
mutateToResult: streamMutFactory,
|
|
1047
|
-
wrapStream: Command.wrapStream(streamMutFactory),
|
|
1048
|
-
fn: Command.fn(client[key].id),
|
|
1049
896
|
streamFn: useCommand().streamFn(client[key].id as any) as any,
|
|
1050
897
|
mutate: (() => {
|
|
1051
898
|
const sm2Act = useStreamMutation2()(client[key] as any, mergedInvalidation)
|
|
@@ -1055,10 +902,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1055
902
|
: (input: any, _ctx: any) => (sm2Act as (i: any) => any)(input)
|
|
1056
903
|
return Object.assign(sm2Act, {
|
|
1057
904
|
id: client[key].id,
|
|
1058
|
-
|
|
1059
|
-
const sfn = useCommand().streamFn(client[key].id as any) as any
|
|
1060
|
-
return sfn(sm2Handler, ...combinators)
|
|
1061
|
-
}
|
|
905
|
+
wrap: useCommand().streamWrap(sm2Handler, client[key].id as any)
|
|
1062
906
|
})
|
|
1063
907
|
})()
|
|
1064
908
|
}
|
|
@@ -1125,9 +969,6 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1125
969
|
: { mutate: MutationWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>> })
|
|
1126
970
|
& (StreamHandler<typeof client[Key]> extends never ? {}
|
|
1127
971
|
: {
|
|
1128
|
-
mutateToResult: StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
|
|
1129
|
-
wrapStream: StreamCommandWithExtensions<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1130
|
-
fn: StreamFnExtension<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1131
972
|
streamFn: StreamFnStreamExtension<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1132
973
|
mutate: StreamMutation2WithExtensions<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1133
974
|
})
|
|
@@ -1138,7 +979,6 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1138
979
|
helpers: {
|
|
1139
980
|
...mapRequest(client),
|
|
1140
981
|
...mapMutation(client, queryInvalidation, invalidationResources),
|
|
1141
|
-
...mapStreamMutation(client, queryInvalidation, invalidationResources),
|
|
1142
982
|
...mapQuery(client)
|
|
1143
983
|
}
|
|
1144
984
|
})
|
|
@@ -1191,7 +1031,6 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
1191
1031
|
// delay initialisation until first use...
|
|
1192
1032
|
fn: (...args: [any]) => useCommand().fn(...args),
|
|
1193
1033
|
wrap: (...args: [any]) => useCommand().wrap(...args),
|
|
1194
|
-
wrapStream: (...args: [any]) => useCommand().wrapStream(...args),
|
|
1195
1034
|
streamFn: (...args: [any]) => useCommand().streamFn(...args),
|
|
1196
1035
|
alt: (...args: [any]) => useCommand().alt(...args),
|
|
1197
1036
|
alt2: (...args: [any]) => useCommand().alt2(...args)
|
package/src/makeUseCommand.ts
CHANGED
|
@@ -7,7 +7,7 @@ type X<X> = X
|
|
|
7
7
|
export interface CommanderResolved<RT, RTHooks>
|
|
8
8
|
extends
|
|
9
9
|
X<typeof CommanderStatic>,
|
|
10
|
-
Pick<CommanderImpl<RT, RTHooks>, "fn" | "wrap" | "
|
|
10
|
+
Pick<CommanderImpl<RT, RTHooks>, "fn" | "wrap" | "streamWrap" | "streamFn" | "alt" | "alt2">
|
|
11
11
|
{
|
|
12
12
|
}
|
|
13
13
|
|
package/src/mutate.ts
CHANGED
|
@@ -414,13 +414,12 @@ export const useMakeMutation = () => {
|
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
/**
|
|
417
|
-
*
|
|
418
|
-
*
|
|
417
|
+
* Returns a stream-based mutation factory for use with `streamFn`.
|
|
418
|
+
* The outer Effect sets up per-invocation invalidation scaffolding
|
|
419
419
|
* and returns a stream that triggers query invalidation via `Stream.ensuring` when it completes.
|
|
420
420
|
*
|
|
421
|
-
* Use
|
|
422
|
-
* the command manages its own reactive state internally.
|
|
423
|
-
* reactive result ref is created.
|
|
421
|
+
* Use with `streamFn` / `Command.streamFn(id)(mutateHandler, ...combinators)` so that
|
|
422
|
+
* the command manages its own reactive state internally.
|
|
424
423
|
*
|
|
425
424
|
* Must be called inside a Vue setup context (uses `useQueryClient` internally).
|
|
426
425
|
*/
|
|
@@ -463,78 +462,3 @@ export const makeStreamMutation2 = () => {
|
|
|
463
462
|
return act
|
|
464
463
|
}
|
|
465
464
|
}
|
|
466
|
-
|
|
467
|
-
export const makeStreamMutation = () => {
|
|
468
|
-
const queryClient = useQueryClient()
|
|
469
|
-
|
|
470
|
-
return (
|
|
471
|
-
self: {
|
|
472
|
-
id: string
|
|
473
|
-
options?: ClientForOptions
|
|
474
|
-
handler: Stream.Stream<any, any, any> | ((i: any) => Stream.Stream<any, any, any>)
|
|
475
|
-
},
|
|
476
|
-
mergedInvalidation?: MutationOptionsBase["queryInvalidation"]
|
|
477
|
-
) => {
|
|
478
|
-
const state = shallowRef<AsyncResult.AsyncResult<any, any>>(AsyncResult.initial())
|
|
479
|
-
|
|
480
|
-
const runStream = (stream: Stream.Stream<any, any, any>, input?: unknown): Effect.Effect<any, any, any> => {
|
|
481
|
-
const invCache = buildInvalidateCache(queryClient, self, mergedInvalidation)
|
|
482
|
-
const keysRef = Ref.makeUnsafe<ReadonlyArray<InvalidationKey>>([])
|
|
483
|
-
// V3: pass onAdded so each mid-stream metadata chunk triggers query
|
|
484
|
-
// invalidation immediately rather than waiting for stream completion.
|
|
485
|
-
const invKeys = makeInvalidationKeysService(keysRef, (key) => invCache(input, Exit.succeed(undefined), [key]))
|
|
486
|
-
return Effect
|
|
487
|
-
.sync(() => {
|
|
488
|
-
state.value = AsyncResult.initial(true)
|
|
489
|
-
})
|
|
490
|
-
.pipe(
|
|
491
|
-
Effect.andThen(
|
|
492
|
-
stream.pipe(
|
|
493
|
-
Stream.provideService(InvalidationKeysFromServer, invKeys),
|
|
494
|
-
Stream.runForEach((value) =>
|
|
495
|
-
Effect.sync(() => {
|
|
496
|
-
state.value = AsyncResult.success(value, { waiting: true })
|
|
497
|
-
})
|
|
498
|
-
),
|
|
499
|
-
Effect.exit,
|
|
500
|
-
Effect.tap((exit) =>
|
|
501
|
-
Effect.sync(() => {
|
|
502
|
-
if (exit._tag === "Success") {
|
|
503
|
-
const current = state.value
|
|
504
|
-
if (AsyncResult.isSuccess(current)) {
|
|
505
|
-
state.value = AsyncResult.success(current.value, { waiting: false })
|
|
506
|
-
} else {
|
|
507
|
-
state.value = AsyncResult.initial(false)
|
|
508
|
-
}
|
|
509
|
-
} else {
|
|
510
|
-
state.value = AsyncResult.failure(exit.cause)
|
|
511
|
-
}
|
|
512
|
-
})
|
|
513
|
-
),
|
|
514
|
-
Effect.flatMap((exit) => {
|
|
515
|
-
const current = state.value
|
|
516
|
-
const lastValue = AsyncResult.isSuccess(current) ? current.value : undefined
|
|
517
|
-
const invExit = exit._tag === "Success" ? Exit.succeed(lastValue) : exit
|
|
518
|
-
const serverKeys = Ref.getUnsafe(keysRef)
|
|
519
|
-
// Stream failures bubble through the execute effect's typed error
|
|
520
|
-
// channel. The reactive `state` ref still mirrors the failure as
|
|
521
|
-
// `AsyncResult.failure` for live progress UI.
|
|
522
|
-
return invCache(input, invExit, serverKeys).pipe(
|
|
523
|
-
Effect.flatMap(() =>
|
|
524
|
-
exit._tag === "Success" ? Effect.succeed(lastValue) : Effect.failCause(exit.cause)
|
|
525
|
-
)
|
|
526
|
-
)
|
|
527
|
-
})
|
|
528
|
-
)
|
|
529
|
-
)
|
|
530
|
-
)
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const handler = self.handler
|
|
534
|
-
const act = Stream.isStream(handler)
|
|
535
|
-
? runStream(handler)
|
|
536
|
-
: (i: any) => runStream((handler as (i: any) => Stream.Stream<any, any, any>)(i), i)
|
|
537
|
-
|
|
538
|
-
return tuple(computed(() => state.value), act)
|
|
539
|
-
}
|
|
540
|
-
}
|
package/src/withToast.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Cause, Context, Effect, Layer, type Option } from "effect-app"
|
|
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)
|
|
@@ -48,12 +48,20 @@ export class WithToast extends Context.Service<WithToast>()("WithToast", {
|
|
|
48
48
|
: options.stableToastId
|
|
49
49
|
|
|
50
50
|
const t = yield* wrapEffect(options.onWaiting)(...args)
|
|
51
|
-
const toastId = t === null
|
|
52
|
-
|
|
53
|
-
|
|
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
|
+
)
|
|
54
59
|
)
|
|
60
|
+
const interruptWaiting = waitingFiber ? Fiber.interrupt(waitingFiber) : Effect.void
|
|
61
|
+
|
|
55
62
|
return yield* self.pipe(
|
|
56
63
|
Effect.tap(Effect.fnUntraced(function*(a) {
|
|
64
|
+
yield* interruptWaiting
|
|
57
65
|
const t = yield* wrapEffect(options.onSuccess)(a, ...args)
|
|
58
66
|
if (t === null) {
|
|
59
67
|
return
|
|
@@ -64,6 +72,7 @@ export class WithToast extends Context.Service<WithToast>()("WithToast", {
|
|
|
64
72
|
)
|
|
65
73
|
})),
|
|
66
74
|
Effect.tapCause(Effect.fnUntraced(function*(cause) {
|
|
75
|
+
yield* interruptWaiting
|
|
67
76
|
yield* Effect.logDebug(
|
|
68
77
|
"WithToast - caught error cause: " + Cause.squash(cause),
|
|
69
78
|
Cause.hasInterruptsOnly(cause),
|
package/test/Mutation.test.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
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 { OperationFailure } from "effect-app/Operations"
|
|
5
4
|
import { CommandContext, DefaultIntl } from "../src/commander.js"
|
|
6
5
|
import { AsyncResult } from "../src/lib.js"
|
|
7
6
|
import { useExperimental } from "./stubs.js"
|
|
@@ -425,31 +424,6 @@ it.live("fail with showSpanInfo disabled", () =>
|
|
|
425
424
|
expect(toasts.length).toBe(1)
|
|
426
425
|
expect(toasts[0].message).toBe("Test Action Failed:\nBoom!")
|
|
427
426
|
}))
|
|
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
|
-
|
|
453
427
|
it.live("fail and recover", () =>
|
|
454
428
|
Effect
|
|
455
429
|
.gen(function*() {
|