/* eslint-disable @typescript-eslint/no-explicit-any */ import { asResult, deepToRaw, type MissingDependencies, reportRuntimeError } from "@effect-app/vue" import { reportMessage } from "@effect-app/vue/errorReporter" import { Cause, Context, Effect, type Exit, type Fiber, flow, Layer, Match, MutableHashMap, Option, Predicate, S } from "effect-app" import { SupportedErrors } from "effect-app/client" import { OperationFailure, OperationSuccess } from "effect-app/Operations" import { isGeneratorFunction, wrapEffect } from "effect-app/utils" import { type Refinement } from "effect/Predicate" import { type AsyncResult } from "effect/unstable/reactivity/AsyncResult" import { type FormatXMLElementFn, type PrimitiveType } from "intl-messageformat" import { computed, type ComputedRef, reactive, ref, toRaw } from "vue" import { Confirm } from "./confirm.js" import { I18n } from "./intl.js" import { WithToast } from "./withToast.js" type IntlRecord = Record> type FnOptions = { i18nCustomKey?: I18nCustomKey /** * passed to the i18n formatMessage calls so you can use it in translation messagee * including the Command `action` string. * Automatically wrapped with Computed if just a thunk. * provided as Command.state tag, so you can access it in the function. */ state?: ComputedRef | (() => State) // TODO: namespaced keys like reactivity keys: ["modify_thing", item], so that one can block also on "modify_thing" * blockKey?: (id: Id) => string | undefined waitKey?: (id: Id) => string | undefined allowed?: (id: Id, state: ComputedRef) => boolean } type FnOptionsInternal = { i18nCustomKey?: I18nCustomKey | undefined state?: IntlRecord | undefined } export const DefaultIntl = { de: { "handle.confirmation": "{action} bestätigen?", "handle.waiting": "{action} wird ausgeführt...", "handle.success": "{action} erfolgreich", "handle.with_errors": "{action} fehlgeschlagen", "handle.with_warnings": "{action} erfolgreich, mit Warnungen", "handle.error_response": "Die Anfrage war nicht erfolgreich:\n{error}\nWir wurden benachrichtigt und werden das Problem in Kürze beheben.", "handle.response_error": "Die Antwort konnte nicht verarbeitet werden:\n{error}", "handle.request_error": "Die Anfrage konnte nicht gesendet werden:\n{error}", "handle.unexpected_error2": "{action} unerwarteter Fehler, probieren sie es in kurze nochmals.", "handle.unexpected_error": "Unerwarteter Fehler:\n{error}", "handle.not_found": "Das gesuchte war nicht gefunden" }, en: { "handle.confirmation": "Confirm {action}?", "handle.waiting": "{action} executing...", "handle.success": "{action} Success", "handle.with_errors": "{action} Failed", "handle.with_warnings": "{action}, with warnings", "handle.error_response": "There was an error in processing the response:\n{error}\nWe have been notified and will fix the problem shortly.", "handle.request_error": "There was an error in the request:\n{error}", "handle.response_error": "The request was not successful:\n{error}", "handle.unexpected_error2": "{action} unexpected error, please try again shortly.", "handle.unexpected_error": "Unexpected Error:\n{error}", "handle.not_found": "The requested item was not found." } } export class CommandContext extends Context.Service string state?: IntlRecord | undefined }>()( "CommandContext" ) {} export type EmitWithCallback = (event: Event, value: A, onDone: () => void) => void /** * Use to wrap emit calls with a callback to signal completion. * Useful when the publisher wants to wait for the subscriber to finish processing. */ export const wrapEmit = ( emit: EmitWithCallback>, event: Event ) => (value: A) => new Promise((resolve) => emit(event, value, resolve)) export declare namespace Commander { export type CommanderBase = & Gen & NonGen & CommandContextLocal & { state: Context.Service<`Commander.Command.${Id}.state`, State> } export type CommanderFn = CommanderBase export type CommanderWrap< RT, Id extends string, I18nCustomKey extends string, State extends IntlRecord | undefined, I, A, E, R > = & CommandContextLocal & GenWrap & NonGenWrap & { state: Context.Service<`Commander.Command.${Id}.state`, State> } export interface CommandContextLocal { id: Id i18nKey: I18nKey namespace: `action.${I18nKey}` namespaced: (k: K) => `action.${I18nKey}.${K}` } export interface CommandProps< A, E, Id extends string, I18nKey extends string, State extends IntlRecord | undefined > extends CommandContextLocal { /** reactive */ action: string /** reactive */ label: string /** reactive */ result: AsyncResult /** reactive */ waiting: boolean /** reactive */ blocked: boolean /** reactive */ allowed: boolean /** reactive */ state: State } export interface CommandOut< Arg, A, E, R, Id extends string, I18nKey extends string, State extends IntlRecord | undefined > extends CommandProps { new(): {} /** click handlers */ handle: ((arg: Arg) => Fiber.Fiber, never>) & { /** @deprecated don't exist */ effect: (arg: Arg) => Effect.Effect } // // TODO: if we keep them, it would probably be nicer as an option api, deciding the return value like in Atom? // /** @experimental */ // compose: (arg: Arg) => Effect.Effect, R> // /** @experimental */ // compose2: (arg: Arg) => Effect.Effect // /** // * @experimental // * captures the current span and returns an Effect that when run will execute the command // */ // handleEffect: (arg: Arg) => Effect.Effect, never>> // /** // * @experimental // */ // exec: (arg: Arg) => Effect.Effect, never, Exclude> } export interface CommandContextLocal2 extends CommandContextLocal { state: State } type ArgForCombinator = [Arg] extends [void] ? undefined : NoInfer type CommandOutHelper< Arg, Eff extends Effect.Effect, Id extends string, I18nKey extends string, State extends IntlRecord | undefined > = CommandOut< Arg, Effect.Success, Effect.Error, Effect.Services, Id, I18nKey, State > export type Gen = { < Eff extends Effect.Yieldable, AEff, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator ): CommandOut< Arg, AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never, Id, I18nKey, State > < Eff extends Effect.Yieldable, AEff, A extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator, a: ( _: Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A ): CommandOutHelper < Eff extends Effect.Yieldable, AEff, A, B extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator, a: ( _: Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B ): CommandOutHelper < Eff extends Effect.Yieldable, AEff, A, B, C extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator, a: ( _: Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C ): CommandOutHelper < Eff extends Effect.Yieldable, AEff, A, B, C, D extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator, a: ( _: Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D ): CommandOutHelper < Eff extends Effect.Yieldable, AEff, A, B, C, D, E extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator, a: ( _: Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E ): CommandOutHelper < Eff extends Effect.Yieldable, AEff, A, B, C, D, E, F extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator, a: ( _: Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, f: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F ): CommandOutHelper < Eff extends Effect.Yieldable, AEff, A, B, C, D, E, F, G extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator, a: ( _: Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, f: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, g: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G ): CommandOutHelper < Eff extends Effect.Yieldable, AEff, A, B, C, D, E, F, G, H extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator, a: ( _: Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, f: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, g: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, h: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => H ): CommandOutHelper < Eff extends Effect.Yieldable, AEff, A, B, C, D, E, F, G, H, I extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Generator, a: ( _: Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Yieldable] ? R : never >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, f: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, g: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, h: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => H, i: ( _: H, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => I ): CommandOutHelper } export type NonGen = { < Eff extends Effect.Effect, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => Eff ): CommandOutHelper < Eff extends Effect.Effect, A, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => A, a: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, A, B, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => A, a: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, A, B, C, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => A, a: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, A, B, C, D, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => A, a: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, A, B, C, D, E, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => A, a: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, A, B, C, D, E, F, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => A, a: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, f: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, A, B, C, D, E, F, G, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => A, a: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, f: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, g: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, A, B, C, D, E, F, G, H, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => A, a: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, f: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, g: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => H, h: ( _: H, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, A, B, C, D, E, F, G, H, I, Arg = void >( body: (arg: Arg, ctx: CommandContextLocal2) => A, a: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, f: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, g: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => H, h: ( _: H, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => I, i: ( _: H, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper } export type GenWrap< RT, Id extends string, I18nKey extends string, Arg, AEff, EEff, REff, State extends IntlRecord | undefined > = { (): Exclude extends never ? CommandOut< Arg, AEff, EEff, REff, Id, I18nKey, State > : MissingDependencies & {} < A extends Effect.Effect >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A ): CommandOutHelper < A, B extends Effect.Effect >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B ): CommandOutHelper < A, B, C extends Effect.Effect >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C ): CommandOutHelper < A, B, C, D extends Effect.Effect >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D ): CommandOutHelper < A, B, C, D, E extends Effect.Effect >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E ): CommandOutHelper < A, B, C, D, E, F extends Effect.Effect >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, f: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F ): CommandOutHelper < A, B, C, D, E, F, G extends Effect.Effect >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, f: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, g: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G ): CommandOutHelper >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, f: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, g: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, h: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => H ): CommandOutHelper >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => A, b: ( _: A, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, c: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, d: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, e: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, f: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, g: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, h: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => H, i: ( _: H, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => I ): CommandOutHelper } export type NonGenWrap< RT, Id extends string, I18nKey extends string, Arg, AEff, EEff, REff, State extends IntlRecord | undefined > = { (): Exclude extends never ? CommandOutHelper, Id, I18nKey, State> : MissingDependencies & {} < Eff extends Effect.Effect, Arg >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, B, Arg >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, B, C, Arg >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, B, C, D, Arg >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, B, C, D, E, Arg >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, B, C, D, E, F, Arg >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, f: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, B, C, D, E, F, G, Arg >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, f: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, g: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, B, C, D, E, F, G, H, Arg >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, f: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, g: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => H, h: ( _: H, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper < Eff extends Effect.Effect, B, C, D, E, F, G, H, I, Arg >( a: ( _: Effect.Effect< AEff, EEff, REff >, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => B, b: ( _: B, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => C, c: ( _: C, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => D, d: ( _: D, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => E, e: ( _: E, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => F, f: ( _: F, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => G, g: ( _: G, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => H, h: ( _: H, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => I, i: ( _: H, arg: ArgForCombinator, ctx: CommandContextLocal2, NoInfer, NoInfer> ) => Eff ): CommandOutHelper } } type ErrorRenderer = (e: E, action: string, ...args: Args) => string | undefined type RegisteredErrorRenderer = { guard: Refinement render: (guarded: A) => string | undefined } export class CommanderErrorRenderers extends Context.Reference("Commander.ErrorRenderers", { defaultValue: () => [] as RegisteredErrorRenderer[] }) {} export const makeRegisteredErrorRenderer = ( guard: Predicate.Refinement, render: (guarded: A) => string | undefined ): RegisteredErrorRenderer => ({ guard, render }) const renderErrorMaker = Effect.gen(function*() { const { intl } = yield* I18n const registeredRenderers = yield* CommanderErrorRenderers return ( (action: string, errorRenderer?: ErrorRenderer) => (e: E, ...args: Args): string => { if (errorRenderer) { const m = errorRenderer(e, action, ...args) if (m !== undefined) { return m } } for (const entry of registeredRenderers) { if (!entry.guard(e)) { continue } const m = entry.render(e) if (m !== undefined) { return m } } if (!S.is(SupportedErrors)(e) && !S.isSchemaError(e)) { if (typeof e === "object" && e !== null) { if ("message" in e) { return `${e.message}` } if ("_tag" in e) { return `${e._tag}` } } return "" } const e2: SupportedErrors | S.SchemaError = e return Match.value(e2).pipe( Match.tags({ NotFoundError: (e) => { return intl.formatMessage({ id: "handle.not_found" }, { type: e.type, id: e.id }) }, SchemaError: (e) => { console.warn(e.toString()) return intl.formatMessage({ id: "validation.failed" }) } }), Match.orElse((e) => `${e.message ?? e._tag ?? e}`) ) } ) }) const defaultFailureMessageHandler = , AME, AMR>( actionMaker: | string | ((o: Option.Option, ...args: Args) => string) | ((o: Option.Option, ...args: Args) => Effect.Effect), errorRenderer?: ErrorRenderer ) => Effect.fnUntraced(function*(o: Option.Option, ...args: Args) { const action = yield* wrapEffect(actionMaker)(o, ...args) const { intl } = yield* I18n const renderError = yield* renderErrorMaker return Option.match(o, { onNone: () => intl.formatMessage( { id: "handle.unexpected_error2" }, { action, error: "" // TODO consider again Cause.pretty(cause), // will be reported to Sentry/Otel anyway.. and we shouldn't bother users with error dumps? } ), onSome: (e) => { const rendered = renderError(action, errorRenderer)(e, ...args) return S.is(OperationFailure)(e) ? { level: "warn" as const, message: `${ intl.formatMessage( { id: "handle.with_warnings" }, { action } ) }${rendered ? "\n" + rendered : ""}` } : { level: "warn" as const, message: `${ intl.formatMessage( { id: "handle.with_errors" }, { action } ) }:\n` + rendered } } }) }) export const CommanderStatic = { accessArgs: ( cb: (a: NoInfer, b: NoInfer) => (self: NoInfer) => Out ) => (self: In, arg: Arg, arg2: Arg2) => cb(arg, arg2)(self), /** Version of @see confirmOrInterrupt that automatically includes the action name in the default messages */ confirmOrInterrupt: Effect.fnUntraced(function*( message: string | undefined = undefined ) { const context = yield* CommandContext const { intl } = yield* I18n yield* Confirm.confirmOrInterrupt( message ?? intl.formatMessage( { id: "handle.confirmation" }, { action: context.action } ) ) }), /** Version of @see confirm that automatically includes the action name in the default messages */ confirm: Effect.fnUntraced(function*( message: string | undefined = undefined ) { const context = yield* CommandContext const { intl } = yield* I18n return yield* Confirm.confirm( message ?? intl.formatMessage( { id: "handle.confirmation" }, { action: context.action } ) ) }), updateAction: >(update: (currentActionId: string, ...args: Args) => string) => (_: Effect.Effect, ...input: Args) => Effect.updateService( _, CommandContext, (c) => ({ ...c, action: update(c.action, ...input) }) ), registerErrorRenderer: ( guard: Predicate.Refinement, render: (guarded: A) => string | undefined ) => Layer.effect( CommanderErrorRenderers, Effect.gen(function*() { const current = yield* CommanderErrorRenderers return [...current, makeRegisteredErrorRenderer(guard, render)] }) ), defaultFailureMessageHandler, renderError: renderErrorMaker, /** * Version of withDefaultToast that automatically includes the action name in the default messages and uses intl. * uses the Command id as i18n namespace. `action.{id}` is the main action name, * and `action.{id}.waiting`, `action.{id}.success`, `action.{id}.failure` can be used to override the default messages for the respective states. * * the computed `state` provided to the Command can be used for interpolation in the i18n messages. (the state is captured at the start of each command execution and remains stable throughout) * * Note: if you provide `onWaiting` or `onSuccess` as `null`, no toast will be shown for that state. * If you provide a string or function, it will be used instead of the i18n message. * If you provide an `errorRenderer`, it will be used to render errors in the failure message. */ withDefaultToast: >( options?: { /** * if true, previous toasts with this key will be replaced */ stableToastId?: | undefined | true | string | ((id: string, arg: NoInfer[0], ctx: NoInfer[1]) => true | string | undefined) errorRenderer?: (e: E, action: string, arg: NoInfer[0], ctx: NoInfer[1]) => string | undefined showSpanInfo?: false onWaiting?: | null | undefined | string | ((id: string, arg: NoInfer[0], ctx: NoInfer[1]) => string | null | undefined) onSuccess?: | null | undefined | string | ((a: A, action: string, arg: NoInfer[0], ctx: NoInfer[1]) => string | null | undefined) } ) => Effect.fnUntraced(function*( self: Effect.Effect, ...args: Args ) { const cc = yield* CommandContext const { intl } = yield* I18n const withToast = yield* WithToast const customWaiting = cc.namespaced("waiting") const hasCustomWaiting = !!intl.messages[customWaiting] const customSuccess = cc.namespaced("success") const hasCustomSuccess = !!intl.messages[customSuccess] const customFailure = cc.namespaced("failure") const hasCustomFailure = !!intl.messages[customFailure] const stableToastId = options?.stableToastId ? typeof options.stableToastId === "string" ? options.stableToastId : typeof options.stableToastId === "boolean" ? cc.id : typeof options.stableToastId === "function" ? (...args: Args) => { const r = (options.stableToastId as any)(cc.id, ...args) if (typeof r === "string") return r if (r === true) return cc.id return undefined } : undefined : undefined return yield* self.pipe( (_) => withToast({ onWaiting: options?.onWaiting === null ? null : hasCustomWaiting ? intl.formatMessage({ id: customWaiting }, cc.state) : intl.formatMessage( { id: "handle.waiting" }, { action: cc.action } ), onSuccess: options?.onSuccess === null ? null : (a, ..._args) => hasCustomSuccess ? intl.formatMessage( { id: customSuccess }, cc.state ) : (intl.formatMessage({ id: "handle.success" }, { action: cc.action }) + (S.is(OperationSuccess)(a) && a.message ? "\n" + a.message : "")), onFailure: defaultFailureMessageHandler( hasCustomFailure ? intl.formatMessage({ id: customFailure }, cc.state) : cc.action, options?.errorRenderer as ErrorRenderer | undefined ), stableToastId, ...options?.showSpanInfo === false ? { showSpanInfo: options.showSpanInfo } : {} })(_, ...args) ) }), /** borrowing the idea from Families in Effect Atom */ family: ( maker: (arg: Arg) => T, keyMaker?: (arg: ArgIn) => Arg ): (arg: ArgIn) => T => { const commands = MutableHashMap.empty>() const registry = new FinalizationRegistry((arg) => { MutableHashMap.remove(commands, arg) }) return (_k: ArgIn) => { const k = keyMaker ? keyMaker(_k) : _k as unknown as Arg // we want to compare structurally, unless custom equal/hash has been implemented const item = MutableHashMap.get(commands, k).pipe(Option.flatMap((r) => Option.fromNullishOr(r.deref()))) if (item.value) { return item.value } const v = maker(k) MutableHashMap.set(commands, k, new WeakRef(v)) registry.register(v, k) return v } } } const makeBaseInfo = ( id: Id, options?: Pick, "i18nCustomKey"> ) => { if (!id) throw new Error("must specify an id") const i18nKey: I18nKey = options?.i18nCustomKey ?? id as unknown as I18nKey const namespace = `action.${i18nKey}` as const const context = { id, i18nKey, namespace, namespaced: (k: K) => `${namespace}.${k}` as const } return context } const waitState = ref>({}) const registerWait = (id: string) => { // console.debug("register wait", id) waitState.value[id] = waitState.value[id] ? waitState.value[id] + 1 : 1 } const unregisterWait = (id: string) => { // console.debug("unregister wait", id) if (waitState.value[id]) { waitState.value[id] = waitState.value[id] - 1 if (waitState.value[id] <= 0) { delete waitState.value[id] } } } const getStateValues = ( options?: FnOptions ): ComputedRef => { const state_ = options?.state const state = !state_ ? computed(() => undefined as State) : typeof state_ === "function" ? computed(state_) : state_ return state } // class preserves JSDoc throughout.. export class CommanderImpl { constructor( private readonly rt: Context.Context, private readonly intl: I18n, private readonly hooks: Layer.Layer ) { } readonly makeContext = ( id: Id, options?: FnOptionsInternal ) => { if (!id) throw new Error("must specify an id") const i18nKey: I18nKey = options?.i18nCustomKey ?? id as unknown as I18nKey const namespace = `action.${i18nKey}` as const // must remain stable through out single call const action = this.intl.formatMessage({ id: namespace, defaultMessage: id }, { ...options?.state, _isLabel: false }) const label = this.intl.formatMessage({ id: namespace, defaultMessage: id }, { ...options?.state, _isLabel: true }) const context = CommandContext.of({ ...makeBaseInfo(id, options), action, label, state: options?.state }) return context } readonly makeCommand = < const Id extends string, const State extends IntlRecord | undefined, const I18nKey extends string = Id >( id_: Id | { id: Id }, options?: FnOptions, errorDef?: Error ) => { const id = typeof id_ === "string" ? id_ : id_.id const state = getStateValues(options) return Object.assign( ( handler: (arg: Arg, ctx: Commander.CommandContextLocal2) => Effect.Effect ) => { // we capture the definition stack here, so we can append it to later stack traces const limit = Error.stackTraceLimit Error.stackTraceLimit = 2 const localErrorDef = new Error() Error.stackTraceLimit = limit if (!errorDef) { errorDef = localErrorDef } const key = `Commander.Command.${id}.state` as const const stateTag = Context.Service(key) const makeContext_ = () => this.makeContext(id, { ...options, state: state?.value }) const initialContext = makeContext_() const context = computed(() => makeContext_()) const action = computed(() => context.value.action) const label = computed(() => context.value.label) const errorReporter = (self: Effect.Effect) => self.pipe( Effect.tapCause( Effect.fnUntraced(function*(cause) { if (Cause.hasInterruptsOnly(cause)) { console.info(`Interrupted while trying to ${id}`) return } const fail = Cause.findErrorOption(cause) if (Option.isSome(fail)) { // if (fail.value._tag === "SuppressErrors") { // console.info( // `Suppressed error trying to ${action}`, // fail.value, // ) // return // } const message = `Failure trying to ${id}` yield* reportMessage(message, { action: id, error: fail.value }) return } const context = yield* CommandContext const extra = { action: context.action, message: `Unexpected Error trying to ${id}` } yield* reportRuntimeError(cause, extra) }, Effect.uninterruptible) ) ) const currentState = Effect.sync(() => state.value) const theHandler = flow( handler, errorReporter, // all must be within the Effect.fn to fit within the Span Effect.provideServiceEffect( stateTag, currentState ), Effect.provideServiceEffect( CommandContext, Effect.sync(() => makeContext_()) ) ) const waitId = options?.waitKey ? options.waitKey(id) : undefined const blockId = options?.blockKey ? options.blockKey(id) : undefined const [result, exec_] = asResult(theHandler) const exec = Effect .fnUntraced( function*(...args: [any, any]) { if (waitId !== undefined) registerWait(waitId) if (blockId !== undefined && blockId !== waitId) { registerWait(blockId) } return yield* exec_(...args) }, Effect.onExit(() => Effect.sync(() => { if (waitId !== undefined) unregisterWait(waitId) if (blockId !== undefined && blockId !== waitId) { unregisterWait(blockId) } }) ) ) const waiting = waitId !== undefined ? computed(() => result.value.waiting || (waitState.value[waitId] ?? 0) > 0) : computed(() => result.value.waiting) const blocked = blockId !== undefined ? computed(() => waiting.value || (waitState.value[blockId] ?? 0) > 0) : computed(() => waiting.value) const computeAllowed = options?.allowed const allowed = computeAllowed ? computed(() => computeAllowed(id, state)) : true const rt = Effect.context().pipe(Effect.provide(this.hooks)).pipe(Effect.runSyncWith(this.rt)) const runFork = Effect.runForkWith(rt) const handle = Object.assign((arg: Arg) => { arg = toRaw(arg) // remove outside vue proxy bs // we capture the call site stack here const limit = Error.stackTraceLimit Error.stackTraceLimit = 2 const errorCall = new Error() Error.stackTraceLimit = limit let cache: false | string = false const captureStackTrace = () => { // in case of an error, we want to append the definition stack to the call site stack, // so we can see where the handler was defined too if (cache !== false) { return cache } if (errorCall.stack) { const stackDef = errorDef!.stack!.trim().split("\n") const stackCall = errorCall.stack.trim().split("\n") let endStackDef = stackDef.slice(2).join("\n").trim() if (!endStackDef.includes(`(`)) { endStackDef = endStackDef.replace(/at (.*)/, "at ($1)") } let endStackCall = stackCall.slice(2).join("\n").trim() if (!endStackCall.includes(`(`)) { endStackCall = endStackCall.replace(/at (.*)/, "at ($1)") } cache = `${endStackDef}\n${endStackCall}` return cache } } const command = currentState.pipe(Effect.flatMap((state) => { const rawArg = deepToRaw(arg) const rawState = deepToRaw(state) return Effect.withSpan( exec(arg, { ...context.value, state } as any), id, { captureStackTrace, attributes: { input: rawArg, state: rawState, action: initialContext.action, label: initialContext.label, id: initialContext.id, i18nKey: initialContext.i18nKey } } ) })) return runFork(command) }, { action, label }) return reactive({ /** static */ id, /** the base i18n key, based on id by default. static */ i18nKey: initialContext.i18nKey, /** the `action.` namespace based on i18nKey.. static */ namespace: initialContext.namespace, /** easy generate namespaced 18n keys, based on namespace. static */ namespaced: initialContext.namespaced, /** reactive */ result, /** reactive */ waiting, /** reactive */ blocked, /** reactive */ allowed, /** reactive */ action, /** reactive */ label, /** reactive */ state, handle }) }, { id } ) } // /** @experimental */ // takeOver: // (command: Commander.CommandOut) => // (...args: Args) => { // // we capture the call site stack here // const limit = Error.stackTraceLimit // Error.stackTraceLimit = 2 // const errorCall = new Error() // const localErrorDef = new Error() // Error.stackTraceLimit = limit // // TODO // const errorDef = localErrorDef // let cache: false | string = false // const captureStackTrace = () => { // // in case of an error, we want to append the definition stack to the call site stack, // // so we can see where the handler was defined too // if (cache !== false) { // return cache // } // if (errorCall.stack) { // const stackDef = errorDef.stack!.trim().split("\n") // const stackCall = errorCall.stack.trim().split("\n") // let endStackDef = stackDef.slice(2).join("\n").trim() // if (!endStackDef.includes(`(`)) { // endStackDef = endStackDef.replace(/at (.*)/, "at ($1)") // } // let endStackCall = stackCall.slice(2).join("\n").trim() // if (!endStackCall.includes(`(`)) { // endStackCall = endStackCall.replace(/at (.*)/, "at ($1)") // } // cache = `${endStackDef}\n${endStackCall}` // return cache // } // } // return Effect.gen(function*() { // const ctx = yield* CommandContext // ctx.action = command.action // return yield* command.exec(...args).pipe( // Effect.flatten, // Effect.withSpan( // command.action, // { captureStackTrace } // ) // ) // }) // }, /** * Define a Command for handling user actions with built-in error reporting and state management. * * @param id The internal identifier for the action. Used as a tracing span and to lookup * the user-facing name via internationalization (`action.${id}`). * @param options Optional configuration for internationalization and state. * @param options.i18nCustomKey Custom i18n key to use instead of `id` (e.g., for grouping similar actions) * @param options.state Optional reactive state object (or function returning one) that is * made available to the command effects and can be used for i18n interpolation. * The state is captured at the start of each command execution and remains stable throughout. * @returns A function that executes the command when called (e.g., directly in `@click` handlers). * Built-in error reporting handles failures automatically. * * **Effect Context**: Effects have access to the `CommandContext` service, which provides * the user-facing action name. * * **Returned Properties**: * - `action`: User-facing action name from intl messages (useful for button labels) * - `result`: The command result state * - `waiting`: Boolean indicating if the command is in progress (shorthand for `result.waiting`) * - `handle`: Function to execute the command * - `exec`: The raw Effect that will be executed when calling `handle` (for advanced use cases) * - `i18nKey`, `namespace`, `namespaced`: Helpers for internationalization keys * * **User Feedback**: Use the `withDefaultToast` helper for status notifications, or render * the `result` inline for custom UI feedback. */ fn = < const Id extends string, const State extends IntlRecord = IntlRecord, const I18nKey extends string = Id >( id: Id | { id: Id }, options?: FnOptions ): Commander.Gen & Commander.NonGen & { state: Context.Service<`Commander.Command.${Id}.state`, State> } => Object.assign( ( fn: any, ...combinators: any[] ): any => { // we capture the definition stack here, so we can append it to later stack traces const limit = Error.stackTraceLimit Error.stackTraceLimit = 2 const errorDef = new Error() Error.stackTraceLimit = limit return this.makeCommand(id, options, errorDef)( Effect.fnUntraced( // fnUntraced only supports generators as first arg, so we convert to generator if needed isGeneratorFunction(fn) ? fn : function*(...args) { return yield* fn(...args) }, ...combinators as [any] ) as any ) }, makeBaseInfo(typeof id === "string" ? id : id.id, options), { state: Context.Service<`Commander.Command.${Id}.state`, State>( `Commander.Command.${typeof id === "string" ? id : id.id}.state` ) } ) /** @deprecated */ alt2: < const Id extends string, MutArg, MutA, MutE, MutR, const I18nKey extends string = Id, State extends IntlRecord | undefined = undefined >( id: | Id | { id: Id; mutate: (arg: MutArg) => Effect.Effect } | ((arg: MutArg) => Effect.Effect) & { id: Id }, options?: FnOptions ) => & Commander.CommandContextLocal & (( handler: ( ctx: Effect.fn.Traced & Effect.fn.Untraced & Commander.CommandContextLocal & { // todo: only if we passed in one mutate: (arg: Arg) => Effect.Effect } ) => (arg: Arg, ctx: Commander.CommandContextLocal2) => Effect.Effect ) => Commander.CommandOut) = ( _id, options? ) => { const isObject = Predicate.isObjectKeyword(_id) const id = isObject ? _id.id : _id const baseInfo = makeBaseInfo(id, options) const idCmd = this.makeCommand(id, options) // TODO: implement proper tracing stack return Object.assign((cb: any) => idCmd(cb( Object.assign( (fn: any, ...combinators: any[]) => Effect.fnUntraced( // fnUntraced only supports generators as first arg, so we convert to generator if needed isGeneratorFunction(fn) ? fn : function*(...args) { return yield* fn(...args) }, ...combinators as [any] ), baseInfo, isObject ? { mutate: "mutate" in _id ? _id.mutate : typeof _id === "function" ? _id : undefined } : {} ) )), baseInfo) as any } alt = this.makeCommand as unknown as < const Id extends string, const I18nKey extends string = Id, State extends IntlRecord | undefined = undefined >( id: Id, customI18nKey?: I18nKey ) => & Commander.CommandContextLocal & (( handler: (arg: Arg, ctx: Commander.CommandContextLocal2) => Effect.Effect ) => Commander.CommandOut) /** * Define a Command for handling user actions with built-in error reporting and state management. * * @param mutation The mutation function to take the identifier and initial handler from. Used as a tracing span and to lookup * the user-facing name via internationalization (`action.${id}`). * @param options Optional configuration for internationalization and state. * @param options.i18nCustomKey Custom i18n key to use instead of `id` (e.g., for grouping similar actions) * @param options.state Optional reactive state object (or function returning one) that is * made available to the command effects and can be used for i18n interpolation. * The state is captured at the start of each command execution and remains stable throughout. * @returns A function that executes the command when called (e.g., directly in `@click` handlers). * Built-in error reporting handles failures automatically. * * **Effect Context**: Effects have access to the `CommandContext` service, which provides * the user-facing action name. * * **Returned Properties**: * - `action`: User-facing action name from intl messages (useful for button labels) * - `result`: The command result state * - `waiting`: Boolean indicating if the command is in progress (shorthand for `result.waiting`) * - `handle`: Function to execute the command * - `exec`: The raw Effect that will be executed when calling `handle` (for advanced use cases) * - `i18nKey`, `namespace`, `namespaced`: Helpers for internationalization keys * * **User Feedback**: Use the `withDefaultToast` helper for status notifications, or render * the `result` inline for custom UI feedback. */ wrap = < const Id extends string, Arg, A, E, R, const State extends IntlRecord = IntlRecord, I18nKey extends string = Id >( mutation: | { mutate: (arg: Arg) => Effect.Effect; id: Id } | ((arg: Arg) => Effect.Effect) & { id: Id }, options?: FnOptions ): Commander.CommanderWrap => Object.assign( ( ...combinators: any[] ): any => { // we capture the definition stack here, so we can append it to later stack traces const limit = Error.stackTraceLimit Error.stackTraceLimit = 2 const errorDef = new Error() Error.stackTraceLimit = limit const mutate = "mutate" in mutation ? mutation.mutate : mutation return this.makeCommand(mutation.id, options, errorDef)( Effect.fnUntraced( // fnUntraced only supports generators as first arg, so we convert to generator if needed isGeneratorFunction(mutate) ? mutate : function*(arg: Arg) { return yield* mutate(arg) }, ...combinators as [any] ) as any ) }, makeBaseInfo(mutation.id, options), { state: Context.Service<`Commander.Command.${Id}.state`, State>( `Commander.Command.${mutation.id}.state` ) } ) } // @effect-diagnostics-next-line missingEffectServiceDependency:off export class Commander extends Context.Service()("Commander", { make: Effect.gen(function*() { const i18n = yield* I18n return (rt: Context.Context, rtHooks: Layer.Layer) => new CommanderImpl(rt, i18n, rtHooks) }) }) { static readonly DefaultWithoutDependencies = Layer.effect(this, this.make) static readonly Default = this.DefaultWithoutDependencies }