diff --git a/core/stdlib/src/types/ts/collections.test-d.ts b/core/stdlib/src/types/ts/collections.test-d.ts new file mode 100644 index 0000000..822fe58 --- /dev/null +++ b/core/stdlib/src/types/ts/collections.test-d.ts @@ -0,0 +1,71 @@ +import { describe, expectTypeOf, it } from 'vitest'; +import type { Path, PathToType } from './collections'; + +describe('collections', () => { + describe('Path', () => { + it('parse simple object path', () => { + type actual = Path<'user.name'>; + type expected = ['user', 'name']; + + expectTypeOf().toEqualTypeOf(); + }); + + it('parse simple array path', () => { + type actual = Path<'user.0'>; + type expected = ['user', '0']; + + expectTypeOf().toEqualTypeOf(); + }); + + it('parse complex object path', () => { + type actual = Path<'user.addresses.0.street'>; + type expected = ['user', 'addresses', '0', 'street']; + + expectTypeOf().toEqualTypeOf(); + }); + + it('parse double dot path', () => { + type actual = Path<'user..name'>; + type expected = ['user', '', 'name']; + + expectTypeOf().toEqualTypeOf(); + }); + }); + + describe('PathToType', () => { + it('convert simple object path', () => { + type actual = PathToType<['user', 'name']>; + type expected = { user: { name: unknown } }; + + expectTypeOf().toEqualTypeOf(); + }); + + it('convert simple array path', () => { + type actual = PathToType<['user', '0']>; + type expected = { user: unknown[] }; + + expectTypeOf().toEqualTypeOf(); + }); + + it('convert complex object path', () => { + type actual = PathToType<['user', 'addresses', '0', 'street']>; + type expected = { user: { addresses: { street: unknown }[] } }; + + expectTypeOf().toEqualTypeOf(); + }); + + it('convert double dot path', () => { + type actual = PathToType<['user', '', 'name']>; + type expected = { user: { '': { name: unknown } } }; + + expectTypeOf().toEqualTypeOf(); + }); + + it('convert to custom target', () => { + type actual = PathToType<['user', 'name'], string>; + type expected = { user: { name: string } }; + + expectTypeOf().toEqualTypeOf(); + }); + }); +}); \ No newline at end of file diff --git a/core/stdlib/src/types/ts/collections.ts b/core/stdlib/src/types/ts/collections.ts new file mode 100644 index 0000000..57b4c2f --- /dev/null +++ b/core/stdlib/src/types/ts/collections.ts @@ -0,0 +1,28 @@ +/** + * A collection definition + */ +export type Collection = Record | any[]; + +/** + * Parse a collection path string into an array of keys + */ +export type Path = + T extends `${infer Key}.${infer Rest}` + ? [Key, ...Path] + : T extends `${infer Key}` + ? [Key] + : []; + +/** + * Convert a collection path array into a Target type + */ +export type PathToType = + T extends [infer Head, ...infer Rest] + ? Head extends `${number}` + ? Rest extends string[] + ? PathToType[] + : never + : Rest extends string[] + ? { [K in Head & string]: PathToType } + : never + : Target; diff --git a/core/stdlib/src/types/ts/index.ts b/core/stdlib/src/types/ts/index.ts index 1b8ba93..7582ae8 100644 --- a/core/stdlib/src/types/ts/index.ts +++ b/core/stdlib/src/types/ts/index.ts @@ -1,4 +1,6 @@ export * from './array'; +export * from './collections'; export * from './function'; export * from './promise'; export * from './string'; +export * from './union'; diff --git a/core/stdlib/src/types/ts/string.test-d.ts b/core/stdlib/src/types/ts/string.test-d.ts index 88d016d..883933a 100644 --- a/core/stdlib/src/types/ts/string.test-d.ts +++ b/core/stdlib/src/types/ts/string.test-d.ts @@ -1,7 +1,18 @@ import { describe, expectTypeOf, it } from 'vitest'; -import type { HasSpaces, Trim } from './string'; +import type { HasSpaces, Trim, Stringable } from './string'; describe('string', () => { + describe('Stringable', () => { + it('should be a string', () => { + expectTypeOf(Number(1)).toExtend(); + expectTypeOf(String(1)).toExtend(); + expectTypeOf(Symbol()).toExtend(); + expectTypeOf(new Array(1)).toExtend(); + expectTypeOf(new Object()).toExtend(); + expectTypeOf(new Date()).toExtend(); + }); + }); + describe('Trim', () => { it('remove leading and trailing spaces from a string', () => { type actual = Trim<' hello '>; diff --git a/core/stdlib/src/types/ts/string.ts b/core/stdlib/src/types/ts/string.ts index 0398e8e..1c94bc0 100644 --- a/core/stdlib/src/types/ts/string.ts +++ b/core/stdlib/src/types/ts/string.ts @@ -1,3 +1,10 @@ +/** + * Stringable type + */ +export interface Stringable { + toString(): string; +} + /** * Trim leading and trailing whitespace from `S` */ diff --git a/core/stdlib/src/types/ts/union.test-d.ts b/core/stdlib/src/types/ts/union.test-d.ts new file mode 100644 index 0000000..c279d47 --- /dev/null +++ b/core/stdlib/src/types/ts/union.test-d.ts @@ -0,0 +1,31 @@ +import { describe, expectTypeOf, it } from 'vitest'; +import type { UnionToIntersection } from './union'; + +describe('union', () => { + describe('UnionToIntersection', () => { + it('convert a union type to an intersection type', () => { + type actual = UnionToIntersection<{ a: string } | { b: number }>; + type expected = { a: string } & { b: number }; + + expectTypeOf().toEqualTypeOf(); + }); + + it('convert a union type to an intersection type with more than two types', () => { + type actual = UnionToIntersection<{ a: string } | { b: number } | { c: boolean }>; + type expected = { a: string } & { b: number } & { c: boolean }; + + expectTypeOf().toEqualTypeOf(); + }); + + it('no change when the input is already an intersection type', () => { + type actual = UnionToIntersection<{ a: string } & { b: number }>; + type expected = { a: string } & { b: number }; + + expectTypeOf().toEqualTypeOf(); + }); + + it('never when union not possible', () => { + expectTypeOf>().toEqualTypeOf(); + }); + }); +}); diff --git a/core/stdlib/src/types/ts/union.ts b/core/stdlib/src/types/ts/union.ts new file mode 100644 index 0000000..72c4f40 --- /dev/null +++ b/core/stdlib/src/types/ts/union.ts @@ -0,0 +1,10 @@ +/** + * Convert a union type to an intersection type + */ +export type UnionToIntersection = ( + Union extends unknown + ? (distributedUnion: Union) => void + : never +) extends ((mergedIntersection: infer Intersection) => void) + ? Intersection & Union + : never;