Giter Site home page Giter Site logo

mobily / ts-belt Goto Github PK

View Code? Open in Web Editor NEW
1.0K 11.0 29.0 16.58 MB

🔧 Fast, modern, and practical utility library for FP in TypeScript.

Home Page: https://mobily.github.io/ts-belt

License: MIT License

JavaScript 9.26% TypeScript 75.48% Shell 0.23% ReScript 14.89% Reason 0.15%
typescript functional-programming fp monad option option-type result result-type array flow

ts-belt's People

Contributors

anthony-khong avatar dilven avatar domeknn avatar eghizio avatar mobily avatar nodonisko avatar pyrolistical avatar remnantkevin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ts-belt's Issues

why does `R.map` expect a non-nullable result?

If I'm fetching an array from a server, and need to find an element in the array before returning the result, I would expect that I could return Result<Option<Elem>>, but instead R.map(A.getBy(/*...*/)) fails to pass type-checks because R.map expects a non-nullable result from its parameter.

Am I misunderstanding how I'm supposed to be achieving this? Is there something wrong with having an Option inside a Result?

S.prepend function

It would be nice if there was a function to prepend one string to another. There is already S.concat, so it seems like there may as well be S.prepend

Implementation Question

Could not find discussion panel nor any discord/slack/anything channel for any Q&A.
Let me know if there is a better place to ask questions regarding ts-belt.
I'm kinda new to FP in general and I can't shift my usual non FP thinking.

Is there anyway that I can tackle this with FP/ts-belt??

Scenario: Need to find position (index+1) where the sum of values equals to -1
Example data :

  • [1,1,-1,1,-1,-1,-1,1,1,1] answer is 7
  • [-1] answer is 1
  • [1,1,-1,-1,-1] answer is 5
let position: number | undefined = undefined;

_.pipe(
  [1,1,-1,1,-1,-1,-1,1,1,1],
  _.A.reduceWithIndex(0, (acc, next, index) => {
    const add = _.N.add(acc)(next);

    if (add === -1 && position === undefined) {
      position = index + 1;
    }

    return add;
  })
);

Is there a way to break out from reduce loop? I guess there is not.
How I can possibly hold a value not using an global variable but maintaining functional paradigms?
What would be a better approach for this case with ts-belt?

Try/catch utility? (feature request)

Hi, previous to ts-belt I had been using the pratica library which had this really neat try/catch utility called encase that was particularly handy for JSON parsing. It'd be cool if ts-belt had something like that too. 😎

F.when/unless different return type to input type?

Hi, I have a use case where I would like to use F.when, but have the return type be different to the type going in to the function. At the moment the return type for F.when and F.unless can only be the input type.

The context here is creating a document in a mongodb database and first checking to see if the document already exists. If it exists (checked via MyModel.exists()), a document object is returned, if it doesnt exist, null is returned.

So I would like to do something like this:

MyModel.exists({foo:'bar'})
  .then(O.fromNullable)
  .then(F.when(O.isNone, () => MyModel.create({foo:'bar'})))

`A.takeWhile` acting like `A.filter`

First of all, thanks for a solid library - it's really a breeze to use!

While doing a bit of Advent of Code, I noticed unexpected output from A.takeWhile - essentially it seems to act more like A.filter

Examples:

// ts-belt:
A.takeWhile([3, 5, 3], n => n < 4); 
// Returns [3, 3]

I'd expect to get back the single-element list: [3].

Ramda (and other similar libraries):

// ramda:
R.takeWhile(n => n < 4, [3, 5, 3])
// Returns [3]

Smells a bit like a bug to me 😉

Also - looking at a bit further, it looks like this case isn't covered by the tests

Btw, the version in question is 3.13.1

TS2345 when using Result with pipe

Hello,

I'm getting a TS error when using Result and pipe and have no idea why!

const processEvent = (
    event: MessageEvent<unknown>,
    handle: (event: ServerEvent) => void,
    error: (error: string) => void
) => {
    pipe(
        R.fromNullable(event, 'Incoming sse/ws message is null!'),
        R.map((event) => event.data), // <--- ERROR HERE
        R.flatMap((data: unknown) => {
            try {
                if (G.isString(data)) return R.Ok(JSON.parse(data));
            } catch (_error) {
                /* ignored */
            }
            return R.Error('Invalid incoming sse/ws message format!');
        }),
        R.flatMap((json: string) => {
            try {
                return R.Ok(ServerEventSchema.parse(json));
            } catch (_error) {
                return R.Error('Invalid incoming sse/ws message schema!');
            }
        }),
        R.tapError(error),
        R.tap(handle)
    );
};

The message I get is the following:

TS2345: Argument of type 'Result<unknown, unknown>' is not assignable to parameter of type '(arg: Result<MessageEvent<unknown>, "Incoming sse/ws message is null!">) => Result<unknown, "Invalid incoming sse/ws message format!">'.   Type 'Ok<unknown>' is not assignable to type '(arg: Result<MessageEvent<unknown>, "Incoming sse/ws message is null!">) => Result<unknown, "Invalid incoming sse/ws message format!">'.     Type 'Ok<unknown>' provides no match for the signature '(arg: Result<MessageEvent<unknown>, "Incoming sse/ws message is null!">): Result<unknown, "Invalid incoming sse/ws message format!">'.

Any ideas?

Thanks!

Unsafely select keys from an object

How can I select just a subset of keys from an object?

Consider this example in an express app. I want to validate the body, params, and query and want to use ts-belt to select just those three from the request. Is there a way to do this with ts-belt? Maybe a unsafeSelectKeys?

const validKeys = ['body', 'params', 'query']

const validReq = D.selectKeys(request, validKeys)

Thanks for the help!

is reverse-currying considered idiomatic?

I was making interchangeable Pos and Size, and they work like

Pos(1, 2) + Size(3, 4) // Pos(4, 6)
Size(1, 2) + Pos(3, 4) // Size(4, 6)

but currying didn't work well with pipe (since pipe is data-first), I had to do something like this:

import { pipe } from '@mobily/ts-belt'

type A = { tag: 'A' }
type B = { tag: 'B' }
type Two = A | B

function foo(a: Two): (c: Two) => Two
function foo(a: Two, b: Two): Two
function foo(a: Two, b?: Two) {
  return b ? a : (c: Two) => foo(c, a)
}

const [a, b] = [{ tag: 'A' }, { tag: 'B' }] as const

console.log(foo(a, b)) // { tag: 'A' }
console.log(foo(b)(a)) // { tag: 'B' }
console.log(pipe(a, foo(b))) // { tag: 'A' }

is this considered idiomatic? I see most of ts-belt's (data-first) functions take the same approch, i.e reversed currying, for example S.concat:

function concat(appendValue: string): (str: string) => string
function concat(str: string, appendValue: string): string

however, I'm confused because ts-belt is written in rescript, and in rescript docs about pipe, it says

Do not abuse pipes; they're a means to an end. Inexperienced engineers sometimes shape a library's API to take advantage of the pipe. This is backwards.

does ts-belt take this approach because of pipe's limitation (cannot change position of argument unless using lambda)?

getName(input)
  ->namePerson(personDetails, _)

Consider drop of readonly modifiers

readonly is constant source of pain, because everytime where code from ts-belt come to contact with normal JS code it throws type error. It happens us a lot because we are using it in legacy codebase a even if you do thing like simple A.map you need always wrap result in F.toMutable.

If I decide to refactor our code to work with it, I need to place readonly literally everywhere. That will break TS in our tests because of our fixtures which are mutable. Same problem is that everywhere where code come to touch with 3rd party libraries I will throw some errors about readonly again.

And last and probably strongest argument is this nice thread on Twitter with some great examples why it's even not that safe >>> https://twitter.com/MichaelArnaldi/status/1601535981949456385

I know this is will produce big change (but shouldn't be breaking) and final resolution is on you @mobily but please consider dropping it for v4.

Question about Option type guards

Hi 👋

A belt port in TS is something I've already dreamed of, so thank you!
I tried adding it to our codebase, but think it might have an issue with the Option type guards (isSome / isNone)

On usage with ts-pattern (for pattern maching):

import { A, O } from "@mobily/ts-belt";
import { match, when } from "ts-pattern";

const array = A.make(Math.floor(Math.random() * 10), "foo");

match(A.get(array, 5))
  .with(when(O.isSome), value => { /* value is O.Option<string>, it should be string */ })
  .with(when(O.isNone), value => { /* value is O.Option<never>, it should be never */ })
  .exhaustive();

I checked the types of the given function and it confirms the issue:

/** Returns `true` if the provided `option` is `Some(value)`, otherwise, returns `false`. */
export declare function isSome<T>(option: Option<T>): option is Option<T>;

/** Returns `true` if the provided `option` is `None`, otherwise, returns `false`. */
export declare function isNone<T>(option: Option<T>): option is Option<never>;

Shouldn't it returns option is T and option is never instead?

Same example with Rescript (v is int, not option(int)):

https://rescript-lang.org/try?code=DYUwLgBAtgngcgV2MAhgI1ANRcBIIC8EAygPZQgAUArAJQBQ9AzgO4CWYAxgBbTxKoMIbLnwBvegB8S5KgDdahAHwQAUkwB0wUgHMATJQBEcnHghsmEAA4AnEExAA7MIYA0EBVIhxSj-ARV1LV0jE1FzS3QHZ0MGAF96IA

Possible to import one function at a time? (✨ Feature request)

Love your lib! Though I'd share one of my thoughts. Didn't see anything about this in the docs. Sorry if I'm reraising a question.

As a person with bundle phobia, it would be nice if I could only import the functions that I need. To my understanding, the current way of importing is to import the whole function set for a given type i.e array. import { A } from '@mobily/ts-belt'

What about something like this:

LoDash approach
import shuffle from '@mobily/ts-belt/a/shuffle'
import shuffle from '@mobily/ts-belt/array/shuffle'

Tree-shaking
I have mixed feelings about this one because in some libraries it doesn't really only give you the one function. Not to sure about how all this works, but it might have something to do with your compiler setup. Even though ergonomic to use I fear that in some cases it might not work as expected.
import { shuffle } from '@mobily/ts-belt/a'

funkia/list compatibility?

  1. Could ts-belt be used with https://github.com/funkia/list ?

List is a purely functional alternative to arrays. ... List is immutable. ... Since List doesn't allow mutations it can be heavily optimized for pure operations.

  1. List's benchmarks are quite impressive. Would like to see a comparison with ts-belt for the operations they share, like map. Or compare ts-belt with Ramda + List, since List was designed to seamlessly integrate with Ramda.

  2. Maybe ts-belt could even benefit from integrating List?

Sequence and Traverse

Hello!

First of all, thank you very much for building ts-belt. It has really attacked what I thought was the biggest problem with fp-ts, namely, bad documentation and incomprehensible errors.

Two functions that I've seen in almost every fp library are Sequence and Traverse. I've found whenever I work with Options and Results that I invariably end up reaching for these functions. However, I can't seem to find any equivalent in ts-belt. Is it expected that the user would create their own?

Questions about common FP functions

Hi @mobily, the library looks really interesting! I'm quite interested in trying it out for future projects. I just have a couple of questions about certain functions, and I hope this is the right place to ask.

  1. How would you do Clojure's assoc and dissoc with ts-belt? Would it have to be D.merge(m, {k: v}) and D.rejectWithKey(m, (_, key) => key == k)?
  2. I noticed that D.xxxWithKey's signature is (v, k) instead of the usual (or rather what I would expect to be) (k, v). Is that just a matter of personal preference or rather a convention from another library/language that I'm not familiar with?
  3. On Remeda, the doc says "Due to the lazy evaluation, it’s also unclear how to use some utility functions within the pipeline." Does this mean that ts-belt does eager evaluation? So something like, say, pipe(m, D.toPairs, A.take(2)) would iterate through all the key-value pairs first before taking the first two elements?

Also, something like Remeda's mapping doc would be very helpful to newcomers! I'm happy to chip in if you think it'd be useful.

Question: Casting type / infering type

I'm struggling to understand how to satisfy TypeScript when using ts-belt. Appreciate any help

How do I cast array of numbers from splitting the string?
Is this the right approach of such ?

const sample1 = "2x3x4";

_.pipe(
  sample1,
  _.F.coerce<() => [number, number, number]>(_.S.split("x")),
  _.O.flatMap(([l, w, h]) => 2 * l * w + 2 * w * h + 2 * h * l)
);

or

const data = `azs
20x3x11
15x27x5
6x29x7`

const getSurfaceArea = (input: number[]) =>
  _.pipe(
    input,
    _.O.flatMap(([l, w, h]) => {
      if (!l || !w || !h) return 0;

      const squareFeetArea = 2 * l * w + 2 * w * h + 2 * h * l;
      const smallestSide = Math.min(l * w, w * h, h * l);

      return squareFeetArea + smallestSide;
    })
  );

const totalSquareFeet = _.pipe(
  data,
  _.S.split("\n"),
  _.A.map((el) => _.A.map(_.S.split("x")(el), Number).filter(Boolean)),
  _.A.map(getSurfaceArea),
  _.A.reduce(0, _.N.add)
);

console.log(totalSquareFeet);

TS2345: Argument of type '(xs: readonly number[][]) => readonly Option[]' is not assignable to parameter of type '(arg: readonly number[][]) => readonly number[]'.   Type 'readonly Option[]' is not assignable to type 'readonly number[]'.     Type 'Option' is not assignable to type 'number'.       Type 'undefined' is not assignable to type 'number'.

Screenshot 2022-11-21 at 00 46 58

how to handle typing of Option/Result

Example Code on Array Docs Gives a Warning

Link: https://mobily.github.io/ts-belt/api/array
node: v18.2.0
typescript: 4.7.3
tsserver: 0.11.1
tsconfig.json: i follow this https://mobily.github.io/ts-belt/docs/getting-started/config

I just tried the code from the array doc and it gives a warning like this:
image

the code runs fine, but the warning annoys me. So i removed the warning by handling the Option using match:

import { A, O, pipe } from "@mobily/ts-belt";

const arr = A.makeWithIndex(5, (i) => i); // if i changed this to [] without O.match, it will throws error
console.log(
  pipe(
    arr,
    A.tail,
    O.match( // initially i wanna use O.getWithDefault([]), but it also gives warning. O.getExn works but throws error if arr = [] :(
      (tail: readonly number[]) => tail,
      () => []
    ),
    A.take(2),
    A.map((val) => val * 2)
  )
);

question: is it some secret pipe behavior that handles Option or A.take type is not complete or the doc needs an update?

thank you

Lenses

Hi! Great work with this library 👍

Are there any plans for adding functions related to lenses? Such as lensProp from Ramda.js?

Cannot use import statement outside a module

Steps

  • locally with node v17.9.1 and in a just created docker container with node 16.7.0
  • npm init
  • add "type": "module" to package.json
  • create a a.js file with just import * as Belt from '@mobily/ts-belt'
  • node a.js
/tsbelt # node a.js
(node:63) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/tsbelt/node_modules/@mobily/ts-belt/dist/esm/index.js:1
import { pipe } from "./pipe.js";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1031:15)
    at Module._compile (node:internal/modules/cjs/loader:1065:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:196:29)
    at ModuleJob.run (node:internal/modules/esm/module_job:183:25)
    at async Loader.import (node:internal/modules/esm/loader:178:24)
    at async Object.loadESM (node:internal/process/esm_loader:68:5)

Docs should clarify on depth of object-related functions

I think it would be helpful if the API documentation would give clarification on whether or not functions such as F.equals or D.merge are shallow or deep. We can work this out by looking at the source code, but this shouldn't be necessary and is espescially annoying for people who don't know rescript.

Use as Rescript package?

Hey there, this project looks really cool!

Is it possible to use this as a regular Rescript package? It looks like the NPM package only published the generated ts/js files. Is there any future version where this is supported?

Feature request: sortByMultiple

Hi there! :)

I was looking for something like lodash's sortBy with multiple accessors, e.g:

sortBy(users, [user => user.name, user => user.age]);

Why?
I know this could be implemented using the existing A.sortBy function but after 2 accessors it becomes tedious and prone to error. Lodash implementation might also not be the fastest possible.

Would something like sortByMultiple be on the roadmap for ts-belt?

Api suggestion:

function sortByMultiple<A, B>(xs: Array<A>, sortFns: Array<(_1: A) => B>): Array<A>
function sortByMultiple<A, B>(sortFns: Array<(_1: A) => B>): (xs: Array<A>) => Array<A>

Thank you for your time 🙏

`A.uniq`: hasOwnProperty is not a function

I'm using A.uniq on an array of Tuples. Unfortunately, that results in an exception being thrown:

@mobily/ts-belt/dist/cjs/Array/index.js:446
        if (!b.hasOwnProperty(key)) {
               ^
TypeError: b.hasOwnProperty is not a function

Import into typescript project whose option module:nodenext doesn't work

First of all, I want to say that I really like ts-belt utilities and really wanted to make it work with my current project.


import { A, D, O, pipe } from '@mobily/ts-belt';
         ^
SyntaxError: Named export 'A' not found. The requested module '@mobily/ts-belt' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:

import pkg from '@mobily/ts-belt';
const { A, D, O, pipe } = pkg;

Issue:

  • When import into a project whose module:nodenext, ts-belt is resolved as a esm module and redirected to dist/esm folder. However, the files of this folder still have .js extension and the package.json does't contain type:module option, which makes node assumes that it is trying to import a CommonJS module and try to resolve the import as such.
  • Furthermore, appying the above fix doesn't solve the problem, as it results in this error:
^^^^^^

SyntaxError: Cannot use import statement outside a module

Since all files inside esm are still understood as commonjs files, the import syntax of esm still doesn't work.


Proposed fix:

  • Add a build step that add additional option "type":"module" to dist/esm/package.json
  • The above option will break esm folder, as node really doesn't understand import without file extension. Typescript compiler also won't add extensions by default. This may require manually updating ts files with import to include extension instead, for example import * from 'Require' to import * from 'Require/index.js'.

D.keys type is correct for object with numeric indexes

D.keys type is incorrect:

const keys = D.keys({ 1: 'ahoj', 2: 'ahoj2' });
console.log(keys);

This will print [ '1', '2' ] but type for keys is readonly (1 | 2)[] which is wrong because it shouldn't be numbers but strings: readonly ('1' | '2')[]

Value first pipe

Hi! I'm confused about pipe accepting a value first. Unless I'm missing something, you can't create a point-free (tacit) function.

With a pipe that takes the value last (as in most, if not all implementations), you can do this:

const someFn = pipe(fnA, fnB, fnC)
// later:
someFn(value)

I think this is very clean. With a pipe that takes the value as it's first argument, you always have to "wrap" the composition in a function:

const someFn = (value) => pipe(value, fnA, fnB, fnC)
// later:
someFn(value)

Making a point-free style impossible. Am I missing something?

Why is noUncheckedIndexedAccess mandatory for Option

Hi,
I am using ts-belt in legacy code-base and there is this message in docs:

Adding noUncheckedIndexedAccess to your tsconfig.json is mandatory for the Option type!

Why it's neccessary, will it not work at all or it will break in some cases? Enabling noUncheckedIndexedAccess in our codebase is not a option because it will throw like in milion places.

TypeScript doesn't use the name of the type while inferring return type

For a function

const toFirstName = (input: string) => R.fromPredicate(input, S.isNotEmpty, 'Enter your first name');

the return type is inferred as

Screenshot from 2022-09-06 11-28-03

which is technically true, but I would have preferred the inferred type to be the same as that of R.fromPredicate()

Screenshot from 2022-09-06 11-29-40

Right now, I need to specify the return type explicitly as

const toFirstName = (input: string): Result<string, string> => R.fromPredicate(input, S.isNotEmpty, 'Enter your first name');

VSCODE pipe -> O.map -> O.getWithDefault Function -> ts2345 error is displayed.

I use VSCode.

in my environment, after using the map function of type option, use the getWithDefault function of type option. Then the ts(2345) error is displayed.

const result = pipe(
  O.fromNullable(null),
  O.map((value) => `${value} world!`), //← ts2345 error is displayed.
  O.getWithDefault("error")
);

but

const temp = pipe(
  O.fromNullable(null),
  O.map((value) => `${value} world!`)
);

const result = O.getWithDefault(temp, "test"); //← ts2345 error is NOT displayed.

and

const temp = pipe(
  O.fromNullable(null),
  O.map((value) => `${value} world!`),
  O.getWithDefault("test" as string) //← ts2345 error is NOT displayed.
);

mytsconfig

{
  "compilerOptions": {
    "strictNullChecks": true,
    "noUncheckedIndexedAccess": false,
    "target": "ESNext",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

typescript: 4.8.4
node: 16.18.0

use tsc command -> ts2345 error is NOT displayed.

Wrong types/behavior for A.groupBy or D.mapWithKey

	const someValues = [ { id: 1, value: "hello" }, { id: 2, value: "world" } ]
    pipe(
        someValues,
        A.groupBy(value => value.id),
        D.mapWithKey((id, values) => {
			console.log(typeof id) // prints out string
			const numId: number = id; // this should throw, but it doesn't because type is number instead of string

			return values;
        }),
    );

This is kind of interesting problem it could be either type issue or implementation issue. First solution could be that it will be same type as value.id (A.groupBy(value => value.id)) and only types that could become object keys should be allowed (probably only number/string ?). Then implementation needs to be fixed.

Or if current implementation is correct it should be types as always string.

Wrong type definitions for `R.flatMap` and `AR.flatMap`

As per documentation, the function given to flatMap is only invoked if the status is Ok. Hence, if the Result was already in an error state BEFORE R.flatMap is invoked, the state doesn't change.

While this is true at runtime, this is not how the type specification is implemented.

For example:

import {R, pipe} from '@mobily/ts-belt';

class Error1 extends Error{}
class Error2 extends Error{}

const value = Math.random() < 0.5 ? null : 'test';
const test = pipe(
  R.fromNullable(value, new Error1()),
  R.flatMap((value) => {
    return Math.random() < 0.5
        ? R.Ok(value)
        : R.Error(new Error2())
    }
  )
)
console.log(test);

The type of test should be Result<string, Error1 | Error2>, but it is Result<string, Error2>.

✨ Introducing `ts-belt` v4 (release candidate)

hello there! 👋

I have been working on the new version of ts-belt with support for Async* modules for a quite long time, and now I feel it's a great time to publish (at least) a release candidate version. It's also an excellent opportunity to gather feedback from you :) The bad news is, the docs for these modules are missing at the moment (I'm working on it!), but let me describe the essentials of each module:

Installation

yarn add @mobily/ts-belt@next

AsyncData

AsyncData contains a variant type for representing the different states in which a value can be during an asynchronous data load.

There are four possible states: Init, Loading, Reloading, and Complete.

type AsyncData<T> = Init | Loading | Reloading<T> | Complete<T>
import { AD } from '@mobily/ts-belt'

Variant constructors:

  • AD.Init
  • AD.Loading
  • AD.Reloading(value)
  • AD.Complete(value)
  • AD.makeInit()
  • AD.makeLoading()
  • AD.makeReloading(value)
  • AD.makeComplete(value)

Functions:

  • AD.isInit
  • AD.isLoading
  • AD.isReloading
  • AD.isComplete
  • AD.isBusy
  • AD.isIdle
  • AD.isEmpty
  • AD.isNotEmpty
  • AD.toBusy
  • AD.toIdle
  • AD.getValue
  • AD.getWithDefault
  • AD.getReloading
  • AD.getComplete
  • AD.map
  • AD.mapWithDefault
  • AD.flatMap
  • AD.tapInit
  • AD.tapLoading
  • AD.tapReloading
  • AD.tapComplete
  • AD.tapEmpty
  • AD.tapNotEmpty
  • AD.all
  • AD.fold

Example: https://codesandbox.io/s/cool-star-6m87kk?file=/src/App.tsx

AsyncDataResult

AsyncDataResult is basically an alias of AsyncData<Result<Ok, Error>>. This variant type can be used to represent the different states in which a data value can exist while being loaded asynchronously, with the possibility of either success or failure.

type AsyncDataResult<A, B> = AsyncData<Result<A, B>>
import { ADR } from '@mobily/ts-belt'

Variant constructors:

  • ADR.Init
  • ADR.Loading
  • ADR.ReloadingOk(value)
  • ADR.ReloadingError(error)
  • ADR.CompleteOk(value)
  • ADR.CompleteError(error)
  • ADR.makeInit()
  • ADR.makeLoading()
  • ADR.makeReloadingOk(value)
  • ADR.makeReloadinError(error)
  • ADR.makeCompleteOk(value)
  • ADR.makeCompleteError(error)

Functions:

  • ADR.isOk
  • ADR.isError
  • ADR.isReloadingOk
  • ADR.isReloadingError
  • ADR.isCompleteOk
  • ADR.isCompleteError
  • ADR.getOk
  • ADR.getReloadingOk
  • ADR.getCompleteOk
  • ADR.getError
  • ADR.getReloadingError
  • ADR.getCompleteError
  • ADR.map
  • ADR.mapError
  • ADR.flatMap
  • ADR.tap
  • ADR.fold
  • ADR.foldOk
  • ADR.toAsyncData

Example: https://codesandbox.io/s/brave-cloud-ov30h7?file=/src/App.tsx

AsyncOption

Same as Option but for handling asynchronous operations.

type AsyncOption<T> = Promise<Option<T>>
import { AO } from '@mobily/ts-belt'

Variant constructors:

  • AO.make(promise)
  • AO.resolve(value)
  • AO.reject()

Functions:

  • AO.filter
  • AO.map
  • AO.flatMap
  • AO.fold
  • AO.mapWithDefault
  • AO.match
  • AO.toNullable
  • AO.toUndefined
  • AO.toResult
  • AO.getWithDefault
  • AO.isNone
  • AO.isSome
  • AO.tap
  • AO.contains
  • AO.flatten

AsyncResult

Same as Result but for handling asynchronous operations.

type AsyncResult<A, B> = Promise<Result<A, B>>
import { AR } from '@mobily/ts-belt'

Variant constructors:

  • AR.make(promise)
  • AR.resolve(value)
  • AR.reject(error)

Functions:

  • AR.flatMap
  • AR.fold
  • AR.map
  • AR.mapWithDefault
  • AR.getWithDefault
  • AR.filter
  • AR.match
  • AR.toNullable
  • AR.toOption
  • AR.toUndefined
  • AR.isOk
  • AR.isError
  • AR.tap
  • AR.tapError
  • AR.handleError
  • AR.mapError
  • AR.catchError
  • AR.recover
  • AR.flip
  • AR.flatten

Minor changes

  • ✨ added A.sample (gets a random element from provided array)
  • ✨ added O.all (transforms an array of Option(s) into a single Option data type)
  • ✨ added R.all (transforms an array of Result(s) into a single Result data type)
  • ✨ added R.filter (returns Ok(value) if result is Ok(value) and the result of predicateFn is truthy, otherwise, returns Error)
  • 🐛 fixed up the groupBy signature

Breaking changes

ts-belt@v4 does not support Flow, due to a lack of proper features in flowgen, sorry about that!

Feel free to post your thoughts, any kind of feedback would be greatly appreciated! 💪

Support for asynchronous operations

Hey there, loving the library thus far.

I was wondering if pipe and flow support async functions without needing to wrap them in an IIFE. Purify supports this via their MaybeAsync and EitherAsync wrappers, and I believe Ramda also supports this via the composeWith and pipeWith functions.

I'm currently evaluating replacing Ramda with either this library or Purify, but to reduce the amount of churn, being able to support async ops in a composition is ideal.

ts-belt not working in node environment?

I have a project that I am creating with SvelteKit, ts-belt seems to work just fine on my client-side code:

	if (browser) {
		console.log(A.map([1, 2, 3], x => x + 2));
	}

However, when I try to run it in on of the server-side files (i.e. +page.server.ts) it throws an error:

import { A } from '@mobily/ts-belt';

export const load: PageServerLoad = () => {
	const arr = A.map([1, 2, 3], x => x + 1);
	console.log(arr);
	
	return {};
};
TypeError: Cannot read properties of undefined (reading 'A')
    at load (.../src/routes/auth/register/+page.server.ts:23:13

I'm not sure if this is a SvelteKit issue, I haven't had that problem with any other library. Any ideas?

A qustion about ReadOnly

Thanks for your work on this awesome library
I'm trying to replace lodash with this in my project, but not sure whether i'm using this library in a idiomatic way

sometimes components only accept mutable types, is it a right practice that manually converting result to un-readonly type by using as keyword?
for example

export declare type PickerColumnItem = {
    label: ReactNode;
    value: string;
};
export declare type PickerColumn = (string | PickerColumnItem)[];
export declare type PickerViewProps = {
    columns: PickerColumn[] | ((value: PickerValue[]) => PickerColumn[]);
}

when i pass a A.map output to the columns prop, tsc complaint that:

The type 'readonly { value: string; label: string; }[]' is 'readonly' and cannot be assigned to the mutable type 'PickerColumn'.

and fix this by as PickerColumnItem[]

Add support for Do - bind Result syntax

Awesome job! I've been using your library for a while and was wondering if you see it possible to add a Do-bind syntax to be able to chain without so much verbosity.

I've sketched my utility for the Results, but this can be extended to Options and Async Result

export const Do = <T, E = Error>(value?: T) => R.makeOk<NonNullable<T> | {}, E>(value ?? {})
export const bind =
	<K extends string ,T, U, E>(key: K, fn: (value: T) => R.Result<U, E>) =>
	(prev: R.Result<T, E>) =>
		R.flatMap(prev, value => R.map(fn(value), result => D.set(value, key, result)))

export const bindTo = <K extends string ,T, E>(key: K) => (prev: R.Result<T, E>)  => R.map<T,E,Record<K, T>>(prev,value => D.fromPairs([[key, value]]))

With this, we can improve readability

const before = pipe(
	resultFn1(x),
	R.flatMap(prev =>
		R.map(resultFn2(y), current => ({
			['meningFullKey2']: current,
			['meningFullKey1']: prev,
		})),
	),
	R.flatMap(prev =>
		R.map(resultFn3(z), current => ({
			['meningFullKey3']: current,
			...prev,
		})),
	),
	R.flatMap(prev =>
		R.map(resultFn3(prev.meningFullKey3, prev.meningFullKey1), current => ({
			['meningFullKey4']: current,
			...prev,
		})),
	),
	// ...
)

const after = pipe(
	R.do(),
	R.bind('meningFullKey1', () => resultFn1(x)),
	R.bind('meningFullKey2', () => resultFn2(y)),
	R.bind('meningFullKey3', () => resultFn3(z)),
	R.bind('meningFullKey4', ({ meningFullKey3, meningFullKey1 }) => resultFn3(meningFullKey3, meningFullKey1)),
	// ...
)

Curry? (feature request)

Hi, I think it would be good to have a curry function in the function api if possible.

Cheers.

A.range documentation error?

Thanks for producing this library.

I'm new to TypeScript and somewhat new to functional programming, but this library seems to have what I would like to see in a functional programming library.

The documentation for A.range states "Returns a new array of numbers from start (inclusive) to finish (exclusive)." However, it actually appears to include the upper bound. Here is the text from a Node session (using ts-node):

λ ./node_modules/.bin/ts-node
import { A } from '@mobily/ts-belt';
undefined
A.range(0, 3);
[ 0, 1, 2, 3 ]
A.rangeBy(0, 3, 1);
[ 0, 1, 2, 3 ]

When I look at the documentation for Belt.Array.range, it reports that the range is inclusive (on both arguments) and the following example duplicates the behavior that I am seeing:

Belt.Array.range(0, 3) == [0, 1, 2, 3]

I'm uncertain if the error is in the documentation or in the implementation.

uniqBy has an incorrect type

function uniqBy<A>(xs: ReadonlyArray<A>, uniqFn: (_1: A) => A): ReadonlyArray<A>

instead of

function uniqBy<A, B>(xs: ReadonlyArray<A>, uniqFn: (_1: A) => B): ReadonlyArray<A>

Make `A.partition` types aware of type guards

It would be a lovely addition if partition were aware of type guard functions. Something like this

export function partition<A>(
  xs: ReadonlyArray<A>,
  predicate: (value: A) => boolean
): [A[], A[]];

export function partition<A, B extends A>(
  xs: ReadonlyArray<A>,
  predicate: (value: A) => value is B
): [B[], Exclude<A, B>[]];

I thought it might be sufficient to drop this in https://github.com/mobily/ts-belt/blob/master/src/Array/Array.ts but I'm not confident enough to open it as a PR without being able to test locally and the tooling for this project is new to me so I couldn't afford to invest the time in getting it set up yet.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.