@effect-app/vue 4.0.0-beta.180 → 4.0.0-beta.182
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 +67 -0
- package/dist/commander.d.ts +95 -9
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +127 -31
- package/dist/makeClient.d.ts +56 -8
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +30 -4
- package/dist/makeUseCommand.d.ts +2 -2
- package/dist/makeUseCommand.d.ts.map +1 -1
- package/dist/mutate.d.ts +4 -3
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +7 -7
- package/examples/streamMutation.ts +9 -3
- package/package.json +2 -2
- package/src/commander.ts +238 -20
- package/src/makeClient.ts +119 -14
- package/src/makeUseCommand.ts +1 -1
- package/src/mutate.ts +11 -7
- package/test/dist/stubs.d.ts +78 -2
- package/test/dist/stubs.d.ts.map +1 -1
- package/test/makeClient.test.ts +7 -8
- package/test/streamFinal.test.ts +5 -5
package/src/commander.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { SupportedErrors } from "effect-app/client"
|
|
|
6
6
|
import { OperationFailure, OperationSuccess } from "effect-app/Operations"
|
|
7
7
|
import { isGeneratorFunction, wrapEffect } from "effect-app/utils"
|
|
8
8
|
import { type Refinement } from "effect/Predicate"
|
|
9
|
-
import
|
|
9
|
+
import type * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
10
10
|
import { type FormatXMLElementFn, type PrimitiveType } from "intl-messageformat"
|
|
11
11
|
import { computed, type ComputedRef, reactive, ref, toRaw } from "vue"
|
|
12
12
|
import { Confirm } from "./confirm.js"
|
|
@@ -14,7 +14,53 @@ import { I18n } from "./intl.js"
|
|
|
14
14
|
import { WithToast } from "./withToast.js"
|
|
15
15
|
|
|
16
16
|
type IntlRecord = Record<string, PrimitiveType | FormatXMLElementFn<string, string>>
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Progress information surfaced by a stream command. Either a plain text label
|
|
20
|
+
* or a `{ text, percentage }` pair when concrete progress is known.
|
|
21
|
+
*/
|
|
22
|
+
export type Progress = string | { readonly text: string; readonly percentage: number }
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Options accepted when calling a stream mutation factory.
|
|
26
|
+
* Supplying `progress` causes the resulting command to expose `running`
|
|
27
|
+
* (the live AsyncResult ref) and `progress` (formatted loading info).
|
|
28
|
+
* When omitted, neither is exposed on the command.
|
|
29
|
+
*/
|
|
30
|
+
export type StreamMutationCallOptions<A, E> = {
|
|
31
|
+
progress?: (result: AsyncResult.AsyncResult<A, E>) => Progress | undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The result of invoking a `mutateStream` factory: the `execute` function (or
|
|
36
|
+
* `Effect`, when the request takes no input) carries `id`, plus `running` and
|
|
37
|
+
* `progress` when the factory was called with a `progress` formatter. Pass
|
|
38
|
+
* directly to `Command.fn` / `Command.wrap` / `Command.wrapStream`, or invoke
|
|
39
|
+
* to run the stream.
|
|
40
|
+
*/
|
|
41
|
+
type StreamMutationCallable<Id extends string, Arg, A, E, R> =
|
|
42
|
+
& (((arg: Arg) => Effect.Effect<any, E, R>) | Effect.Effect<any, E, R>)
|
|
43
|
+
& {
|
|
44
|
+
readonly id: Id
|
|
45
|
+
readonly _streamCallable: true
|
|
46
|
+
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
47
|
+
readonly progress?: ComputedRef<Progress | undefined>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type StreamMutationFactory<Id extends string, Arg, A, E, R> =
|
|
51
|
+
& ((options?: StreamMutationCallOptions<A, E>) => StreamMutationCallable<Id, Arg, A, E, R>)
|
|
52
|
+
& { readonly id: Id; readonly _streamFactory: true }
|
|
53
|
+
|
|
54
|
+
const isStreamFactory = (x: unknown): x is StreamMutationFactory<string, any, any, any, any> =>
|
|
55
|
+
typeof x === "function" && (x as any)._streamFactory === true
|
|
56
|
+
|
|
57
|
+
const isStreamCallable = (x: unknown): x is StreamMutationCallable<string, any, any, any, any> =>
|
|
58
|
+
x !== null && x !== undefined && (x as any)._streamCallable === true
|
|
59
|
+
type FnOptions<
|
|
60
|
+
Id extends string,
|
|
61
|
+
I18nCustomKey extends string,
|
|
62
|
+
State extends IntlRecord | undefined
|
|
63
|
+
> = {
|
|
18
64
|
i18nCustomKey?: I18nCustomKey
|
|
19
65
|
/**
|
|
20
66
|
* passed to the i18n formatMessage calls so you can use it in translation messagee
|
|
@@ -139,7 +185,19 @@ export declare namespace Commander {
|
|
|
139
185
|
/** reactive */
|
|
140
186
|
label: string
|
|
141
187
|
/** reactive */
|
|
142
|
-
result: AsyncResult<A, E>
|
|
188
|
+
result: AsyncResult.AsyncResult<A, E>
|
|
189
|
+
/**
|
|
190
|
+
* reactive – set when the command wraps a stream (`wrapStream` / `wrap` with `mutateStream`)
|
|
191
|
+
* or when the `progress` option is provided to `fn`.
|
|
192
|
+
* Reflects the live AsyncResult of the underlying stream.
|
|
193
|
+
*/
|
|
194
|
+
running: AsyncResult.AsyncResult<any, any> | undefined
|
|
195
|
+
/**
|
|
196
|
+
* reactive – formatted progress info computed from `running` via the
|
|
197
|
+
* `progress` option. Useful as the loading state on a `CommandButton`.
|
|
198
|
+
* Undefined when no `progress` formatter was supplied.
|
|
199
|
+
*/
|
|
200
|
+
progress: Progress | undefined
|
|
143
201
|
/** reactive */
|
|
144
202
|
waiting: boolean
|
|
145
203
|
/** reactive */
|
|
@@ -1983,7 +2041,11 @@ const unregisterWait = (id: string) => {
|
|
|
1983
2041
|
}
|
|
1984
2042
|
}
|
|
1985
2043
|
|
|
1986
|
-
const getStateValues = <
|
|
2044
|
+
const getStateValues = <
|
|
2045
|
+
const Id extends string,
|
|
2046
|
+
const I18nKey extends string,
|
|
2047
|
+
State extends IntlRecord | undefined
|
|
2048
|
+
>(
|
|
1987
2049
|
options?: FnOptions<Id, I18nKey, State>
|
|
1988
2050
|
): ComputedRef<State> => {
|
|
1989
2051
|
const state_ = options?.state
|
|
@@ -2035,11 +2097,17 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2035
2097
|
readonly makeCommand = <
|
|
2036
2098
|
const Id extends string,
|
|
2037
2099
|
const State extends IntlRecord | undefined,
|
|
2038
|
-
const I18nKey extends string = Id
|
|
2100
|
+
const I18nKey extends string = Id,
|
|
2101
|
+
RunningA = unknown,
|
|
2102
|
+
RunningE = unknown
|
|
2039
2103
|
>(
|
|
2040
2104
|
id_: Id | { id: Id },
|
|
2041
2105
|
options?: FnOptions<Id, I18nKey, State>,
|
|
2042
|
-
errorDef?: Error
|
|
2106
|
+
errorDef?: Error,
|
|
2107
|
+
streamMeta?: {
|
|
2108
|
+
running?: ComputedRef<AsyncResult.AsyncResult<RunningA, RunningE>> | undefined
|
|
2109
|
+
progress?: ComputedRef<Progress | undefined> | undefined
|
|
2110
|
+
}
|
|
2043
2111
|
) => {
|
|
2044
2112
|
const id = typeof id_ === "string" ? id_ : id_.id
|
|
2045
2113
|
const state = getStateValues(options)
|
|
@@ -2224,6 +2292,12 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2224
2292
|
|
|
2225
2293
|
/** reactive */
|
|
2226
2294
|
result,
|
|
2295
|
+
/** reactive – live AsyncResult of the underlying stream, exposed only when
|
|
2296
|
+
* the stream factory was called with a `progress` formatter */
|
|
2297
|
+
running: streamMeta?.running,
|
|
2298
|
+
/** reactive – formatted progress info for current `running` state, when `progress`
|
|
2299
|
+
* formatter was supplied to the stream factory */
|
|
2300
|
+
progress: streamMeta?.progress,
|
|
2227
2301
|
/** reactive */
|
|
2228
2302
|
waiting,
|
|
2229
2303
|
/** reactive */
|
|
@@ -2325,14 +2399,41 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2325
2399
|
fn = <
|
|
2326
2400
|
const Id extends string,
|
|
2327
2401
|
const State extends IntlRecord = IntlRecord,
|
|
2328
|
-
const I18nKey extends string = Id
|
|
2402
|
+
const I18nKey extends string = Id,
|
|
2403
|
+
RunningA = unknown,
|
|
2404
|
+
RunningE = unknown
|
|
2329
2405
|
>(
|
|
2330
|
-
id:
|
|
2406
|
+
id:
|
|
2407
|
+
| Id
|
|
2408
|
+
| { id: Id }
|
|
2409
|
+
| StreamMutationCallable<Id, any, RunningA, RunningE, any>
|
|
2410
|
+
| StreamMutationFactory<Id, any, RunningA, RunningE, any>,
|
|
2331
2411
|
options?: FnOptions<Id, I18nKey, State>
|
|
2332
2412
|
): Commander.Gen<RT | RTHooks, Id, I18nKey, State> & Commander.NonGen<RT | RTHooks, Id, I18nKey, State> & {
|
|
2333
2413
|
state: Context.Service<`Commander.Command.${Id}.state`, State>
|
|
2334
|
-
} =>
|
|
2335
|
-
|
|
2414
|
+
} => {
|
|
2415
|
+
// Resolve id and (optionally) per-build stream metadata.
|
|
2416
|
+
const resolvedId: Id = typeof id === "string" ? id : (id as { id: Id }).id
|
|
2417
|
+
const factory = isStreamFactory(id)
|
|
2418
|
+
const callable = !factory && isStreamCallable(id)
|
|
2419
|
+
const resolveStreamMeta = ():
|
|
2420
|
+
| {
|
|
2421
|
+
running?: ComputedRef<AsyncResult.AsyncResult<RunningA, RunningE>> | undefined
|
|
2422
|
+
progress?: ComputedRef<Progress | undefined> | undefined
|
|
2423
|
+
}
|
|
2424
|
+
| undefined =>
|
|
2425
|
+
{
|
|
2426
|
+
if (factory) {
|
|
2427
|
+
const c = id()
|
|
2428
|
+
return { running: c.running, progress: c.progress }
|
|
2429
|
+
}
|
|
2430
|
+
if (callable) {
|
|
2431
|
+
const c = id as StreamMutationCallable<Id, any, RunningA, RunningE, any>
|
|
2432
|
+
return { running: c.running, progress: c.progress }
|
|
2433
|
+
}
|
|
2434
|
+
return undefined
|
|
2435
|
+
}
|
|
2436
|
+
return Object.assign(
|
|
2336
2437
|
(
|
|
2337
2438
|
fn: any,
|
|
2338
2439
|
...combinators: any[]
|
|
@@ -2343,7 +2444,9 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2343
2444
|
const errorDef = new Error()
|
|
2344
2445
|
Error.stackTraceLimit = limit
|
|
2345
2446
|
|
|
2346
|
-
|
|
2447
|
+
const streamMeta = resolveStreamMeta()
|
|
2448
|
+
|
|
2449
|
+
return this.makeCommand(resolvedId, options, errorDef, streamMeta)(
|
|
2347
2450
|
Effect.fnUntraced(
|
|
2348
2451
|
// fnUntraced only supports generators as first arg, so we convert to generator if needed
|
|
2349
2452
|
isGeneratorFunction(fn) ? fn : function*(...args) {
|
|
@@ -2353,13 +2456,14 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2353
2456
|
) as any
|
|
2354
2457
|
)
|
|
2355
2458
|
},
|
|
2356
|
-
makeBaseInfo(
|
|
2459
|
+
makeBaseInfo(resolvedId, options),
|
|
2357
2460
|
{
|
|
2358
2461
|
state: Context.Service<`Commander.Command.${Id}.state`, State>(
|
|
2359
|
-
`Commander.Command.${
|
|
2462
|
+
`Commander.Command.${resolvedId}.state`
|
|
2360
2463
|
)
|
|
2361
2464
|
}
|
|
2362
2465
|
)
|
|
2466
|
+
}
|
|
2363
2467
|
|
|
2364
2468
|
/** @deprecated */
|
|
2365
2469
|
alt2: <
|
|
@@ -2464,10 +2568,28 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2464
2568
|
>(
|
|
2465
2569
|
mutation:
|
|
2466
2570
|
| { mutate: (arg: Arg) => Effect.Effect<A, E, R>; id: Id }
|
|
2467
|
-
| ((arg: Arg) => Effect.Effect<A, E, R>) & { id: Id }
|
|
2571
|
+
| ((arg: Arg) => Effect.Effect<A, E, R>) & { id: Id }
|
|
2572
|
+
| StreamMutationFactory<Id, Arg, A, E, R>
|
|
2573
|
+
| {
|
|
2574
|
+
id: Id
|
|
2575
|
+
mutateStream:
|
|
2576
|
+
| StreamMutationFactory<Id, Arg, A, E, R>
|
|
2577
|
+
| StreamMutationCallable<Id, Arg, A, E, R>
|
|
2578
|
+
}
|
|
2579
|
+
| StreamMutationCallable<Id, Arg, A, E, R>,
|
|
2468
2580
|
options?: FnOptions<Id, I18nKey, State>
|
|
2469
|
-
): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> =>
|
|
2470
|
-
|
|
2581
|
+
): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> => {
|
|
2582
|
+
if (mutation !== null && typeof mutation === "object" && "mutateStream" in mutation) {
|
|
2583
|
+
return this.wrapStream(mutation as any, options) as any
|
|
2584
|
+
}
|
|
2585
|
+
if (isStreamCallable(mutation) || isStreamFactory(mutation)) {
|
|
2586
|
+
return this.wrapStream(mutation as any, options) as any
|
|
2587
|
+
}
|
|
2588
|
+
// At this point mutation is either { mutate, id } or (fn & { id })
|
|
2589
|
+
const callMutation = mutation as
|
|
2590
|
+
| { mutate: (arg: Arg) => Effect.Effect<A, E, R>; id: Id }
|
|
2591
|
+
| (((arg: Arg) => Effect.Effect<A, E, R>) & { id: Id })
|
|
2592
|
+
return Object.assign(
|
|
2471
2593
|
(
|
|
2472
2594
|
...combinators: any[]
|
|
2473
2595
|
): any => {
|
|
@@ -2476,9 +2598,11 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2476
2598
|
Error.stackTraceLimit = 2
|
|
2477
2599
|
const errorDef = new Error()
|
|
2478
2600
|
Error.stackTraceLimit = limit
|
|
2479
|
-
const mutate = "mutate" in
|
|
2601
|
+
const mutate = "mutate" in callMutation
|
|
2602
|
+
? callMutation.mutate
|
|
2603
|
+
: callMutation
|
|
2480
2604
|
|
|
2481
|
-
return this.makeCommand(
|
|
2605
|
+
return this.makeCommand(callMutation.id, options, errorDef)(
|
|
2482
2606
|
Effect.fnUntraced(
|
|
2483
2607
|
// fnUntraced only supports generators as first arg, so we convert to generator if needed
|
|
2484
2608
|
isGeneratorFunction(mutate) ? mutate : function*(arg: Arg) {
|
|
@@ -2488,13 +2612,107 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2488
2612
|
) as any
|
|
2489
2613
|
)
|
|
2490
2614
|
},
|
|
2491
|
-
makeBaseInfo(
|
|
2615
|
+
makeBaseInfo(callMutation.id, options),
|
|
2492
2616
|
{
|
|
2493
2617
|
state: Context.Service<`Commander.Command.${Id}.state`, State>(
|
|
2494
|
-
`Commander.Command.${
|
|
2618
|
+
`Commander.Command.${callMutation.id}.state`
|
|
2495
2619
|
)
|
|
2496
2620
|
}
|
|
2497
2621
|
)
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
/**
|
|
2625
|
+
* Define a Command from a stream-type mutation (`mutateStream` factory).
|
|
2626
|
+
* The stream's reactive `AsyncResult` ref is exposed as `running` for independent progress tracking.
|
|
2627
|
+
* The command's own `result` reflects the execution outcome of the `execute` function.
|
|
2628
|
+
* Supports the same combinator pipeline as `wrap` (e.g. `withDefaultToast`).
|
|
2629
|
+
*
|
|
2630
|
+
* Each invocation of the resulting wrap call produces a fresh `[ref, execute]` pair
|
|
2631
|
+
* (the `mutateStream` factory is called once per build), so independent commands
|
|
2632
|
+
* don't share progress state.
|
|
2633
|
+
*
|
|
2634
|
+
* Accepts either:
|
|
2635
|
+
* - An object with `id` and `mutateStream` factory (e.g. a client entry)
|
|
2636
|
+
* - The `mutateStream` factory directly (callable, with `id`)
|
|
2637
|
+
* - An already-called factory result (`[resultRef, execute] & { id }`) — shared ref across builds
|
|
2638
|
+
*
|
|
2639
|
+
* @example
|
|
2640
|
+
* ```ts
|
|
2641
|
+
* // Via client entry (recommended):
|
|
2642
|
+
* const exportCmd = Command.wrapStream(client.myExport)()
|
|
2643
|
+
*
|
|
2644
|
+
* // Via factory directly:
|
|
2645
|
+
* const exportCmd = Command.wrapStream(client.myExport.mutateStream)()
|
|
2646
|
+
*
|
|
2647
|
+
* // Via already-called factory (shared ref):
|
|
2648
|
+
* const stream = client.myExport.mutateStream()
|
|
2649
|
+
* const exportCmd = Command.wrapStream(stream)()
|
|
2650
|
+
* ```
|
|
2651
|
+
*/
|
|
2652
|
+
wrapStream = <
|
|
2653
|
+
const Id extends string,
|
|
2654
|
+
Arg,
|
|
2655
|
+
A,
|
|
2656
|
+
E,
|
|
2657
|
+
R,
|
|
2658
|
+
const State extends IntlRecord = IntlRecord,
|
|
2659
|
+
const I18nKey extends string = Id
|
|
2660
|
+
>(
|
|
2661
|
+
mutation:
|
|
2662
|
+
| {
|
|
2663
|
+
id: Id
|
|
2664
|
+
mutateStream:
|
|
2665
|
+
| StreamMutationFactory<Id, Arg, A, E, R>
|
|
2666
|
+
| StreamMutationCallable<Id, Arg, A, E, R>
|
|
2667
|
+
}
|
|
2668
|
+
| StreamMutationFactory<Id, Arg, A, E, R>
|
|
2669
|
+
| StreamMutationCallable<Id, Arg, A, E, R>,
|
|
2670
|
+
options?: FnOptions<Id, I18nKey, State>
|
|
2671
|
+
): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> => {
|
|
2672
|
+
const id = mutation.id
|
|
2673
|
+
// Resolve `source` to the factory or already-invoked callable.
|
|
2674
|
+
const source: StreamMutationFactory<Id, Arg, A, E, R> | StreamMutationCallable<Id, Arg, A, E, R> =
|
|
2675
|
+
mutation !== null && typeof mutation === "object" && "mutateStream" in mutation
|
|
2676
|
+
? (mutation.mutateStream as any)
|
|
2677
|
+
: (mutation as any)
|
|
2678
|
+
const resolveCallable = (): StreamMutationCallable<Id, Arg, A, E, R> =>
|
|
2679
|
+
(isStreamFactory(source)
|
|
2680
|
+
? (source as StreamMutationFactory<Id, Arg, A, E, R>)()
|
|
2681
|
+
: source) as StreamMutationCallable<Id, Arg, A, E, R>
|
|
2682
|
+
return Object.assign(
|
|
2683
|
+
(...combinators: any[]): any => {
|
|
2684
|
+
// we capture the definition stack here, so we can append it to later stack traces
|
|
2685
|
+
const limit = Error.stackTraceLimit
|
|
2686
|
+
Error.stackTraceLimit = 2
|
|
2687
|
+
const errorDef = new Error()
|
|
2688
|
+
Error.stackTraceLimit = limit
|
|
2689
|
+
|
|
2690
|
+
// Fresh per build: invoke the factory once per command instance so each
|
|
2691
|
+
// wrap call gets its own state + execute pair. `running`/`progress`
|
|
2692
|
+
// are only surfaced when the factory was called with a `progress` formatter.
|
|
2693
|
+
const callable = resolveCallable()
|
|
2694
|
+
const mutate: (_arg: Arg) => Effect.Effect<any, E, R> = Effect.isEffect(callable)
|
|
2695
|
+
? (_arg: Arg) => callable
|
|
2696
|
+
: callable as (arg: Arg) => Effect.Effect<any, E, R>
|
|
2697
|
+
const streamMeta = { running: callable.running, progress: callable.progress }
|
|
2698
|
+
|
|
2699
|
+
return this.makeCommand(id, options, errorDef, streamMeta)(
|
|
2700
|
+
Effect.fnUntraced(
|
|
2701
|
+
isGeneratorFunction(mutate) ? mutate : function*(arg: Arg) {
|
|
2702
|
+
return yield* mutate(arg)
|
|
2703
|
+
},
|
|
2704
|
+
...combinators as [any]
|
|
2705
|
+
) as any
|
|
2706
|
+
)
|
|
2707
|
+
},
|
|
2708
|
+
makeBaseInfo(id, options),
|
|
2709
|
+
{
|
|
2710
|
+
state: Context.Service<`Commander.Command.${Id}.state`, State>(
|
|
2711
|
+
`Commander.Command.${id}.state`
|
|
2712
|
+
)
|
|
2713
|
+
}
|
|
2714
|
+
)
|
|
2715
|
+
}
|
|
2498
2716
|
}
|
|
2499
2717
|
|
|
2500
2718
|
// @effect-diagnostics-next-line missingEffectServiceDependency:off
|
package/src/makeClient.ts
CHANGED
|
@@ -8,8 +8,8 @@ import type { InvalidationCallback } from "effect-app/client/makeClient"
|
|
|
8
8
|
import type * as ExitResult from "effect/Exit"
|
|
9
9
|
import { type Fiber } from "effect/Fiber"
|
|
10
10
|
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
|
|
11
|
-
import { type ComputedRef, onBeforeUnmount, ref, type WatchSource } from "vue"
|
|
12
|
-
import { type Commander, CommanderStatic } from "./commander.js"
|
|
11
|
+
import { computed, type ComputedRef, onBeforeUnmount, ref, type WatchSource } from "vue"
|
|
12
|
+
import { type Commander, CommanderStatic, type Progress } from "./commander.js"
|
|
13
13
|
import { type I18n } from "./intl.js"
|
|
14
14
|
import { type CommanderResolved, makeUseCommand } from "./makeUseCommand.js"
|
|
15
15
|
import { makeMutation, makeStreamMutation, type MutationOptionsBase, useMakeMutation } from "./mutate.js"
|
|
@@ -225,16 +225,71 @@ export type MutationWithExtensions<RT, Req> = Req extends
|
|
|
225
225
|
: never
|
|
226
226
|
|
|
227
227
|
/**
|
|
228
|
-
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
228
|
+
* Options for invoking a `mutateStream` factory. Supplying `progress` produces
|
|
229
|
+
* a tuple-with-id that carries `running` (the live AsyncResult ref) and
|
|
230
|
+
* `progress` (a `ComputedRef<Progress | undefined>` formatted from each value),
|
|
231
|
+
* which `Command.fn` / `Command.wrapStream` surface as the command's `running`
|
|
232
|
+
* and `progress`. When omitted, the resulting command exposes neither.
|
|
233
|
+
*/
|
|
234
|
+
export type MutateStreamCallOptions<A, E> = {
|
|
235
|
+
progress?: (result: AsyncResult.AsyncResult<A, E>) => Progress | undefined
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The `mutateStream` factory for a stream-type request handler. Always invoke
|
|
240
|
+
* (optionally with `{ progress }`) to get a fresh callable `execute` — each call
|
|
241
|
+
* produces a new state + execute pair so independent invocations don't share
|
|
242
|
+
* state. The callable updates its underlying ref live with each emitted value
|
|
243
|
+
* and carries `id`, plus `running` and `progress` when the factory was called
|
|
244
|
+
* with a `progress` formatter. When the request declares a `final` schema,
|
|
245
|
+
* the callable resolves with the last emitted value typed as `Final`; otherwise
|
|
246
|
+
* it resolves with the success type. The factory itself carries the request
|
|
247
|
+
* `id` so it can be passed to `Command.fn` / `Command.wrapStream` directly.
|
|
232
248
|
*/
|
|
233
249
|
export type StreamMutationWithExtensions<Req> = Req extends
|
|
234
|
-
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
250
|
+
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
|
|
251
|
+
& ((options?: MutateStreamCallOptions<A, E>) =>
|
|
252
|
+
& ((input: I) => Effect.Effect<Final, E, R>)
|
|
253
|
+
& {
|
|
254
|
+
readonly id: Id
|
|
255
|
+
readonly _streamCallable: true
|
|
256
|
+
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
257
|
+
readonly progress?: ComputedRef<Progress | undefined>
|
|
258
|
+
})
|
|
259
|
+
& { readonly id: Id; readonly _streamFactory: true }
|
|
260
|
+
: Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
|
|
261
|
+
& ((options?: MutateStreamCallOptions<A, E>) =>
|
|
262
|
+
& Effect.Effect<Final, E, R>
|
|
263
|
+
& {
|
|
264
|
+
readonly id: Id
|
|
265
|
+
readonly _streamCallable: true
|
|
266
|
+
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
267
|
+
readonly progress?: ComputedRef<Progress | undefined>
|
|
268
|
+
})
|
|
269
|
+
& { readonly id: Id; readonly _streamFactory: true }
|
|
270
|
+
: never
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* The pre-built `wrapStream` CommanderWrap for a stream-type request handler.
|
|
274
|
+
* The command's `result` and `running` are the live stream ref.
|
|
275
|
+
* Callable like `wrap`: `client.myExport.wrapStream()` returns the CommandOut.
|
|
276
|
+
*/
|
|
277
|
+
export type StreamCommandWithExtensions<RT, Req> = Req extends
|
|
278
|
+
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer _Final>
|
|
279
|
+
? Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>
|
|
280
|
+
: Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer _Final>
|
|
281
|
+
? Commander.CommanderWrap<RT, Id, Id, undefined, void, A, E, R>
|
|
282
|
+
: never
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The `fn` builder for a stream-type request handler — identical to calling
|
|
286
|
+
* `Command.fn(id)` where `id` comes from the request.
|
|
287
|
+
*/
|
|
288
|
+
export type StreamFnExtension<RT, Req> = Req extends
|
|
289
|
+
RequestStreamHandlerWithInput<infer _I, infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
|
|
290
|
+
? Commander.CommanderFn<RT, Id, Id, undefined>
|
|
291
|
+
: Req extends RequestStreamHandler<infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
|
|
292
|
+
? Commander.CommanderFn<RT, Id, Id, undefined>
|
|
238
293
|
: never
|
|
239
294
|
|
|
240
295
|
// we don't really care about the RT, as we are in charge of ensuring runtime safety anyway
|
|
@@ -767,6 +822,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
767
822
|
queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
|
|
768
823
|
invalidationResources?: InvalidationResourcesFor<M>
|
|
769
824
|
) => {
|
|
825
|
+
const Command = useCommand()
|
|
770
826
|
const streamMutation = useStreamMutation()
|
|
771
827
|
const invalidation = queryInvalidation?.(client)
|
|
772
828
|
const queryResources = makeQueryResources(invalidationResources)
|
|
@@ -786,14 +842,36 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
786
842
|
})))
|
|
787
843
|
: undefined
|
|
788
844
|
const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
|
|
789
|
-
|
|
845
|
+
const smFactory = Object.assign(
|
|
846
|
+
(opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
|
|
847
|
+
const [resultRef, execute] = streamMutation(client[key] as any, mergedInvalidation)
|
|
848
|
+
const extras: {
|
|
849
|
+
id: string
|
|
850
|
+
_streamCallable: true
|
|
851
|
+
running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
|
|
852
|
+
progress?: ComputedRef<Progress | undefined>
|
|
853
|
+
} = { id: client[key].id, _streamCallable: true }
|
|
854
|
+
if (opts?.progress) {
|
|
855
|
+
const fmt = opts.progress
|
|
856
|
+
extras.running = resultRef
|
|
857
|
+
extras.progress = computed(() => fmt(resultRef.value))
|
|
858
|
+
}
|
|
859
|
+
return Object.assign(execute, extras)
|
|
860
|
+
},
|
|
861
|
+
{ id: client[key].id, _streamFactory: true as const }
|
|
862
|
+
)
|
|
863
|
+
;(acc as any)[camelCase(key) + "Stream"] = Object.assign(smFactory, {
|
|
864
|
+
fn: Command.fn(client[key].id)
|
|
865
|
+
})
|
|
790
866
|
return acc
|
|
791
867
|
},
|
|
792
868
|
{} as {
|
|
793
869
|
[
|
|
794
870
|
Key in keyof typeof client as StreamHandler<typeof client[Key]> extends never ? never
|
|
795
871
|
: `${ToCamel<string & Key>}Stream`
|
|
796
|
-
]:
|
|
872
|
+
]:
|
|
873
|
+
& StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
|
|
874
|
+
& { fn: StreamFnExtension<RT | RTHooks, StreamHandler<typeof client[Key]>> }
|
|
797
875
|
}
|
|
798
876
|
)
|
|
799
877
|
return streams
|
|
@@ -862,10 +940,30 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
862
940
|
})))
|
|
863
941
|
: undefined
|
|
864
942
|
const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
|
|
943
|
+
const streamMutFactory = Object.assign(
|
|
944
|
+
(opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
|
|
945
|
+
const [resultRef, execute] = streamMutation(client[key] as any, mergedInvalidation)
|
|
946
|
+
const extras: {
|
|
947
|
+
id: string
|
|
948
|
+
_streamCallable: true
|
|
949
|
+
running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
|
|
950
|
+
progress?: ComputedRef<Progress | undefined>
|
|
951
|
+
} = { id: client[key].id, _streamCallable: true }
|
|
952
|
+
if (opts?.progress) {
|
|
953
|
+
const fmt = opts.progress
|
|
954
|
+
extras.running = resultRef
|
|
955
|
+
extras.progress = computed(() => fmt(resultRef.value))
|
|
956
|
+
}
|
|
957
|
+
return Object.assign(execute, extras)
|
|
958
|
+
},
|
|
959
|
+
{ id: client[key].id, _streamFactory: true as const }
|
|
960
|
+
)
|
|
865
961
|
return {
|
|
866
962
|
...client[key],
|
|
867
963
|
request: h_,
|
|
868
|
-
mutateStream:
|
|
964
|
+
mutateStream: streamMutFactory,
|
|
965
|
+
wrapStream: Command.wrapStream(streamMutFactory),
|
|
966
|
+
fn: Command.fn(client[key].id)
|
|
869
967
|
}
|
|
870
968
|
})()
|
|
871
969
|
: {
|
|
@@ -927,7 +1025,11 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
927
1025
|
& (CommandHandler<typeof client[Key]> extends never ? {}
|
|
928
1026
|
: { mutate: MutationWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>> })
|
|
929
1027
|
& (StreamHandler<typeof client[Key]> extends never ? {}
|
|
930
|
-
: {
|
|
1028
|
+
: {
|
|
1029
|
+
mutateStream: StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
|
|
1030
|
+
wrapStream: StreamCommandWithExtensions<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1031
|
+
fn: StreamFnExtension<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1032
|
+
})
|
|
931
1033
|
& { Input: typeof client[Key] extends RequestHandlerWithInput<infer I, any, any, any, any, any> ? I : never }
|
|
932
1034
|
}
|
|
933
1035
|
)
|
|
@@ -988,6 +1090,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
988
1090
|
// delay initialisation until first use...
|
|
989
1091
|
fn: (...args: [any]) => useCommand().fn(...args),
|
|
990
1092
|
wrap: (...args: [any]) => useCommand().wrap(...args),
|
|
1093
|
+
wrapStream: (...args: [any]) => useCommand().wrapStream(...args),
|
|
991
1094
|
alt: (...args: [any]) => useCommand().alt(...args),
|
|
992
1095
|
alt2: (...args: [any]) => useCommand().alt2(...args)
|
|
993
1096
|
} as ReturnType<typeof useCommand>,
|
|
@@ -1025,6 +1128,8 @@ export interface CommandBase<I = void, A = void> {
|
|
|
1025
1128
|
allowed: boolean
|
|
1026
1129
|
action: string
|
|
1027
1130
|
label: string
|
|
1131
|
+
/** formatted progress info for current `running` state, when `progress` was supplied */
|
|
1132
|
+
progress?: Progress | undefined
|
|
1028
1133
|
}
|
|
1029
1134
|
|
|
1030
1135
|
export interface EffectCommand<I = void, A = unknown, E = unknown> extends CommandBase<I, Fiber<A, E>> {}
|
package/src/makeUseCommand.ts
CHANGED
|
@@ -5,7 +5,7 @@ type X<X> = X
|
|
|
5
5
|
|
|
6
6
|
// helps retain JSDoc
|
|
7
7
|
export interface CommanderResolved<RT, RTHooks>
|
|
8
|
-
extends X<typeof CommanderStatic>, Pick<CommanderImpl<RT, RTHooks>, "fn" | "wrap" | "alt" | "alt2">
|
|
8
|
+
extends X<typeof CommanderStatic>, Pick<CommanderImpl<RT, RTHooks>, "fn" | "wrap" | "wrapStream" | "alt" | "alt2">
|
|
9
9
|
{
|
|
10
10
|
}
|
|
11
11
|
|
package/src/mutate.ts
CHANGED
|
@@ -420,7 +420,8 @@ export const useMakeMutation = () => {
|
|
|
420
420
|
* success or failure.
|
|
421
421
|
*
|
|
422
422
|
* When the request declares a `final` schema, `execute` resolves with the last emitted value
|
|
423
|
-
* typed as `Final`; otherwise it resolves with
|
|
423
|
+
* typed as `Final`; otherwise it resolves with the last emitted value typed as the success type.
|
|
424
|
+
* Stream failures bubble through the execute effect's typed error channel `E`.
|
|
424
425
|
*
|
|
425
426
|
* Must be called inside a Vue setup context (uses `useQueryClient` internally).
|
|
426
427
|
*/
|
|
@@ -437,7 +438,7 @@ export const makeStreamMutation = () => {
|
|
|
437
438
|
) => {
|
|
438
439
|
const state = shallowRef<AsyncResult.AsyncResult<any, any>>(AsyncResult.initial())
|
|
439
440
|
|
|
440
|
-
const runStream = (stream: Stream.Stream<any, any, any>, input?: unknown): Effect.Effect<any,
|
|
441
|
+
const runStream = (stream: Stream.Stream<any, any, any>, input?: unknown): Effect.Effect<any, any, any> => {
|
|
441
442
|
const invCache = buildInvalidateCache(queryClient, self, mergedInvalidation)
|
|
442
443
|
const keysRef = Ref.makeUnsafe<ReadonlyArray<InvalidationKey>>([])
|
|
443
444
|
// V3: pass onAdded so each mid-stream metadata chunk triggers query
|
|
@@ -476,11 +477,14 @@ export const makeStreamMutation = () => {
|
|
|
476
477
|
const lastValue = AsyncResult.isSuccess(current) ? current.value : undefined
|
|
477
478
|
const invExit = exit._tag === "Success" ? Exit.succeed(lastValue) : exit
|
|
478
479
|
const serverKeys = Ref.getUnsafe(keysRef)
|
|
479
|
-
//
|
|
480
|
-
//
|
|
481
|
-
//
|
|
482
|
-
|
|
483
|
-
|
|
480
|
+
// Stream failures bubble through the execute effect's typed error
|
|
481
|
+
// channel. The reactive `state` ref still mirrors the failure as
|
|
482
|
+
// `AsyncResult.failure` for live progress UI.
|
|
483
|
+
return invCache(input, invExit, serverKeys).pipe(
|
|
484
|
+
Effect.flatMap(() =>
|
|
485
|
+
exit._tag === "Success" ? Effect.succeed(lastValue) : Effect.failCause(exit.cause)
|
|
486
|
+
)
|
|
487
|
+
)
|
|
484
488
|
})
|
|
485
489
|
)
|
|
486
490
|
)
|