Skip to content

sondresj/dots

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DoTS

JSR CI

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 are null-prototype objects

Yet another functional programming package??

There is a lot of prior art similar to this package, such as Effect-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!

How does it work?

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 a yielder instead, which is a generator function that yields itself. This is why the monadic types here implements [Symbol.iterator] and also why the yield* operator is required in a do-block.

Example

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(),
        ),
    )
})

Releases

No releases published

Packages

No packages published