/* eslint-disable @typescript-eslint/no-explicit-any */ import { it } from "@effect/vitest" import { Cause, Effect, Exit, Fiber, Option } from "effect-app" import { OperationFailure } from "effect-app/Operations" import { CommandContext, DefaultIntl } from "../src/commander.js" import { AsyncResult } from "../src/lib.js" import { useExperimental } from "./stubs.js" const unwrap = (r: Fiber.Fiber, never>) => Fiber.join(r).pipe(Effect.flatten) // declare const mutation: { // name: "myMutation" // mutate: (a: number, b: string) => Effect.Effect // } describe("alt2", () => { it.live("works", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts }) let executed = false const someMutation = { id: "Test Action", mutate: (() => {}) as unknown as (a: number) => Effect.Effect } as const const someMutation2 = Object.assign( (() => {}) as unknown as (a: number) => Effect.Effect, { id: "Test Action" } as const ) const command1 = Command.alt2(someMutation2)((fn) => fn( function*() { expect(typeof fn.mutate).toBe("function") expect(fn.id).toBe("Test Action") expect(fn.namespace).toBe("action.Test Action") expect(fn.namespaced("a")).toBe("action.Test Action.a") } ) ) yield* unwrap(command1.handle()) expect(command1.action).toBe("Test Action") expect(command1.id).toBe("Test Action") expect(command1.namespace).toBe("action.Test Action") expect(command1.namespaced("a")).toBe("action.Test Action.a") const command = Command.alt2(someMutation)((fn) => fn( function*() { expect(typeof fn.mutate).toBe("function") expect(command.id).toBe("Test Action") expect(command.namespace).toBe("action.Test Action") expect(command.namespaced("a")).toBe("action.Test Action.a") expect(fn.id).toBe(command.id) expect(fn.namespace).toBe(command.namespace) expect(fn.namespaced("a")).toBe(command.namespaced("a")) expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") expect(command.waiting).toBe(true) expect(yield* CommandContext).toMatchObject({ action: "Test Action", id: "Test Action" }) expect(toasts.length).toBe(0) return "test-value" }, Effect.tap(Effect.fnUntraced(function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") })), Effect.tap(() => Effect.currentSpan.pipe( Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))) ) ), Effect.tap(() => Effect.sync(() => executed = true)) ) ) expect(command.action).toBe("Test Action") expect(command.id).toBe("Test Action") expect(command.namespace).toBe("action.Test Action") expect(command.namespaced("a")).toBe("action.Test Action.a") const r = yield* unwrap(command.handle()) expect(command.waiting).toBe(false) expect(r).toBe("test-value") // to confirm that the initial function has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. expect(command.result.pipe(AsyncResult.value)).toEqual(Option.some("test-value")) expect(toasts.length).toBe(0) // const wrap = Command.wrap(mutation) // wrap() // wrap((_, ...args) => Effect.tap(_, () => console.log("called with", _, args))) })) }) it.live("works", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts }) let executed = false const command = Command.fn("Test Action")( function*() { expect(command.id).toBe("Test Action") expect(command.namespace).toBe("action.Test Action") expect(command.namespaced("a")).toBe("action.Test Action.a") expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") expect(command.waiting).toBe(true) expect(yield* CommandContext).toMatchObject({ action: "Test Action", id: "Test Action" }) expect(toasts.length).toBe(0) return "test-value" }, Effect.tap(Effect.fnUntraced(function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") })), Effect.tap(() => Effect.currentSpan.pipe( Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))) ) ), Effect.tap(() => Effect.sync(() => executed = true)) ) expect(command.action).toBe("Test Action") expect(command.id).toBe("Test Action") expect(command.namespace).toBe("action.Test Action") expect(command.namespaced("a")).toBe("action.Test Action.a") const r = yield* unwrap(command.handle()) expect(command.waiting).toBe(false) expect(r).toBe("test-value") // to confirm that the initial function has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. expect(command.result.pipe(AsyncResult.value)).toEqual(Option.some("test-value")) expect(toasts.length).toBe(0) // const wrap = Command.wrap(mutation) // wrap() // wrap((_, ...args) => Effect.tap(_, () => console.log("called with", _, args))) })) it.live("works non-gen", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts }) let executed = false const command = Command.fn("Test Action")( () => Effect.gen( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") expect(command.waiting).toBe(true) expect(yield* CommandContext).toMatchObject({ action: "Test Action", id: "Test Action" }) expect(toasts.length).toBe(0) return "test-value" } ), Effect.tap(Effect.fnUntraced(function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") })), Effect.tap(() => Effect.currentSpan.pipe( Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))) ) ), Effect.tap(() => Effect.sync(() => executed = true)) ) expect(command.action).toBe("Test Action") const r = yield* unwrap(command.handle()) expect(command.waiting).toBe(false) expect(r).toBe("test-value") // to confirm that the initial function has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. expect(command.result.pipe(AsyncResult.value)).toEqual(Option.some("test-value")) expect(toasts.length).toBe(0) })) it.live("has custom action name", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts, messages: { "action.Test Action": "Test Action Translated" } }) let executed = false const command = Command.fn("Test Action")( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") expect(yield* CommandContext).toMatchObject({ action: "Test Action Translated", id: "Test Action" }) return "test-value" }, Effect.tap(() => Effect.sync(() => executed = true)) ) expect(command.action).toBe("Test Action Translated") const r = yield* unwrap(command.handle()) expect(r).toBe("test-value") // to confirm that the initial function has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. })) it.live("can map the result", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts }) let executed = false const command = Command.fn("Test Action")( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") return "test-value" }, Effect.map((_) => _ + _), Effect.tap(() => Effect.sync(() => executed = true)) ) const r = yield* unwrap(command.handle()) expect(r).toBe("test-valuetest-value") // to confirm that the initial function and map has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. })) it.live("can receive and use input", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts }) let executed = false const command = Command.fn("Test Action")( function*(input1: number, input2) { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") return { input1, input2 } }, Effect.tap(() => Effect.sync(() => executed = true)) ) const r = yield* unwrap(command.handle(1)) expect(r.input1).toBe(1) // to confirm that the initial function has ran and received input. const { allowed, blocked, handle, result, waiting, ...rest } = command const ctx = { ...rest, state: undefined } expect(JSON.stringify(r.input2)).equal(JSON.stringify(ctx)) expect(executed).toBe(true) // to confirm that the combinators have ran. })) it.live("can replace the result", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts, messages: { "action.Test Action": "Test Action Translated" } }) let executed = false const command = Command.fn("Test Action")( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") return "test-value" }, Effect.andThen(Effect.succeed(42)), Effect.tap(() => Effect.sync(() => executed = true)) ) const r = yield* unwrap(command.handle()) expect(r).toBe(42) // to confirm that the initial function and zipRight has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. })) it.live("with toasts", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) let executed = false const command = Command.fn("Test Action")( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") expect(toasts.length).toBe(1) expect(toasts[0].message).toBe("Test Action executing...") return "test-value" }, Effect.tap(Effect.fnUntraced(function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") })), Effect.tap(() => Effect.currentSpan.pipe( Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))) ) ), // WithToast.handle({ // onFailure: "failed", // onSuccess: () => Effect.map(CommandContext, (_) => _.action), // onWaiting: null // }), Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) const r = yield* unwrap(command.handle()) expect(r).toBe("test-value") // to confirm that the initial function has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. expect(toasts.length).toBe(1) expect(toasts[0].message).toBe("Test Action Success") })) it.live("interrupted", () => Effect .gen(function*() { let executed = false const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.fn("Test Action")( function*() { expect(toasts.length).toBe(1) yield* Effect.interrupt return "test-value" }, Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) const r = yield* Fiber.join(command.handle()) expect(executed).toBe(false) // we were interrupted after all :) expect(Exit.hasInterrupts(r)).toBe(true) // to confirm that the initial function has interrupted expect(command.waiting).toBe(false) expect(Exit.hasInterrupts(AsyncResult.toExit(command.result))).toBe(true) expect(toasts.length).toBe(0) // toast is removed on interruption. TODO: maybe a nicer user experience can be had? })) it.live("fail", () => Effect .gen(function*() { let executed = false const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.fn("Test Action")( function*() { expect(toasts.length).toBe(1) return yield* Effect.fail({ message: "Boom!" }) }, Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) const r = yield* Fiber.join(command.handle()) expect(executed).toBe(false) // we failed after all :) expect(Exit.isFailure(r) && Cause.hasFails(r.cause)).toBe(true) // to confirm that the initial function has failed expect(command.waiting).toBe(false) expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true) expect(toasts.length).toBe(1) // toast should show error expect(toasts[0].type).toBe("warning") expect(toasts[0].message).toContain("Test Action Failed:\nBoom!") expect(toasts[0].message).toMatch(/Trace: [a-f0-9]{32}/) expect(toasts[0].message).toMatch(/Span: [a-f0-9]{16}/) })) it.live("fail with showSpanInfo disabled", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.fn("Test Action")( function*() { return yield* Effect.fail({ message: "Boom!" }) }, Command.withDefaultToast({ showSpanInfo: false }) ) yield* Fiber.join(command.handle()) expect(toasts.length).toBe(1) expect(toasts[0].message).toBe("Test Action Failed:\nBoom!") })) it.live("fail with custom errorRenderer uses warning toast", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.fn("Test Action")( function*() { return yield* Effect.fail(OperationFailure.make({ message: null })) }, Command.withDefaultToast({ errorRenderer: () => "Rendered Boom!" }) ) yield* Fiber.join(command.handle()) expect(toasts.length).toBe(1) expect(toasts[0].type).toBe("warning") expect(toasts[0].message).toContain("Test Action, with warnings\nRendered Boom!") expect(toasts[0].message).toMatch(/Trace: [a-f0-9]{32}/) expect(toasts[0].message).toMatch(/Span: [a-f0-9]{16}/) })) it.live("fail and recover", () => Effect .gen(function*() { let executed = false const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.fn("Test Action")( function*() { expect(toasts.length).toBe(1) return yield* Effect.fail({ message: "Boom!" }) }, Effect.orElseSucceed(() => "recovered"), // we recover from the error here, so the final result is success Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) const r = yield* unwrap(command.handle()) expect(executed).toBe(true) // we recovered after all :) expect(r).toBe("recovered") // to confirm that the initial function has failed but we recovered expect(command.waiting).toBe(false) expect(AsyncResult.toExit(command.result)).toEqual(Exit.succeed("recovered")) expect(toasts.length).toBe(1) // toast should show error expect(toasts[0].message).toBe("Test Action Success") })) it.live("defect", () => Effect .gen(function*() { let executed = false const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.fn("Test Action")( function*() { expect(toasts.length).toBe(1) return yield* Effect.die({ message: "Boom!" }) }, Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) const r = yield* Fiber.join(command.handle()) // TODO: confirm we reported error expect(executed).toBe(false) // we died after all :) expect(Exit.isFailure(r) && Cause.hasDies(r.cause)).toBe(true) // to confirm that the initial function has died expect(command.waiting).toBe(false) expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true) expect(toasts.length).toBe(1) // toast should show error expect(toasts[0].type).toBe("error") expect(toasts[0].message).toContain("Test Action unexpected error, please try again shortly.") })) it.live("works with alt", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts }) let executed = false const command = Command.alt("Test Action")( Effect.fnUntraced( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") expect(command.waiting).toBe(true) expect(yield* CommandContext).toMatchObject({ action: "Test Action", id: "Test Action" }) expect(toasts.length).toBe(0) return "test-value" }, Effect.tap(Effect.fnUntraced(function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") })), Effect.tap(() => Effect.currentSpan.pipe( Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))) ) ), Effect.tap(() => Effect.sync(() => executed = true)) ) ) expect(command.action).toBe("Test Action") const r = yield* unwrap(command.handle()) expect(command.waiting).toBe(false) expect(r).toBe("test-value") // to confirm that the initial function has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. expect(command.result.pipe(AsyncResult.value)).toEqual(Option.some("test-value")) expect(toasts.length).toBe(0) })) it.live("has custom action name with alt", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts, messages: { "action.Test Action": "Test Action Translated" } }) let executed = false const command = Command.alt("Test Action")( Effect.fnUntraced( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") expect(yield* CommandContext).toMatchObject({ action: "Test Action Translated", id: "Test Action" }) return "test-value" }, Effect.tap(() => Effect.sync(() => executed = true)) ) ) expect(command.action).toBe("Test Action Translated") const r = yield* unwrap(command.handle()) expect(r).toBe("test-value") // to confirm that the initial function has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. })) it.live("can map the result with alt", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts }) let executed = false const command = Command.alt("Test Action")(Effect.fnUntraced( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") return "test-value" }, Effect.map((_) => _ + _), Effect.tap(() => Effect.sync(() => executed = true)) )) const r = yield* unwrap(command.handle()) expect(r).toBe("test-valuetest-value") // to confirm that the initial function and map has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. })) it.live("can receive and use input with alt", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts }) let executed = false const command = Command.alt("Test Action")((input1: number, input2) => Effect .gen( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") return { input1, input2 } } ) .pipe( Effect.tap(() => Effect.sync(() => executed = true)) ) ) const r = yield* unwrap(command.handle(1)) const { allowed, blocked, handle, result, waiting, ...rest } = command const ctx = { ...rest, state: undefined } expect(r.input1).toBe(1) expect(JSON.stringify(r.input2)).equal(JSON.stringify(ctx)) // to confirm that the initial function has ran and received input. expect(executed).toBe(true) // to confirm that the combinators have ran. })) it.live("can replace the result with alt", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts, messages: { "action.Test Action": "Test Action Translated" } }) let executed = false const command = Command.alt("Test Action")( Effect.fnUntraced( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") return "test-value" }, Effect.andThen(Effect.succeed(42)), Effect.tap(() => Effect.sync(() => executed = true)) ) ) const r = yield* unwrap(command.handle()) expect(r).toBe(42) // to confirm that the initial function and zipRight has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. })) it.live("with toasts with alt", () => Effect .gen(function*() { const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) let executed = false const command = Command.alt("Test Action")( Effect.fnUntraced( function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") expect(toasts.length).toBe(1) expect(toasts[0].message).toBe("Test Action executing...") return "test-value" }, Effect.tap(Effect.fnUntraced(function*() { expect(yield* Effect.currentSpan.pipe(Effect.map((_) => _.name))).toBe("Test Action") })), Effect.tap(() => Effect.currentSpan.pipe( Effect.map((_) => _.name), Effect.tap((_) => Effect.sync(() => expect(_).toBe("Test Action"))) ) ), Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) ) const r = yield* unwrap(command.handle()) expect(r).toBe("test-value") // to confirm that the initial function has ran. expect(executed).toBe(true) // to confirm that the combinators have ran. expect(toasts.length).toBe(1) expect(toasts[0].message).toBe("Test Action Success") })) it.live("interrupted with alt", () => Effect .gen(function*() { let executed = false const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.alt("Test Action")( Effect.fnUntraced( function*() { expect(toasts.length).toBe(1) // @effect-diagnostics-next-line missingReturnYieldStar:off yield* Effect.interrupt return "test-value" }, Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) ) const r = yield* Fiber.join(command.handle()) expect(executed).toBe(false) // we were interrupted after all :) expect(Exit.hasInterrupts(r)).toBe(true) // to confirm that the initial function has interrupted expect(command.waiting).toBe(false) expect(Exit.hasInterrupts(AsyncResult.toExit(command.result))).toBe(true) expect(toasts.length).toBe(0) // toast is removed on interruption. TODO: maybe a nicer user experience can be had? })) it.live("fail with alt", () => Effect .gen(function*() { let executed = false const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.alt("Test Action")( Effect.fnUntraced( function*() { expect(toasts.length).toBe(1) return yield* Effect.fail({ message: "Boom!" }) }, Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) ) const r = yield* Fiber.join(command.handle()) expect(executed).toBe(false) // we failed after all :) expect(Exit.isFailure(r) && Cause.hasFails(r.cause)).toBe(true) // to confirm that the initial function has failed expect(command.waiting).toBe(false) expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true) expect(toasts.length).toBe(1) // toast should show error expect(toasts[0].type).toBe("warning") expect(toasts[0].message).toContain("Test Action Failed:\nBoom!") })) it.live("fail and recover with alt", () => Effect .gen(function*() { let executed = false const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.alt("Test Action")( Effect.fnUntraced( function*() { expect(toasts.length).toBe(1) return yield* Effect.fail({ message: "Boom!" }) }, Effect.orElseSucceed(() => "recovered"), // we recover from the error here, so the final result is success Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) ) const r = yield* unwrap(command.handle()) expect(executed).toBe(true) // we recovered after all :) expect(r).toBe("recovered") // to confirm that the initial function has failed but we recovered expect(command.waiting).toBe(false) expect(AsyncResult.toExit(command.result)).toEqual(Exit.succeed("recovered")) expect(toasts.length).toBe(1) // toast should show error expect(toasts[0].message).toBe("Test Action Success") })) it.live("defect with alt", () => Effect .gen(function*() { let executed = false const toasts: any[] = [] const Command = useExperimental({ toasts, messages: DefaultIntl.en }) const command = Command.alt("Test Action")( Effect.fnUntraced( function*() { expect(toasts.length).toBe(1) return yield* Effect.die({ message: "Boom!" }) }, Command.withDefaultToast(), Effect.tap(() => Effect.sync(() => executed = true)) ) ) const r = yield* Fiber.join(command.handle()) // TODO: confirm we reported error expect(executed).toBe(false) // we died after all :) expect(Exit.isFailure(r) && Cause.hasDies(r.cause)).toBe(true) // to confirm that the initial function has died expect(command.waiting).toBe(false) expect(Exit.isFailure(AsyncResult.toExit(command.result))).toBe(true) expect(toasts.length).toBe(1) // toast should show error expect(toasts[0].type).toBe("error") expect(toasts[0].message).toContain("Test Action unexpected error, please try again shortly.") })) describe("state-in-toast", () => { it("works", () => { const toasts: any[] = [] const removeMutation = Object.assign( Effect.fn(function*(_item: string) { yield* Effect.sleep(1000) }), { id: "remove_thing" } ) const item = "x" const Command = useExperimental({ toasts, messages: DefaultIntl.en }) Command.fn(removeMutation, { state: () => ({ item }), waitKey: (id) => `${id}.${item}`, blockKey: () => `modify_thing.${item}` // allowed: () => role.value === "admin" })( function*() { // yield* Command.confirmOrInterrupt(yield* I18n.formatMessage({ id: "confirm.remove_item" }, { item })) yield* removeMutation(item) }, Command.withDefaultToast({ onSuccess: (a, b, c, d) => { console.log("Success", { a, b, c, d }) expectTypeOf(d.state).toEqualTypeOf<{ readonly item: "x" }>() } }) ) Command.fn(removeMutation, { state: () => ({ item }), waitKey: (id) => `${id}.${item}`, blockKey: () => `modify_thing.${item}` // allowed: () => role.value === "admin" })( function*() { // yield* Command.confirmOrInterrupt(yield* I18n.formatMessage({ id: "confirm.remove_item" }, { item })) yield* removeMutation(item) }, Command.withDefaultToast({ onSuccess: (a, b, c) => { console.log("Success", { a, b, c }) expectTypeOf(c).toEqualTypeOf() } }) ) }) })