Skip to content
Open
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
147 changes: 147 additions & 0 deletions test/assoc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { expectType, expectError } from 'tsd';
import { __, assoc } from '../es';

type Obj = {
str: string;
num: number;
};

const obj: Obj = { str: 'foo', num: 1 };

//
// assoc(key)
//

// assoc(key)(__, obj)(val)
expectType<Obj>(assoc('str')(__, obj)('bar'));
// fails for wrong value type
expectError(assoc('str')(__, obj)(2));
// fails if key not unknown
expectError(assoc('what')(__, obj)('bar'));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc('what')(__, {} as Record<string, number>)(2));

// assoc(key)(val, obj)
expectType<Obj>(assoc('str')('bar', obj));
// fails for wrong value type
expectError(assoc('str')(2, obj));
// fails if key not unknown
expectError(assoc('what')('bar', obj));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc('what')(2, {} as Record<string, number>));

// assoc(key)(val)(obj)
expectType<Obj>(assoc('str')('bar')(obj));
// fails for wrong value type
expectError(assoc('str')(2)(obj));
// fails if key not unknown
expectError(assoc('what')('foo')(obj));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc('what')(2)({} as Record<string, number>));


//
// assoc(__, val)
//

// assoc(__, val)(key)(obj)
expectType<Obj>(assoc(__, 'bar')('str')(obj));
// fails for wrong value type
expectError(assoc(__, 2)('str')(obj));
// fails if key not unknown
expectError(assoc(__, 'bar')('what')(obj));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc(__, 2)('what')({} as Record<string, number>));

// assoc(__, val)(__, key)(obj)
expectType<Obj>(assoc(__, 'bar')(__, obj)('str'));
// fails for wrong value type
expectError(assoc(__, 2)(__, obj)('str'));
// fails if key not unknown
expectError(assoc(__, 'bar')(__, obj)('what'));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc(__, 2)(__, {} as Record<string, number>)('str'));

// assoc(__, val)(key, obj)
expectType<Obj>(assoc(__, 'bar')('str', obj));
// fails for wrong value type
expectError(assoc(__, 2)('str', obj));
// fails if key not unknown
expectError(assoc(__, 'bar')('what', obj));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc(__, 2)('str', {} as Record<string, number>));

//
// assoc(key, val)
//

// assoc(key, val)(obj)
expectType<Obj>(assoc('str', 'bar')(obj));
// fails for wrong value type
expectError(assoc('str', 2)(obj));
// fails if key not unknown
expectError(assoc('what', 'bar')(obj));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc('str', 2)({} as Record<string, number>));

//
// assoc__, __, obj)
//

// assoc(__, __, obj)(key)(val)
expectType<Obj>(assoc(__, __, obj)('str')('bar'));
// fails for wrong value type
expectError(assoc(__, __, obj)('str')(2));
// fails if key not unknown
expectError(assoc(__, __, obj)('what')('bar'));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc(__, __, {} as Record<string, number>)('str')(2));

// assoc(__, __, obj)(__, val)(key)
expectType<Obj>(assoc(__, __, obj)(__, 'bar')('str'));
// fails for wrong value type
expectError(assoc(__, __, obj)(__, 2)('str'));
// fails if key not unknown
expectError(assoc(__, __, obj)(__, 'bar')('what'));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc(__, __, {} as Record<string, number>)(__, 2)('str'));

// assoc(__, __, obj)(key, val)
expectType<Obj>(assoc(__, __, obj)('str', 'bar'));
// fails for wrong value type
expectError(assoc(__, __, obj)('str', 2));
// fails if key not unknown
expectError(assoc(__, __, obj)('what', 'bar'));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc(__, __, {} as Record<string, number>)('str', 2));

//
// rest
//

// assoc(__, val, obj)(prop)
expectType<Obj>(assoc(__,'bar', obj)('str'));
// fails for wrong value type
expectError(assoc(__, 2, obj)('str'));
// fails if key not unknown
expectError(assoc(__, 'bar', obj)('what'));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc(__, 2, {} as Record<string, number>)('str'));

// assoc(key, __, obj)(__, val)
expectType<Obj>(assoc('str', __, obj)('bar'));
// fails for wrong value type
expectError(assoc('str', __, obj)(2));
// fails if key not unknown
expectError(assoc('what', __, obj)('bar'));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc('str', __, {} as Record<string, number>)(2));

// assoc(key, val, obj)
expectType<Obj>(assoc('str', 'bar', obj));
// fails for wrong value type
expectError(assoc('str', 2, obj));
// fails if key not unknown
expectError(assoc('what', 'bar', obj));
// Record<string, number> works as expected
expectType<Record<string, number>>(assoc('str', 2, {} as Record<string, number>));
65 changes: 65 additions & 0 deletions test/dissoc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { expectType, expectError } from 'tsd';
import { __, dissoc } from '../es';

// `dissoc` does a `delete obj.key` under the hood, so the behavior for `dissoc` should make that
type Obj = {
str: string;
num: number;
opt?: boolean;
orUndefined: boolean | undefined;
orNull: boolean | null;
};

const obj: Obj = { str: 'foo', num: 1, orUndefined: true, orNull: true };

// must mark failed operations with ts-expect-error, otherwise `npm run test` fails
// @ts-expect-error
delete obj.str;
// @ts-expect-error
delete obj.num;
delete obj.opt;
delete obj.orUndefined;
// @ts-expect-error
delete obj.orNull;

// only `opt` and `orUndefined` are allowed operations
// `dissoc` should match that behavior

expectError(dissoc('str', obj));
expectError(dissoc('num', obj));
expectType<Obj>(dissoc('opt', obj));
expectType<Obj>(dissoc('orUndefined', obj));
expectError(dissoc('orNull', obj));

expectError(dissoc('str')(obj));
expectError(dissoc('num')(obj));
expectType<Obj>(dissoc('opt')(obj));
expectType<Obj>(dissoc('orUndefined')(obj));
expectError(dissoc('orNull')(obj));

expectError(dissoc(__, obj)('str'));
expectError(dissoc(__, obj)('num'));
expectType<Obj>(dissoc(__, obj)('opt'));
expectType<Obj>(dissoc(__, obj)('orUndefined'));
expectError(dissoc(__, obj)('orNull'));

// Record<string, number> is allowed
const rec: Record<string, number> = { foo: 1, bar: 2 };

// delete operation is ok for all keys
delete rec.foo;
delete rec.bar;
delete rec.unknownKey;

// and so are for `dissoc`
dissoc('foo', rec);
dissoc('bar', rec);
dissoc('unknownKey', rec);

dissoc('foo')(rec);
dissoc('bar')(rec);
dissoc('unknownKey')(rec);

dissoc(__, rec)('foo');
dissoc(__, rec)('bar');
dissoc(__, rec)('unknownKey');
34 changes: 34 additions & 0 deletions test/modify.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { expectError, expectType } from 'tsd';

import { modify, toUpper, add, identity, pipe, map } from '../es';

type Obj = {
foo: string;
bar: number;
};

expectType<Obj>(modify('foo', toUpper, {} as Obj));
expectType<Obj>(modify('bar', add(1), {} as Obj));
expectType<Obj>(modify('foo', toUpper)({} as Obj));
expectType<Obj>(modify('bar', add(1))({} as Obj));
expectType<Obj>(modify('foo')(toUpper)({} as Obj));
expectType<Obj>(modify('bar')(add(1))({} as Obj));
expectType<Obj>(modify('foo')(toUpper, {} as Obj));
expectType<Obj>(modify('bar')(add(1), {} as Obj));

// fails when function has wrong argument type
expectError(modify('foo', add(1), {} as Obj));
expectError(modify('bar', toUpper, {} as Obj));

// fails when key does not exist on Obj
expectError(modify('unknownKey', toUpper, {} as Obj));

// works with generic fn
expectType<Obj>(modify('foo', identity, {} as Obj));

// pipe and map sanity checks
const f = pipe(
map<Obj, Obj>(modify('foo', toUpper))
);

expectType<Obj[]>(f([] as Obj[]));
98 changes: 37 additions & 61 deletions types/assoc.d.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,49 @@
import { Placeholder } from './util/tools';

// assoc(__, val, obj)(prop), this tests if prop is keyof obj and if val is typeof obj[prop] for best return type
export function assoc<T, U>(__: Placeholder, val: T, obj: U): <K extends string>(prop: K) => K extends keyof U ? T extends U[K] ? U : Record<K, T> & Omit<U, K> : U & Record<K, T>;
// assoc(prop, __, obj)(val), when K is keyof obj, tests if val is typeof obj[prop] for best return type
export function assoc<U, K extends keyof U>(prop: K, __: Placeholder, obj: U): <T>(val: T) => T extends U[K] ? U : Record<K, T> & Omit<U, K>;
// assoc(prop, __, obj)(val), when prop is not keyof obj
export function assoc<U, K extends string>(prop: K, __: Placeholder, obj: U): <T>(val: T) => U & Record<K, T>;
// assoc(prop, val, obj) when prop is keyof obj and val is same type
export function assoc<K extends keyof U, U>(prop: K, val: U[K], obj: U): U;
// assoc(prop, val, obj) when prop is keyof obj and val is not same type
export function assoc<T, K extends keyof U, U>(prop: K, val: T, obj: U): Record<K, T> & Omit<U, K>;
// assoc(prop, val, obj) when prop is not keyof obj
export function assoc<T, U, K extends string>(prop: K, val: T, obj: U): U & Record<K, T>;
// assoc(prop)
export function assoc<K extends PropertyKey>(prop: K): {
// assoc(prop)(val)(obj)
<T>(val: T): <U extends Record<K, T>>(obj: U) => U;

// assoc(prop)(__, obj)(val)
<U extends Record<K, any>>(__: Placeholder, obj: U): <T extends U[K]>(val: T) => U;

// assoc(prop)(val, obj) when obj has key prop, tests if val is typeof obj[prop] for best return type
<U extends Record<K, T>, T>(val: T, obj: U): U;
};

// assoc(__, val)
export function assoc<T>(__: Placeholder, val: T) : {
// assoc(__, val)(__, obj)
<U>(__2: Placeholder, obj: U): {
// assoc(__, val)(__, obj)(prop), prop is keyof obj, tests if val is typeof obj[prop] for best return type
<K extends keyof U>(prop: K): U[K] extends T ? U : Record<K, T> & Omit<U, K>;
// assoc(__, val)(__, obj)(prop), prop is not keyof obj
<K extends string>(prop: K): U & Record<K, T>;
};
// assoc(__, val)(prop, obj), when obj has key prop, tests if val is typeof obj[prop] for best return type
<K extends keyof U, U>(prop: K, obj: U): U[K] extends T ? U : Record<K, T> & Omit<U, K>;
// assoc(__, val)(prop, obj), when obj does not have key prop
<K extends string, U>(prop: K, obj: U): U & Record<K, T>;
// assoc(__, val)(prop)(obj)
<K extends PropertyKey>(prop: K extends Placeholder ? never : K): <U extends Record<K, T>>(obj: U) => U;

// assoc(__, val)(prop)
<K extends string>(prop: K): {
// assoc(__, val)(prop)(obj) when obj has key prop, tests if val is typeof obj[prop] for best return type
<U extends Record<K, any>>(obj: U): U[K] extends T ? U : Record<K, T> & Omit<U, K>;
// assoc(__, val)(prop)(obj) when obj does not have key prop
<U>(obj: U): U & Record<K, T>;
}
};
// assoc(__, val)(__, obj)(prop)
<U>(__2: Placeholder, obj: U): <K extends keyof U>(prop: T extends U[K] ? K : never) => U;

// assoc(prop, val)
export function assoc<T, K extends string>(prop: K, val: T) : {
// assoc(prop, val)(obj), when obj has key prop, tests if val is typeof obj[prop] for best return type
<U extends Record<K, any>>(obj: U): U[K] extends T ? U : Record<K, T> & Omit<U, K>;
// assoc(prop, val)(obj), when obj does not have key prop
<U>(obj: U): U & Record<K, T>;
// assoc(__, val)(prop, obj)
<U extends Record<K, T>, K extends keyof U>(prop: K, obj: U): U;
};

// assoc(prop)
export function assoc<K extends string>(prop: K): {
// assoc(prop)(__, obj) when prop is keyof obj
<U extends Record<K, any>>(__: Placeholder, obj: U): {
// assoc(prop)(__, obj)(val) if val is typeof obj[prop]
<T extends U[K]>(val: T): U;
// assoc(prop)(__, obj)(val) if val is not typeof obj[prop]
<T>(val: T): Record<K, T> & Omit<U, K>;
}
// assoc(prop)(__, obj) when prop is not keyof obj
<U>(__: Placeholder, obj: U): <T>(val: T) => U & Record<K, T>;
// assoc(prop, val)(obj)
export function assoc<K extends PropertyKey, T>(prop: K extends Placeholder ? never : K, val: T): <U extends Record<K, T>>(obj: U) => U;

// assoc(prop)(val, obj) when obj has key prop, tests if val is typeof obj[prop] for best return type
<T, U extends Record<K, any>>(val: T, obj: U): U[K] extends T ? U : Record<K, T> & Omit<U, K>;
// assoc(prop)(val, obj) when obj does not have a key prop
<T, U>(val: T, obj: U): U & Record<K, T>;
// assoc (__, __, obj)
export function assoc<U>(__: Placeholder, __2: Placeholder, obj: U): {
// assoc(__, __, obj)(prop)(val)
<K extends keyof U>(prop: K extends Placeholder ? never : K): <T extends U[K]>(val: T) => U;

// assoc(__, __, obj)(__, val)(prop)
<T>(__: Placeholder, val: T): <K extends keyof U>(prop: T extends U[K] ? K : never) => U;

// assoc(prop)(val)
<T>(val: T): {
// assoc(prop)(val)(obj) when obj has key prop and val is typeof obj[prop]
<U extends Record<K, T>>(obj: U): U;
// assoc(prop)(val)(obj) when obj has key prop and val is not typeof obj[prop]
<U extends Record<K, any>>(obj: U): Record<K, T> & Omit<U, K>;
// assoc(prop)(val)(obj) when obj does not have key prop
<U>(obj: U): U & Record<K, T>;
}
// assoc(__, __, obj)(prop, val)
<K extends keyof U, T extends U[K]>(prop: K, val: T): U;
};

// assoc(__, val, obj)(prop)
export function assoc<U, T extends U[keyof U]>(__: Placeholder, val: T, obj: U): <K extends keyof U>(prop: T extends U[K] ? K : never) => U;

// assoc(prop, __, obj)(val)
export function assoc<U, K extends keyof U>(prop: K, __: Placeholder, obj: U): <T extends U[K]>(val: T) => U;

// assoc(prop, val, obj)
export function assoc<U, K extends keyof U, T extends U[K]>(prop: K, val: T, obj: U): U;
8 changes: 6 additions & 2 deletions types/dissoc.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export function dissoc<K extends string | number>(prop: K): <T extends object>(obj: T) => Omit<T, K>;
export function dissoc<T extends object, K extends keyof T>(prop: K, obj: T): Omit<T, K>;
import { Placeholder } from './util/tools';

// `string extends keyof U` is true only for `Record<string, T>`, where the keys are not known, something that still needs to be supported
export function dissoc<K extends PropertyKey>(prop: K extends Placeholder ? never : K): <U extends { [P in K]?: any}>(obj: string extends keyof U ? U : undefined extends U[K] ? U : never) => U;
export function dissoc<U>(__: Placeholder, obj: U): <K extends keyof U>(prop: string extends keyof U ? K : undefined extends U[K] ? K : never) => U;
export function dissoc<U, K extends keyof U>(prop: string extends keyof U ? K : undefined extends U[K] ? K : never, obj: U): U;
21 changes: 12 additions & 9 deletions types/modify.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
export function modify<K extends string, A, P>(
prop: K,
fn: (a: A) => P,
): <T extends Record<K, A>>(target: T) => Omit<T, K> & Record<K, P>;
// modify(prop)
export function modify<K extends PropertyKey>(prop: K): {
// modify(prop)(fn)(obj)
<T>(fn: (value: T) => T): <U extends Record<K, T>>(object: U) => U;
// modify(prop)(fn), obj)
<T, U extends Record<K, T>>(fn: (value: T) => T, object: U): U;
};

export function modify<T extends object, K extends keyof T, P>(
prop: K,
fn: (a: T[K]) => P,
obj: T,
): Omit<T, K> & Record<K, P>;
// modify(prop, fn)(obj)
export function modify<K extends PropertyKey, T>(prop: K, fn: (value: T) => T): <U extends Record<K, T>>(object: U) => U;

// modify(prop, fn, obj)
export function modify<U, K extends keyof U>(prop: K, fn: (value: U[K]) => U[K], object: U): U;
Loading