From e0d6a21fb79233ee5763ad3eeb006cd390509be0 Mon Sep 17 00:00:00 2001 From: ersimont <8042088+ersimont@users.noreply.github.com> Date: Sun, 12 May 2024 14:40:27 -0400 Subject: [PATCH] feat(micro-dash): remove dependency on the `utility-types` library --- package-lock.json | 13 +-- package.json | 1 - projects/micro-dash/ng-package.json | 3 +- projects/micro-dash/package.json | 2 +- projects/micro-dash/src/lib/array/compact.ts | 6 +- projects/micro-dash/src/lib/interfaces.ts | 15 ++- .../micro-dash/src/lib/lang/is-match.spec.ts | 5 +- projects/micro-dash/src/lib/lang/is-match.ts | 2 +- .../micro-dash/src/lib/object/invoke.spec.ts | 103 ++++++++++++++++++ projects/micro-dash/src/lib/object/invoke.ts | 2 +- .../typing-tests/object/invoke.dts-spec.ts | 85 --------------- projects/ng-dev/src/lib/interfaces.ts | 3 + projects/ng-dev/src/lib/spies/test-call.ts | 6 +- 13 files changed, 133 insertions(+), 113 deletions(-) delete mode 100644 projects/micro-dash/src/typing-tests/object/invoke.dts-spec.ts create mode 100644 projects/ng-dev/src/lib/interfaces.ts diff --git a/package-lock.json b/package-lock.json index 5a6dc2b5..01e24b32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "s-libs", - "version": "17.0.0-next.1", + "version": "17.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "s-libs", - "version": "17.0.0-next.1", + "version": "17.1.0", "dependencies": { "@angular/animations": "^17.0.0", "@angular/cdk": "17.0", @@ -20,7 +20,6 @@ "@angular/router": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", - "utility-types": "~3.10.0", "zone.js": "~0.14.2" }, "devDependencies": { @@ -21199,14 +21198,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/utility-types": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz", - "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==", - "engines": { - "node": ">= 4" - } - }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index 043ae71a..8203faa1 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@angular/router": "^17.0.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", - "utility-types": "~3.10.0", "zone.js": "~0.14.2" }, "devDependencies": { diff --git a/projects/micro-dash/ng-package.json b/projects/micro-dash/ng-package.json index 9a04ffb2..e4dbf214 100644 --- a/projects/micro-dash/ng-package.json +++ b/projects/micro-dash/ng-package.json @@ -4,6 +4,5 @@ "lib": { "entryFile": "src/public-api.ts", "flatModuleFile": "micro-dash" - }, - "allowedNonPeerDependencies": ["utility-types"] + } } diff --git a/projects/micro-dash/package.json b/projects/micro-dash/package.json index ab1c6bab..db395946 100644 --- a/projects/micro-dash/package.json +++ b/projects/micro-dash/package.json @@ -10,6 +10,6 @@ "url": "https://github.com/simontonsoftware/s-libs.git", "directory": "projects/micro-dash" }, - "dependencies": { "tslib": "^2.3.0", "utility-types": "^3.10.0" }, + "dependencies": { "tslib": "^2.3.0" }, "sideEffects": false } diff --git a/projects/micro-dash/src/lib/array/compact.ts b/projects/micro-dash/src/lib/array/compact.ts index b4efcb61..24791e5d 100644 --- a/projects/micro-dash/src/lib/array/compact.ts +++ b/projects/micro-dash/src/lib/array/compact.ts @@ -1,4 +1,4 @@ -import { Falsey } from 'utility-types'; +import { Falsy } from '../interfaces'; import { identity } from '../util'; /** @@ -8,7 +8,7 @@ import { identity } from '../util'; * - Lodash: 113 bytes * - Micro-dash: 62 bytes */ -export function compact(array: readonly T[]): Array> { +export function compact(array: readonly T[]): Array> { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- not sure why this rule triggers - return array.filter(identity) as Array>; + return array.filter(identity) as Array>; } diff --git a/projects/micro-dash/src/lib/interfaces.ts b/projects/micro-dash/src/lib/interfaces.ts index 4481bd96..7e8414d9 100644 --- a/projects/micro-dash/src/lib/interfaces.ts +++ b/projects/micro-dash/src/lib/interfaces.ts @@ -1,10 +1,12 @@ +// aliases export type Nil = null | undefined; export type Primitive = boolean | number | string; export type Existent = Primitive | object; +export type Falsy = '' | 0 | false | null | undefined; export type EmptyObject = Record; export type ObjectWith = Record; -export type StringifiedKey = Cast; +// iteratees export type ArrayIteratee = (item: I, index: number) => O; export type ArrayNarrowingIteratee = (item: any, index: number) => item is O; export type ObjectIteratee = ( @@ -21,6 +23,7 @@ export type KeyNarrowingIteratee = ( ) => key is O; export type ValueIteratee = (value: T) => O; +// coercion export type Cast = Exclude extends never ? I : O; export type Narrow = Extract | Extract; export type IfCouldBe = Narrow extends never @@ -32,12 +35,13 @@ export type IfIndexType = string extends T ? If : Else; +// object parts type IndexKeys = { [K in keyof T]: IfIndexType }[keyof T]; type NonIndexKeys = { [K in keyof T]: IfIndexType }[keyof T]; export type PartialExceptIndexes = { [K in NonIndexKeys]?: T[K]; } & { [K in IndexKeys]: T[K] }; - +export type StringifiedKey = Cast; export type ValuesType = T extends readonly [] ? T[number] : T extends ArrayLike @@ -45,9 +49,16 @@ export type ValuesType = T extends readonly [] : T extends object ? T[keyof T] : never; +export type DeepPartial = T extends Array + ? Array> + : T extends object + ? { [K in keyof T]?: DeepPartial } + : T | undefined; +// misc export type Evaluate = T extends infer I ? { [K in keyof I]: T[K] } : never; +// very special-case export type Drop1Arg = T extends ( arg1: any, ...rest: infer A diff --git a/projects/micro-dash/src/lib/lang/is-match.spec.ts b/projects/micro-dash/src/lib/lang/is-match.spec.ts index 9f73d666..8ca8724a 100644 --- a/projects/micro-dash/src/lib/lang/is-match.spec.ts +++ b/projects/micro-dash/src/lib/lang/is-match.spec.ts @@ -1,5 +1,4 @@ -import { Falsey } from 'utility-types'; -import { ObjectWith } from '../interfaces'; +import { Falsy, ObjectWith } from '../interfaces'; import { isMatch } from './is-match'; describe('isMatch()', () => { @@ -102,7 +101,7 @@ describe('isMatch()', () => { }); it('should return `true` when comparing an empty `source`', () => { - const object = { a: 1 } as any[] | Falsey | { a: 1 }; + const object = { a: 1 } as any[] | Falsy | { a: 1 }; expect(isMatch(object, [])).toBe(true); expect(isMatch(object, {})).toBe(true); expect(isMatch(object, null)).toBe(true); diff --git a/projects/micro-dash/src/lib/lang/is-match.ts b/projects/micro-dash/src/lib/lang/is-match.ts index bec8c958..e04e3fb7 100644 --- a/projects/micro-dash/src/lib/lang/is-match.ts +++ b/projects/micro-dash/src/lib/lang/is-match.ts @@ -1,5 +1,5 @@ -import { DeepPartial } from 'utility-types'; import { every } from '../collection/every'; +import { DeepPartial } from '../interfaces'; import { isEmpty } from './is-empty'; /** diff --git a/projects/micro-dash/src/lib/object/invoke.spec.ts b/projects/micro-dash/src/lib/object/invoke.spec.ts index bbee72ac..683113f7 100644 --- a/projects/micro-dash/src/lib/object/invoke.spec.ts +++ b/projects/micro-dash/src/lib/object/invoke.spec.ts @@ -1,3 +1,6 @@ +import { staticTest } from '@s-libs/ng-dev'; +import { expectTypeOf } from 'expect-type'; +import { Nil } from '../interfaces'; import { invoke } from './invoke'; describe('invoke()', () => { @@ -10,6 +13,106 @@ describe('invoke()', () => { expect(spy.calls.first().object).toBe(obj); }); + it('has fancy typing', () => { + staticTest(() => { + // + // empty path + // + + expectTypeOf(invoke({ a: () => 1 }, [])).toEqualTypeOf(); + expectTypeOf( + invoke({} as { a: () => string } | undefined, []), + ).toEqualTypeOf(); + + // + // 1 element path + // + + expectTypeOf(invoke({ a: () => 1 }, ['a'])).toEqualTypeOf(); + expectTypeOf( + invoke({ a: (a: boolean) => a }, ['a'], true), + ).toEqualTypeOf(); + expectTypeOf(invoke({} as { a?: () => string }, ['a'])).toEqualTypeOf< + string | undefined + >(); + expectTypeOf( + invoke({} as Nil | { a: () => string }, ['a']), + ).toEqualTypeOf(); + + // + // 2 element path + // + + expectTypeOf( + invoke({ a: { b: () => 1 } }, ['a', 'b']), + ).toEqualTypeOf(); + expectTypeOf( + invoke({ a: { b: (a: boolean) => a } }, ['a', 'b'], true), + ).toEqualTypeOf(); + expectTypeOf( + invoke({} as { a: { b?: () => string } }, ['a', 'b']), + ).toEqualTypeOf(); + expectTypeOf( + invoke({} as { a?: { b: () => string } }, ['a', 'b']), + ).toEqualTypeOf(); + expectTypeOf( + invoke({} as Nil | { a: { b: () => string } }, ['a', 'b']), + ).toEqualTypeOf(); + + // + // 3 element path + // + + const path3: ['a', 'b', 'c'] = ['a', 'b', 'c']; + expectTypeOf( + invoke({ a: { b: { c: () => 1 } } }, path3), + ).toEqualTypeOf(); + expectTypeOf( + invoke({ a: { b: { c: (a: boolean) => a } } }, path3, true), + ).toEqualTypeOf(); + expectTypeOf( + invoke({} as { a: { b: { c?: () => string } } }, path3), + ).toEqualTypeOf(); + expectTypeOf( + invoke({} as { a: { b?: { c: () => string } } }, path3), + ).toEqualTypeOf(); + expectTypeOf( + invoke({} as { a?: { b: { c: () => string } } }, path3), + ).toEqualTypeOf(); + expectTypeOf( + invoke({} as Nil | { a: { b: { c: () => string } } }, path3), + ).toEqualTypeOf(); + + // // + // // 4 element path + // // + // + // const path4: ["a", "b", "c", "d"] = ["a", "b", "c", "d"]; + // // $ExpectType number + // invoke({ a: { b: { c: { d: () => 1 } } } }, path4); + // // $ExpectType boolean + // invoke({ a: { b: { c: { d: (a: boolean) => a } } } }, path4, true); + // // $ExpectType string | undefined + // invoke({} as { a: { b: { c: { d?: () => string } } } }, path4); + // // $ExpectType string | undefined + // invoke({} as { a: { b: { c?: { d: () => string } } } }, path4); + // // $ExpectType string | undefined + // invoke({} as { a: { b?: { c: { d: () => string } } } }, path4); + // // $ExpectType string | undefined + // invoke({} as { a?: { b: { c: { d: () => string } } } }, path4); + // // $ExpectType string | undefined + // invoke({} as { a: { b: { c: { d: () => string } } } } | Nil, path4); + + // + // fallback: n element path + // + + const pathN: string[] = ['a']; + // $ExpectType any + invoke({ a: () => 1 }, pathN); + }); + }); + // // stolen from https://github.com/lodash/lodash // diff --git a/projects/micro-dash/src/lib/object/invoke.ts b/projects/micro-dash/src/lib/object/invoke.ts index 1b159b65..8b045f15 100644 --- a/projects/micro-dash/src/lib/object/invoke.ts +++ b/projects/micro-dash/src/lib/object/invoke.ts @@ -1,9 +1,9 @@ -import { NonUndefined } from 'utility-types'; import { Existent, Nil } from '../interfaces'; import { isFunction } from '../lang'; import { get } from './get'; type Fn = (...args: any[]) => any; +type NonUndefined = T extends undefined ? never : T; type Obj1 = { [key in K1]?: V }; type Path1< diff --git a/projects/micro-dash/src/typing-tests/object/invoke.dts-spec.ts b/projects/micro-dash/src/typing-tests/object/invoke.dts-spec.ts deleted file mode 100644 index c06e7fdb..00000000 --- a/projects/micro-dash/src/typing-tests/object/invoke.dts-spec.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Nil } from '../../lib/interfaces'; -import { invoke } from '../../lib/object'; - -// -// empty path -// - -// $ExpectType undefined -invoke({ a: () => 1 }, []); -// $ExpectType undefined -invoke({} as { a: () => string } | undefined, []); - -// -// 1 element path -// - -// $ExpectType number -invoke({ a: () => 1 }, ['a']); -// $ExpectType boolean -invoke({ a: (a: boolean) => a }, ['a'], true); -// $ExpectType string | undefined -invoke({} as { a?: () => string }, ['a']); -// $ExpectType string | undefined -invoke({} as { a: () => string } | Nil, ['a']); - -// -// 2 element path -// - -// $ExpectType number -invoke({ a: { b: () => 1 } }, ['a', 'b']); -// $ExpectType boolean -invoke({ a: { b: (a: boolean) => a } }, ['a', 'b'], true); -// $ExpecType string | undefined -invoke({} as { a: { b?: () => string } }, ['a', 'b']); -// $ExpecType string | undefined -invoke({} as { a?: { b: () => string } }, ['a', 'b']); -// $ExpecType string | undefined -invoke({} as { a: { b: () => string } } | Nil, ['a', 'b']); - -// -// 3 element path -// - -const path3: ['a', 'b', 'c'] = ['a', 'b', 'c']; -// $ExpectType number -invoke({ a: { b: { c: () => 1 } } }, path3); -// $ExpecType boolean -invoke({ a: { b: { c: (a: boolean) => a } } }, path3, true); -// $ExpecType string | undefined -invoke({} as { a: { b: { c?: () => string } } }, path3); -// $ExpecType string | undefined -invoke({} as { a: { b?: { c: () => string } } }, path3); -// $ExpecType string | undefined -invoke({} as { a?: { b: { c: () => string } } }, path3); -// $ExpecType string | undefined -invoke({} as { a: { b: { c: () => string } } } | Nil, path3); - -// // -// // 4 element path -// // -// -// const path4: ["a", "b", "c", "d"] = ["a", "b", "c", "d"]; -// // $ExpectType number -// invoke({ a: { b: { c: { d: () => 1 } } } }, path4); -// // $ExpecType boolean -// invoke({ a: { b: { c: { d: (a: boolean) => a } } } }, path4, true); -// // $ExpecType string | undefined -// invoke({} as { a: { b: { c: { d?: () => string } } } }, path4); -// // $ExpecType string | undefined -// invoke({} as { a: { b: { c?: { d: () => string } } } }, path4); -// // $ExpecType string | undefined -// invoke({} as { a: { b?: { c: { d: () => string } } } }, path4); -// // $ExpecType string | undefined -// invoke({} as { a?: { b: { c: { d: () => string } } } }, path4); -// // $ExpecType string | undefined -// invoke({} as { a: { b: { c: { d: () => string } } } } | Nil, path4); - -// -// fallback: n element path -// - -const pathN: string[] = ['a']; -// $ExpectType any -invoke({ a: () => 1 }, pathN); diff --git a/projects/ng-dev/src/lib/interfaces.ts b/projects/ng-dev/src/lib/interfaces.ts new file mode 100644 index 00000000..1a9f4e24 --- /dev/null +++ b/projects/ng-dev/src/lib/interfaces.ts @@ -0,0 +1,3 @@ +export type ResolveType = F extends (...args: any[]) => Promise + ? U + : never; diff --git a/projects/ng-dev/src/lib/spies/test-call.ts b/projects/ng-dev/src/lib/spies/test-call.ts index 3f7a92ed..b32c8e37 100644 --- a/projects/ng-dev/src/lib/spies/test-call.ts +++ b/projects/ng-dev/src/lib/spies/test-call.ts @@ -1,6 +1,6 @@ import { Deferred } from '@s-libs/js-core'; -import { PromiseType } from 'utility-types'; import { AngularContext } from '../angular-context'; +import { ResolveType } from '../interfaces'; /** * A mock method call that was made and is ready to be answered. This interface allows access to the underlying jasmine.CallInfo, and allows resolving or rejecting the asynchronous call's result. @@ -10,14 +10,14 @@ export class TestCall { callInfo!: jasmine.CallInfo; constructor( - private deferred: Deferred>>, + private deferred: Deferred>, private autoTick: boolean, ) {} /** * Resolve the call with the given value. */ - flush(value: PromiseType>): void { + flush(value: ResolveType): void { this.deferred.resolve(value); this.maybeTick(); }