Created
January 20, 2026 14:27
-
-
Save rauhryan/5ef8c9477accfedf2e8e7b9bd16a8602 to your computer and use it in GitHub Desktop.
AsyncOrGenerator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { call, useAbortSignal, type Operation } from 'effection' | |
| // ============================================================================ | |
| // Types | |
| // ============================================================================ | |
| /** Result type for form validators */ | |
| export type ValidatorResult = | |
| | string | |
| | undefined | |
| | { fields: Record<string, string> } | |
| /** Cancellation behavior for async operations */ | |
| export type CancellationMode = 'none' | 'replace' | |
| /** | |
| * A function that returns either a Promise or an Effection Operation. | |
| * Receives params with an AbortSignal for cancellation support. | |
| */ | |
| export type AsyncOrGenerator<TParams, TResult> = ( | |
| params: TParams & { signal: AbortSignal } | |
| ) => Promise<TResult> | Operation<TResult> | |
| // ============================================================================ | |
| // Type Guards | |
| // ============================================================================ | |
| /** Check if function is a generator function */ | |
| export function isGeneratorFunction(fn: unknown): boolean { | |
| return ( | |
| typeof fn === 'function' && fn.constructor?.name === 'GeneratorFunction' | |
| ) | |
| } | |
| /** Check if value is an Operation (has Symbol.iterator) */ | |
| export function isOperation(value: unknown): value is Operation<unknown> { | |
| return ( | |
| value !== null && typeof value === 'object' && Symbol.iterator in value | |
| ) | |
| } | |
| // ============================================================================ | |
| // Operations | |
| // ============================================================================ | |
| /** | |
| * Runs a user-provided async or generator function within effection. | |
| * Provides AbortSignal to both via useAbortSignal(). | |
| * | |
| * @example | |
| * ```ts | |
| * // With async function | |
| * yield* runWithSignal( | |
| * async ({ value, signal }) => { | |
| * const res = await fetch('/api', { signal }) | |
| * return res.json() | |
| * }, | |
| * { value: formData } | |
| * ) | |
| * | |
| * // With generator function | |
| * yield* runWithSignal( | |
| * function* ({ value, signal }) { | |
| * yield* sleep(300) | |
| * return yield* call(() => fetch('/api', { signal })) | |
| * }, | |
| * { value: formData } | |
| * ) | |
| * ``` | |
| */ | |
| export function* runWithSignal<TParams, TResult>( | |
| fn: AsyncOrGenerator<TParams, TResult>, | |
| params: TParams | |
| ): Operation<TResult> { | |
| const signal = yield* useAbortSignal() | |
| const paramsWithSignal = { ...params, signal } | |
| const result = fn(paramsWithSignal) | |
| // If it's a generator/operation, yield* it directly | |
| if (isOperation(result)) { | |
| return yield* result | |
| } | |
| // If it's a promise, wrap with call() | |
| return yield* call(() => result as Promise<TResult>) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment