Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various updates #31

Merged
merged 20 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
37 changes: 0 additions & 37 deletions .circleci/config.yml

This file was deleted.

28 changes: 28 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI

on:
pull_request:
branches:
- '**'

jobs:
build-lint-test:
runs-on: ubuntu-latest
steps:
- name: Clone @siegrift/tsfunct
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'yarn'
- name: Install Dependencies
run: yarn install --frozen-lockfile
- name: Build
run: yarn build
- name: Lint
run: yarn lint
- name: Checks
run: yarn checks
- name: Test
run: yarn test
13 changes: 5 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
*.js
!.test.js
!jest.config.js
validator/build
node_modules
yarn-lock.json
dist
.vscode
# Keep in sync with .prettierignore
/node_modules
/dist
/build
/coverage
8 changes: 8 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Keep in sync with .gitignore
/node_modules
/dist
/build
/coverage

# Extra ignores
docs
17 changes: 17 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"bracketSpacing": true,
"printWidth": 100,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"overrides": [
{
"files": "*.md",
"options": {
"parser": "markdown",
"proseWrap": "always"
}
}
]
}
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.rulers": [100]
}
82 changes: 33 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Tsfunct [![CircleCI](https://circleci.com/gh/Siegrift/tsfunct.svg?style=svg)](https://circleci.com/gh/Siegrift/tsfunct)
# Tsfunct ![CI status](https://github.com/github/docs/actions/workflows/main.yml/badge.svg)

Tsfunct is a **T**ype**S**cript **funct**ional library made directly with and for TS with its static
typesystem in mind.
Expand All @@ -13,31 +13,30 @@ or if you use npm

`npm i @siegrift/tsfunct --save`

_**Important:** Some functions (set and friends) work reliably only with TS ^3.7, because of [this
issue](https://github.com/microsoft/TypeScript/issues/33468). Also, this library will be using
latest TS features when needed. Keep this in mind if you are trying to use it in your project._
_**Important:** This library is using latest TS features when needed. Keep this in mind if you are
trying to use it in your project._

## API and documentation

Documentation is automatically generated from source code and can be found at github pages
[here](https://siegrift.github.io/tsfunct/).
Extended documentation can be found at [github pages](https://siegrift.github.io/tsfunct/). You can
also play with the library on [CodeSandbox](https://codesandbox.io/s/tsfunct-zysfi).

You can also play with the library on [CodeSandbox](https://codesandbox.io/s/tsfunct-zysfi).
The error handling utilities, `go` and `goSync` are inspired by
[promise-utils](https://github.com/api3dao/promise-utils) library which offers extended
functionality.

_You can read the list and sources of all helpers in the src/lib folder
[here](https://github.com/Siegrift/tsfunct/tree/master/src/lib)._
_You can check the sources of all helpers in the
[src/lib](https://github.com/Siegrift/tsfunct/tree/master/src/lib) folder._

## Motivation

There are two big libraries which provide helper functions for JS/TS. These are
[lodash](https://github.com/lodash/lodash) and [ramda](https://github.com/ramda/ramda). Both of
these libraries are made for JS and the TS typings for many functions are poor. Also, these
functions aim to be as general as possible, which makes it harder or even impossible to type
properly.
Most popular libraries providing helper functions for JS and TS are
[lodash](https://github.com/lodash/lodash) and [ramda](https://github.com/ramda/ramda). Many
functions have poor TS typings, often for the added benefit of flexibility.

There are certain helpers _(mainly for immutable object manipulation)_ which can be typed better.
Let's take a look at `get(obj, path)` helper in both lodash _(4.14.132)_ and ramda _(0.26.9)_,
when using it on a strongly typed TS object.
There are certain helpers _(mainly for immutable object manipulation)_ which can be typed better if
they are designed specifically for TS. Let's take a look at `get(obj, path)` helper in both lodash
_(4.14.132)_ and ramda _(0.26.9)_, when using it on a strongly typed TS object.

![Weak typed result](assets/weak_typed_get.png)<br/> _(Lodash gets it at least correct, but cannot
determine the result type. Ramda allows you to pass a type that is being returned, but you can omit
Expand All @@ -59,63 +58,48 @@ helper:
update function can be `undefined` (if any intermediate value doesn't exist). However, when calling
it for a second time, it is guaranteed that the values on the path exist._

Refer to documentation, source code and tests for more examples.

## Immutability

All functions in this library are **effectively immutable**. That means that if you use the helpers
according to their idiomatic usage, library is immutable. However, there are no deep copies created
for the source values and you are able to modify the original entity if you try really hard.
All functions in this library are immutable. However, some functions allow you to pass a predicate
function. If this function is mutable they will modify the source value as well.

```javascript
const original = [{ a: 0 }, { a: 1 }, { a: 2 }]
const mapped = map(original, (val) => (val.a = 3))
const original = [{ a: 0 }, { a: 1 }, { a: 2 }];
const mapped = map(original, (val) => (val.a = 3));
// 'mapped' will equal to [3, 3, 3]
// 'original' will equal to [{ a: 3 }, { a: 3 }, { a: 3 }]
```

## Chaining

TLDR: It is a bad idea. If you want to learn more, read
Chaining is not supported and it's not a good idea in general. If you want to learn more, read
[this article](https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba)

## Functional programming style

Most of the functions in this library are written **imperatively** _(e.g. `const get = (object,
path) => implementation` compared to traditional functional `const get = (path) => (object) =>
implementation`)_ for better typing and autocompletion support. These helpers **aren't composable**
together and if you would like to do multiple transformations you would have to either nest the
calls _(which hurts readability)_ or introduce unnecessary local variables.

For this reason, there are also **functional alternatives** of most common methods _(in the future
maybe all of them)_, which offer the same type guarantees and their imperative clones. These fp
helpers have prefix `fp` _(e.g. functional version of `set` helper is called `fpSet`)_.

_(If you are looking for more FP helpers have a look at [monocle](https://github.com/gcanti/monocle-ts) or [fp
ts](https://github.com/gcanti/fp-ts) or [lodash
fp](https://github.com/lodash/lodash/wiki/FP-Guide))_

## Codebase overview
Functions in this library are written imperatively _(e.g.
`const get = (object, path) => implementation` compared to traditional functional
`const get = (path) => (object) => implementation`)_ for better typing and autocompletion support.

Each helper is written in its own module without depending on other helper. This allows you to copy
the source of single helper you want without installing the whole library.
If you are looking for more FP helpers have a look at
[monocle](https://github.com/gcanti/monocle-ts) or [fp ts](https://github.com/gcanti/fp-ts) or
[lodash fp](https://github.com/lodash/lodash/wiki/FP-Guide))

## Limitations

Most of the helpers are typed manually and have some restrictions on its arguments. For example,
path array can be **up to X elements** only in some helpers...
path array can be "up to X elements" only in some helpers...

Bear in mind that TS is unsound! Types might easily lie to you if you are not careful. For example,

```javascript
const arr: number[] = [1, 2, 3]
const num: number = get(arr, [999]) // this line won't trigger TS error!
console.log(num) // undefined!
const arr: number[] = [1, 2, 3];
const num: number = get(arr, [999]); // this line won't trigger TS error!
console.log(num); // undefined!
```

Other limitation is for example TS path autocompletion for immutability helpers, which I reported
and is tracked in [this issue](https://github.com/microsoft/TypeScript/issues/31630) and will be
fixed in the future.
Other limitation is for example TS path autocompletion for immutability helpers, which is tracked in
[this issue](https://github.com/microsoft/TypeScript/issues/31630) and will be fixed in the future.

## Issues

Expand Down
11 changes: 11 additions & 0 deletions check-docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { execSync } = require('child_process');
const { mkdtempSync } = require('fs');
const { tmpdir } = require('os');
const { join } = require('path');

const docsDir = mkdtempSync(join(tmpdir(), 'tsfunct-docs-check'));
execSync(`DOCS_OUT_DIR=${docsDir} yarn docs:raw`, { stdio: 'inherit' });

execSync(`diff --no-dereference ${docsDir} ${join(__dirname, './docs')}`, {
stdio: 'inherit',
});
14 changes: 8 additions & 6 deletions check_exports.py → check-exports.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
from os import listdir
from re import match

with open('index.ts') as f:
with open('src/index.ts') as f:
exports = [x.strip() for x in f.readlines()]

# verify exports structure (and create the export names)
exportNames = set()
for e in exports:
m = match("^export { default as (.*) } from '(.*)'$", e)
m = match("^export { default as (.*) } from '(.*)';$", e)
if not m:
# allow export * for modules that have multiple exports
if match("^export \* from '(.*)';$", e): continue
print(f'Line "{e}" is not a valid export!')
exit(1)

g = m.groups()
if len(g) != 2 or f'./src/{g[0]}' != g[1]:
if len(g) != 2 or f'./{g[0]}' != g[1]:
print(f'Export "{e}" doesn\'t match the library folder name!')
exit(1)

exportNames.add(g[0])

# verify that all helpers are exported (except the ignored ones)
ignored = set(['test', 'common'])
ignored = set(['common', 'go', 'index.ts', 'tsconfig.json'])
srcFolders = set(listdir('src'))
diff = srcFolders.difference(ignored.union(exportNames))
if len(diff) != 0:
Expand Down
1 change: 1 addition & 0 deletions docs/.nojekyll
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
Loading