@async-kit/flowx 0.1.25 → 0.2.0

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 0.2.0 (2026-03-12)
2
+
3
+ ### 🚀 Features
4
+
5
+ - add cachex, eventx, and workflowx packages ([50277b9](https://github.com/NexaLeaf/async-kit/commit/50277b9))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - Palanisamy Muthusamy
10
+
11
+
12
+
1
13
 
2
14
 
3
15
 
package/README.md CHANGED
@@ -114,6 +114,23 @@ const result = await pipeline<string>()
114
114
  // → { raw: 'invalid json', error: SyntaxError }
115
115
  ```
116
116
 
117
+ If the **fallback itself throws**, the error is wrapped in `PipelineStepError` (same as a normal step error) so you always get a consistent error type:
118
+
119
+ ```typescript
120
+ try {
121
+ await pipeline<string>()
122
+ .pipeWithFallback(
123
+ { name: 'fetch', fn: s => primaryApi.get(s) },
124
+ (_err, id) => fallbackApi.get(id) // this might also fail
125
+ )
126
+ .run(userId);
127
+ } catch (err) {
128
+ if (err instanceof PipelineStepError) {
129
+ console.log('step or fallback failed at step', err.stepIndex, err.cause);
130
+ }
131
+ }
132
+ ```
133
+
117
134
  ## `parallel(tasks, options?)`
118
135
 
119
136
  Run tasks concurrently, resolve in declaration order. Rejects on first failure.
@@ -142,7 +159,7 @@ const total = await sequence(orders, 0, async (acc, order) => {
142
159
 
143
160
  | Class | When |
144
161
  |---|---|
145
- | `PipelineStepError` | A step threw — has `.stepIndex`, `.stepName`, `.cause`, `.inputValue` |
162
+ | `PipelineStepError` | A step threw **or a fallback threw** — has `.stepIndex`, `.stepName`, `.cause`, `.inputValue` |
146
163
  | `PipelineTimeoutError` | Per-step timeout exceeded — has `.stepIndex`, `.stepName`, `.timeoutMs` |
147
164
 
148
165
  ## Examples
package/dist/index.js CHANGED
@@ -120,7 +120,11 @@ var Pipeline = class _Pipeline {
120
120
  const nextRecord = this.records[i + 1];
121
121
  if (nextRecord?.isFallback) {
122
122
  const pipelineErr = new PipelineStepError(stepIndex, record.name, err, inputValue);
123
- current = await nextRecord.fn(pipelineErr, inputValue);
123
+ try {
124
+ current = await nextRecord.fn(pipelineErr, inputValue);
125
+ } catch (fallbackErr) {
126
+ throw new PipelineStepError(stepIndex, record.name, fallbackErr, inputValue);
127
+ }
124
128
  i++;
125
129
  onStepComplete?.(stepIndex, record.name, current);
126
130
  } else {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/flowx.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export type { StepFn, StepContext, NamedStep, FlowxOptions, ParallelOptions, SequenceOptions } from './types.js';\nimport type { StepFn, StepContext, NamedStep, FlowxOptions, ParallelOptions, SequenceOptions } from './types.js';\n\n// ─── Errors ──────────────────────────────────────────────────────────────────\n\nexport class PipelineStepError extends Error {\n constructor(\n public readonly stepIndex: number,\n public readonly stepName: string | undefined,\n public override readonly cause: unknown,\n public readonly inputValue: unknown\n ) {\n super(\n `Pipeline failed at step ${stepIndex}${stepName ? ` (${stepName})` : ''}: ${String(cause)}`\n );\n this.name = 'PipelineStepError';\n }\n}\n\nexport class PipelineTimeoutError extends Error {\n constructor(\n public readonly stepIndex: number,\n public readonly stepName: string | undefined,\n public readonly timeoutMs: number\n ) {\n super(`Step ${stepIndex}${stepName ? ` (${stepName})` : ''} timed out after ${timeoutMs}ms`);\n this.name = 'PipelineTimeoutError';\n }\n}\n\n// ─── Internal Step Record ────────────────────────────────────────────────────\n\ninterface StepRecord {\n fn: StepFn<unknown, unknown>;\n name: string | undefined;\n timeoutMs: number | undefined;\n isFallback: false;\n}\n\ninterface FallbackRecord {\n fn: (error: PipelineStepError, input: unknown) => unknown | Promise<unknown>;\n isFallback: true;\n}\n\ntype AnyRecord = StepRecord | FallbackRecord;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst neverAborted = new AbortController().signal;\n\nfunction raceStepTimeout<T>(\n promise: Promise<T>,\n ms: number,\n index: number,\n name: string | undefined\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new PipelineTimeoutError(index, name, ms)), ms);\n promise.then(\n (v) => { clearTimeout(timer); resolve(v); },\n (e) => { clearTimeout(timer); reject(e); }\n );\n });\n}\n\nfunction normalizeStep<In, Out>(\n step: StepFn<In, Out> | NamedStep<In, Out>\n): { fn: StepFn<In, Out>; name: string | undefined; timeoutMs: number | undefined } {\n if (typeof step === 'function') {\n return { fn: step, name: undefined, timeoutMs: undefined };\n }\n return { fn: step.fn, name: step.name, timeoutMs: step.timeoutMs };\n}\n\n// ─── Pipeline ────────────────────────────────────────────────────────────────\n\n/**\n * Flowx — composable, type-safe async pipeline builder.\n *\n * Each step receives the output of the previous step as its first argument,\n * plus a `StepContext` carrying the abort signal, step index, and step name.\n *\n * @example\n * const result = await pipeline<string>()\n * .pipe(s => s.trim())\n * .pipe({ name: 'uppercase', fn: s => s.toUpperCase() })\n * .tap(s => console.log('after uppercase:', s))\n * .run(' hello ');\n */\nexport class Pipeline<TIn, TOut> {\n private readonly records: AnyRecord[];\n\n constructor(records: AnyRecord[] = []) {\n this.records = records;\n }\n\n /**\n * Add a transform step. Accepts either a plain function or a `NamedStep`\n * object with an optional per-step `timeoutMs`.\n */\n pipe<TNext>(\n step: StepFn<TOut, TNext> | NamedStep<TOut, TNext>\n ): Pipeline<TIn, TNext> {\n const { fn, name, timeoutMs } = normalizeStep(step);\n const record: StepRecord = {\n fn: fn as StepFn<unknown, unknown>,\n name,\n timeoutMs,\n isFallback: false,\n };\n return new Pipeline<TIn, TNext>([...this.records, record]);\n }\n\n /**\n * Add a step with an inline fallback. If the step throws, `fallback` is\n * called with the error and the original input to that step.\n */\n pipeWithFallback<TNext>(\n step: StepFn<TOut, TNext> | NamedStep<TOut, TNext>,\n fallback: (error: PipelineStepError, input: TOut) => TNext | Promise<TNext>\n ): Pipeline<TIn, TNext> {\n const { fn, name, timeoutMs } = normalizeStep(step);\n const stepRecord: StepRecord = {\n fn: fn as StepFn<unknown, unknown>,\n name,\n timeoutMs,\n isFallback: false,\n };\n const fallbackRecord: FallbackRecord = {\n fn: fallback as (error: PipelineStepError, input: unknown) => unknown,\n isFallback: true,\n };\n return new Pipeline<TIn, TNext>([...this.records, stepRecord, fallbackRecord]);\n }\n\n /**\n * Add a side-effect step. Runs `fn` for its effect only; passes the current\n * value through unchanged. Errors in `fn` propagate normally.\n */\n tap(fn: (value: TOut, context: StepContext) => void | Promise<void>): Pipeline<TIn, TOut> {\n const tapStep: StepFn<TOut, TOut> = async (value, ctx) => {\n await fn(value, ctx);\n return value;\n };\n return this.pipe(tapStep);\n }\n\n async run(input: TIn, options: FlowxOptions = {}): Promise<TOut> {\n const signal = options.signal ?? neverAborted;\n const { onStepComplete, stepTimeoutMs } = options;\n\n let current: unknown = input;\n let stepIndex = 0;\n\n for (let i = 0; i < this.records.length; i++) {\n const record = this.records[i];\n\n if (record.isFallback) {\n // Fallback records are consumed inline by the preceding step handler.\n continue;\n }\n\n if (signal.aborted) {\n throw new DOMException('Pipeline aborted', 'AbortError');\n }\n\n const inputValue = current;\n const ctx: StepContext = { signal, stepIndex, stepName: record.name };\n const effectiveTimeout = record.timeoutMs ?? stepTimeoutMs;\n\n try {\n const rawResult = record.fn(current, ctx);\n let stepPromise: Promise<unknown> = rawResult instanceof Promise ? rawResult : Promise.resolve(rawResult);\n if (effectiveTimeout != null) {\n stepPromise = raceStepTimeout(stepPromise, effectiveTimeout, stepIndex, record.name);\n }\n current = await stepPromise;\n onStepComplete?.(stepIndex, record.name, current);\n } catch (err) {\n // Check if the next record is a fallback for this step.\n const nextRecord = this.records[i + 1];\n if (nextRecord?.isFallback) {\n const pipelineErr = new PipelineStepError(stepIndex, record.name, err, inputValue);\n current = await nextRecord.fn(pipelineErr, inputValue);\n i++; // skip the fallback record in the outer loop\n onStepComplete?.(stepIndex, record.name, current);\n } else {\n throw new PipelineStepError(stepIndex, record.name, err, inputValue);\n }\n }\n\n stepIndex++;\n }\n\n return current as TOut;\n }\n\n get stepCount(): number {\n return this.records.filter((r) => !r.isFallback).length;\n }\n\n /** Returns step metadata for debugging/tooling. */\n toArray(): Array<{ index: number; name: string | undefined; timeoutMs: number | undefined }> {\n let idx = 0;\n const result: Array<{ index: number; name: string | undefined; timeoutMs: number | undefined }> = [];\n for (const r of this.records) {\n if (!r.isFallback) {\n result.push({ index: idx++, name: r.name, timeoutMs: r.timeoutMs });\n }\n }\n return result;\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/** Create a new empty pipeline starting with type `T`. */\nexport function pipeline<T>(): Pipeline<T, T> {\n return new Pipeline<T, T>();\n}\n\n// ─── parallel ────────────────────────────────────────────────────────────────\n\n/**\n * Run tasks concurrently, return results in declaration order.\n * Rejects if any task throws (like `Promise.all`).\n */\nexport async function parallel<T>(\n tasks: Array<() => Promise<T>>,\n options: ParallelOptions = {}\n): Promise<T[]> {\n const { concurrency, signal } = options;\n\n if (concurrency == null || concurrency >= tasks.length) {\n // Unbounded — just Promise.all\n if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');\n return Promise.all(tasks.map((t) => t()));\n }\n\n // Bounded concurrency via semaphore\n const results: T[] = new Array(tasks.length);\n let nextIndex = 0;\n let hasError = false;\n let firstError: unknown;\n\n const run = async (): Promise<void> => {\n while (nextIndex < tasks.length) {\n if (signal?.aborted || hasError) break;\n const idx = nextIndex++;\n try {\n results[idx] = await tasks[idx]();\n } catch (err) {\n hasError = true;\n firstError = err;\n break;\n }\n }\n };\n\n const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, run);\n await Promise.all(workers);\n\n if (hasError) throw firstError;\n return results;\n}\n\n/**\n * Run tasks concurrently and return all results regardless of success/failure.\n * Equivalent to `Promise.allSettled` but with optional concurrency control.\n */\nexport async function parallelSettled<T>(\n tasks: Array<() => Promise<T>>,\n options: Pick<ParallelOptions, 'concurrency' | 'signal'> = {}\n): Promise<PromiseSettledResult<T>[]> {\n const { concurrency, signal } = options;\n const wrapped = tasks.map((t) => (): Promise<PromiseSettledResult<T>> =>\n t().then(\n (value) => ({ status: 'fulfilled' as const, value }),\n (reason) => ({ status: 'rejected' as const, reason })\n )\n );\n\n return parallel(wrapped, { concurrency, signal }) as Promise<PromiseSettledResult<T>[]>;\n}\n\n// ─── sequence ────────────────────────────────────────────────────────────────\n\n/**\n * Async left-fold over an array. Like `Array.reduce` but supports async reducers\n * and can be cancelled via `AbortSignal`.\n */\nexport async function sequence<T, A>(\n items: T[],\n initial: A,\n fn: (acc: A, item: T, index: number) => Promise<A> | A,\n options: SequenceOptions = {}\n): Promise<A> {\n const { signal } = options;\n let acc = initial;\n for (let i = 0; i < items.length; i++) {\n if (signal?.aborted) throw new DOMException('Sequence aborted', 'AbortError');\n acc = await fn(acc, items[i], i);\n }\n return acc;\n}\n"]}
1
+ {"version":3,"sources":["../src/flowx.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export type { StepFn, StepContext, NamedStep, FlowxOptions, ParallelOptions, SequenceOptions } from './types.js';\nimport type { StepFn, StepContext, NamedStep, FlowxOptions, ParallelOptions, SequenceOptions } from './types.js';\n\n// ─── Errors ──────────────────────────────────────────────────────────────────\n\nexport class PipelineStepError extends Error {\n constructor(\n public readonly stepIndex: number,\n public readonly stepName: string | undefined,\n public override readonly cause: unknown,\n public readonly inputValue: unknown\n ) {\n super(\n `Pipeline failed at step ${stepIndex}${stepName ? ` (${stepName})` : ''}: ${String(cause)}`\n );\n this.name = 'PipelineStepError';\n }\n}\n\nexport class PipelineTimeoutError extends Error {\n constructor(\n public readonly stepIndex: number,\n public readonly stepName: string | undefined,\n public readonly timeoutMs: number\n ) {\n super(`Step ${stepIndex}${stepName ? ` (${stepName})` : ''} timed out after ${timeoutMs}ms`);\n this.name = 'PipelineTimeoutError';\n }\n}\n\n// ─── Internal Step Record ────────────────────────────────────────────────────\n\ninterface StepRecord {\n fn: StepFn<unknown, unknown>;\n name: string | undefined;\n timeoutMs: number | undefined;\n isFallback: false;\n}\n\ninterface FallbackRecord {\n fn: (error: PipelineStepError, input: unknown) => unknown | Promise<unknown>;\n isFallback: true;\n}\n\ntype AnyRecord = StepRecord | FallbackRecord;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst neverAborted = new AbortController().signal;\n\nfunction raceStepTimeout<T>(\n promise: Promise<T>,\n ms: number,\n index: number,\n name: string | undefined\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new PipelineTimeoutError(index, name, ms)), ms);\n promise.then(\n (v) => { clearTimeout(timer); resolve(v); },\n (e) => { clearTimeout(timer); reject(e); }\n );\n });\n}\n\nfunction normalizeStep<In, Out>(\n step: StepFn<In, Out> | NamedStep<In, Out>\n): { fn: StepFn<In, Out>; name: string | undefined; timeoutMs: number | undefined } {\n if (typeof step === 'function') {\n return { fn: step, name: undefined, timeoutMs: undefined };\n }\n return { fn: step.fn, name: step.name, timeoutMs: step.timeoutMs };\n}\n\n// ─── Pipeline ────────────────────────────────────────────────────────────────\n\n/**\n * Flowx — composable, type-safe async pipeline builder.\n *\n * Each step receives the output of the previous step as its first argument,\n * plus a `StepContext` carrying the abort signal, step index, and step name.\n *\n * @example\n * const result = await pipeline<string>()\n * .pipe(s => s.trim())\n * .pipe({ name: 'uppercase', fn: s => s.toUpperCase() })\n * .tap(s => console.log('after uppercase:', s))\n * .run(' hello ');\n */\nexport class Pipeline<TIn, TOut> {\n private readonly records: AnyRecord[];\n\n constructor(records: AnyRecord[] = []) {\n this.records = records;\n }\n\n /**\n * Add a transform step. Accepts either a plain function or a `NamedStep`\n * object with an optional per-step `timeoutMs`.\n */\n pipe<TNext>(\n step: StepFn<TOut, TNext> | NamedStep<TOut, TNext>\n ): Pipeline<TIn, TNext> {\n const { fn, name, timeoutMs } = normalizeStep(step);\n const record: StepRecord = {\n fn: fn as StepFn<unknown, unknown>,\n name,\n timeoutMs,\n isFallback: false,\n };\n return new Pipeline<TIn, TNext>([...this.records, record]);\n }\n\n /**\n * Add a step with an inline fallback. If the step throws, `fallback` is\n * called with the error and the original input to that step.\n */\n pipeWithFallback<TNext>(\n step: StepFn<TOut, TNext> | NamedStep<TOut, TNext>,\n fallback: (error: PipelineStepError, input: TOut) => TNext | Promise<TNext>\n ): Pipeline<TIn, TNext> {\n const { fn, name, timeoutMs } = normalizeStep(step);\n const stepRecord: StepRecord = {\n fn: fn as StepFn<unknown, unknown>,\n name,\n timeoutMs,\n isFallback: false,\n };\n const fallbackRecord: FallbackRecord = {\n fn: fallback as (error: PipelineStepError, input: unknown) => unknown,\n isFallback: true,\n };\n return new Pipeline<TIn, TNext>([...this.records, stepRecord, fallbackRecord]);\n }\n\n /**\n * Add a side-effect step. Runs `fn` for its effect only; passes the current\n * value through unchanged. Errors in `fn` propagate normally.\n */\n tap(fn: (value: TOut, context: StepContext) => void | Promise<void>): Pipeline<TIn, TOut> {\n const tapStep: StepFn<TOut, TOut> = async (value, ctx) => {\n await fn(value, ctx);\n return value;\n };\n return this.pipe(tapStep);\n }\n\n async run(input: TIn, options: FlowxOptions = {}): Promise<TOut> {\n const signal = options.signal ?? neverAborted;\n const { onStepComplete, stepTimeoutMs } = options;\n\n let current: unknown = input;\n let stepIndex = 0;\n\n for (let i = 0; i < this.records.length; i++) {\n const record = this.records[i];\n\n if (record.isFallback) {\n // Fallback records are consumed inline by the preceding step handler.\n continue;\n }\n\n if (signal.aborted) {\n throw new DOMException('Pipeline aborted', 'AbortError');\n }\n\n const inputValue = current;\n const ctx: StepContext = { signal, stepIndex, stepName: record.name };\n const effectiveTimeout = record.timeoutMs ?? stepTimeoutMs;\n\n try {\n const rawResult = record.fn(current, ctx);\n let stepPromise: Promise<unknown> = rawResult instanceof Promise ? rawResult : Promise.resolve(rawResult);\n if (effectiveTimeout != null) {\n stepPromise = raceStepTimeout(stepPromise, effectiveTimeout, stepIndex, record.name);\n }\n current = await stepPromise;\n onStepComplete?.(stepIndex, record.name, current);\n } catch (err) {\n // Check if the next record is a fallback for this step.\n const nextRecord = this.records[i + 1];\n if (nextRecord?.isFallback) {\n const pipelineErr = new PipelineStepError(stepIndex, record.name, err, inputValue);\n try {\n current = await nextRecord.fn(pipelineErr, inputValue);\n } catch (fallbackErr) {\n // Fallback itself threw — wrap so callers can distinguish it\n throw new PipelineStepError(stepIndex, record.name, fallbackErr, inputValue);\n }\n i++; // skip the fallback record in the outer loop\n onStepComplete?.(stepIndex, record.name, current);\n } else {\n throw new PipelineStepError(stepIndex, record.name, err, inputValue);\n }\n }\n\n stepIndex++;\n }\n\n return current as TOut;\n }\n\n get stepCount(): number {\n return this.records.filter((r) => !r.isFallback).length;\n }\n\n /** Returns step metadata for debugging/tooling. */\n toArray(): Array<{ index: number; name: string | undefined; timeoutMs: number | undefined }> {\n let idx = 0;\n const result: Array<{ index: number; name: string | undefined; timeoutMs: number | undefined }> = [];\n for (const r of this.records) {\n if (!r.isFallback) {\n result.push({ index: idx++, name: r.name, timeoutMs: r.timeoutMs });\n }\n }\n return result;\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/** Create a new empty pipeline starting with type `T`. */\nexport function pipeline<T>(): Pipeline<T, T> {\n return new Pipeline<T, T>();\n}\n\n// ─── parallel ────────────────────────────────────────────────────────────────\n\n/**\n * Run tasks concurrently, return results in declaration order.\n * Rejects if any task throws (like `Promise.all`).\n */\nexport async function parallel<T>(\n tasks: Array<() => Promise<T>>,\n options: ParallelOptions = {}\n): Promise<T[]> {\n const { concurrency, signal } = options;\n\n if (concurrency == null || concurrency >= tasks.length) {\n // Unbounded — just Promise.all\n if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');\n return Promise.all(tasks.map((t) => t()));\n }\n\n // Bounded concurrency via semaphore\n const results: T[] = new Array(tasks.length);\n let nextIndex = 0;\n let hasError = false;\n let firstError: unknown;\n\n const run = async (): Promise<void> => {\n while (nextIndex < tasks.length) {\n if (signal?.aborted || hasError) break;\n const idx = nextIndex++;\n try {\n results[idx] = await tasks[idx]();\n } catch (err) {\n hasError = true;\n firstError = err;\n break;\n }\n }\n };\n\n const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, run);\n await Promise.all(workers);\n\n if (hasError) throw firstError;\n return results;\n}\n\n/**\n * Run tasks concurrently and return all results regardless of success/failure.\n * Equivalent to `Promise.allSettled` but with optional concurrency control.\n */\nexport async function parallelSettled<T>(\n tasks: Array<() => Promise<T>>,\n options: Pick<ParallelOptions, 'concurrency' | 'signal'> = {}\n): Promise<PromiseSettledResult<T>[]> {\n const { concurrency, signal } = options;\n const wrapped = tasks.map((t) => (): Promise<PromiseSettledResult<T>> =>\n t().then(\n (value) => ({ status: 'fulfilled' as const, value }),\n (reason) => ({ status: 'rejected' as const, reason })\n )\n );\n\n return parallel(wrapped, { concurrency, signal }) as Promise<PromiseSettledResult<T>[]>;\n}\n\n// ─── sequence ────────────────────────────────────────────────────────────────\n\n/**\n * Async left-fold over an array. Like `Array.reduce` but supports async reducers\n * and can be cancelled via `AbortSignal`.\n */\nexport async function sequence<T, A>(\n items: T[],\n initial: A,\n fn: (acc: A, item: T, index: number) => Promise<A> | A,\n options: SequenceOptions = {}\n): Promise<A> {\n const { signal } = options;\n let acc = initial;\n for (let i = 0; i < items.length; i++) {\n if (signal?.aborted) throw new DOMException('Sequence aborted', 'AbortError');\n acc = await fn(acc, items[i], i);\n }\n return acc;\n}\n"]}
package/dist/index.mjs CHANGED
@@ -118,7 +118,11 @@ var Pipeline = class _Pipeline {
118
118
  const nextRecord = this.records[i + 1];
119
119
  if (nextRecord?.isFallback) {
120
120
  const pipelineErr = new PipelineStepError(stepIndex, record.name, err, inputValue);
121
- current = await nextRecord.fn(pipelineErr, inputValue);
121
+ try {
122
+ current = await nextRecord.fn(pipelineErr, inputValue);
123
+ } catch (fallbackErr) {
124
+ throw new PipelineStepError(stepIndex, record.name, fallbackErr, inputValue);
125
+ }
122
126
  i++;
123
127
  onStepComplete?.(stepIndex, record.name, current);
124
128
  } else {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/flowx.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["export type { StepFn, StepContext, NamedStep, FlowxOptions, ParallelOptions, SequenceOptions } from './types.js';\nimport type { StepFn, StepContext, NamedStep, FlowxOptions, ParallelOptions, SequenceOptions } from './types.js';\n\n// ─── Errors ──────────────────────────────────────────────────────────────────\n\nexport class PipelineStepError extends Error {\n constructor(\n public readonly stepIndex: number,\n public readonly stepName: string | undefined,\n public override readonly cause: unknown,\n public readonly inputValue: unknown\n ) {\n super(\n `Pipeline failed at step ${stepIndex}${stepName ? ` (${stepName})` : ''}: ${String(cause)}`\n );\n this.name = 'PipelineStepError';\n }\n}\n\nexport class PipelineTimeoutError extends Error {\n constructor(\n public readonly stepIndex: number,\n public readonly stepName: string | undefined,\n public readonly timeoutMs: number\n ) {\n super(`Step ${stepIndex}${stepName ? ` (${stepName})` : ''} timed out after ${timeoutMs}ms`);\n this.name = 'PipelineTimeoutError';\n }\n}\n\n// ─── Internal Step Record ────────────────────────────────────────────────────\n\ninterface StepRecord {\n fn: StepFn<unknown, unknown>;\n name: string | undefined;\n timeoutMs: number | undefined;\n isFallback: false;\n}\n\ninterface FallbackRecord {\n fn: (error: PipelineStepError, input: unknown) => unknown | Promise<unknown>;\n isFallback: true;\n}\n\ntype AnyRecord = StepRecord | FallbackRecord;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst neverAborted = new AbortController().signal;\n\nfunction raceStepTimeout<T>(\n promise: Promise<T>,\n ms: number,\n index: number,\n name: string | undefined\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new PipelineTimeoutError(index, name, ms)), ms);\n promise.then(\n (v) => { clearTimeout(timer); resolve(v); },\n (e) => { clearTimeout(timer); reject(e); }\n );\n });\n}\n\nfunction normalizeStep<In, Out>(\n step: StepFn<In, Out> | NamedStep<In, Out>\n): { fn: StepFn<In, Out>; name: string | undefined; timeoutMs: number | undefined } {\n if (typeof step === 'function') {\n return { fn: step, name: undefined, timeoutMs: undefined };\n }\n return { fn: step.fn, name: step.name, timeoutMs: step.timeoutMs };\n}\n\n// ─── Pipeline ────────────────────────────────────────────────────────────────\n\n/**\n * Flowx — composable, type-safe async pipeline builder.\n *\n * Each step receives the output of the previous step as its first argument,\n * plus a `StepContext` carrying the abort signal, step index, and step name.\n *\n * @example\n * const result = await pipeline<string>()\n * .pipe(s => s.trim())\n * .pipe({ name: 'uppercase', fn: s => s.toUpperCase() })\n * .tap(s => console.log('after uppercase:', s))\n * .run(' hello ');\n */\nexport class Pipeline<TIn, TOut> {\n private readonly records: AnyRecord[];\n\n constructor(records: AnyRecord[] = []) {\n this.records = records;\n }\n\n /**\n * Add a transform step. Accepts either a plain function or a `NamedStep`\n * object with an optional per-step `timeoutMs`.\n */\n pipe<TNext>(\n step: StepFn<TOut, TNext> | NamedStep<TOut, TNext>\n ): Pipeline<TIn, TNext> {\n const { fn, name, timeoutMs } = normalizeStep(step);\n const record: StepRecord = {\n fn: fn as StepFn<unknown, unknown>,\n name,\n timeoutMs,\n isFallback: false,\n };\n return new Pipeline<TIn, TNext>([...this.records, record]);\n }\n\n /**\n * Add a step with an inline fallback. If the step throws, `fallback` is\n * called with the error and the original input to that step.\n */\n pipeWithFallback<TNext>(\n step: StepFn<TOut, TNext> | NamedStep<TOut, TNext>,\n fallback: (error: PipelineStepError, input: TOut) => TNext | Promise<TNext>\n ): Pipeline<TIn, TNext> {\n const { fn, name, timeoutMs } = normalizeStep(step);\n const stepRecord: StepRecord = {\n fn: fn as StepFn<unknown, unknown>,\n name,\n timeoutMs,\n isFallback: false,\n };\n const fallbackRecord: FallbackRecord = {\n fn: fallback as (error: PipelineStepError, input: unknown) => unknown,\n isFallback: true,\n };\n return new Pipeline<TIn, TNext>([...this.records, stepRecord, fallbackRecord]);\n }\n\n /**\n * Add a side-effect step. Runs `fn` for its effect only; passes the current\n * value through unchanged. Errors in `fn` propagate normally.\n */\n tap(fn: (value: TOut, context: StepContext) => void | Promise<void>): Pipeline<TIn, TOut> {\n const tapStep: StepFn<TOut, TOut> = async (value, ctx) => {\n await fn(value, ctx);\n return value;\n };\n return this.pipe(tapStep);\n }\n\n async run(input: TIn, options: FlowxOptions = {}): Promise<TOut> {\n const signal = options.signal ?? neverAborted;\n const { onStepComplete, stepTimeoutMs } = options;\n\n let current: unknown = input;\n let stepIndex = 0;\n\n for (let i = 0; i < this.records.length; i++) {\n const record = this.records[i];\n\n if (record.isFallback) {\n // Fallback records are consumed inline by the preceding step handler.\n continue;\n }\n\n if (signal.aborted) {\n throw new DOMException('Pipeline aborted', 'AbortError');\n }\n\n const inputValue = current;\n const ctx: StepContext = { signal, stepIndex, stepName: record.name };\n const effectiveTimeout = record.timeoutMs ?? stepTimeoutMs;\n\n try {\n const rawResult = record.fn(current, ctx);\n let stepPromise: Promise<unknown> = rawResult instanceof Promise ? rawResult : Promise.resolve(rawResult);\n if (effectiveTimeout != null) {\n stepPromise = raceStepTimeout(stepPromise, effectiveTimeout, stepIndex, record.name);\n }\n current = await stepPromise;\n onStepComplete?.(stepIndex, record.name, current);\n } catch (err) {\n // Check if the next record is a fallback for this step.\n const nextRecord = this.records[i + 1];\n if (nextRecord?.isFallback) {\n const pipelineErr = new PipelineStepError(stepIndex, record.name, err, inputValue);\n current = await nextRecord.fn(pipelineErr, inputValue);\n i++; // skip the fallback record in the outer loop\n onStepComplete?.(stepIndex, record.name, current);\n } else {\n throw new PipelineStepError(stepIndex, record.name, err, inputValue);\n }\n }\n\n stepIndex++;\n }\n\n return current as TOut;\n }\n\n get stepCount(): number {\n return this.records.filter((r) => !r.isFallback).length;\n }\n\n /** Returns step metadata for debugging/tooling. */\n toArray(): Array<{ index: number; name: string | undefined; timeoutMs: number | undefined }> {\n let idx = 0;\n const result: Array<{ index: number; name: string | undefined; timeoutMs: number | undefined }> = [];\n for (const r of this.records) {\n if (!r.isFallback) {\n result.push({ index: idx++, name: r.name, timeoutMs: r.timeoutMs });\n }\n }\n return result;\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/** Create a new empty pipeline starting with type `T`. */\nexport function pipeline<T>(): Pipeline<T, T> {\n return new Pipeline<T, T>();\n}\n\n// ─── parallel ────────────────────────────────────────────────────────────────\n\n/**\n * Run tasks concurrently, return results in declaration order.\n * Rejects if any task throws (like `Promise.all`).\n */\nexport async function parallel<T>(\n tasks: Array<() => Promise<T>>,\n options: ParallelOptions = {}\n): Promise<T[]> {\n const { concurrency, signal } = options;\n\n if (concurrency == null || concurrency >= tasks.length) {\n // Unbounded — just Promise.all\n if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');\n return Promise.all(tasks.map((t) => t()));\n }\n\n // Bounded concurrency via semaphore\n const results: T[] = new Array(tasks.length);\n let nextIndex = 0;\n let hasError = false;\n let firstError: unknown;\n\n const run = async (): Promise<void> => {\n while (nextIndex < tasks.length) {\n if (signal?.aborted || hasError) break;\n const idx = nextIndex++;\n try {\n results[idx] = await tasks[idx]();\n } catch (err) {\n hasError = true;\n firstError = err;\n break;\n }\n }\n };\n\n const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, run);\n await Promise.all(workers);\n\n if (hasError) throw firstError;\n return results;\n}\n\n/**\n * Run tasks concurrently and return all results regardless of success/failure.\n * Equivalent to `Promise.allSettled` but with optional concurrency control.\n */\nexport async function parallelSettled<T>(\n tasks: Array<() => Promise<T>>,\n options: Pick<ParallelOptions, 'concurrency' | 'signal'> = {}\n): Promise<PromiseSettledResult<T>[]> {\n const { concurrency, signal } = options;\n const wrapped = tasks.map((t) => (): Promise<PromiseSettledResult<T>> =>\n t().then(\n (value) => ({ status: 'fulfilled' as const, value }),\n (reason) => ({ status: 'rejected' as const, reason })\n )\n );\n\n return parallel(wrapped, { concurrency, signal }) as Promise<PromiseSettledResult<T>[]>;\n}\n\n// ─── sequence ────────────────────────────────────────────────────────────────\n\n/**\n * Async left-fold over an array. Like `Array.reduce` but supports async reducers\n * and can be cancelled via `AbortSignal`.\n */\nexport async function sequence<T, A>(\n items: T[],\n initial: A,\n fn: (acc: A, item: T, index: number) => Promise<A> | A,\n options: SequenceOptions = {}\n): Promise<A> {\n const { signal } = options;\n let acc = initial;\n for (let i = 0; i < items.length; i++) {\n if (signal?.aborted) throw new DOMException('Sequence aborted', 'AbortError');\n acc = await fn(acc, items[i], i);\n }\n return acc;\n}\n"]}
1
+ {"version":3,"sources":["../src/flowx.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["export type { StepFn, StepContext, NamedStep, FlowxOptions, ParallelOptions, SequenceOptions } from './types.js';\nimport type { StepFn, StepContext, NamedStep, FlowxOptions, ParallelOptions, SequenceOptions } from './types.js';\n\n// ─── Errors ──────────────────────────────────────────────────────────────────\n\nexport class PipelineStepError extends Error {\n constructor(\n public readonly stepIndex: number,\n public readonly stepName: string | undefined,\n public override readonly cause: unknown,\n public readonly inputValue: unknown\n ) {\n super(\n `Pipeline failed at step ${stepIndex}${stepName ? ` (${stepName})` : ''}: ${String(cause)}`\n );\n this.name = 'PipelineStepError';\n }\n}\n\nexport class PipelineTimeoutError extends Error {\n constructor(\n public readonly stepIndex: number,\n public readonly stepName: string | undefined,\n public readonly timeoutMs: number\n ) {\n super(`Step ${stepIndex}${stepName ? ` (${stepName})` : ''} timed out after ${timeoutMs}ms`);\n this.name = 'PipelineTimeoutError';\n }\n}\n\n// ─── Internal Step Record ────────────────────────────────────────────────────\n\ninterface StepRecord {\n fn: StepFn<unknown, unknown>;\n name: string | undefined;\n timeoutMs: number | undefined;\n isFallback: false;\n}\n\ninterface FallbackRecord {\n fn: (error: PipelineStepError, input: unknown) => unknown | Promise<unknown>;\n isFallback: true;\n}\n\ntype AnyRecord = StepRecord | FallbackRecord;\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nconst neverAborted = new AbortController().signal;\n\nfunction raceStepTimeout<T>(\n promise: Promise<T>,\n ms: number,\n index: number,\n name: string | undefined\n): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new PipelineTimeoutError(index, name, ms)), ms);\n promise.then(\n (v) => { clearTimeout(timer); resolve(v); },\n (e) => { clearTimeout(timer); reject(e); }\n );\n });\n}\n\nfunction normalizeStep<In, Out>(\n step: StepFn<In, Out> | NamedStep<In, Out>\n): { fn: StepFn<In, Out>; name: string | undefined; timeoutMs: number | undefined } {\n if (typeof step === 'function') {\n return { fn: step, name: undefined, timeoutMs: undefined };\n }\n return { fn: step.fn, name: step.name, timeoutMs: step.timeoutMs };\n}\n\n// ─── Pipeline ────────────────────────────────────────────────────────────────\n\n/**\n * Flowx — composable, type-safe async pipeline builder.\n *\n * Each step receives the output of the previous step as its first argument,\n * plus a `StepContext` carrying the abort signal, step index, and step name.\n *\n * @example\n * const result = await pipeline<string>()\n * .pipe(s => s.trim())\n * .pipe({ name: 'uppercase', fn: s => s.toUpperCase() })\n * .tap(s => console.log('after uppercase:', s))\n * .run(' hello ');\n */\nexport class Pipeline<TIn, TOut> {\n private readonly records: AnyRecord[];\n\n constructor(records: AnyRecord[] = []) {\n this.records = records;\n }\n\n /**\n * Add a transform step. Accepts either a plain function or a `NamedStep`\n * object with an optional per-step `timeoutMs`.\n */\n pipe<TNext>(\n step: StepFn<TOut, TNext> | NamedStep<TOut, TNext>\n ): Pipeline<TIn, TNext> {\n const { fn, name, timeoutMs } = normalizeStep(step);\n const record: StepRecord = {\n fn: fn as StepFn<unknown, unknown>,\n name,\n timeoutMs,\n isFallback: false,\n };\n return new Pipeline<TIn, TNext>([...this.records, record]);\n }\n\n /**\n * Add a step with an inline fallback. If the step throws, `fallback` is\n * called with the error and the original input to that step.\n */\n pipeWithFallback<TNext>(\n step: StepFn<TOut, TNext> | NamedStep<TOut, TNext>,\n fallback: (error: PipelineStepError, input: TOut) => TNext | Promise<TNext>\n ): Pipeline<TIn, TNext> {\n const { fn, name, timeoutMs } = normalizeStep(step);\n const stepRecord: StepRecord = {\n fn: fn as StepFn<unknown, unknown>,\n name,\n timeoutMs,\n isFallback: false,\n };\n const fallbackRecord: FallbackRecord = {\n fn: fallback as (error: PipelineStepError, input: unknown) => unknown,\n isFallback: true,\n };\n return new Pipeline<TIn, TNext>([...this.records, stepRecord, fallbackRecord]);\n }\n\n /**\n * Add a side-effect step. Runs `fn` for its effect only; passes the current\n * value through unchanged. Errors in `fn` propagate normally.\n */\n tap(fn: (value: TOut, context: StepContext) => void | Promise<void>): Pipeline<TIn, TOut> {\n const tapStep: StepFn<TOut, TOut> = async (value, ctx) => {\n await fn(value, ctx);\n return value;\n };\n return this.pipe(tapStep);\n }\n\n async run(input: TIn, options: FlowxOptions = {}): Promise<TOut> {\n const signal = options.signal ?? neverAborted;\n const { onStepComplete, stepTimeoutMs } = options;\n\n let current: unknown = input;\n let stepIndex = 0;\n\n for (let i = 0; i < this.records.length; i++) {\n const record = this.records[i];\n\n if (record.isFallback) {\n // Fallback records are consumed inline by the preceding step handler.\n continue;\n }\n\n if (signal.aborted) {\n throw new DOMException('Pipeline aborted', 'AbortError');\n }\n\n const inputValue = current;\n const ctx: StepContext = { signal, stepIndex, stepName: record.name };\n const effectiveTimeout = record.timeoutMs ?? stepTimeoutMs;\n\n try {\n const rawResult = record.fn(current, ctx);\n let stepPromise: Promise<unknown> = rawResult instanceof Promise ? rawResult : Promise.resolve(rawResult);\n if (effectiveTimeout != null) {\n stepPromise = raceStepTimeout(stepPromise, effectiveTimeout, stepIndex, record.name);\n }\n current = await stepPromise;\n onStepComplete?.(stepIndex, record.name, current);\n } catch (err) {\n // Check if the next record is a fallback for this step.\n const nextRecord = this.records[i + 1];\n if (nextRecord?.isFallback) {\n const pipelineErr = new PipelineStepError(stepIndex, record.name, err, inputValue);\n try {\n current = await nextRecord.fn(pipelineErr, inputValue);\n } catch (fallbackErr) {\n // Fallback itself threw — wrap so callers can distinguish it\n throw new PipelineStepError(stepIndex, record.name, fallbackErr, inputValue);\n }\n i++; // skip the fallback record in the outer loop\n onStepComplete?.(stepIndex, record.name, current);\n } else {\n throw new PipelineStepError(stepIndex, record.name, err, inputValue);\n }\n }\n\n stepIndex++;\n }\n\n return current as TOut;\n }\n\n get stepCount(): number {\n return this.records.filter((r) => !r.isFallback).length;\n }\n\n /** Returns step metadata for debugging/tooling. */\n toArray(): Array<{ index: number; name: string | undefined; timeoutMs: number | undefined }> {\n let idx = 0;\n const result: Array<{ index: number; name: string | undefined; timeoutMs: number | undefined }> = [];\n for (const r of this.records) {\n if (!r.isFallback) {\n result.push({ index: idx++, name: r.name, timeoutMs: r.timeoutMs });\n }\n }\n return result;\n }\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\n/** Create a new empty pipeline starting with type `T`. */\nexport function pipeline<T>(): Pipeline<T, T> {\n return new Pipeline<T, T>();\n}\n\n// ─── parallel ────────────────────────────────────────────────────────────────\n\n/**\n * Run tasks concurrently, return results in declaration order.\n * Rejects if any task throws (like `Promise.all`).\n */\nexport async function parallel<T>(\n tasks: Array<() => Promise<T>>,\n options: ParallelOptions = {}\n): Promise<T[]> {\n const { concurrency, signal } = options;\n\n if (concurrency == null || concurrency >= tasks.length) {\n // Unbounded — just Promise.all\n if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');\n return Promise.all(tasks.map((t) => t()));\n }\n\n // Bounded concurrency via semaphore\n const results: T[] = new Array(tasks.length);\n let nextIndex = 0;\n let hasError = false;\n let firstError: unknown;\n\n const run = async (): Promise<void> => {\n while (nextIndex < tasks.length) {\n if (signal?.aborted || hasError) break;\n const idx = nextIndex++;\n try {\n results[idx] = await tasks[idx]();\n } catch (err) {\n hasError = true;\n firstError = err;\n break;\n }\n }\n };\n\n const workers = Array.from({ length: Math.min(concurrency, tasks.length) }, run);\n await Promise.all(workers);\n\n if (hasError) throw firstError;\n return results;\n}\n\n/**\n * Run tasks concurrently and return all results regardless of success/failure.\n * Equivalent to `Promise.allSettled` but with optional concurrency control.\n */\nexport async function parallelSettled<T>(\n tasks: Array<() => Promise<T>>,\n options: Pick<ParallelOptions, 'concurrency' | 'signal'> = {}\n): Promise<PromiseSettledResult<T>[]> {\n const { concurrency, signal } = options;\n const wrapped = tasks.map((t) => (): Promise<PromiseSettledResult<T>> =>\n t().then(\n (value) => ({ status: 'fulfilled' as const, value }),\n (reason) => ({ status: 'rejected' as const, reason })\n )\n );\n\n return parallel(wrapped, { concurrency, signal }) as Promise<PromiseSettledResult<T>[]>;\n}\n\n// ─── sequence ────────────────────────────────────────────────────────────────\n\n/**\n * Async left-fold over an array. Like `Array.reduce` but supports async reducers\n * and can be cancelled via `AbortSignal`.\n */\nexport async function sequence<T, A>(\n items: T[],\n initial: A,\n fn: (acc: A, item: T, index: number) => Promise<A> | A,\n options: SequenceOptions = {}\n): Promise<A> {\n const { signal } = options;\n let acc = initial;\n for (let i = 0; i < items.length; i++) {\n if (signal?.aborted) throw new DOMException('Sequence aborted', 'AbortError');\n acc = await fn(acc, items[i], i);\n }\n return acc;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@async-kit/flowx",
3
- "version": "0.1.25",
3
+ "version": "0.2.0",
4
4
  "description": "Composable async pipeline builder for JavaScript/TypeScript",
5
5
  "keywords": [
6
6
  "async",