diff --git a/.eslintrc.json b/.eslintrc.json index 06ca787..500c754 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -160,6 +160,10 @@ "types/**/*.ts", "test/**/*.ts" ], + "globals": { + // a TypeScript lib type with no runtime binding, so `no-undef` cannot see it + "Generator": "readonly" + }, "parser": "@typescript-eslint/parser", "plugins": [ "@typescript-eslint" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b0337d..590ff44 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,6 +13,11 @@ 1. Run `npm install` to install needed local dependencies. +1. This package has no runtime dependencies, and shouldn't gain one. The type-level utilities + it needs (`Curry`, `Assign`, `Narrow`, ...) live in `types/util/_internal.d.ts`. A util file + whose name starts with `_` is internal: `npm run build` imports from it and ships it, but + does not re-export it, so adding to it does not widen the public API. + 1. Run `npm run build` first, then `npm run test` and `npm run lint` and address any errors. Preferably, fix commits in place using `git rebase` or `git commit --amend` to make the changes easier to review and to keep the history tidy. diff --git a/package-lock.json b/package-lock.json index ac90069..2ee240d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,6 @@ "name": "types-ramda", "version": "0.32.0", "license": "MIT", - "dependencies": { - "ts-toolbelt": "^9.6.0" - }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", @@ -3672,11 +3669,6 @@ "typescript": ">=4.2.0" } }, - "node_modules/ts-toolbelt": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", - "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==" - }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -6660,11 +6652,6 @@ "dev": true, "requires": {} }, - "ts-toolbelt": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", - "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==" - }, "tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", diff --git a/package.json b/package.json index c2ed213..db7d81e 100644 --- a/package.json +++ b/package.json @@ -58,9 +58,6 @@ "tsd": { "directory": "test/" }, - "dependencies": { - "ts-toolbelt": "^9.6.0" - }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", diff --git a/scripts/buildScripts.mjs b/scripts/buildScripts.mjs index 37d1a0f..6a39680 100644 --- a/scripts/buildScripts.mjs +++ b/scripts/buildScripts.mjs @@ -143,6 +143,10 @@ const genExport = (x) => { const genExports = (types) => types.map(genExport).join('\n\n'); +// A util file whose name starts with `_` is internal: index.d.ts imports from it and it ships +// alongside the other declarations, but it is not re-exported as part of the public API. +const isInternal = (utilPath) => basename(utilPath).startsWith('_'); + export const write = (utilDir, outDir, types) => { const utilPaths = readdirSync(utilDir).map(p => join(utilDir, p)); @@ -151,12 +155,11 @@ export const write = (utilDir, outDir, types) => { const exportsCode = genExports(types); const otherExports = [ - ...utilPaths.map(p => `export * from './${basename(p, '.d.ts')}';`), + ...utilPaths.filter(p => !isInternal(p)).map(p => `export * from './${basename(p, '.d.ts')}';`), 'export as namespace R;' ].join('\n'); const code = [ - 'import * as _ from \'ts-toolbelt\';', ...importsCode, '', exportsCode, diff --git a/test/binary.test.ts b/test/binary.test.ts new file mode 100644 index 0000000..f5788b2 --- /dev/null +++ b/test/binary.test.ts @@ -0,0 +1,18 @@ +import { expectType } from 'tsd'; + +import { binary } from '../es'; + +const f = (a: number, b: string, c: boolean) => 1; + +expectType(binary(f)(1, 'x')); + +const rest = (...args: string[]) => 1; + +// the rest element supplies both parameters, and supplies them exactly - not as `string | +// undefined`, the way running off the end of a fixed-length list would +expectType<(arg_0: string, arg_1: string) => number>(binary(rest)); + +const one = (a: number) => 1; + +// a function shorter than the arity is padded out, not truncated +expectType(binary(one)(1, undefined)); diff --git a/test/curry.test.ts b/test/curry.test.ts new file mode 100644 index 0000000..eea64ed --- /dev/null +++ b/test/curry.test.ts @@ -0,0 +1,17 @@ +import { expectType } from 'tsd'; + +import { curry, __ } from '../es'; + +const f3 = (a: number, b: string, c: boolean) => 'r'; + +// every way of splitting the arguments lands on the same result +expectType(curry(f3)(1, 'a', true)); +expectType(curry(f3)(1)('a')(true)); +expectType(curry(f3)(1, 'a')(true)); +expectType(curry(f3)(1)('a', true)); + +// `__` is accepted in any position, and holds the parameter under it open for a later call +expectType(curry(f3)(__, 'a')(1, true)); +expectType(curry(f3)(__, __, true)(1, 'a')); +expectType(curry(f3)(1, __, true)('a')); +expectType(curry(f3)(__, 'a', true)(1)); diff --git a/test/curryN.test.ts b/test/curryN.test.ts new file mode 100644 index 0000000..636aa6d --- /dev/null +++ b/test/curryN.test.ts @@ -0,0 +1,15 @@ +import { expectType } from 'tsd'; + +import { curryN } from '../es'; + +const optional = (a: number, b?: string) => true; + +// an optional parameter is still one of the N parameters being curried +expectType(curryN(2, optional)(1)('x')); +expectType(curryN(2, optional)(1, 'x')); + +const rest = (a: number, ...others: string[]) => true; + +// N reaches into the rest element rather than stopping at the last fixed parameter +expectType(curryN(3, rest)(1)('x')('y')); +expectType(curryN(3, rest)(1, 'x', 'y')); diff --git a/test/flatten.test.ts b/test/flatten.test.ts new file mode 100644 index 0000000..8be3950 --- /dev/null +++ b/test/flatten.test.ts @@ -0,0 +1,10 @@ +import { expectType } from 'tsd'; + +import { flatten } from '../es'; + +// flattens all the way down, not one level +expectType<[1, 2, 3, 4]>(flatten([1, [2, [3, [4]]]] as [1, [2, [3, [4]]]])); +expectType(flatten([[[1]]] as number[][][])); + +// a tuple holding a plain array still flattens through it +expectType<(string | number)[]>(flatten([[[1]], 'x'] as [number[][], string])); diff --git a/test/flip.test.ts b/test/flip.test.ts index 8133a9b..ce79c66 100644 --- a/test/flip.test.ts +++ b/test/flip.test.ts @@ -4,3 +4,6 @@ import { flip, lt } from '../es'; expectType(flip(lt)(2, 1)); expectType(flip(lt)(2)(1)); + +// only the first two parameters swap; the rest keep their order +expectType(flip((a: number, b: string, c: boolean) => 'r')('x', 1, true)); diff --git a/test/mergeDeepLeft.test.ts b/test/mergeDeepLeft.test.ts new file mode 100644 index 0000000..3baee50 --- /dev/null +++ b/test/mergeDeepLeft.test.ts @@ -0,0 +1,8 @@ +import { expectType } from 'tsd'; + +import { mergeDeepLeft } from '../es'; + +expectType<{ a: { b: number; c: number } }>(mergeDeepLeft({ a: { b: 1 } }, { a: { c: 2 } })); + +// the left side wins on conflict, at any depth +expectType<{ a: { b: number } }>(mergeDeepLeft({ a: { b: 1 } }, { a: { b: 'x' } })); diff --git a/test/mergeDeepRight.test.ts b/test/mergeDeepRight.test.ts new file mode 100644 index 0000000..b42d8f6 --- /dev/null +++ b/test/mergeDeepRight.test.ts @@ -0,0 +1,12 @@ +import { expectType } from 'tsd'; + +import { mergeDeepRight } from '../es'; + +// nested objects are merged, not replaced wholesale as mergeRight would +expectType<{ a: { b: number; c: number } }>(mergeDeepRight({ a: { b: 1 } }, { a: { c: 2 } })); + +// the right side wins on conflict, at any depth +expectType<{ a: { b: string } }>(mergeDeepRight({ a: { b: 1 } }, { a: { b: 'x' } })); + +// built-ins are values to be replaced, not objects to recurse into +expectType<{ a: { x: number } }>(mergeDeepRight({ a: new Date() }, { a: { x: 1 } })); diff --git a/test/mergeLeft.test.ts b/test/mergeLeft.test.ts index 6f8882a..eb910d2 100644 --- a/test/mergeLeft.test.ts +++ b/test/mergeLeft.test.ts @@ -7,3 +7,9 @@ const foo2 = { foo: 2 }; expectType<{ foo: string; bar: string; }>(mergeLeft(foo, bar)); expectType<{ foo: string; }>(mergeLeft(foo, foo2)); + +declare const optional: { a?: number, z: 1 }, required: { a: string, y: 2 }; + +// an optional key on the left may or may not be there, so it unions with the right rather than +// shadowing it outright +expectType<{ a: string | number, z: 1, y: 2 }>(mergeLeft(optional, required)); diff --git a/test/nAry.test.ts b/test/nAry.test.ts new file mode 100644 index 0000000..5972d11 --- /dev/null +++ b/test/nAry.test.ts @@ -0,0 +1,17 @@ +import { expectType } from 'tsd'; + +import { nAry } from '../es'; + +const f = (a: number, b: string, c: boolean) => 1; + +expectType(nAry(2, f)(1, 'x')); +expectType(nAry(0, f)()); + +const rest = (a: number, ...others: string[]) => true; + +expectType(nAry(3, rest)(1, 'x', 'y')); + +declare const n: number; + +// a non-literal arity yields no parameters at all rather than diverging +expectType(nAry(n, f)()); diff --git a/test/unnest.test.ts b/test/unnest.test.ts new file mode 100644 index 0000000..7bca61c --- /dev/null +++ b/test/unnest.test.ts @@ -0,0 +1,7 @@ +import { expectType } from 'tsd'; + +import { unnest } from '../es'; + +// removes exactly one level, unlike flatten +expectType(unnest([[[1]]] as number[][][])); +expectType<[1, 2, [3]]>(unnest([[1], [2, [3]]] as [[1], [2, [3]]])); diff --git a/test/zipObj.test.ts b/test/zipObj.test.ts index ff0d74f..dfcb2a9 100644 --- a/test/zipObj.test.ts +++ b/test/zipObj.test.ts @@ -30,3 +30,6 @@ expectType<{string: string, number: number}>(pipe( (a: [string, number]) => a, zipObj(['string', 'number']) )(['a', 42])); + +// where a key repeats, the first occurrence wins +expectType<{a: 1}>(zipObj(['a', 'a'], [1, 2])); diff --git a/types/addIndex.d.ts b/types/addIndex.d.ts index 57be3d9..19bdaff 100644 --- a/types/addIndex.d.ts +++ b/types/addIndex.d.ts @@ -1,18 +1,18 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; // Special case for forEach export function addIndex( fn: (f: (item: T) => void, list: readonly T[]) => T[], -): _.F.Curry<(a: (item: T, idx: number, list: T[]) => void, b: readonly T[]) => T[]>; +): Curry<(a: (item: T, idx: number, list: T[]) => void, b: readonly T[]) => T[]>; // Special case for filter export function addIndex( fn: (f: (item: T) => boolean, list: readonly T[]) => T[], -): _.F.Curry<(a: (item: T, idx: number, list: T[]) => boolean, b: readonly T[]) => T[]>; +): Curry<(a: (item: T, idx: number, list: T[]) => boolean, b: readonly T[]) => T[]>; // Special case for map export function addIndex( fn: (f: (item: T) => U, list: readonly T[]) => U[], -): _.F.Curry<(a: (item: T, idx: number, list: T[]) => U, b: readonly T[]) => U[]>; +): Curry<(a: (item: T, idx: number, list: T[]) => U, b: readonly T[]) => U[]>; // Special case for reduce export function addIndex( fn: (f: (acc: U, item: T) => U, aci: U, list: readonly T[]) => U, -): _.F.Curry<(a: (acc: U, item: T, idx: number, list: T[]) => U, b: U, c: readonly T[]) => U>; +): Curry<(a: (acc: U, item: T, idx: number, list: T[]) => U, b: U, c: readonly T[]) => U>; diff --git a/types/assocPath.d.ts b/types/assocPath.d.ts index b53afc8..9d0da05 100644 --- a/types/assocPath.d.ts +++ b/types/assocPath.d.ts @@ -1,7 +1,7 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; import { Placeholder, Path } from './util/tools'; -export function assocPath(path: Path): _.F.Curry<(a: T, b: U) => U>; +export function assocPath(path: Path): Curry<(a: T, b: U) => U>; export function assocPath(path: Path, val: T): (obj: U) => U; export function assocPath(__: Placeholder, val: T, obj: U): (path: Path) => U; export function assocPath(path: Path, __: Placeholder, obj: U): (val: T) => U; diff --git a/types/binary.d.ts b/types/binary.d.ts index f806790..e8e5647 100644 --- a/types/binary.d.ts +++ b/types/binary.d.ts @@ -1,4 +1,3 @@ -import * as _ from 'ts-toolbelt'; -import { Take } from './util/tools'; +import { TakeFirst } from './util/_internal'; -export function binary any>(fn: T): (...arg: _.T.Take, 2>) => ReturnType; +export function binary any>(fn: T): (...arg: TakeFirst, 2>) => ReturnType; diff --git a/types/construct.d.ts b/types/construct.d.ts index 450915a..9995be9 100644 --- a/types/construct.d.ts +++ b/types/construct.d.ts @@ -1,6 +1,6 @@ +import { Curry } from './util/_internal'; -import * as _ from 'ts-toolbelt'; export function construct( constructor: { new (...a: A): T } | ((...a: A) => T), -): _.F.Curry<(...a: A) => T>; +): Curry<(...a: A) => T>; diff --git a/types/constructN.d.ts b/types/constructN.d.ts index 07e42df..b4ec6f2 100644 --- a/types/constructN.d.ts +++ b/types/constructN.d.ts @@ -1,8 +1,8 @@ +import { Curry } from './util/_internal'; -import * as _ from 'ts-toolbelt'; import { mergeArrWithLeft, Tuple } from './util/tools'; export function constructN( n: N, constructor: { new (...a: A): T } | ((...a: A) => T), -): _.F.Curry<(...a: mergeArrWithLeft, A>) => T>; +): Curry<(...a: mergeArrWithLeft, A>) => T>; diff --git a/types/converge.d.ts b/types/converge.d.ts index d9b912b..11af82a 100644 --- a/types/converge.d.ts +++ b/types/converge.d.ts @@ -1,4 +1,4 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; import { Fn, IfFunctionsArgumentsDoNotOverlap, ReturnTypesOfFns, LargestArgumentsList } from './util/tools'; export function converge< @@ -9,7 +9,7 @@ export function converge< >( converging: (...args: ReturnTypesOfFns) => TResult, branches: FunctionsList, -): _.F.Curry<(...args: LargestArgumentsList) => TResult>; +): Curry<(...args: LargestArgumentsList) => TResult>; export function converge< CArgs extends ReadonlyArray, TResult, @@ -23,4 +23,4 @@ export function converge< >( converging: (...args: CArgs) => TResult, branches: FunctionsList, -): _.F.Curry<(...args: LargestArgumentsList) => TResult>; +): Curry<(...args: LargestArgumentsList) => TResult>; diff --git a/types/curry.d.ts b/types/curry.d.ts index 4ff8c8f..41f5e43 100644 --- a/types/curry.d.ts +++ b/types/curry.d.ts @@ -1,3 +1,3 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; -export function curry any>(f: F): _.F.Curry; +export function curry any>(f: F): Curry; diff --git a/types/curryN.d.ts b/types/curryN.d.ts index fb019ee..db145f3 100644 --- a/types/curryN.d.ts +++ b/types/curryN.d.ts @@ -1,12 +1,11 @@ -import * as _ from 'ts-toolbelt'; -import { Take } from './util/tools'; +import { Curry, TakeFirst } from './util/_internal'; export function curryN( length: N, ): any>( fn: F, -) => _.F.Curry<(...a: _.T.Take, N>) => ReturnType>; +) => Curry<(...a: TakeFirst, N>) => ReturnType>; export function curryN any>( length: N, fn: F, -): _.F.Curry<(...a: _.T.Take, N>) => ReturnType>; +): Curry<(...a: TakeFirst, N>) => ReturnType>; diff --git a/types/flatten.d.ts b/types/flatten.d.ts index 980098f..cef288d 100644 --- a/types/flatten.d.ts +++ b/types/flatten.d.ts @@ -1,3 +1,3 @@ -import * as _ from 'ts-toolbelt'; +import { Flatten } from './util/_internal'; -export function flatten(list: T): _.T.Flatten; +export function flatten(list: T): Flatten; diff --git a/types/flip.d.ts b/types/flip.d.ts index d72025e..7c5f96e 100644 --- a/types/flip.d.ts +++ b/types/flip.d.ts @@ -1,9 +1,9 @@ -import * as _ from 'ts-toolbelt'; +import { Curry, Swap2 } from './util/_internal'; export function flip(fn: (arg0: T, arg1: U) => TResult): { (arg1: U): (arg0: T) => TResult; (arg1: U, arg0: T): TResult; }; -export function flip any, P extends _.F.Parameters>( +export function flip any, P extends Parameters>( fn: F, -): _.F.Curry<(...args: _.T.Merge<[P[1], P[0]], P>) => _.F.Return>; +): Curry<(...args: Swap2

) => ReturnType>; diff --git a/types/mergeAll.d.ts b/types/mergeAll.d.ts index c0e7cde..67d1928 100644 --- a/types/mergeAll.d.ts +++ b/types/mergeAll.d.ts @@ -1,8 +1,8 @@ -import * as _ from 'ts-toolbelt'; +import { Assign } from './util/_internal'; // for when passing in an object literal of different objects, eg `mergeAll([obj1: T1, obj2: T2, obj3: T3])` // you get back essentially a cleaner version of `T1 & T2 & T3` -export function mergeAll(list: [T, ...Ts]): _.O.Assign; +export function mergeAll(list: [T, ...Ts]): Assign; // for when passing in an `T[]` where all the objects are the same shape `mergeAll([obj1, obj2, obj3]: T[]) // this just returns T export function mergeAll(list: readonly T[]): T; diff --git a/types/mergeDeepLeft.d.ts b/types/mergeDeepLeft.d.ts index 3162108..e880b63 100644 --- a/types/mergeDeepLeft.d.ts +++ b/types/mergeDeepLeft.d.ts @@ -1,4 +1,4 @@ -import * as _ from 'ts-toolbelt'; +import { Assign } from './util/_internal'; -export function mergeDeepLeft(l: L): (r: R) => _.O.Assign; -export function mergeDeepLeft(l: L, r: R): _.O.Assign; +export function mergeDeepLeft(l: L): (r: R) => Assign; +export function mergeDeepLeft(l: L, r: R): Assign; diff --git a/types/mergeDeepRight.d.ts b/types/mergeDeepRight.d.ts index 8824416..9aff4cd 100644 --- a/types/mergeDeepRight.d.ts +++ b/types/mergeDeepRight.d.ts @@ -1,4 +1,4 @@ -import * as _ from 'ts-toolbelt'; +import { Assign } from './util/_internal'; -export function mergeDeepRight(l: L): (r: R) => _.O.Assign; -export function mergeDeepRight(l: L, r: R): _.O.Assign; +export function mergeDeepRight(l: L): (r: R) => Assign; +export function mergeDeepRight(l: L, r: R): Assign; diff --git a/types/mergeLeft.d.ts b/types/mergeLeft.d.ts index a9b2b1f..1570345 100644 --- a/types/mergeLeft.d.ts +++ b/types/mergeLeft.d.ts @@ -1,5 +1,5 @@ -import * as _ from 'ts-toolbelt'; +import { Assign } from './util/_internal'; // Note: ramda `mergeLeft` uses `Object.assign` in code, so we need to use `O.Assign` here, and not `O.Merge` -export function mergeLeft(l: L): (r: R) => _.O.Assign; -export function mergeLeft(l: L, r: R): _.O.Assign; +export function mergeLeft(l: L): (r: R) => Assign; +export function mergeLeft(l: L, r: R): Assign; diff --git a/types/mergeRight.d.ts b/types/mergeRight.d.ts index 075e08a..283438f 100644 --- a/types/mergeRight.d.ts +++ b/types/mergeRight.d.ts @@ -1,5 +1,5 @@ -import * as _ from 'ts-toolbelt'; +import { Assign } from './util/_internal'; // Note: ramda `mergeLeft` uses `Object.assign` in code, so we need to use `O.Assign` here, and not `O.Merge` -export function mergeRight(l: L): (r: R) => _.O.Assign; -export function mergeRight(l: L, r: R): _.O.Assign; +export function mergeRight(l: L): (r: R) => Assign; +export function mergeRight(l: L, r: R): Assign; diff --git a/types/nAry.d.ts b/types/nAry.d.ts index 9056138..62f1323 100644 --- a/types/nAry.d.ts +++ b/types/nAry.d.ts @@ -1,10 +1,9 @@ -import * as _ from 'ts-toolbelt'; -import { Take } from './util/tools'; +import { TakeFirst } from './util/_internal'; export function nAry( n: N, -): unknown>(fn: T) => (...arg: _.T.Take, N>) => ReturnType; +): unknown>(fn: T) => (...arg: TakeFirst, N>) => ReturnType; export function nAry unknown>( n: N, fn: T, -): (...arg: _.T.Take, N>) => ReturnType; +): (...arg: TakeFirst, N>) => ReturnType; diff --git a/types/pathOr.d.ts b/types/pathOr.d.ts index a1468d7..9dcdc63 100644 --- a/types/pathOr.d.ts +++ b/types/pathOr.d.ts @@ -1,6 +1,6 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; import { Path } from './util/tools'; -export function pathOr(defaultValue: T): _.F.Curry<(a: Path, b: any) => T>; +export function pathOr(defaultValue: T): Curry<(a: Path, b: any) => T>; export function pathOr(defaultValue: T, path: Path): (obj: any) => T; export function pathOr(defaultValue: T, path: Path, obj: any): T; diff --git a/types/pathSatisfies.d.ts b/types/pathSatisfies.d.ts index 12efb29..8c8796b 100644 --- a/types/pathSatisfies.d.ts +++ b/types/pathSatisfies.d.ts @@ -1,6 +1,6 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; import { Path } from './util/tools'; -export function pathSatisfies(pred: (val: T) => boolean): _.F.Curry<(a: Path, b: U) => boolean>; +export function pathSatisfies(pred: (val: T) => boolean): Curry<(a: Path, b: U) => boolean>; export function pathSatisfies(pred: (val: T) => boolean, path: Path): (obj: U) => boolean; export function pathSatisfies(pred: (val: T) => boolean, path: Path, obj: U): boolean; diff --git a/types/propSatisfies.d.ts b/types/propSatisfies.d.ts index e20e041..36f84a8 100644 --- a/types/propSatisfies.d.ts +++ b/types/propSatisfies.d.ts @@ -1,4 +1,4 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; export function propSatisfies( pred: (val: any) => val is P, @@ -15,4 +15,4 @@ export function propSatisfies

(pred: (val: any) => val is P): { }; export function propSatisfies(pred: (val: any) => boolean, name: keyof any, obj: any): boolean; export function propSatisfies(pred: (val: any) => boolean, name: keyof any): (obj: any) => boolean; -export function propSatisfies(pred: (val: any) => boolean): _.F.Curry<(a: keyof any, b: any) => boolean>; +export function propSatisfies(pred: (val: any) => boolean): Curry<(a: keyof any, b: any) => boolean>; diff --git a/types/reduceBy.d.ts b/types/reduceBy.d.ts index 8c64b5a..6e5e49f 100644 --- a/types/reduceBy.d.ts +++ b/types/reduceBy.d.ts @@ -1,12 +1,12 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; export function reduceBy( valueFn: (acc: TResult, elem: T) => TResult, -): _.F.Curry<(a: TResult, b: (elem: T) => string, c: readonly T[]) => { [index: string]: TResult }>; +): Curry<(a: TResult, b: (elem: T) => string, c: readonly T[]) => { [index: string]: TResult }>; export function reduceBy( valueFn: (acc: TResult, elem: T) => TResult, acc: TResult, -): _.F.Curry<(a: (elem: T) => string, b: readonly T[]) => { [index: string]: TResult }>; +): Curry<(a: (elem: T) => string, b: readonly T[]) => { [index: string]: TResult }>; export function reduceBy( valueFn: (acc: TResult, elem: T) => TResult, acc: TResult, diff --git a/types/reduceWhile.d.ts b/types/reduceWhile.d.ts index b164b45..52e842d 100644 --- a/types/reduceWhile.d.ts +++ b/types/reduceWhile.d.ts @@ -1,12 +1,12 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; export function reduceWhile( predicate: (acc: TResult, elem: T) => boolean, -): _.F.Curry<(a: (acc: TResult, elem: T) => TResult, b: TResult, c: readonly T[]) => TResult>; +): Curry<(a: (acc: TResult, elem: T) => TResult, b: TResult, c: readonly T[]) => TResult>; export function reduceWhile( predicate: (acc: TResult, elem: T) => boolean, fn: (acc: TResult, elem: T) => TResult, -): _.F.Curry<(a: TResult, b: readonly T[]) => TResult>; +): Curry<(a: TResult, b: readonly T[]) => TResult>; export function reduceWhile( predicate: (acc: TResult, elem: T) => boolean, fn: (acc: TResult, elem: T) => TResult, diff --git a/types/symmetricDifferenceWith.d.ts b/types/symmetricDifferenceWith.d.ts index d2771ef..d160dda 100644 --- a/types/symmetricDifferenceWith.d.ts +++ b/types/symmetricDifferenceWith.d.ts @@ -1,8 +1,8 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; export function symmetricDifferenceWith( pred: (a: T, b: T) => boolean, -): _.F.Curry<(a: readonly T[], b: readonly T[]) => T[]>; +): Curry<(a: readonly T[], b: readonly T[]) => T[]>; export function symmetricDifferenceWith( pred: (a: T, b: T) => boolean, diff --git a/types/thunkify.d.ts b/types/thunkify.d.ts index 810ef11..1af935d 100644 --- a/types/thunkify.d.ts +++ b/types/thunkify.d.ts @@ -1,5 +1,5 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; export function thunkify any>( fn: F, -): _.F.Curry<(...args: Parameters) => () => ReturnType>; +): Curry<(...args: Parameters) => () => ReturnType>; diff --git a/types/tryCatch.d.ts b/types/tryCatch.d.ts index 1c4b349..77d7e91 100644 --- a/types/tryCatch.d.ts +++ b/types/tryCatch.d.ts @@ -1,9 +1,8 @@ -import * as _ from 'ts-toolbelt'; export function tryCatch any>( tryer: F, -): , E = unknown>(catcher: (error: E, ...args: _.F.Parameters) => RE) => F | (() => RE); +): , E = unknown>(catcher: (error: E, ...args: Parameters) => RE) => F | (() => RE); export function tryCatch any, RE = ReturnType, E = unknown>( tryer: F, - catcher: (error: E, ...args: _.F.Parameters) => RE, + catcher: (error: E, ...args: Parameters) => RE, ): F | (() => RE); diff --git a/types/unionWith.d.ts b/types/unionWith.d.ts index 2d4b1c0..93f7901 100644 --- a/types/unionWith.d.ts +++ b/types/unionWith.d.ts @@ -1,4 +1,4 @@ -import * as _ from 'ts-toolbelt'; +import { Curry } from './util/_internal'; -export function unionWith(pred: (a: T, b: T) => boolean): _.F.Curry<(a: readonly T[], b: readonly T[]) => T[]>; +export function unionWith(pred: (a: T, b: T) => boolean): Curry<(a: readonly T[], b: readonly T[]) => T[]>; export function unionWith(pred: (a: T, b: T) => boolean, list1: readonly T[], list2: readonly T[]): T[]; diff --git a/types/unnest.d.ts b/types/unnest.d.ts index 9adfb4c..5033c3c 100644 --- a/types/unnest.d.ts +++ b/types/unnest.d.ts @@ -1,3 +1,3 @@ -import * as _ from 'ts-toolbelt'; +import { UnNest } from './util/_internal'; -export function unnest(list: T): _.T.UnNest; +export function unnest(list: T): UnNest; diff --git a/types/util/_internal.d.ts b/types/util/_internal.d.ts new file mode 100644 index 0000000..05a701e --- /dev/null +++ b/types/util/_internal.d.ts @@ -0,0 +1,304 @@ +// Type-level utilities that used to come from `ts-toolbelt`. +// +// ramda needed twelve types out of that package, but its entry point is a namespace barrel that +// drags all 241 of its files into the program of every project consuming these types. The +// implementations below replace them. They are deliberately NOT re-exported from the package's +// public surface - `_`-prefixed files under `types/util` are internal to the build. +// +// ts-toolbelt predates TypeScript 4.5, so it counted with a 202-entry lookup table of number +// literals (`Iteration`), which is what made it expensive to check and capped it at +/-100 +// elements. Everything here recurses over variadic tuples instead, so there is no arithmetic and +// no upper bound. + +type AnyList = readonly unknown[]; + +/** `A1 extends A2 ? A1 : A2` - reassures the checker that a computed type fits a constraint. */ +type Cast = A1 extends A2 ? A1 : A2; + +/** + * The placeholder's brand. `R.__` is the only value of this type, and `Curry` treats an argument + * of this type as "skip this parameter, I'll supply it later". + */ +declare const placeholder: unique symbol; +export type PlaceholderBrand = typeof placeholder & {}; + +/** + * All the primitive types. + */ +export type Primitive = boolean | string | number | bigint | symbol | undefined | null; + +/** + * Take the first `N` elements of list `L`, positionally: the result is always exactly `N` long, + * padding with `undefined` past the end of `L` and repeating `L`'s rest element where it has one. + * A non-literal `N` yields `[]`, since `Acc` starts out empty and `0 extends number`. + */ +export type TakeFirst = + Acc['length'] extends N + ? Acc + : L extends readonly [infer H, ...infer T] + ? TakeFirst + : number extends L['length'] + ? TakeFirst + : L extends readonly [] + ? TakeFirst + : L extends readonly [(infer H)?, ...infer T] + ? TakeFirst + : TakeFirst; + +/** + * Swap the first two elements of a list, leaving the rest in place. Used by `flip`. + */ +export type Swap2 = L extends readonly [infer A, infer B, ...infer R] + ? [B, A, ...R] + : L; + +/** + * Flatten list `L` by one level. + */ +export type UnNest = number extends L['length'] + ? (L[number] extends infer E ? (E extends AnyList ? E[number] : E) : never)[] + : _UnNest; + +type _UnNest = L extends readonly [infer H, ...infer T] + ? [...(H extends AnyList ? H : [H]), ..._UnNest] + : []; + +/** Invariant type equality - `A` and `B` are the same type, not merely mutually assignable. */ +type Equals = (() => T extends A ? 1 : 2) extends (() => T extends B ? 1 : 2) ? true : false; + +/** + * Flatten list `L` all the way down. + * + * This has to un-nest one level at a time until the result stops changing, rather than recursing + * structurally into each element. A tuple can hold a non-tuple array (`[number[][], string]`), + * and un-nesting that degrades the whole thing to a plain array - which then still has levels + * left to remove. Recursing per element instead both stops early on the array and drops it. + */ +export type Flatten = _Flatten; + +type _Flatten = Equals extends true + ? L + : _Flatten, L>; + +/** + * Build an object from a list of keys and a list of values, pairing them by position. Keys past + * the end of the values get `undefined`, surplus values are dropped, and where a key repeats the + * first occurrence wins. + */ +export type ZipObj = _ZipObjAcc extends infer O + ? { [P in keyof O]: O[P] } + : never; + +type _ZipObjAcc = K extends readonly [infer KH, ...infer KT] + ? _ZipObjAcc< + KT, + V extends readonly [unknown, ...infer VT] ? VT : [], + KH extends PropertyKey + ? KH extends keyof Acc + ? Acc + : Acc & Record + : Acc + > + : Acc; + +// --------------------------------------------------------------------------------------------- +// Curry +// --------------------------------------------------------------------------------------------- + +type RemoveNullish = A extends null | undefined ? never : A; + +/** + * Every parameter becomes optional and may be filled with the placeholder instead. + */ +type Gaps = Cast< +{ [K in keyof L]?: L[K] | PlaceholderBrand } extends infer G ? { [K in keyof G]: RemoveNullish } & {} : never, +AnyList +>; + +/** + * The parameters still outstanding after `P` has been applied to a function taking `L`: a + * placeholder in `P` keeps the parameter under it, anything else consumes it, and parameters + * past the end of `P` are all still outstanding. + */ +type GapsOf

= P extends readonly [infer PH, ...infer PT] + ? L extends readonly [infer LH, ...infer LT] + ? PH extends PlaceholderBrand + ? [LH, ...GapsOf] + : GapsOf + : [] + : L; + +/** Whether every remaining parameter is optional, ie. the call can be made now. */ +type NoneRequired = L extends readonly [unknown, ...unknown[]] ? false : true; + +/** + * A curried function: it accepts any prefix of its parameters, with `R.__` standing in for any + * parameter to be supplied later, and returns either the result or a curried function taking + * whatever is left. + */ +export type Curry any> = < + P extends Gaps>, + G extends AnyList = GapsOf>, + R = ReturnType +>( + ...p: Gaps> | P +) => NoneRequired extends true ? R : Curry<(...p: G) => R>; + +// --------------------------------------------------------------------------------------------- +// Narrow +// --------------------------------------------------------------------------------------------- + +type Narrowable = string | number | bigint | boolean; + +type NarrowRaw = + | (A extends [] ? [] : never) + | (A extends Narrowable ? A : never) + | { [K in keyof A]: A[K] extends Function ? A[K] : NarrowRaw }; + +/** + * `A1 extends A2 ? A1 : Catch` - like `Cast` but with a chosen fallback. + * + * `Narrow` must route through this rather than writing the conditional inline. Spelling it as + * `A extends [] ? A : NarrowRaw` produces an identical type but defers the conditional, and a + * deferred conditional is not an inference site - the mapped type inside `NarrowRaw` stops being + * visible to inference and arguments widen (`['a', 'b']` infers as `string[]`, not `['a', 'b']`). + * Passing `NarrowRaw` as an argument here keeps it visible. + */ +type Try = A1 extends A2 ? A1 : Catch; + +/** + * Infer a literal type for `A` rather than widening it, the way a `const` assertion would. + */ +export type Narrow = Try>; + +// --------------------------------------------------------------------------------------------- +// Assign +// --------------------------------------------------------------------------------------------- + +/** Types that merging treats as opaque values rather than recursing into. */ +type BuiltIn = Function | Error | Date | { readonly [Symbol.toStringTag]: string } | RegExp | Generator; + +type Anyfy = { [K in keyof O]: any }; + +/** `O[K]`, or `undefined` where `O` has no such key. */ +type At = A extends AnyList + ? number extends A['length'] + ? K extends number | `${number}` + ? A[never] | undefined + : undefined + : K extends keyof A + ? A[K] + : undefined + : unknown extends A + ? unknown + : K extends keyof A + ? A[K] + : undefined; + +type OptionalKeysOf = { [K in keyof O]-?: {} extends Pick ? K : never }[keyof O]; +type RequiredKeysOf = { [K in keyof O]-?: {} extends Pick ? never : K }[keyof O]; + +type ListObjectOf = number extends L['length'] + ? Pick + : Omit; + +/** 1 when `L` has at least as many required elements as `L1`. */ +type Longer = L extends unknown + ? L1 extends unknown + ? [RequiredKeysOf>] extends [RequiredKeysOf>] + ? 1 + : 0 + : never + : never; + +/** + * One property of a merge: the left side wins, except that an optional key on the left unions + * with the right, and a key missing from the left falls through to it. + */ +type MergeProp = K extends OOKeys + ? Exclude | O1K + : [OK] extends [never] + ? O1K + : OK extends undefined + ? O1K + : OK; + +type MergeFlatObject> = { + [K in keyof (Anyfy & O1)]: MergeProp, At, OOKeys, K>; +} & {}; + +type MergeFlatList> = + number extends (L | L1)['length'] + ? MergeFlatChoice[] + : Longer extends 1 + ? { [K in keyof L]: MergeProp, OOKeys, K> } + : { [K in keyof L1]: MergeProp, L1[K], OOKeys, K> }; + +type MergeFlatChoice = O extends BuiltIn + ? O + : O1 extends BuiltIn + ? O + : O extends AnyList + ? O1 extends AnyList + ? MergeFlatList + : MergeFlatObject + : MergeFlatObject; + +type MergeFlat = O extends unknown + ? O1 extends unknown + ? MergeFlatChoice + : never + : never; + +type MergeDeepList = number extends (L | L1)['length'] + ? MergeDeepChoice[] + : Longer extends 1 + ? { [K in keyof L]: MergeDeepChoice, OptionalKeysOf, K> } + : { [K in keyof L1]: MergeDeepChoice, L1[K], OptionalKeysOf, K> }; + +type MergeDeepObject> = { + [K in keyof (Anyfy & O1)]: MergeDeepChoice, At, OOKeys, K>; +}; + +type MergeDeepChoice = [OK] extends [never] + ? MergeProp + : [O1K] extends [never] + ? MergeProp + : OK extends BuiltIn + ? MergeProp + : O1K extends BuiltIn + ? MergeProp + : OK extends AnyList + ? O1K extends AnyList + ? MergeDeepList + : MergeProp + : OK extends object + ? O1K extends object + ? MergeDeepObject + : MergeProp + : MergeProp; + +type MergeDeep = O extends unknown + ? O1 extends unknown + ? MergeDeepChoice + : never + : never; + +type Merge = { + flat: MergeFlat; + deep: MergeDeep; +}[depth]; + +/** + * Fold the objects of `Os` onto `O` from left to right, so later objects win. `depth` chooses + * whether nested objects are replaced wholesale or merged recursively. + */ +export type Assign = + O extends unknown ? (Os extends unknown ? _Assign : never) : never; + +type _Assign = Os extends readonly [ + infer Head, + ...infer Tail +] + ? _Assign, O, depth>, object>, Tail, depth> + : O; diff --git a/types/util/tools.d.ts b/types/util/tools.d.ts index 679e629..0eb1fe7 100644 --- a/types/util/tools.d.ts +++ b/types/util/tools.d.ts @@ -1,4 +1,4 @@ -import { A, M } from 'ts-toolbelt'; +import { PlaceholderBrand, Primitive } from './_internal'; // Here lies a loose collection of tools that compute types for the functions in "index.d.ts" // The goal of this file is to keep "index.d.ts" readable as well as hiding implementations @@ -168,7 +168,7 @@ type Arr1LessThanOrEqual< export type InferAllAType = T extends { all: (fn: (a: infer A) => boolean) => boolean } ? A : never; /** - * Return true if types T1 and T2 can intersect, e.g. both are primitives or both are objects. + * ReturnType true if types T1 and T2 can intersect, e.g. both are primitives or both are objects. * Taking into account branded types too. * * @param T1 First readonly array @@ -184,8 +184,8 @@ type Intersectable = [T1] extends [T2] ? [T2] extends [object] ? true : false - : [T1] extends [M.Primitive] - ? [T2] extends [M.Primitive] + : [T1] extends [Primitive] + ? [T2] extends [Primitive] ? true : false : false; @@ -372,7 +372,7 @@ export type Path = Array; /** * A placeholder used to skip parameters, instead adding a parameter to the returned function. */ -export type Placeholder = A.x & { '@@functional/placeholder': true }; +export type Placeholder = PlaceholderBrand & { '@@functional/placeholder': true }; /** * A runtime-branded value used to stop `reduce` and `transduce` early. @@ -454,7 +454,7 @@ export type ToTupleOfArray = Tuple extends [] /** * Map tuple of ordinary type to tuple of function type * @param R Parameter type of every function - * @param Tuple Return type of every function + * @param Tuple ReturnType type of every function */ export type ToTupleOfFunction = Tuple extends [] ? [] diff --git a/types/util/zipObj.d.ts b/types/util/zipObj.d.ts index fd0d828..ec0b88d 100644 --- a/types/util/zipObj.d.ts +++ b/types/util/zipObj.d.ts @@ -1,7 +1,7 @@ -import * as _ from 'ts-toolbelt'; +import { ZipObj } from './_internal'; export type _ZipObj = number extends V['length'] ? { [T in K[number]]: V[number] } : number extends K['length'] ? - { [T in K[number]]: V[number] } : _.L.ZipObj; + { [T in K[number]]: V[number] } : ZipObj; diff --git a/types/zipObj.d.ts b/types/zipObj.d.ts index 6432e0b..da2a5ef 100644 --- a/types/zipObj.d.ts +++ b/types/zipObj.d.ts @@ -1,9 +1,9 @@ -import * as _ from 'ts-toolbelt'; +import { Narrow } from './util/_internal'; import { _ZipObj } from './util/zipObj'; -export function zipObj(keys: _.F.Narrow): -(values: _.F.Narrow) => +export function zipObj(keys: Narrow): +(values: Narrow) => _ZipObj; -export function zipObj(keys: _.F.Narrow, values: _.F.Narrow): +export function zipObj(keys: Narrow, values: Narrow): _ZipObj;