[](https://www.npmjs.com/package/@simpill/patterns.utils) [](https://github.com/SkinnnyJay/simpill-utils/tree/main/utils/@simpill-patterns.utils)
npm
npm install @simpill/patterns.utilsGitHub (from monorepo)
git clone https://github.com/SkinnnyJay/simpill-utils.git && cd simpill-utils/utils/@simpill-patterns.utils && npm install && npm run buildThen in your project: npm install /path/to/simpill-utils/utils/@simpill-patterns.utils or npm link from that directory.
import {
ok,
err,
isOk,
isErr,
unwrapOr,
fromThrowable,
pipeAsync,
strategySelector,
} from "@simpill/patterns.utils";
const r = ok(42);
if (isOk(r)) console.log(r.value);
const e = err(new Error("fail"));
const value = unwrapOr(e, 0);
const pipeline = pipeAsync(addOne, double);
const compute = strategySelector({ add: ([a, b]) => a + b, mul: ([a, b]) => a * b });| Kind | Exports | What you get |
|---|---|---|
| High-value (real behavior) | Result, pipeAsync, strategySelector, chainOfResponsibility, createStateMachine, createObservable, createBuilder, createFlyweightFactory, createMediator, createComposite, createMethodProxy, runCommandWithUndo, raceOk, toResult, fromPromise | Implementations that save boilerplate and enforce structure. |
| Type / pattern helpers | createAdapter, adapt, createFacade, createFacadeFrom, createFactory, createCommand | Typed names for patterns (e.g. “this function is an Adapter”). No extra runtime behavior—use when you want consistent vocabulary and types. |
Use high-value APIs when you need the behavior. Use type helpers when you want to label a pattern in types and docs (e.g. dependency injection, testing mocks, or team conventions).
| Feature | Description |
|---|---|
| Result | ok, err, isOk, isErr, unwrapOr, fromThrowable, toResult, fromPromise |
| pipeAsync | Async function composition |
| strategySelector | Dispatch by key with optional defaultKey |
| chainOfResponsibility | handled, unhandled, ChainHandler |
| Command | createCommand, runCommand, runCommandWithUndo |
| Adapter | createAdapter, adapt |
| Builder | createBuilder |
| Decorator | decorate |
| Factory | createFactory |
| Facade | createFacade, createFacadeFrom |
| Flyweight | createFlyweightFactory |
| Mediator | createMediator |
| Observer | createObservable |
| Composite | createComposite, traverseComposite, mapComposite, reduceComposite |
| Proxy | createMethodProxy |
| State | createStateMachine |
| raceOk | First Ok wins from many Result promises |
| Need | Use |
|---|---|
| Return success/failure without throwing | Result (ok/err, unwrapOr, fromThrowable, toResult, fromPromise) |
| Async pipeline (f1 then f2 then f3) | pipeAsync |
| Dispatch by string key with fallback | strategySelector / strategySelectorOptional |
| Handlers in sequence until one handles | chainOfResponsibility |
| Typed “conversion function” (e.g. for DI) | Adapter (createAdapter, adapt) |
| Stepwise construction with validation | Builder |
| Single shared instance per key | Flyweight (createFlyweightFactory) |
| Notify many listeners | Observer (createObservable) |
| Typed event bus (event → payload) | Mediator (createMediator) |
| State-dependent behavior | State (createStateMachine) |
| Tree of nodes (e.g. UI, AST) | Composite |
| Intercept method calls (logging, metrics) | Proxy (createMethodProxy) |
| Execute + optional undo | Command (runCommandWithUndo) |
| First success among many async Results | raceOk |
This package is minimal compared to fp-ts (no full type-class hierarchy) or neverthrow (no ResultAsync); use it when you want Result/strategy/pipe and small patterns without extra dependencies.
- ResultAsync / neverthrow-style — No lazy Result-from-Promise wrapper; use fromPromise or toResult with async functions and pipeAsync for pipelines.
- fp-ts Option / Task / Either — No Option type or Task; Result covers success/failure. For full type-class style use fp-ts.
- Full type-class hierarchy — No Functor, Monad, or Traversable; just Result, pipeAsync, and pattern helpers.
import { ... } from "@simpill/patterns.utils"; // Everything
import { ... } from "@simpill/patterns.utils/client"; // Client
import { ... } from "@simpill/patterns.utils/server"; // Server
import { ... } from "@simpill/patterns.utils/shared"; // Shared only- Result<T, E>, ok, err, isOk, isErr, unwrapOr, fromThrowable, toResult, fromPromise
- pipeAsync(...fns) → composed async function
- strategySelector(strategies, options?) → (key, input) => O
- strategySelectorOptional — returns undefined for unknown key
- chainOfResponsibility, handled, unhandled, ChainHandler, ChainResult, ChainOptions
- createCommand, runCommand, runCommandWithUndo, Command, CommandExecution
- createAdapter, adapt, Adapter
- createBuilder, Builder
- decorate, Decorator
- createFacade, createFacadeFrom, Facade
- createFactory, Factory
- createFlyweightFactory, FlyweightFactory
- createMediator, Mediator, MediatorHandler
- createObservable, Observable, Observer, Unsubscribe
- createComposite, CompositeNode, addChild, removeChild, traverseComposite, mapComposite, reduceComposite
- createMethodProxy, MethodProxyHooks
- createStateMachine, StateMachine, StateTransitions, StateTransition, StateMachineOptions
- raceOk, RaceOkOptions
Full combinator set, neverthrow-parity, as tree-shakeable standalone functions over the same plain-object Result — no classes, no extra dependency:
- Transform:
map(r, fn),mapErr(r, fn),andThen(r, fn)(flatMap; Err short-circuits),orElse(r, fn)(error recovery),tap(r, fn)/tapErr(r, fn)(side effects, result passes through). - Consume:
match(r, onOk, onErr)— exhaustive fold; both branches must be handled. - Aggregate:
combine(results)— Ok(values[]) or the first Err;combineWithAllErrors(results)— collects every error. - Interop:
fromNullable(value, onNullish);fromThrowable(fn, mapError?)now takes an optional error mapper.
const r = andThen(parse(input), (n) => (n > 0 ? ok(n * 2) : err("must be positive")));
const msg = match(r, (v) => `got ${v}`, (e) => `failed: ${e}`);safeTry emulates Rust's ? operator — yield* safeUnwrap(result) unwraps an Ok or early-returns the Err:
const r = safeTry(function* () {
const user = yield* safeUnwrap(parseUser(raw));
const plan = yield* safeUnwrap(lookupPlan(user));
return ok({ user, plan });
}); // Result<{user, plan}, ParseError | LookupError>safeTryAsync is the same over an async generator (awaits allowed in the body).
toResult(promise, mapError?) and fromPromise(fn, mapError?) turn a Promise into Promise<Result<T, AppError>> (default mapError wraps to AppError). For chaining: mapAsync(r, asyncFn) transforms an Ok asynchronously, and andThenAsync(rOrPromise, fn) chains Promise<Result> steps linearly with Err short-circuit — no manual isOk checks between steps. Combine with raceOk for “first success.” For retry that returns Result use @simpill/resilience.utils retryResult.
The API Reference above lists every public export. From shared: Result (ok, err, isOk, isErr, map, mapErr, andThen, orElse, tap, tapErr, match, unwrap, unwrapErr, unwrapOr, unwrapOrElse, combine, combineWithAllErrors, fromNullable, fromThrowable, mapAsync, andThenAsync, safeTry, safeTryAsync, safeUnwrap, toResult, fromPromise), pipeAsync, strategySelector, strategySelectorOptional, chainOfResponsibility (handled, unhandled), Command, Adapter, Builder, Decorator, Facade, Factory, Flyweight, Mediator, Observable, Proxy, StateMachine, Composite helpers, raceOk. Client/server re-export the same; see package index and shared/index for the full list.
unwrap(r) returns the Ok value or throws (Error instances rethrown as-is; other error values wrapped with cause). unwrapErr(r) returns the Err value or throws on Ok. Prefer unwrapOr(r, fallback) / unwrapOrElse(r, (e) => fallback) or match at boundaries where throwing is not acceptable — unwrap is for the edges where an Err is a programmer error.
pipeAsync composes async functions with .then(); it does not accept AbortSignal. To cancel, pass an AbortSignal into your pipeline (e.g. as first argument) and have each step check signal.aborted or pass it to fetch. When a step rejects, the pipeline short-circuits and the returned promise rejects; there is no built-in “cancel pipeline” API.
toResult and fromPromise use @simpill/errors.utils AppError by default (via mapError). Use Result<T, AppError> so errors have code, cause, and meta. Example: const res = await fromPromise(() => fetchUser(id)); if (isErr(res)) logger.error({ code: res.error.code, cause: res.error.cause }); else setUser(res.value);. For custom codes pass mapError: fromPromise(fn, (e) => new AppError("Fetch failed", { code: ERROR_CODES.NOT_FOUND, cause: e })).
Sync: andThen chains Result-returning steps with Err short-circuit. Async: andThenAsync does the same over Promise<Result>:
const r = await andThenAsync(
andThenAsync(fromPromise(() => loadUser(id)), (user) =>
mapAsync(fromPromise(() => loadOrders(user.id)), async (orders) => ({ user, orders }))
),
(both) => ok(both)
);Or, flatter, with safeTryAsync:
const r = await safeTryAsync(async function* () {
const user = yield* safeUnwrap(await fromPromise(() => loadUser(id)));
const orders = yield* safeUnwrap(await fromPromise(() => loadOrders(user.id)));
return ok({ user, orders });
});npx ts-node examples/01-basic-usage.ts| Example | Description |
|---|---|
| 01-basic-usage.ts | Result, pipeAsync, strategySelector |
| 02-core-patterns.ts | Adapter, Decorator, Observer, State, Mediator |
| 03-structure-proxy.ts | Composite, Proxy |
| 04-creational.ts | Builder, Facade, Flyweight |
| 05-result-chain-command.ts | toResult, fromPromise, raceOk, chainOfResponsibility, runCommandWithUndo |
- Examples: examples/ — run with
npx ts-node examples/<file>.ts(see table above). - Monorepo: CONTRIBUTING for creating and maintaining packages.
- README standard: Package README standard.
- CONTRIBUTING — Monorepo package guide.
ISC