@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/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 { type AsyncResult } from "effect/unstable/reactivity/AsyncResult"
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
- type FnOptions<Id extends string, I18nCustomKey extends string, State extends IntlRecord | undefined> = {
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 = <const Id extends string, const I18nKey extends string, State extends IntlRecord | undefined>(
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: Id | { id: 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
- Object.assign(
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
- return this.makeCommand(id, options, errorDef)(
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(typeof id === "string" ? id : id.id, options),
2448
+ makeBaseInfo(resolvedId, options),
2357
2449
  {
2358
2450
  state: Context.Service<`Commander.Command.${Id}.state`, State>(
2359
- `Commander.Command.${typeof id === "string" ? id : id.id}.state`
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
- Object.assign(
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 mutation ? mutation.mutate : mutation
2593
+ const mutate = "mutate" in callMutation
2594
+ ? callMutation.mutate
2595
+ : callMutation
2480
2596
 
2481
- return this.makeCommand(mutation.id, options, errorDef)(
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(mutation.id, options),
2607
+ makeBaseInfo(callMutation.id, options),
2492
2608
  {
2493
2609
  state: Context.Service<`Commander.Command.${Id}.state`, State>(
2494
- `Commander.Command.${mutation.id}.state`
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
- * The `mutateStream` tuple for a stream-type request handler:
229
- * `[resultRef, execute]` where `execute` updates the ref live with each emitted value.
230
- * When the request declares a `final` schema, `execute` resolves with the last emitted value
231
- * typed as `Final`; otherwise it resolves with `void`.
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 _Id, infer Final>
235
- ? readonly [ComputedRef<AsyncResult.AsyncResult<A, E>>, (input: I) => Effect.Effect<Final, never, R>]
236
- : Req extends RequestStreamHandler<infer A, infer E, infer R, infer _Request, infer _Id, infer Final>
237
- ? readonly [ComputedRef<AsyncResult.AsyncResult<A, E>>, Effect.Effect<Final, never, R>]
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
- ;(acc as any)[camelCase(key) + "Stream"] = streamMutation(client[key] as any, mergedInvalidation)
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
- ]: StreamMutationWithExtensions<StreamHandler<typeof client[Key]>>
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: streamMutation(client[key] as any, mergedInvalidation)
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
- : { mutateStream: StreamMutationWithExtensions<StreamHandler<typeof client[Key]>> })
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>> {}
@@ -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 `void`.
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
  */