- Notifications
You must be signed in to change notification settings - Fork0
Do TS: Typescript Do notation
License
sondresj/dots
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Friendly monadic types inspired by Rust.Fully typed Do-notation and misc functional programming utils.
Warning
This project is still a work in progress, breaking changes is likely.Semantic versioning does not necessarily apply until a 1.0 release
Note
Instances of Option, Result, Task and Iter are frozen (immutable) and arenull-prototype objects
There is a lot of prior art similar to this package, such asEffect-TS.DoTS aims to be pragmatic and provide a minimal api surface to get the job done with the lowest possible barrier of entry for developers new to the functional programming paradigm.However, you should probably use Effect-TS. It's an awesome library!
Do notation in typescript is not natively supported, but can be achieved using generator functions.However, the typescript types for Generator and Iterator assumes the same type T is yielded from a generator,to work around this, we can yield ayielder instead, which is a generator function that yields itself.This is why the monadic types here implements [Symbol.iterator] and also why theyield*
operator is required in a do-block.
import { Do, Done, Fail, None, type Option, Some, Task, taskify } from "dots";export class RequestError extends Error { constructor( public readonly status: number, public readonly content: { message: string; status: string; body: Option<any>; }, public readonly inner: Option<Error>, ) { super(content.message); }}const _taskFetch = taskify(fetch);const taskFetch: typeof _taskFetch = (...args) => _taskFetch(...args).mapFailure((err) => { // Fetch rejected with no response from the uri return new RequestError( 500, { message: (err as any)?.message ?? "Unknown Error", status: "Unknown", body: None(), }, Some(err as any), ); });export const request = Do.bind(function* <T>( method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE", uri: string, headers: Record<PropertyKey, any> = {}, body: Record<PropertyKey, any> = {},) { const response = yield* taskFetch(uri, { method, headers, body: method !== "GET" ? JSON.stringify(body) : undefined, }); const json = yield* Task(response.json()).mapFailure((err) => { return new RequestError( 500, { message: (err as any)?.message ?? "Invalid JSON Response", status: "Unknown", body: None(), // could be response.text() instead }, Some(err as any), ); }); if (response.ok) { return Done<T, RequestError>(json as NonNullable<T>); } return Fail<T, RequestError>( new RequestError( response.status, { status: response.statusText, message: "Response indicated not OK", body: Some(json), }, None(), ), );});