@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/src/commander.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { asResult, asStreamResult, deepToRaw, type MissingDependencies, reportRuntimeError } from "@effect-app/vue"
3
3
  import { reportMessage } from "@effect-app/vue/errorReporter"
4
- import { Cause, Context, Effect, type Exit, type Fiber, flow, Layer, Match, MutableHashMap, Option, Predicate, S } from "effect-app"
4
+ import { Cause, Context, Effect, type Exit, Fiber, flow, Layer, Match, MutableHashMap, Option, Predicate, S } from "effect-app"
5
5
  import { SupportedErrors } from "effect-app/client"
6
- import { OperationFailure, OperationSuccess } from "effect-app/Operations"
7
6
  import { isGeneratorFunction, wrapEffect } from "effect-app/utils"
8
7
  import { type Refinement } from "effect/Predicate"
9
8
  import * as Stream from "effect/Stream"
@@ -23,41 +22,6 @@ type IntlRecord = Record<string, PrimitiveType | FormatXMLElementFn<string, stri
23
22
  */
24
23
  export type Progress = string | { readonly text: string; readonly percentage: number }
25
24
 
26
- /**
27
- * Options accepted when calling a stream mutation factory.
28
- * Supplying `progress` causes the resulting command to expose `running`
29
- * (the live AsyncResult ref) and `progress` (formatted loading info).
30
- * When omitted, neither is exposed on the command.
31
- */
32
- export type StreamMutationCallOptions<A, E> = {
33
- progress?: (result: AsyncResult.AsyncResult<A, E>) => Progress | undefined
34
- }
35
-
36
- /**
37
- * The result of invoking a `mutateToResult` factory: the `execute` function (or
38
- * `Effect`, when the request takes no input) carries `id`, plus `running` and
39
- * `progress` when the factory was called with a `progress` formatter. Pass
40
- * directly to `Command.fn` / `Command.wrap` / `Command.wrapStream`, or invoke
41
- * to run the stream.
42
- */
43
- type StreamMutationCallable<Id extends string, Arg, A, E, R> =
44
- & (((arg: Arg) => Effect.Effect<any, E, R>) | Effect.Effect<any, E, R>)
45
- & {
46
- readonly id: Id
47
- readonly _streamCallable: true
48
- readonly running?: ComputedRef<AsyncResult.AsyncResult<A, E>>
49
- readonly progress?: ComputedRef<Progress | undefined>
50
- }
51
-
52
- type StreamMutationFactory<Id extends string, Arg, A, E, R> =
53
- & ((options?: StreamMutationCallOptions<A, E>) => StreamMutationCallable<Id, Arg, A, E, R>)
54
- & { readonly id: Id; readonly _streamFactory: true }
55
-
56
- const isStreamFactory = (x: unknown): x is StreamMutationFactory<string, any, any, any, any> =>
57
- typeof x === "function" && (x as any)._streamFactory === true
58
-
59
- const isStreamCallable = (x: unknown): x is StreamMutationCallable<string, any, any, any, any> =>
60
- x !== null && x !== undefined && (x as any)._streamCallable === true
61
25
  type FnOptions<
62
26
  Id extends string,
63
27
  I18nCustomKey extends string,
@@ -218,15 +182,8 @@ export declare namespace Commander {
218
182
  /** reactive */
219
183
  result: AsyncResult.AsyncResult<A, E>
220
184
  /**
221
- * reactive – set when the command wraps a stream (`wrapStream` / `wrap` with `mutateToResult`)
222
- * or when the `progress` option is provided to `fn`.
223
- * Reflects the live AsyncResult of the underlying stream.
224
- */
225
- running: AsyncResult.AsyncResult<any, any> | undefined
226
- /**
227
- * reactive – formatted progress info computed from `running` via the
228
- * `progress` option. Useful as the loading state on a `CommandButton`.
229
- * Undefined when no `progress` formatter was supplied.
185
+ * reactive – formatted progress info driven by `Command.mapProgress` or `Command.updateProgress`
186
+ * inside a `streamFn` handler. Undefined for non-stream commands.
230
187
  */
231
188
  progress: Progress | undefined
232
189
  /** reactive */
@@ -1873,6 +1830,97 @@ export declare namespace Commander {
1873
1830
  ? CommandOut<Arg, SA2, SE2 | EE2, SR2 | ER2, Id, I18nKey, State>
1874
1831
  : never
1875
1832
  }
1833
+
1834
+ /**
1835
+ * Type returned by `mutate.wrap` on a stream handler — analogous to `CommanderWrap` but for streams.
1836
+ * The handler is pre-baked (from the stream mutation), so this is called with only optional combinators.
1837
+ */
1838
+ export type StreamerWrap<
1839
+ RT,
1840
+ Id extends string,
1841
+ I18nKey extends string,
1842
+ State extends IntlRecord | undefined,
1843
+ Arg,
1844
+ SA,
1845
+ SE,
1846
+ SR
1847
+ > =
1848
+ & CommandContextLocal<Id, I18nKey>
1849
+ & { readonly state: Context.Service<`Commander.Command.${Id}.state`, State> }
1850
+ & {
1851
+ (): Exclude<SR, RT> extends never ? CommandOut<Arg, SA, SE, SR, Id, I18nKey, State>
1852
+ : MissingDependencies<RT, SR> & {}
1853
+ <A extends Stream.Stream<any, any, RT | CommandContext | `Commander.Command.${Id}.state`>>(
1854
+ a: (
1855
+ _: Stream.Stream<SA, SE, SR>,
1856
+ arg: ArgForCombinator<Arg>,
1857
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1858
+ ) => A
1859
+ ): CommandOut<Arg, Stream.Success<A>, Stream.Error<A>, Stream.Services<A>, Id, I18nKey, State>
1860
+ <
1861
+ B,
1862
+ A extends Stream.Stream<any, any, RT | CommandContext | `Commander.Command.${Id}.state`>
1863
+ >(
1864
+ a: (
1865
+ _: Stream.Stream<SA, SE, SR>,
1866
+ arg: ArgForCombinator<Arg>,
1867
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1868
+ ) => B,
1869
+ b: (
1870
+ _: B,
1871
+ arg: ArgForCombinator<Arg>,
1872
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1873
+ ) => A
1874
+ ): CommandOut<Arg, Stream.Success<A>, Stream.Error<A>, Stream.Services<A>, Id, I18nKey, State>
1875
+ <
1876
+ B,
1877
+ C,
1878
+ A extends Stream.Stream<any, any, RT | CommandContext | `Commander.Command.${Id}.state`>
1879
+ >(
1880
+ a: (
1881
+ _: Stream.Stream<SA, SE, SR>,
1882
+ arg: ArgForCombinator<Arg>,
1883
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1884
+ ) => B,
1885
+ b: (
1886
+ _: B,
1887
+ arg: ArgForCombinator<Arg>,
1888
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1889
+ ) => C,
1890
+ c: (
1891
+ _: C,
1892
+ arg: ArgForCombinator<Arg>,
1893
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1894
+ ) => A
1895
+ ): CommandOut<Arg, Stream.Success<A>, Stream.Error<A>, Stream.Services<A>, Id, I18nKey, State>
1896
+ <
1897
+ B,
1898
+ C,
1899
+ D,
1900
+ A extends Stream.Stream<any, any, RT | CommandContext | `Commander.Command.${Id}.state`>
1901
+ >(
1902
+ a: (
1903
+ _: Stream.Stream<SA, SE, SR>,
1904
+ arg: ArgForCombinator<Arg>,
1905
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1906
+ ) => B,
1907
+ b: (
1908
+ _: B,
1909
+ arg: ArgForCombinator<Arg>,
1910
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1911
+ ) => C,
1912
+ c: (
1913
+ _: C,
1914
+ arg: ArgForCombinator<Arg>,
1915
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1916
+ ) => D,
1917
+ d: (
1918
+ _: D,
1919
+ arg: ArgForCombinator<Arg>,
1920
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1921
+ ) => A
1922
+ ): CommandOut<Arg, Stream.Success<A>, Stream.Error<A>, Stream.Services<A>, Id, I18nKey, State>
1923
+ }
1876
1924
  }
1877
1925
 
1878
1926
  type ErrorRenderer<E, Args extends readonly any[]> = (e: E, action: string, ...args: Args) => string | undefined
@@ -1966,25 +2014,15 @@ const defaultFailureMessageHandler = <E, Args extends Array<unknown>, AME, AMR>(
1966
2014
  ),
1967
2015
  onSome: (e) => {
1968
2016
  const rendered = renderError(action, errorRenderer)(e, ...args)
1969
- return S.is(OperationFailure)(e)
1970
- ? {
1971
- level: "warn" as const,
1972
- message: `${
1973
- intl.formatMessage(
1974
- { id: "handle.with_warnings" },
1975
- { action }
1976
- )
1977
- }${rendered ? "\n" + rendered : ""}`
1978
- }
1979
- : {
1980
- level: "warn" as const,
1981
- message: `${
1982
- intl.formatMessage(
1983
- { id: "handle.with_errors" },
1984
- { action }
1985
- )
1986
- }:\n` + rendered
1987
- }
2017
+ return {
2018
+ level: "warn" as const,
2019
+ message: `${
2020
+ intl.formatMessage(
2021
+ { id: "handle.with_errors" },
2022
+ { action }
2023
+ )
2024
+ }:\n` + rendered
2025
+ }
1988
2026
  }
1989
2027
  })
1990
2028
  })
@@ -2180,14 +2218,13 @@ export const CommanderStatic = {
2180
2218
  ),
2181
2219
  onSuccess: options?.onSuccess === null
2182
2220
  ? null
2183
- : (a, ..._args) =>
2221
+ : (_a, ..._args) =>
2184
2222
  hasCustomSuccess
2185
2223
  ? intl.formatMessage(
2186
2224
  { id: customSuccess },
2187
2225
  cc.state
2188
2226
  )
2189
- : (intl.formatMessage({ id: "handle.success" }, { action: cc.action })
2190
- + (S.is(OperationSuccess)(a) && a.message ? "\n" + a.message : "")),
2227
+ : intl.formatMessage({ id: "handle.success" }, { action: cc.action }),
2191
2228
  onFailure: defaultFailureMessageHandler(
2192
2229
  hasCustomFailure ? intl.formatMessage({ id: customFailure }, cc.state) : cc.action,
2193
2230
  options?.errorRenderer as ErrorRenderer<E, Args> | undefined
@@ -2296,7 +2333,14 @@ export const CommanderStatic = {
2296
2333
 
2297
2334
  const toastId: string | number | undefined = waitingMsg === null
2298
2335
  ? stableToastId
2299
- : yield* toast.info(waitingMsg, { id: stableToastId ?? null })
2336
+ : stableToastId ?? `wait-${Math.random().toString(36).slice(2)}`
2337
+
2338
+ const waitingFiber = waitingMsg === null ? undefined : yield* Effect.forkDetach(
2339
+ Effect.sleep("1 seconds").pipe(
2340
+ Effect.andThen(toast.info(waitingMsg, { id: toastId!, timeout: Infinity }))
2341
+ )
2342
+ )
2343
+ const interruptWaiting = waitingFiber ? Fiber.interrupt(waitingFiber) : Effect.void
2300
2344
 
2301
2345
  const failureHandler = defaultFailureMessageHandler<E, [], never, never>(
2302
2346
  hasCustomFailure ? intl.formatMessage({ id: customFailure }, cc.state) : cc.action,
@@ -2316,9 +2360,10 @@ export const CommanderStatic = {
2316
2360
  // Update CommandProgress so CommandButton progress indicator is also driven
2317
2361
  yield* CommandProgress.use((s) => s.update(p))
2318
2362
  if (toastId !== undefined) {
2363
+ yield* interruptWaiting
2319
2364
  const progressText = typeof p === "string" ? p : p.text
2320
2365
  const msg = waitingMsg ? `${waitingMsg}\n${progressText}` : progressText
2321
- yield* toast.info(msg, { id: toastId })
2366
+ yield* toast.info(msg, { id: toastId, timeout: Infinity })
2322
2367
  }
2323
2368
  }
2324
2369
  }
@@ -2326,6 +2371,7 @@ export const CommanderStatic = {
2326
2371
  ),
2327
2372
  Stream.tapCause(Effect.fnUntraced(function*(cause) {
2328
2373
  didFail = true
2374
+ yield* interruptWaiting
2329
2375
  if (Cause.hasInterruptsOnly(cause)) {
2330
2376
  if (toastId !== undefined) yield* toast.dismiss(toastId)
2331
2377
  return
@@ -2351,9 +2397,9 @@ export const CommanderStatic = {
2351
2397
  }
2352
2398
  }, Effect.uninterruptible)),
2353
2399
  Stream.ensuring(Effect.suspend(() => {
2354
- if (didFail) return Effect.void
2400
+ if (didFail) return interruptWaiting
2355
2401
 
2356
- if (options?.onSuccess === null) return Effect.void
2402
+ if (options?.onSuccess === null) return interruptWaiting
2357
2403
 
2358
2404
  const successMsg: string | null = typeof options?.onSuccess === "string"
2359
2405
  ? options.onSuccess
@@ -2362,13 +2408,14 @@ export const CommanderStatic = {
2362
2408
  : hasCustomSuccess
2363
2409
  ? intl.formatMessage({ id: customSuccess }, cc.state)
2364
2410
  : intl.formatMessage({ id: "handle.success" }, { action: cc.action })
2365
- + (S.is(OperationSuccess)(lastValue) && lastValue.message ? "\n" + lastValue.message : "")
2366
2411
 
2367
- if (successMsg === null) return Effect.void
2412
+ if (successMsg === null) return interruptWaiting
2368
2413
 
2369
- return toast.success(
2370
- successMsg,
2371
- toastId !== undefined ? { id: toastId, timeout: baseTimeout } : { timeout: baseTimeout }
2414
+ return interruptWaiting.pipe(
2415
+ Effect.andThen(toast.success(
2416
+ successMsg,
2417
+ toastId !== undefined ? { id: toastId, timeout: baseTimeout } : { timeout: baseTimeout }
2418
+ ))
2372
2419
  )
2373
2420
  }))
2374
2421
  )
@@ -2495,17 +2542,11 @@ export class CommanderImpl<RT, RTHooks> {
2495
2542
  readonly makeCommand = <
2496
2543
  const Id extends string,
2497
2544
  const State extends IntlRecord | undefined,
2498
- const I18nKey extends string = Id,
2499
- RunningA = unknown,
2500
- RunningE = unknown
2545
+ const I18nKey extends string = Id
2501
2546
  >(
2502
2547
  id_: Id | { id: Id },
2503
2548
  options?: FnOptions<Id, I18nKey, State>,
2504
- errorDef?: Error,
2505
- streamMeta?: {
2506
- running?: ComputedRef<AsyncResult.AsyncResult<RunningA, RunningE>> | undefined
2507
- progress?: ComputedRef<Progress | undefined> | undefined
2508
- }
2549
+ errorDef?: Error
2509
2550
  ) => {
2510
2551
  const id = typeof id_ === "string" ? id_ : id_.id
2511
2552
  const state = getStateValues(options)
@@ -2690,12 +2731,8 @@ export class CommanderImpl<RT, RTHooks> {
2690
2731
 
2691
2732
  /** reactive */
2692
2733
  result,
2693
- /** reactive live AsyncResult of the underlying stream, exposed only when
2694
- * the stream factory was called with a `progress` formatter */
2695
- running: streamMeta?.running,
2696
- /** reactive – formatted progress info for current `running` state, when `progress`
2697
- * formatter was supplied to the stream factory */
2698
- progress: streamMeta?.progress,
2734
+ /** always undefined for non-stream commands */
2735
+ progress: undefined,
2699
2736
  /** reactive */
2700
2737
  waiting,
2701
2738
  /** reactive */
@@ -2797,40 +2834,14 @@ export class CommanderImpl<RT, RTHooks> {
2797
2834
  fn = <
2798
2835
  const Id extends string,
2799
2836
  const State extends IntlRecord = IntlRecord,
2800
- const I18nKey extends string = Id,
2801
- RunningA = unknown,
2802
- RunningE = unknown
2837
+ const I18nKey extends string = Id
2803
2838
  >(
2804
- id:
2805
- | Id
2806
- | { id: Id }
2807
- | StreamMutationCallable<Id, any, RunningA, RunningE, any>
2808
- | StreamMutationFactory<Id, any, RunningA, RunningE, any>,
2839
+ id: Id | { id: Id },
2809
2840
  options?: FnOptions<Id, I18nKey, State>
2810
2841
  ): Commander.Gen<RT | RTHooks, Id, I18nKey, State> & Commander.NonGen<RT | RTHooks, Id, I18nKey, State> & {
2811
2842
  state: Context.Service<`Commander.Command.${Id}.state`, State>
2812
2843
  } => {
2813
- // Resolve id and (optionally) per-build stream metadata.
2814
- const resolvedId: Id = typeof id === "string" ? id : (id as { id: Id }).id
2815
- const factory = isStreamFactory(id)
2816
- const callable = !factory && isStreamCallable(id)
2817
- const resolveStreamMeta = ():
2818
- | {
2819
- running?: ComputedRef<AsyncResult.AsyncResult<RunningA, RunningE>> | undefined
2820
- progress?: ComputedRef<Progress | undefined> | undefined
2821
- }
2822
- | undefined =>
2823
- {
2824
- if (factory) {
2825
- const c = id()
2826
- return { running: c.running, progress: c.progress }
2827
- }
2828
- if (callable) {
2829
- const c = id as StreamMutationCallable<Id, any, RunningA, RunningE, any>
2830
- return { running: c.running, progress: c.progress }
2831
- }
2832
- return undefined
2833
- }
2844
+ const resolvedId: Id = typeof id === "string" ? id : id.id
2834
2845
  return Object.assign(
2835
2846
  (
2836
2847
  fn: any,
@@ -2842,9 +2853,7 @@ export class CommanderImpl<RT, RTHooks> {
2842
2853
  const errorDef = new Error()
2843
2854
  Error.stackTraceLimit = limit
2844
2855
 
2845
- const streamMeta = resolveStreamMeta()
2846
-
2847
- return this.makeCommand(resolvedId, options, errorDef, streamMeta)(
2856
+ return this.makeCommand(resolvedId, options, errorDef)(
2848
2857
  Effect.fnUntraced(
2849
2858
  // fnUntraced only supports generators as first arg, so we convert to generator if needed
2850
2859
  isGeneratorFunction(fn) ? fn : function*(...args) {
@@ -3047,8 +3056,6 @@ export class CommanderImpl<RT, RTHooks> {
3047
3056
  namespace: initialContext.namespace,
3048
3057
  namespaced: initialContext.namespaced,
3049
3058
  result,
3050
- /** always undefined for streamFn commands — `result` already exposes the live stream state */
3051
- running: undefined,
3052
3059
  /** reactive – progress driven by `Command.mapProgress` or `Command.updateProgress` inside the stream */
3053
3060
  progress,
3054
3061
  waiting,
@@ -3270,147 +3277,77 @@ export class CommanderImpl<RT, RTHooks> {
3270
3277
  * **User Feedback**: Use the `withDefaultToast` helper for status notifications, or render
3271
3278
  * the `result` inline for custom UI feedback.
3272
3279
  */
3273
- wrap = <
3280
+ streamWrap = <
3274
3281
  const Id extends string,
3275
3282
  Arg,
3276
- A,
3277
- E,
3278
- R,
3283
+ SA,
3284
+ SE,
3285
+ SR,
3279
3286
  const State extends IntlRecord = IntlRecord,
3280
3287
  I18nKey extends string = Id
3281
3288
  >(
3282
- mutation:
3283
- | { mutate: (arg: Arg) => Effect.Effect<A, E, R>; id: Id }
3284
- | ((arg: Arg) => Effect.Effect<A, E, R>) & { id: Id }
3285
- | StreamMutationFactory<Id, Arg, A, E, R>
3286
- | {
3287
- id: Id
3288
- mutateToResult:
3289
- | StreamMutationFactory<Id, Arg, A, E, R>
3290
- | StreamMutationCallable<Id, Arg, A, E, R>
3291
- }
3292
- | StreamMutationCallable<Id, Arg, A, E, R>,
3289
+ handler: (arg: Arg, ctx: Commander.CommandContextLocal2<Id, I18nKey, State>) => Stream.Stream<SA, SE, SR>,
3290
+ id: Id,
3293
3291
  options?: FnOptions<Id, I18nKey, State>
3294
- ): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> => {
3295
- if (mutation !== null && typeof mutation === "object" && "mutateToResult" in mutation) {
3296
- return this.wrapStream(mutation as any, options) as any
3297
- }
3298
- if (isStreamCallable(mutation) || isStreamFactory(mutation)) {
3299
- return this.wrapStream(mutation as any, options) as any
3300
- }
3301
- // At this point mutation is either { mutate, id } or (fn & { id })
3302
- const callMutation = mutation as
3303
- | { mutate: (arg: Arg) => Effect.Effect<A, E, R>; id: Id }
3304
- | (((arg: Arg) => Effect.Effect<A, E, R>) & { id: Id })
3292
+ ): Commander.StreamerWrap<RT | RTHooks, Id, I18nKey, State, Arg, SA, SE, SR> => {
3305
3293
  return Object.assign(
3306
- (
3307
- ...combinators: any[]
3308
- ): any => {
3309
- // we capture the definition stack here, so we can append it to later stack traces
3294
+ (...combinators: any[]): any => {
3310
3295
  const limit = Error.stackTraceLimit
3311
3296
  Error.stackTraceLimit = 2
3312
3297
  const errorDef = new Error()
3313
3298
  Error.stackTraceLimit = limit
3314
- const mutate = "mutate" in callMutation
3315
- ? callMutation.mutate
3316
- : callMutation
3317
-
3318
- return this.makeCommand(callMutation.id, options, errorDef)(
3319
- Effect.fnUntraced(
3320
- // fnUntraced only supports generators as first arg, so we convert to generator if needed
3321
- isGeneratorFunction(mutate) ? mutate : function*(arg: Arg) {
3322
- return yield* mutate(arg)
3323
- },
3324
- ...combinators as [any]
3325
- ) as any
3299
+ return this.makeStreamCommand(id, options, errorDef)(
3300
+ combinators.length === 0
3301
+ ? handler
3302
+ : (arg: Arg, ctx: Commander.CommandContextLocal2<Id, I18nKey, State>) => {
3303
+ let current: any = handler(arg, ctx)
3304
+ for (const combinator of combinators) {
3305
+ current = combinator(current, arg, ctx)
3306
+ }
3307
+ return current
3308
+ }
3326
3309
  )
3327
3310
  },
3328
- makeBaseInfo(callMutation.id, options),
3311
+ makeBaseInfo(id, options),
3329
3312
  {
3330
3313
  state: Context.Service<`Commander.Command.${Id}.state`, State>(
3331
- `Commander.Command.${callMutation.id}.state`
3314
+ `Commander.Command.${id}.state`
3332
3315
  )
3333
3316
  }
3334
3317
  )
3335
3318
  }
3336
3319
 
3337
- /**
3338
- * Define a Command from a stream-type mutation (`mutateToResult` factory).
3339
- * The stream's reactive `AsyncResult` ref is exposed as `running` for independent progress tracking.
3340
- * The command's own `result` reflects the execution outcome of the `execute` function.
3341
- * Supports the same combinator pipeline as `wrap` (e.g. `withDefaultToast`).
3342
- *
3343
- * Each invocation of the resulting wrap call produces a fresh `[ref, execute]` pair
3344
- * (the `mutateToResult` factory is called once per build), so independent commands
3345
- * don't share progress state.
3346
- *
3347
- * Accepts either:
3348
- * - An object with `id` and `mutateToResult` factory (e.g. a client entry)
3349
- * - The `mutateToResult` factory directly (callable, with `id`)
3350
- * - An already-called factory result (`[resultRef, execute] & { id }`) — shared ref across builds
3351
- *
3352
- * @example
3353
- * ```ts
3354
- * // Via client entry (recommended):
3355
- * const exportCmd = Command.wrapStream(client.myExport)()
3356
- *
3357
- * // Via factory directly:
3358
- * const exportCmd = Command.wrapStream(client.myExport.mutateToResult)()
3359
- *
3360
- * // Via already-called factory (shared ref):
3361
- * const stream = client.myExport.mutateToResult()
3362
- * const exportCmd = Command.wrapStream(stream)()
3363
- * ```
3364
- */
3365
- wrapStream = <
3320
+ wrap = <
3366
3321
  const Id extends string,
3367
3322
  Arg,
3368
3323
  A,
3369
3324
  E,
3370
3325
  R,
3371
3326
  const State extends IntlRecord = IntlRecord,
3372
- const I18nKey extends string = Id
3327
+ I18nKey extends string = Id
3373
3328
  >(
3374
3329
  mutation:
3375
- | {
3376
- id: Id
3377
- mutateToResult:
3378
- | StreamMutationFactory<Id, Arg, A, E, R>
3379
- | StreamMutationCallable<Id, Arg, A, E, R>
3380
- }
3381
- | StreamMutationFactory<Id, Arg, A, E, R>
3382
- | StreamMutationCallable<Id, Arg, A, E, R>,
3330
+ | { mutate: (arg: Arg) => Effect.Effect<A, E, R>; id: Id }
3331
+ | ((arg: Arg) => Effect.Effect<A, E, R>) & { id: Id },
3383
3332
  options?: FnOptions<Id, I18nKey, State>
3384
3333
  ): Commander.CommanderWrap<RT | RTHooks, Id, I18nKey, State, Arg, A, E, R> => {
3385
- const id = mutation.id
3386
- // Resolve `source` to the factory or already-invoked callable.
3387
- const source: StreamMutationFactory<Id, Arg, A, E, R> | StreamMutationCallable<Id, Arg, A, E, R> =
3388
- mutation !== null && typeof mutation === "object" && "mutateToResult" in mutation
3389
- ? (mutation.mutateToResult as any)
3390
- : (mutation as any)
3391
- const resolveCallable = (): StreamMutationCallable<Id, Arg, A, E, R> =>
3392
- (isStreamFactory(source)
3393
- ? (source as StreamMutationFactory<Id, Arg, A, E, R>)()
3394
- : source) as StreamMutationCallable<Id, Arg, A, E, R>
3334
+ const callMutation = mutation
3395
3335
  return Object.assign(
3396
- (...combinators: any[]): any => {
3336
+ (
3337
+ ...combinators: any[]
3338
+ ): any => {
3397
3339
  // we capture the definition stack here, so we can append it to later stack traces
3398
3340
  const limit = Error.stackTraceLimit
3399
3341
  Error.stackTraceLimit = 2
3400
3342
  const errorDef = new Error()
3401
3343
  Error.stackTraceLimit = limit
3344
+ const mutate = "mutate" in callMutation
3345
+ ? callMutation.mutate
3346
+ : callMutation
3402
3347
 
3403
- // Fresh per build: invoke the factory once per command instance so each
3404
- // wrap call gets its own state + execute pair. `running`/`progress`
3405
- // are only surfaced when the factory was called with a `progress` formatter.
3406
- const callable = resolveCallable()
3407
- const mutate: (_arg: Arg) => Effect.Effect<any, E, R> = Effect.isEffect(callable)
3408
- ? (_arg: Arg) => callable
3409
- : callable as (arg: Arg) => Effect.Effect<any, E, R>
3410
- const streamMeta = { running: callable.running, progress: callable.progress }
3411
-
3412
- return this.makeCommand(id, options, errorDef, streamMeta)(
3348
+ return this.makeCommand(callMutation.id, options, errorDef)(
3413
3349
  Effect.fnUntraced(
3350
+ // fnUntraced only supports generators as first arg, so we convert to generator if needed
3414
3351
  isGeneratorFunction(mutate) ? mutate : function*(arg: Arg) {
3415
3352
  return yield* mutate(arg)
3416
3353
  },
@@ -3418,10 +3355,10 @@ export class CommanderImpl<RT, RTHooks> {
3418
3355
  ) as any
3419
3356
  )
3420
3357
  },
3421
- makeBaseInfo(id, options),
3358
+ makeBaseInfo(callMutation.id, options),
3422
3359
  {
3423
3360
  state: Context.Service<`Commander.Command.${Id}.state`, State>(
3424
- `Commander.Command.${id}.state`
3361
+ `Commander.Command.${callMutation.id}.state`
3425
3362
  )
3426
3363
  }
3427
3364
  )