@effect-app/vue 4.0.0-beta.180 → 4.0.0-beta.181
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 +51 -0
- package/dist/commander.d.ts +89 -9
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +127 -31
- package/dist/makeClient.d.ts +51 -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 +2 -2
- package/dist/mutate.js +1 -1
- package/examples/streamMutation.ts +4 -2
- package/package.json +2 -2
- package/src/commander.ts +228 -20
- package/src/makeClient.ts +114 -14
- package/src/makeUseCommand.ts +1 -1
- package/src/mutate.ts +1 -1
- 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,42 @@ 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
|
+
type StreamMutationTuple<Id extends string, Arg, A, E, R> =
|
|
35
|
+
& readonly [
|
|
36
|
+
ComputedRef<AsyncResult.AsyncResult<A, E>>,
|
|
37
|
+
((arg: Arg) => Effect.Effect<any, never, R>) | Effect.Effect<any, never, R>
|
|
38
|
+
]
|
|
39
|
+
& {
|
|
40
|
+
readonly id: Id
|
|
41
|
+
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
42
|
+
readonly progress?: ComputedRef<Progress | undefined>
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type StreamMutationFactory<Id extends string, Arg, A, E, R> =
|
|
46
|
+
& ((options?: StreamMutationCallOptions<A, E>) => StreamMutationTuple<Id, Arg, A, E, R>)
|
|
47
|
+
& { readonly id: Id }
|
|
48
|
+
type FnOptions<
|
|
49
|
+
Id extends string,
|
|
50
|
+
I18nCustomKey extends string,
|
|
51
|
+
State extends IntlRecord | undefined
|
|
52
|
+
> = {
|
|
18
53
|
i18nCustomKey?: I18nCustomKey
|
|
19
54
|
/**
|
|
20
55
|
* passed to the i18n formatMessage calls so you can use it in translation messagee
|
|
@@ -139,7 +174,19 @@ export declare namespace Commander {
|
|
|
139
174
|
/** reactive */
|
|
140
175
|
label: string
|
|
141
176
|
/** reactive */
|
|
142
|
-
result: AsyncResult<A, E>
|
|
177
|
+
result: AsyncResult.AsyncResult<A, E>
|
|
178
|
+
/**
|
|
179
|
+
* reactive – set when the command wraps a stream (`wrapStream` / `wrap` with `mutateStream`)
|
|
180
|
+
* or when the `progress` option is provided to `fn`.
|
|
181
|
+
* Reflects the live AsyncResult of the underlying stream.
|
|
182
|
+
*/
|
|
183
|
+
running: AsyncResult.AsyncResult<any, any> | undefined
|
|
184
|
+
/**
|
|
185
|
+
* reactive – formatted progress info computed from `running` via the
|
|
186
|
+
* `progress` option. Useful as the loading state on a `CommandButton`.
|
|
187
|
+
* Undefined when no `progress` formatter was supplied.
|
|
188
|
+
*/
|
|
189
|
+
progress: Progress | undefined
|
|
143
190
|
/** reactive */
|
|
144
191
|
waiting: boolean
|
|
145
192
|
/** reactive */
|
|
@@ -1983,7 +2030,11 @@ const unregisterWait = (id: string) => {
|
|
|
1983
2030
|
}
|
|
1984
2031
|
}
|
|
1985
2032
|
|
|
1986
|
-
const getStateValues = <
|
|
2033
|
+
const getStateValues = <
|
|
2034
|
+
const Id extends string,
|
|
2035
|
+
const I18nKey extends string,
|
|
2036
|
+
State extends IntlRecord | undefined
|
|
2037
|
+
>(
|
|
1987
2038
|
options?: FnOptions<Id, I18nKey, State>
|
|
1988
2039
|
): ComputedRef<State> => {
|
|
1989
2040
|
const state_ = options?.state
|
|
@@ -2035,11 +2086,17 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2035
2086
|
readonly makeCommand = <
|
|
2036
2087
|
const Id extends string,
|
|
2037
2088
|
const State extends IntlRecord | undefined,
|
|
2038
|
-
const I18nKey extends string = Id
|
|
2089
|
+
const I18nKey extends string = Id,
|
|
2090
|
+
RunningA = unknown,
|
|
2091
|
+
RunningE = unknown
|
|
2039
2092
|
>(
|
|
2040
2093
|
id_: Id | { id: Id },
|
|
2041
2094
|
options?: FnOptions<Id, I18nKey, State>,
|
|
2042
|
-
errorDef?: Error
|
|
2095
|
+
errorDef?: Error,
|
|
2096
|
+
streamMeta?: {
|
|
2097
|
+
running?: ComputedRef<AsyncResult.AsyncResult<RunningA, RunningE>> | undefined
|
|
2098
|
+
progress?: ComputedRef<Progress | undefined> | undefined
|
|
2099
|
+
}
|
|
2043
2100
|
) => {
|
|
2044
2101
|
const id = typeof id_ === "string" ? id_ : id_.id
|
|
2045
2102
|
const state = getStateValues(options)
|
|
@@ -2224,6 +2281,12 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2224
2281
|
|
|
2225
2282
|
/** reactive */
|
|
2226
2283
|
result,
|
|
2284
|
+
/** reactive – live AsyncResult of the underlying stream, exposed only when
|
|
2285
|
+
* the stream factory was called with a `progress` formatter */
|
|
2286
|
+
running: streamMeta?.running,
|
|
2287
|
+
/** reactive – formatted progress info for current `running` state, when `progress`
|
|
2288
|
+
* formatter was supplied to the stream factory */
|
|
2289
|
+
progress: streamMeta?.progress,
|
|
2227
2290
|
/** reactive */
|
|
2228
2291
|
waiting,
|
|
2229
2292
|
/** reactive */
|
|
@@ -2325,14 +2388,41 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2325
2388
|
fn = <
|
|
2326
2389
|
const Id extends string,
|
|
2327
2390
|
const State extends IntlRecord = IntlRecord,
|
|
2328
|
-
const I18nKey extends string = Id
|
|
2391
|
+
const I18nKey extends string = Id,
|
|
2392
|
+
RunningA = unknown,
|
|
2393
|
+
RunningE = unknown
|
|
2329
2394
|
>(
|
|
2330
|
-
id:
|
|
2395
|
+
id:
|
|
2396
|
+
| Id
|
|
2397
|
+
| { id: Id }
|
|
2398
|
+
| StreamMutationTuple<Id, any, RunningA, RunningE, any>
|
|
2399
|
+
| StreamMutationFactory<Id, any, RunningA, RunningE, any>,
|
|
2331
2400
|
options?: FnOptions<Id, I18nKey, State>
|
|
2332
2401
|
): Commander.Gen<RT | RTHooks, Id, I18nKey, State> & Commander.NonGen<RT | RTHooks, Id, I18nKey, State> & {
|
|
2333
2402
|
state: Context.Service<`Commander.Command.${Id}.state`, State>
|
|
2334
|
-
} =>
|
|
2335
|
-
|
|
2403
|
+
} => {
|
|
2404
|
+
// Resolve id and (optionally) per-build stream metadata.
|
|
2405
|
+
const resolvedId: Id = typeof id === "string" ? id : (id as { id: Id }).id
|
|
2406
|
+
const isStreamFactory = typeof id === "function" && "id" in id && (id as any).length <= 1
|
|
2407
|
+
const isStreamTuple = Array.isArray(id) && "id" in id
|
|
2408
|
+
const resolveStreamMeta = ():
|
|
2409
|
+
| {
|
|
2410
|
+
running?: ComputedRef<AsyncResult.AsyncResult<RunningA, RunningE>> | undefined
|
|
2411
|
+
progress?: ComputedRef<Progress | undefined> | undefined
|
|
2412
|
+
}
|
|
2413
|
+
| undefined =>
|
|
2414
|
+
{
|
|
2415
|
+
if (isStreamTuple) {
|
|
2416
|
+
const t = id as StreamMutationTuple<Id, any, RunningA, RunningE, any>
|
|
2417
|
+
return { running: t.running, progress: t.progress }
|
|
2418
|
+
}
|
|
2419
|
+
if (isStreamFactory) {
|
|
2420
|
+
const t = id()
|
|
2421
|
+
return { running: t.running, progress: t.progress }
|
|
2422
|
+
}
|
|
2423
|
+
return undefined
|
|
2424
|
+
}
|
|
2425
|
+
return Object.assign(
|
|
2336
2426
|
(
|
|
2337
2427
|
fn: any,
|
|
2338
2428
|
...combinators: any[]
|
|
@@ -2343,7 +2433,9 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2343
2433
|
const errorDef = new Error()
|
|
2344
2434
|
Error.stackTraceLimit = limit
|
|
2345
2435
|
|
|
2346
|
-
|
|
2436
|
+
const streamMeta = resolveStreamMeta()
|
|
2437
|
+
|
|
2438
|
+
return this.makeCommand(resolvedId, options, errorDef, streamMeta)(
|
|
2347
2439
|
Effect.fnUntraced(
|
|
2348
2440
|
// fnUntraced only supports generators as first arg, so we convert to generator if needed
|
|
2349
2441
|
isGeneratorFunction(fn) ? fn : function*(...args) {
|
|
@@ -2353,13 +2445,14 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2353
2445
|
) as any
|
|
2354
2446
|
)
|
|
2355
2447
|
},
|
|
2356
|
-
makeBaseInfo(
|
|
2448
|
+
makeBaseInfo(resolvedId, options),
|
|
2357
2449
|
{
|
|
2358
2450
|
state: Context.Service<`Commander.Command.${Id}.state`, State>(
|
|
2359
|
-
`Commander.Command.${
|
|
2451
|
+
`Commander.Command.${resolvedId}.state`
|
|
2360
2452
|
)
|
|
2361
2453
|
}
|
|
2362
2454
|
)
|
|
2455
|
+
}
|
|
2363
2456
|
|
|
2364
2457
|
/** @deprecated */
|
|
2365
2458
|
alt2: <
|
|
@@ -2464,10 +2557,31 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2464
2557
|
>(
|
|
2465
2558
|
mutation:
|
|
2466
2559
|
| { mutate: (arg: Arg) => Effect.Effect<A, E, R>; id: Id }
|
|
2467
|
-
| ((arg: Arg) => Effect.Effect<A, E, R>) & { id: Id }
|
|
2560
|
+
| ((arg: Arg) => Effect.Effect<A, E, R>) & { id: Id }
|
|
2561
|
+
| StreamMutationFactory<Id, Arg, A, E, R>
|
|
2562
|
+
| {
|
|
2563
|
+
id: Id
|
|
2564
|
+
mutateStream:
|
|
2565
|
+
| StreamMutationFactory<Id, Arg, A, E, R>
|
|
2566
|
+
| StreamMutationTuple<Id, Arg, A, E, R>
|
|
2567
|
+
}
|
|
2568
|
+
| StreamMutationTuple<Id, Arg, A, E, R>,
|
|
2468
2569
|
options?: FnOptions<Id, I18nKey, State>
|
|
2469
|
-
): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> =>
|
|
2470
|
-
|
|
2570
|
+
): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> => {
|
|
2571
|
+
if (mutation !== null && typeof mutation === "object" && "mutateStream" in mutation) {
|
|
2572
|
+
return this.wrapStream(mutation as any, options) as any
|
|
2573
|
+
}
|
|
2574
|
+
if (Array.isArray(mutation) && "id" in mutation) {
|
|
2575
|
+
return this.wrapStream(mutation as any, options) as any
|
|
2576
|
+
}
|
|
2577
|
+
if (typeof mutation === "function" && "id" in mutation && (mutation as any).length <= 1) {
|
|
2578
|
+
return this.wrapStream(mutation as any, options) as any
|
|
2579
|
+
}
|
|
2580
|
+
// At this point mutation is either { mutate, id } or (fn & { id })
|
|
2581
|
+
const callMutation = mutation as
|
|
2582
|
+
| { mutate: (arg: Arg) => Effect.Effect<A, E, R>; id: Id }
|
|
2583
|
+
| (((arg: Arg) => Effect.Effect<A, E, R>) & { id: Id })
|
|
2584
|
+
return Object.assign(
|
|
2471
2585
|
(
|
|
2472
2586
|
...combinators: any[]
|
|
2473
2587
|
): any => {
|
|
@@ -2476,9 +2590,11 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2476
2590
|
Error.stackTraceLimit = 2
|
|
2477
2591
|
const errorDef = new Error()
|
|
2478
2592
|
Error.stackTraceLimit = limit
|
|
2479
|
-
const mutate = "mutate" in
|
|
2593
|
+
const mutate = "mutate" in callMutation
|
|
2594
|
+
? callMutation.mutate
|
|
2595
|
+
: callMutation
|
|
2480
2596
|
|
|
2481
|
-
return this.makeCommand(
|
|
2597
|
+
return this.makeCommand(callMutation.id, options, errorDef)(
|
|
2482
2598
|
Effect.fnUntraced(
|
|
2483
2599
|
// fnUntraced only supports generators as first arg, so we convert to generator if needed
|
|
2484
2600
|
isGeneratorFunction(mutate) ? mutate : function*(arg: Arg) {
|
|
@@ -2488,13 +2604,105 @@ export class CommanderImpl<RT, RTHooks> {
|
|
|
2488
2604
|
) as any
|
|
2489
2605
|
)
|
|
2490
2606
|
},
|
|
2491
|
-
makeBaseInfo(
|
|
2607
|
+
makeBaseInfo(callMutation.id, options),
|
|
2492
2608
|
{
|
|
2493
2609
|
state: Context.Service<`Commander.Command.${Id}.state`, State>(
|
|
2494
|
-
`Commander.Command.${
|
|
2610
|
+
`Commander.Command.${callMutation.id}.state`
|
|
2495
2611
|
)
|
|
2496
2612
|
}
|
|
2497
2613
|
)
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
/**
|
|
2617
|
+
* Define a Command from a stream-type mutation (`mutateStream` factory).
|
|
2618
|
+
* The stream's reactive `AsyncResult` ref is exposed as `running` for independent progress tracking.
|
|
2619
|
+
* The command's own `result` reflects the execution outcome of the `execute` function.
|
|
2620
|
+
* Supports the same combinator pipeline as `wrap` (e.g. `withDefaultToast`).
|
|
2621
|
+
*
|
|
2622
|
+
* Each invocation of the resulting wrap call produces a fresh `[ref, execute]` pair
|
|
2623
|
+
* (the `mutateStream` factory is called once per build), so independent commands
|
|
2624
|
+
* don't share progress state.
|
|
2625
|
+
*
|
|
2626
|
+
* Accepts either:
|
|
2627
|
+
* - An object with `id` and `mutateStream` factory (e.g. a client entry)
|
|
2628
|
+
* - The `mutateStream` factory directly (callable, with `id`)
|
|
2629
|
+
* - An already-called factory result (`[resultRef, execute] & { id }`) — shared ref across builds
|
|
2630
|
+
*
|
|
2631
|
+
* @example
|
|
2632
|
+
* ```ts
|
|
2633
|
+
* // Via client entry (recommended):
|
|
2634
|
+
* const exportCmd = Command.wrapStream(client.myExport)()
|
|
2635
|
+
*
|
|
2636
|
+
* // Via factory directly:
|
|
2637
|
+
* const exportCmd = Command.wrapStream(client.myExport.mutateStream)()
|
|
2638
|
+
*
|
|
2639
|
+
* // Via already-called factory (shared ref):
|
|
2640
|
+
* const stream = client.myExport.mutateStream()
|
|
2641
|
+
* const exportCmd = Command.wrapStream(stream)()
|
|
2642
|
+
* ```
|
|
2643
|
+
*/
|
|
2644
|
+
wrapStream = <
|
|
2645
|
+
const Id extends string,
|
|
2646
|
+
Arg,
|
|
2647
|
+
A,
|
|
2648
|
+
E,
|
|
2649
|
+
R,
|
|
2650
|
+
const State extends IntlRecord = IntlRecord,
|
|
2651
|
+
const I18nKey extends string = Id
|
|
2652
|
+
>(
|
|
2653
|
+
mutation:
|
|
2654
|
+
| {
|
|
2655
|
+
id: Id
|
|
2656
|
+
mutateStream:
|
|
2657
|
+
| StreamMutationFactory<Id, Arg, A, E, R>
|
|
2658
|
+
| StreamMutationTuple<Id, Arg, A, E, R>
|
|
2659
|
+
}
|
|
2660
|
+
| StreamMutationFactory<Id, Arg, A, E, R>
|
|
2661
|
+
| StreamMutationTuple<Id, Arg, A, E, R>,
|
|
2662
|
+
options?: FnOptions<Id, I18nKey, State>
|
|
2663
|
+
): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> => {
|
|
2664
|
+
const id = mutation.id
|
|
2665
|
+
// Resolve `source` to the factory or already-called tuple.
|
|
2666
|
+
const source: StreamMutationFactory<Id, Arg, A, E, R> | StreamMutationTuple<Id, Arg, A, E, R> =
|
|
2667
|
+
mutation !== null && typeof mutation === "object" && "mutateStream" in mutation
|
|
2668
|
+
? (mutation.mutateStream as any)
|
|
2669
|
+
: (mutation as any)
|
|
2670
|
+
const resolveTuple = (): StreamMutationTuple<Id, Arg, A, E, R> => (typeof source === "function" ? source() : source)
|
|
2671
|
+
return Object.assign(
|
|
2672
|
+
(...combinators: any[]): any => {
|
|
2673
|
+
// we capture the definition stack here, so we can append it to later stack traces
|
|
2674
|
+
const limit = Error.stackTraceLimit
|
|
2675
|
+
Error.stackTraceLimit = 2
|
|
2676
|
+
const errorDef = new Error()
|
|
2677
|
+
Error.stackTraceLimit = limit
|
|
2678
|
+
|
|
2679
|
+
// Fresh per build: call the factory once per command instance so each
|
|
2680
|
+
// wrap call gets its own ComputedRef + execute pair. `running`/`progress`
|
|
2681
|
+
// are only surfaced when the factory was called with a `progress` formatter.
|
|
2682
|
+
const tuple = resolveTuple()
|
|
2683
|
+
const [, executeRaw] = tuple
|
|
2684
|
+
const mutate: (_arg: Arg) => Effect.Effect<any, never, R> = Effect.isEffect(executeRaw)
|
|
2685
|
+
? (_arg: Arg) => executeRaw
|
|
2686
|
+
: executeRaw
|
|
2687
|
+
const streamMeta = { running: tuple.running, progress: tuple.progress }
|
|
2688
|
+
|
|
2689
|
+
return this.makeCommand(id, options, errorDef, streamMeta)(
|
|
2690
|
+
Effect.fnUntraced(
|
|
2691
|
+
isGeneratorFunction(mutate) ? mutate : function*(arg: Arg) {
|
|
2692
|
+
return yield* mutate(arg)
|
|
2693
|
+
},
|
|
2694
|
+
...combinators as [any]
|
|
2695
|
+
) as any
|
|
2696
|
+
)
|
|
2697
|
+
},
|
|
2698
|
+
makeBaseInfo(id, options),
|
|
2699
|
+
{
|
|
2700
|
+
state: Context.Service<`Commander.Command.${Id}.state`, State>(
|
|
2701
|
+
`Commander.Command.${id}.state`
|
|
2702
|
+
)
|
|
2703
|
+
}
|
|
2704
|
+
)
|
|
2705
|
+
}
|
|
2498
2706
|
}
|
|
2499
2707
|
|
|
2500
2708
|
// @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,68 @@ 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 `[resultRef, execute]` tuple —
|
|
241
|
+
* each call produces a new `ComputedRef` + execute pair so independent invocations
|
|
242
|
+
* don't share state. `execute` updates the ref live with each emitted value.
|
|
243
|
+
* When the request declares a `final` schema, `execute` resolves with the last
|
|
244
|
+
* emitted value typed as `Final`; otherwise it resolves with the success type.
|
|
245
|
+
* The factory itself carries the request `id` so it can be passed to
|
|
246
|
+
* `Command.fn` / `Command.wrapStream` directly.
|
|
232
247
|
*/
|
|
233
248
|
export type StreamMutationWithExtensions<Req> = Req extends
|
|
234
|
-
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
249
|
+
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
|
|
250
|
+
& ((options?: MutateStreamCallOptions<A, E>) =>
|
|
251
|
+
& readonly [ComputedRef<AsyncResult.AsyncResult<A, E>>, (input: I) => Effect.Effect<Final, never, R>]
|
|
252
|
+
& {
|
|
253
|
+
readonly id: Id
|
|
254
|
+
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
255
|
+
readonly progress?: ComputedRef<Progress | undefined>
|
|
256
|
+
})
|
|
257
|
+
& { readonly id: Id }
|
|
258
|
+
: Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer Final> ?
|
|
259
|
+
& ((options?: MutateStreamCallOptions<A, E>) =>
|
|
260
|
+
& readonly [ComputedRef<AsyncResult.AsyncResult<A, E>>, Effect.Effect<Final, never, R>]
|
|
261
|
+
& {
|
|
262
|
+
readonly id: Id
|
|
263
|
+
readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
|
|
264
|
+
readonly progress?: ComputedRef<Progress | undefined>
|
|
265
|
+
})
|
|
266
|
+
& { readonly id: Id }
|
|
267
|
+
: never
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The pre-built `wrapStream` CommanderWrap for a stream-type request handler.
|
|
271
|
+
* The command's `result` and `running` are the live stream ref.
|
|
272
|
+
* Callable like `wrap`: `client.myExport.wrapStream()` returns the CommandOut.
|
|
273
|
+
*/
|
|
274
|
+
export type StreamCommandWithExtensions<RT, Req> = Req extends
|
|
275
|
+
RequestStreamHandlerWithInput<infer I, infer A, infer E, infer R, infer _Request, infer Id, infer _Final>
|
|
276
|
+
? Commander.CommanderWrap<RT, Id, Id, undefined, I, A, E, R>
|
|
277
|
+
: Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer Id, infer _Final>
|
|
278
|
+
? Commander.CommanderWrap<RT, Id, Id, undefined, void, A, E, R>
|
|
279
|
+
: never
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The `fn` builder for a stream-type request handler — identical to calling
|
|
283
|
+
* `Command.fn(id)` where `id` comes from the request.
|
|
284
|
+
*/
|
|
285
|
+
export type StreamFnExtension<RT, Req> = Req extends
|
|
286
|
+
RequestStreamHandlerWithInput<infer _I, infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
|
|
287
|
+
? Commander.CommanderFn<RT, Id, Id, undefined>
|
|
288
|
+
: Req extends RequestStreamHandler<infer _A, infer _E, infer _R, infer _Request, infer Id, infer _Final>
|
|
289
|
+
? Commander.CommanderFn<RT, Id, Id, undefined>
|
|
238
290
|
: never
|
|
239
291
|
|
|
240
292
|
// we don't really care about the RT, as we are in charge of ensuring runtime safety anyway
|
|
@@ -767,6 +819,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
767
819
|
queryInvalidation?: (client: ClientFrom<M>) => QueryInvalidation<M>,
|
|
768
820
|
invalidationResources?: InvalidationResourcesFor<M>
|
|
769
821
|
) => {
|
|
822
|
+
const Command = useCommand()
|
|
770
823
|
const streamMutation = useStreamMutation()
|
|
771
824
|
const invalidation = queryInvalidation?.(client)
|
|
772
825
|
const queryResources = makeQueryResources(invalidationResources)
|
|
@@ -786,14 +839,35 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
786
839
|
})))
|
|
787
840
|
: undefined
|
|
788
841
|
const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
|
|
789
|
-
|
|
842
|
+
const smFactory = Object.assign(
|
|
843
|
+
(opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
|
|
844
|
+
const tuple = streamMutation(client[key] as any, mergedInvalidation)
|
|
845
|
+
const extras: {
|
|
846
|
+
id: string
|
|
847
|
+
running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
|
|
848
|
+
progress?: ComputedRef<Progress | undefined>
|
|
849
|
+
} = { id: client[key].id }
|
|
850
|
+
if (opts?.progress) {
|
|
851
|
+
const fmt = opts.progress
|
|
852
|
+
extras.running = tuple[0]
|
|
853
|
+
extras.progress = computed(() => fmt(tuple[0].value))
|
|
854
|
+
}
|
|
855
|
+
return Object.assign(tuple, extras)
|
|
856
|
+
},
|
|
857
|
+
{ id: client[key].id }
|
|
858
|
+
)
|
|
859
|
+
;(acc as any)[camelCase(key) + "Stream"] = Object.assign(smFactory, {
|
|
860
|
+
fn: Command.fn(client[key].id)
|
|
861
|
+
})
|
|
790
862
|
return acc
|
|
791
863
|
},
|
|
792
864
|
{} as {
|
|
793
865
|
[
|
|
794
866
|
Key in keyof typeof client as StreamHandler<typeof client[Key]> extends never ? never
|
|
795
867
|
: `${ToCamel<string & Key>}Stream`
|
|
796
|
-
]:
|
|
868
|
+
]:
|
|
869
|
+
& StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
|
|
870
|
+
& { fn: StreamFnExtension<RT | RTHooks, StreamHandler<typeof client[Key]>> }
|
|
797
871
|
}
|
|
798
872
|
)
|
|
799
873
|
return streams
|
|
@@ -862,10 +936,29 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
862
936
|
})))
|
|
863
937
|
: undefined
|
|
864
938
|
const mergedInvalidation = mergeInvalidation(fromRequest, invalidation?.[key])
|
|
939
|
+
const streamMutFactory = Object.assign(
|
|
940
|
+
(opts?: { progress?: (result: AsyncResult.AsyncResult<any, any>) => Progress | undefined }) => {
|
|
941
|
+
const tuple = streamMutation(client[key] as any, mergedInvalidation)
|
|
942
|
+
const extras: {
|
|
943
|
+
id: string
|
|
944
|
+
running?: ComputedRef<AsyncResult.AsyncResult<any, any>>
|
|
945
|
+
progress?: ComputedRef<Progress | undefined>
|
|
946
|
+
} = { id: client[key].id }
|
|
947
|
+
if (opts?.progress) {
|
|
948
|
+
const fmt = opts.progress
|
|
949
|
+
extras.running = tuple[0]
|
|
950
|
+
extras.progress = computed(() => fmt(tuple[0].value))
|
|
951
|
+
}
|
|
952
|
+
return Object.assign(tuple, extras)
|
|
953
|
+
},
|
|
954
|
+
{ id: client[key].id }
|
|
955
|
+
)
|
|
865
956
|
return {
|
|
866
957
|
...client[key],
|
|
867
958
|
request: h_,
|
|
868
|
-
mutateStream:
|
|
959
|
+
mutateStream: streamMutFactory,
|
|
960
|
+
wrapStream: Command.wrapStream(streamMutFactory),
|
|
961
|
+
fn: Command.fn(client[key].id)
|
|
869
962
|
}
|
|
870
963
|
})()
|
|
871
964
|
: {
|
|
@@ -927,7 +1020,11 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
927
1020
|
& (CommandHandler<typeof client[Key]> extends never ? {}
|
|
928
1021
|
: { mutate: MutationWithExtensions<RT | RTHooks, CommandHandler<typeof client[Key]>> })
|
|
929
1022
|
& (StreamHandler<typeof client[Key]> extends never ? {}
|
|
930
|
-
: {
|
|
1023
|
+
: {
|
|
1024
|
+
mutateStream: StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
|
|
1025
|
+
wrapStream: StreamCommandWithExtensions<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1026
|
+
fn: StreamFnExtension<RT | RTHooks, StreamHandler<typeof client[Key]>>
|
|
1027
|
+
})
|
|
931
1028
|
& { Input: typeof client[Key] extends RequestHandlerWithInput<infer I, any, any, any, any, any> ? I : never }
|
|
932
1029
|
}
|
|
933
1030
|
)
|
|
@@ -988,6 +1085,7 @@ export const makeClient = <RT_, RTHooks>(
|
|
|
988
1085
|
// delay initialisation until first use...
|
|
989
1086
|
fn: (...args: [any]) => useCommand().fn(...args),
|
|
990
1087
|
wrap: (...args: [any]) => useCommand().wrap(...args),
|
|
1088
|
+
wrapStream: (...args: [any]) => useCommand().wrapStream(...args),
|
|
991
1089
|
alt: (...args: [any]) => useCommand().alt(...args),
|
|
992
1090
|
alt2: (...args: [any]) => useCommand().alt2(...args)
|
|
993
1091
|
} as ReturnType<typeof useCommand>,
|
|
@@ -1025,6 +1123,8 @@ export interface CommandBase<I = void, A = void> {
|
|
|
1025
1123
|
allowed: boolean
|
|
1026
1124
|
action: string
|
|
1027
1125
|
label: string
|
|
1126
|
+
/** formatted progress info for current `running` state, when `progress` was supplied */
|
|
1127
|
+
progress?: Progress | undefined
|
|
1028
1128
|
}
|
|
1029
1129
|
|
|
1030
1130
|
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,7 @@ 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
424
|
*
|
|
425
425
|
* Must be called inside a Vue setup context (uses `useQueryClient` internally).
|
|
426
426
|
*/
|