@effect-app/vue 4.0.0-beta.182 → 4.0.0-beta.183

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,12 +1,13 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { asResult, deepToRaw, type MissingDependencies, reportRuntimeError } from "@effect-app/vue"
2
+ import { asResult, asStreamResult, deepToRaw, type MissingDependencies, reportRuntimeError } from "@effect-app/vue"
3
3
  import { reportMessage } from "@effect-app/vue/errorReporter"
4
4
  import { Cause, Context, Effect, type Exit, type Fiber, flow, Layer, Match, MutableHashMap, Option, Predicate, S } from "effect-app"
5
5
  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 * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
9
+ import * as Stream from "effect/Stream"
10
+ import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"
10
11
  import { type FormatXMLElementFn, type PrimitiveType } from "intl-messageformat"
11
12
  import { computed, type ComputedRef, reactive, ref, toRaw } from "vue"
12
13
  import { Confirm } from "./confirm.js"
@@ -125,6 +126,35 @@ export class CommandContext extends Context.Service<CommandContext, {
125
126
  "CommandContext"
126
127
  ) {}
127
128
 
129
+ /**
130
+ * Service available inside `streamFn` stream handlers that lets you imperatively push
131
+ * progress updates to the command's reactive `progress` ref.
132
+ *
133
+ * Use `Command.mapProgress(fn)` or `Command.updateProgress(progress)` to interact with this service.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * // Using mapProgress (recommended) — applied as a stream pipe operator:
138
+ * const exportCmd = Command.streamFn("exportData")(
139
+ * function*(arg, ctx) {
140
+ * return makeExportStream(arg.id).pipe(
141
+ * Command.mapProgress((r) =>
142
+ * AsyncResult.isSuccess(r) && r.value._tag === "OperationProgress"
143
+ * ? { text: `${r.value.completed}/${r.value.total}`, percentage: r.value.completed / r.value.total * 100 }
144
+ * : undefined
145
+ * )
146
+ * )
147
+ * }
148
+ * )
149
+ * // exportCmd.progress is updated for every OperationProgress event
150
+ * ```
151
+ */
152
+ export class CommandProgress extends Context.Reference<{
153
+ readonly update: (progress: Progress | undefined) => Effect.Effect<void>
154
+ }>("Commander.CommandProgress", {
155
+ defaultValue: () => ({ update: (_progress: Progress | undefined): Effect.Effect<void> => Effect.void })
156
+ }) {}
157
+
128
158
  export type EmitWithCallback<A, Event extends string> = (event: Event, value: A, onDone: () => void) => void
129
159
 
130
160
  /**
@@ -1716,6 +1746,132 @@ export declare namespace Commander {
1716
1746
  ) => Eff
1717
1747
  ): CommandOutHelper<Arg, Eff, Id, I18nKey, State>
1718
1748
  }
1749
+
1750
+ /**
1751
+ * Type for `streamFn` — generator overload where the body yields Effects and returns a `Stream`.
1752
+ * `waiting` stays `true` while the stream is running, and updates the `result` ref per emitted value.
1753
+ */
1754
+ export type StreamGen<RT, Id extends string, I18nKey extends string, State extends IntlRecord | undefined> = {
1755
+ <
1756
+ Eff extends Effect.Yieldable<any, any, any, RT | CommandContext | `Commander.Command.${Id}.state`>,
1757
+ SA,
1758
+ SE,
1759
+ SR,
1760
+ Arg = void
1761
+ >(
1762
+ body: (
1763
+ arg: Arg,
1764
+ ctx: CommandContextLocal2<Id, I18nKey, State>
1765
+ ) => Generator<Eff, Stream.Stream<SA, SE, SR>, never>
1766
+ ): CommandOut<
1767
+ Arg,
1768
+ SA,
1769
+ | SE
1770
+ | ([Eff] extends [never] ? never
1771
+ : [Eff] extends [Effect.Yieldable<any, infer _A, infer E, infer _R>] ? E
1772
+ : never),
1773
+ | SR
1774
+ | ([Eff] extends [never] ? never
1775
+ : [Eff] extends [Effect.Yieldable<any, infer _A, infer _E, infer R>] ? R
1776
+ : never),
1777
+ Id,
1778
+ I18nKey,
1779
+ State
1780
+ >
1781
+ <
1782
+ Eff extends Effect.Yieldable<any, any, any, RT | CommandContext | `Commander.Command.${Id}.state`>,
1783
+ SA,
1784
+ SE,
1785
+ SR,
1786
+ B,
1787
+ Arg = void
1788
+ >(
1789
+ body: (
1790
+ arg: Arg,
1791
+ ctx: CommandContextLocal2<Id, I18nKey, State>
1792
+ ) => Generator<Eff, Stream.Stream<SA, SE, SR>, never>,
1793
+ a: (
1794
+ _: Effect.Effect<
1795
+ Stream.Stream<SA, SE, SR>,
1796
+ ([Eff] extends [never] ? never
1797
+ : [Eff] extends [Effect.Yieldable<any, infer _A, infer E, infer _R>] ? E
1798
+ : never),
1799
+ ([Eff] extends [never] ? never
1800
+ : [Eff] extends [Effect.Yieldable<any, infer _A, infer _E, infer R>] ? R
1801
+ : never)
1802
+ >,
1803
+ arg: ArgForCombinator<Arg>,
1804
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1805
+ ) => B
1806
+ ): B extends Stream.Stream<infer SA2, infer SE2, infer SR2> ? CommandOut<Arg, SA2, SE2, SR2, Id, I18nKey, State>
1807
+ : B extends Effect.Effect<Stream.Stream<infer SA2, infer SE2, infer SR2>, infer EE2, infer ER2>
1808
+ ? CommandOut<Arg, SA2, SE2 | EE2, SR2 | ER2, Id, I18nKey, State>
1809
+ : never
1810
+ }
1811
+
1812
+ /**
1813
+ * Type for `streamFn` — non-generator overload accepting a function that returns a `Stream` directly,
1814
+ * or an `Effect` that resolves to a `Stream`.
1815
+ */
1816
+ export type NonGenStream<RT, Id extends string, I18nKey extends string, State extends IntlRecord | undefined> = {
1817
+ <
1818
+ SA,
1819
+ SE,
1820
+ SR extends RT | CommandContext | `Commander.Command.${Id}.state`,
1821
+ Arg = void
1822
+ >(
1823
+ body: (arg: Arg, ctx: CommandContextLocal2<Id, I18nKey, State>) => Stream.Stream<SA, SE, SR>
1824
+ ): CommandOut<Arg, SA, SE, SR, Id, I18nKey, State>
1825
+ <
1826
+ SA,
1827
+ SE,
1828
+ SR,
1829
+ A extends Stream.Stream<any, any, RT | CommandContext | `Commander.Command.${Id}.state`>,
1830
+ Arg = void
1831
+ >(
1832
+ body: (arg: Arg, ctx: CommandContextLocal2<Id, I18nKey, State>) => Stream.Stream<SA, SE, SR>,
1833
+ a: (
1834
+ _: Stream.Stream<SA, SE, SR>,
1835
+ arg: ArgForCombinator<Arg>,
1836
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1837
+ ) => A
1838
+ ): CommandOut<Arg, Stream.Success<A>, Stream.Error<A>, Stream.Services<A>, Id, I18nKey, State>
1839
+ <
1840
+ SA,
1841
+ SE,
1842
+ SR,
1843
+ EE,
1844
+ ER extends RT | CommandContext | `Commander.Command.${Id}.state`,
1845
+ Arg = void
1846
+ >(
1847
+ body: (
1848
+ arg: Arg,
1849
+ ctx: CommandContextLocal2<Id, I18nKey, State>
1850
+ ) => Effect.Effect<Stream.Stream<SA, SE, SR>, EE, ER>
1851
+ ): CommandOut<Arg, SA, SE | EE, SR | ER, Id, I18nKey, State>
1852
+ <
1853
+ SA,
1854
+ SE,
1855
+ SR,
1856
+ EE,
1857
+ ER,
1858
+ B,
1859
+ Arg = void
1860
+ >(
1861
+ body: (
1862
+ arg: Arg,
1863
+ ctx: CommandContextLocal2<Id, I18nKey, State>
1864
+ ) => Effect.Effect<Stream.Stream<SA, SE, SR>, EE, ER>,
1865
+ a: (
1866
+ _: Effect.Effect<Stream.Stream<SA, SE, SR>, EE, ER>,
1867
+ arg: ArgForCombinator<Arg>,
1868
+ ctx: CommandContextLocal2<NoInfer<Id>, NoInfer<I18nKey>, NoInfer<State>>
1869
+ ) => B
1870
+ ): B extends Stream.Stream<infer SA2, infer SE2, infer SR2> ? CommandOut<Arg, SA2, SE2, SR2, Id, I18nKey, State>
1871
+ : B extends Effect.Effect<Stream.Stream<infer SA2, infer SE2, infer SR2>, infer EE2, infer ER2>
1872
+ ? CommandOut<Arg, SA2, SE2 | EE2, SR2 | ER2, Id, I18nKey, State>
1873
+ : never
1874
+ }
1719
1875
  }
1720
1876
 
1721
1877
  type ErrorRenderer<E, Args extends readonly any[]> = (e: E, action: string, ...args: Args) => string | undefined
@@ -1838,6 +1994,66 @@ export const CommanderStatic = {
1838
1994
  ) =>
1839
1995
  (self: In, arg: Arg, arg2: Arg2) => cb(arg, arg2)(self),
1840
1996
 
1997
+ /**
1998
+ * Stream pipe operator that maps each emitted value to a `Progress` entry and updates the
1999
+ * command's reactive `progress` ref via the `CommandProgress` service.
2000
+ *
2001
+ * The mapper receives an `AsyncResult<A, E>` (each emitted value wrapped as
2002
+ * `AsyncResult.success(value, { waiting: true })`), matching the same shape used by
2003
+ * `CommandButton`'s `:progress-map` prop.
2004
+ *
2005
+ * Designed to be used inside a `streamFn` handler (either directly with `.pipe()`, or as
2006
+ * a combinator argument):
2007
+ *
2008
+ * @example
2009
+ * ```ts
2010
+ * // Inside the handler body:
2011
+ * Command.streamFn("exportData")(function*(arg, ctx) {
2012
+ * return makeExportStream(arg.id).pipe(
2013
+ * Command.mapProgress((r) =>
2014
+ * AsyncResult.isSuccess(r) && r.value._tag === "OperationProgress"
2015
+ * ? { text: `${r.value.completed}/${r.value.total}`, percentage: r.value.completed / r.value.total * 100 }
2016
+ * : undefined
2017
+ * )
2018
+ * )
2019
+ * })
2020
+ *
2021
+ * // Or as a stream combinator argument:
2022
+ * Command.streamFn("exportData")(
2023
+ * function*(arg, ctx) { return makeExportStream(arg.id) },
2024
+ * (s) => s.pipe(Command.mapProgress((r) => AsyncResult.isSuccess(r) && r.value._tag === "OperationProgress" ? { text: `${r.value.completed}/${r.value.total}` } : undefined))
2025
+ * )
2026
+ * ```
2027
+ */
2028
+ mapProgress:
2029
+ <A, E>(fn: (result: AsyncResult.AsyncResult<A, E>) => Progress | undefined) =>
2030
+ <R>(stream: Stream.Stream<A, E, R>): Stream.Stream<A, E, R> =>
2031
+ stream.pipe(
2032
+ Stream.tap((v) => {
2033
+ const p = fn(AsyncResult.success(v, { waiting: true }))
2034
+ return p !== undefined ? CommandProgress.use((s) => s.update(p)) : Effect.void
2035
+ })
2036
+ ),
2037
+
2038
+ /**
2039
+ * Imperatively push a progress update from inside a `streamFn` handler.
2040
+ * Requires `CommandProgress` to be in context — provided automatically for all `streamFn` streams.
2041
+ *
2042
+ * @example
2043
+ * ```ts
2044
+ * // In a streamFn handler:
2045
+ * stream.pipe(
2046
+ * Stream.tap((event) =>
2047
+ * event._tag === "OperationProgress"
2048
+ * ? Command.updateProgress({ text: `${event.completed}/${event.total}`, percentage: event.completed / event.total * 100 })
2049
+ * : Effect.void
2050
+ * )
2051
+ * )
2052
+ * ```
2053
+ */
2054
+ updateProgress: (progress: Progress | undefined): Effect.Effect<void> =>
2055
+ CommandProgress.use((s) => s.update(progress)),
2056
+
1841
2057
  /** Version of @see confirmOrInterrupt that automatically includes the action name in the default messages */
1842
2058
  confirmOrInterrupt: Effect.fnUntraced(function*(
1843
2059
  message: string | undefined = undefined
@@ -2465,7 +2681,322 @@ export class CommanderImpl<RT, RTHooks> {
2465
2681
  )
2466
2682
  }
2467
2683
 
2684
+ /**
2685
+ * Internal factory for stream-backed commands. Accepts a handler that returns a `Stream` directly.
2686
+ * Services (`CommandContext`, `stateTag`) are provided to the stream via `Stream.provideServiceEffect`.
2687
+ */
2688
+ readonly makeStreamCommand = <
2689
+ const Id extends string,
2690
+ const State extends IntlRecord | undefined,
2691
+ const I18nKey extends string = Id
2692
+ >(
2693
+ id_: Id | { id: Id },
2694
+ options?: FnOptions<Id, I18nKey, State>,
2695
+ errorDef?: Error
2696
+ ) => {
2697
+ const id = typeof id_ === "string" ? id_ : id_.id
2698
+ const state = getStateValues(options)
2699
+
2700
+ return Object.assign(
2701
+ <Arg, SA, SE, SR>(
2702
+ handler: (arg: Arg, ctx: Commander.CommandContextLocal2<Id, I18nKey, State>) => Stream.Stream<SA, SE, SR>
2703
+ ) => {
2704
+ const limit = Error.stackTraceLimit
2705
+ Error.stackTraceLimit = 2
2706
+ const localErrorDef = new Error()
2707
+ Error.stackTraceLimit = limit
2708
+ if (!errorDef) {
2709
+ errorDef = localErrorDef
2710
+ }
2711
+
2712
+ const key = `Commander.Command.${id}.state` as const
2713
+ const stateTag = Context.Service<typeof key, State>(key)
2714
+
2715
+ const makeContext_ = () => this.makeContext(id, { ...options, state: state?.value })
2716
+ const initialContext = makeContext_()
2717
+ const context = computed(() => makeContext_())
2718
+ const action = computed(() => context.value.action)
2719
+ const label = computed(() => context.value.label)
2720
+
2721
+ const currentState = Effect.sync(() => state.value)
2722
+
2723
+ // Reactive ref driven by the CommandProgress service — updated imperatively
2724
+ // from inside the stream via `Command.mapProgress(fn)` or `Command.updateProgress(p)`.
2725
+ const progressRef = ref<Progress | undefined>(undefined)
2726
+ const commandProgressService = {
2727
+ update: (p: Progress | undefined) =>
2728
+ Effect.sync(() => {
2729
+ progressRef.value = p
2730
+ })
2731
+ }
2732
+
2733
+ const streamErrorReporter = <A, E, R>(self: Stream.Stream<A, E, R>) =>
2734
+ self.pipe(
2735
+ Stream.tapCause(
2736
+ Effect.fnUntraced(function*(cause) {
2737
+ if (Cause.hasInterruptsOnly(cause)) {
2738
+ console.info(`Interrupted while trying to ${id}`)
2739
+ return
2740
+ }
2741
+
2742
+ const fail = Cause.findErrorOption(cause)
2743
+ if (Option.isSome(fail)) {
2744
+ const message = `Failure trying to ${id}`
2745
+ yield* reportMessage(message, {
2746
+ action: id,
2747
+ error: fail.value
2748
+ })
2749
+ return
2750
+ }
2751
+
2752
+ const ctx = yield* CommandContext
2753
+ const extra = {
2754
+ action: ctx.action,
2755
+ message: `Unexpected Error trying to ${id}`
2756
+ }
2757
+ yield* reportRuntimeError(cause, extra)
2758
+ }, Effect.uninterruptible)
2759
+ )
2760
+ )
2761
+
2762
+ const theStreamHandler = (arg: Arg, ctx: Commander.CommandContextLocal2<Id, I18nKey, State>) =>
2763
+ handler(arg, ctx).pipe(
2764
+ streamErrorReporter,
2765
+ Stream.provideService(CommandProgress, commandProgressService),
2766
+ Stream.provideServiceEffect(stateTag, currentState),
2767
+ Stream.provideServiceEffect(CommandContext, Effect.sync(() => makeContext_()))
2768
+ )
2769
+
2770
+ const waitId = options?.waitKey ? options.waitKey(id) : undefined
2771
+ const blockId = options?.blockKey ? options.blockKey(id) : undefined
2772
+
2773
+ const [result, exec_] = asStreamResult(theStreamHandler)
2774
+
2775
+ const exec = Effect
2776
+ .fnUntraced(
2777
+ function*(...args: [any, any]) {
2778
+ if (waitId !== undefined) registerWait(waitId)
2779
+ if (blockId !== undefined && blockId !== waitId) {
2780
+ registerWait(blockId)
2781
+ }
2782
+ return yield* exec_(...args)
2783
+ },
2784
+ Effect.onExit(() =>
2785
+ Effect.sync(() => {
2786
+ if (waitId !== undefined) unregisterWait(waitId)
2787
+ if (blockId !== undefined && blockId !== waitId) {
2788
+ unregisterWait(blockId)
2789
+ }
2790
+ })
2791
+ )
2792
+ )
2793
+
2794
+ const waiting = waitId !== undefined
2795
+ ? computed(() => result.value.waiting || (waitState.value[waitId] ?? 0) > 0)
2796
+ : computed(() => result.value.waiting)
2797
+
2798
+ const blocked = blockId !== undefined
2799
+ ? computed(() => waiting.value || (waitState.value[blockId] ?? 0) > 0)
2800
+ : computed(() => waiting.value)
2801
+
2802
+ const computeAllowed = options?.allowed
2803
+ const allowed = computeAllowed ? computed(() => computeAllowed(id, state)) : true
2804
+
2805
+ const rt = Effect.context<RT | RTHooks>().pipe(Effect.provide(this.hooks)).pipe(Effect.runSyncWith(this.rt))
2806
+ const runFork = Effect.runForkWith(rt)
2807
+
2808
+ const progress = progressRef
2809
+
2810
+ const handle = Object.assign((arg: Arg) => {
2811
+ arg = toRaw(arg)
2812
+ progressRef.value = undefined // reset progress on new invocation
2813
+ const limit = Error.stackTraceLimit
2814
+ Error.stackTraceLimit = 2
2815
+ const errorCall = new Error()
2816
+ Error.stackTraceLimit = limit
2817
+
2818
+ let cache: false | string = false
2819
+ const captureStackTrace = () => {
2820
+ if (cache !== false) {
2821
+ return cache
2822
+ }
2823
+ if (errorCall.stack) {
2824
+ const stackDef = errorDef!.stack!.trim().split("\n")
2825
+ const stackCall = errorCall.stack.trim().split("\n")
2826
+ let endStackDef = stackDef.slice(2).join("\n").trim()
2827
+ if (!endStackDef.includes(`(`)) {
2828
+ endStackDef = endStackDef.replace(/at (.*)/, "at ($1)")
2829
+ }
2830
+ let endStackCall = stackCall.slice(2).join("\n").trim()
2831
+ if (!endStackCall.includes(`(`)) {
2832
+ endStackCall = endStackCall.replace(/at (.*)/, "at ($1)")
2833
+ }
2834
+ cache = `${endStackDef}\n${endStackCall}`
2835
+ return cache
2836
+ }
2837
+ }
2838
+
2839
+ const command = currentState.pipe(Effect.flatMap((state) => {
2840
+ const rawArg = deepToRaw(arg)
2841
+ const rawState = deepToRaw(state)
2842
+ return Effect.withSpan(
2843
+ exec(arg, { ...context.value, state } as any),
2844
+ id,
2845
+ {
2846
+ captureStackTrace,
2847
+ attributes: {
2848
+ input: rawArg,
2849
+ state: rawState,
2850
+ action: initialContext.action,
2851
+ label: initialContext.label,
2852
+ id: initialContext.id,
2853
+ i18nKey: initialContext.i18nKey
2854
+ }
2855
+ }
2856
+ )
2857
+ }))
2858
+
2859
+ return runFork(command as any)
2860
+ }, { action, label })
2861
+
2862
+ return reactive({
2863
+ id,
2864
+ i18nKey: initialContext.i18nKey,
2865
+ namespace: initialContext.namespace,
2866
+ namespaced: initialContext.namespaced,
2867
+ result,
2868
+ /** always undefined for streamFn commands — `result` already exposes the live stream state */
2869
+ running: undefined,
2870
+ /** reactive – progress driven by `Command.mapProgress` or `Command.updateProgress` inside the stream */
2871
+ progress,
2872
+ waiting,
2873
+ blocked,
2874
+ allowed,
2875
+ action,
2876
+ label,
2877
+ state,
2878
+ handle
2879
+ })
2880
+ },
2881
+ { id }
2882
+ )
2883
+ }
2884
+
2885
+ /**
2886
+ * Define a stream-backed Command for handling user actions.
2887
+ *
2888
+ * Like `fn`, but the body generator (or function) must **return** a `Stream` rather than
2889
+ * an `Effect`. The command's `waiting` state stays `true` while the stream is running and
2890
+ * is set to `false` once it terminates. The reactive `result` ref is updated for every
2891
+ * value emitted by the stream.
2892
+ *
2893
+ * Three handler shapes are accepted:
2894
+ * 1. **Generator returning a Stream** (primary) — may yield Effects freely before returning the stream:
2895
+ * ```ts
2896
+ * Command.streamFn("exportData")(
2897
+ * function*(arg, ctx) {
2898
+ * const token = yield* getAuthToken
2899
+ * return Stream.fromEffect(startExport(token, arg.id)).pipe(
2900
+ * Stream.flatMap((job) => pollProgress(job.id))
2901
+ * )
2902
+ * }
2903
+ * )
2904
+ * ```
2905
+ * 2. **Function returning a Stream directly**: `(arg, ctx) => Stream.make(1, 2, 3)`
2906
+ * 3. **Function returning `Effect<Stream>`**: `(arg, ctx) => Effect.map(setup, (s) => s.stream)`
2907
+ *
2908
+ * @param id The internal identifier for the action (used for tracing and i18n lookup).
2909
+ * @param options Same options as `fn` (`state`, `blockKey`, `waitKey`, `allowed`, `i18nCustomKey`).
2910
+ *
2911
+ * **Progress** — use `Command.mapProgress(fn)` as a stream pipe operator; the mapper receives
2912
+ * `AsyncResult<A, E>` (each value wrapped as `AsyncResult.success(v, { waiting: true })`),
2913
+ * matching the same shape as CommandButton’s `:progress-map` prop. Or call
2914
+ * `Command.updateProgress(p)` for imperative control:
2915
+ *
2916
+ * ```ts
2917
+ * // mapProgress as a combinator arg (outside the handler):
2918
+ * Command.streamFn("exportData")(
2919
+ * function*(arg, ctx) { return makeExportStream(arg.id) },
2920
+ * (s) => s.pipe(Command.mapProgress((r) => AsyncResult.isSuccess(r) && r.value._tag === "OperationProgress" ? { text: `${r.value.completed}/${r.value.total}` } : undefined))
2921
+ * )
2922
+ *
2923
+ * // Or inline inside the handler body:
2924
+ * Command.streamFn("exportData")(function*(arg, ctx) {
2925
+ * return makeExportStream(arg.id).pipe(Command.mapProgress((r) => AsyncResult.isSuccess(r) ? ... : undefined))
2926
+ * })
2927
+ * ```
2928
+ *
2929
+ * **Pipeable combinators** — the 2nd–Nth args follow the same pattern as `fn`: each combinator
2930
+ * receives `(stream, arg, ctx)` and returns a transformed stream:
2931
+ * ```ts
2932
+ * Command.streamFn("exportData")(
2933
+ * handler,
2934
+ * (s, arg, ctx) => s.pipe(Command.mapProgress(fn), Stream.take(100))
2935
+ * )
2936
+ * ```
2937
+ *
2938
+ * **Returned Properties**: `action`, `label`, `result`, `progress`, `waiting`, `blocked`,
2939
+ * `allowed`, `handle`, `i18nKey`, `namespace`, `namespaced`.
2940
+ */
2941
+ streamFn = <
2942
+ const Id extends string,
2943
+ const State extends IntlRecord = IntlRecord,
2944
+ const I18nKey extends string = Id
2945
+ >(
2946
+ id: Id | { id: Id },
2947
+ options?: FnOptions<Id, I18nKey, State>
2948
+ ):
2949
+ & Commander.StreamGen<RT | RTHooks, Id, I18nKey, State>
2950
+ & Commander.NonGenStream<RT | RTHooks, Id, I18nKey, State>
2951
+ & {
2952
+ state: Context.Service<`Commander.Command.${Id}.state`, State>
2953
+ } =>
2954
+ {
2955
+ const resolvedId = typeof id === "string" ? id : id.id
2956
+
2957
+ type StreamOrEffect = Stream.Stream<any, any, any> | Effect.Effect<Stream.Stream<any, any, any>, any, any>
2958
+
2959
+ const toRawHandler = (fn: any): (arg: any, ctx: any) => StreamOrEffect => {
2960
+ if (isGeneratorFunction(fn)) {
2961
+ return Effect.fnUntraced(function*(arg: any, ctx: any) {
2962
+ return yield* (fn as (arg: any, ctx: any) => Generator<any, Stream.Stream<any, any, any>, any>)(arg, ctx)
2963
+ })
2964
+ }
2965
+ return fn
2966
+ }
2967
+
2968
+ const toFinalStream = (value: StreamOrEffect): Stream.Stream<any, any, any> =>
2969
+ Stream.isStream(value) ? value : Stream.unwrap(value as Effect.Effect<Stream.Stream<any, any, any>, any, any>)
2970
+
2971
+ return Object.assign(
2972
+ (fn: any, ...combinators: Array<(s: any, arg: any, ctx: any) => any>): any => {
2973
+ const limit = Error.stackTraceLimit
2974
+ Error.stackTraceLimit = 2
2975
+ const errorDef = new Error()
2976
+ Error.stackTraceLimit = limit
2977
+
2978
+ const rawHandler = toRawHandler(fn)
2979
+ const handler = (arg: any, ctx: any) => {
2980
+ let current: any = rawHandler(arg, ctx)
2981
+ for (const combinator of combinators) {
2982
+ current = combinator(current, arg, ctx)
2983
+ }
2984
+ return toFinalStream(current)
2985
+ }
2986
+
2987
+ return this.makeStreamCommand(id, options, errorDef)(handler)
2988
+ },
2989
+ makeBaseInfo(resolvedId, options),
2990
+ {
2991
+ state: Context.Service<`Commander.Command.${Id}.state`, State>(
2992
+ `Commander.Command.${resolvedId}.state`
2993
+ )
2994
+ }
2995
+ )
2996
+ }
2997
+
2468
2998
  /** @deprecated */
2999
+
2469
3000
  alt2: <
2470
3001
  const Id extends string,
2471
3002
  MutArg,