Skip to content

Commit 80b4f1e

Browse files
authored
Merge pull request #17 from kakasoo/kakasoo/codebase-review
test, docs: comprehensive test coverage, JSDoc, and README rewrite
2 parents aa1794e + 3620496 commit 80b4f1e

32 files changed

Lines changed: 1417 additions & 559 deletions

README.md

Lines changed: 141 additions & 267 deletions
Large diffs are not rendered by default.

docs/README_KO.md

Lines changed: 141 additions & 266 deletions
Large diffs are not rendered by default.

src/functions/DeepStrictAssert.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
import { DeepStrictObjectKeys } from '../types/DeepStrictObjectKeys';
22
import { DeepStrictPick } from '../types/DeepStrictPick';
33

4+
/**
5+
* @title Runtime Function for Type-Safe Deep Property Extraction.
6+
*
7+
* A curried function that takes an object and returns a picker function.
8+
* The picker function accepts a dot-notation key path and returns a new object
9+
* containing only the specified nested property, preserving the original structure.
10+
*
11+
* This is the runtime counterpart of the {@link DeepStrictPick} type.
12+
*
13+
* @template T - The object type of the input
14+
* @param input - The source object to extract properties from
15+
* @returns A function that accepts a key path `K` and returns the deeply-picked result
16+
*
17+
* @example
18+
* ```ts
19+
* const result = deepStrictAssert({ a: { b: 1, c: 2 } })('a.b');
20+
* // result: { a: { b: 1 } }
21+
* ```
22+
*/
423
export const deepStrictAssert =
524
<T extends object>(input: T) =>
625
<K extends DeepStrictObjectKeys<T>>(key: K): DeepStrictPick<T, K> => {

src/functions/DeepStrictObjectKeys.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { DeepStrictObjectKeys } from '../types';
22

3+
/** @internal Removes a leading dot from a string type. e.g., `".foo"` becomes `"foo"`. */
34
type RemoveStartWithDot<T extends string> = T extends `.${infer R extends string}` ? R : T;
45

6+
/** @internal Replaces all `[*]` array notation with `${number}` for runtime key path access. */
57
type Replace<S extends string> = S extends '[*]'
68
? `${number}`
79
: S extends `[*].${infer Rest}`
@@ -12,11 +14,36 @@ type Replace<S extends string> = S extends '[*]'
1214
? `${Prefix}.${number}${Replace<Rest>}`
1315
: S; // 더 이상 `[ ]`가 없으면 문자열 반환
1416

17+
/**
18+
* @internal The return type for {@link deepStrictObjectKeys}.
19+
* Converts type-level keys (with `[*]` notation) to runtime-friendly keys (with `${number}` notation),
20+
* strips leading dots, and wraps the result in an array type.
21+
*/
1522
type ReturnType<
1623
Target extends object,
1724
Joiner extends { array: string; object: string } = { array: '[*]'; object: '.' },
1825
> = [Target] extends [never] ? [] : RemoveStartWithDot<Replace<DeepStrictObjectKeys<Target, Joiner, false>>>[];
1926

27+
/**
28+
* @title Runtime Function for Extracting All Nested Keys from an Object.
29+
*
30+
* Recursively traverses the input object and returns a flat array of all key paths
31+
* using dot notation. Nested objects produce paths like `"a.b.c"`, and arrays produce
32+
* indexed paths at runtime.
33+
*
34+
* This is the runtime counterpart of the {@link DeepStrictObjectKeys} type.
35+
*
36+
* @template Target - The object type to extract keys from
37+
* @template Joiner - Separator symbols (defaults to `{ array: '[*]', object: '.' }`)
38+
* @param target - The object instance to extract keys from
39+
* @returns An array of all dot-notation key paths in the object
40+
*
41+
* @example
42+
* ```ts
43+
* const keys = deepStrictObjectKeys({ a: { b: 1, c: 2 } });
44+
* // keys: ["a", "a.b", "a.c"]
45+
* ```
46+
*/
2047
export function deepStrictObjectKeys<
2148
Target extends object,
2249
Joiner extends { array: string; object: string } = { array: '[*]'; object: '.' },

src/types/DeepDateToString.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
11
import { DeepStrictUnbrand } from './DeepStrictUnbrand';
22

33
/**
4-
* A utility type that recursively converts all `Date` types within a nested object or array to `string`.
4+
* @title Type for Recursively Converting All Date Types to String.
55
*
6-
* - If `T` is an array of objects, the type processes each element recursively.
7-
* - If `T` is a `Date`, it is converted to `string`.
8-
* - If `T` is an object, each key is checked recursively for `Date` types or nested objects.
6+
* A utility type that recursively traverses a nested object or array type and converts
7+
* every `Date` type to `string`. This is useful for representing serialized forms of objects
8+
* where dates are transmitted as ISO strings (e.g., JSON responses from APIs).
9+
*
10+
* Conversion rules:
11+
* - If `T` is an array of objects, each element is processed recursively.
12+
* - If `T` is a `Date`, it becomes `string`.
13+
* - If `T` is an object, each property is checked: `Date` properties become `string`,
14+
* nested objects are recursed into, and primitives are preserved as-is.
15+
* - Union types containing `Date` (e.g., `Date | null`) have only the `Date` portion converted.
16+
* - Branded types are unbranded via {@link DeepStrictUnbrand} before recursion.
17+
*
18+
* @template T - The object type to convert
19+
* @returns A new type with all `Date` occurrences replaced by `string`
20+
*
21+
* @example
22+
* ```typescript
23+
* type Ex1 = DeepDateToString<{ created: Date; name: string }>; // { created: string; name: string }
24+
* type Ex2 = DeepDateToString<{ items: { date: Date }[] }>; // { items: { date: string }[] }
25+
* type Ex3 = DeepDateToString<{ prop: Date | null }>; // { prop: string | null }
26+
* ```
927
*/
1028
export type DeepDateToString<T extends object> =
1129
DeepStrictUnbrand<T> extends Array<infer I extends object>

src/types/DeepStrictMerge.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,17 @@ namespace DeepStrictMerge {
5151
* 2. For array types, if both `Target` and `Source` are arrays and their elements are objects, those elements are merged.
5252
* - If the elements of the arrays are not objects, merging is not possible.
5353
* 3. If only one of the types is an array, merging is not possible.
54+
*
55+
* @template Target - The primary object type. Its values take precedence on overlapping keys.
56+
* @template Source - The secondary object type whose unique keys are added to the result.
57+
* @returns A deeply merged object type combining `Target` and `Source`
58+
*
59+
* @example
60+
* ```ts
61+
* type Ex1 = DeepStrictMerge<{ a: 1 }, { b: 2 }>; // { a: 1; b: 2 }
62+
* type Ex2 = DeepStrictMerge<{ a: { b: 1 } }, { a: { c: 2 } }>; // { a: { b: 1; c: 2 } }
63+
* type Ex3 = DeepStrictMerge<{ a: 1 }, { a: 2 }>; // { a: 1 } (Target wins)
64+
* ```
5465
*/
5566
export type DeepStrictMerge<Target extends object, Source extends object> =
5667
Target extends Array<infer TE extends object>

src/types/DeepStrictObjectKeys.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,10 @@ namespace DeepStrictObjectKeys {
7171
* type Example1 = DeepStrictObjectKeys<{ a: { b: 1; c: 2 } }>; // "a" | "a.b" | "a.c"
7272
* type Example2 = DeepStrictObjectKeys<{ a: { b: 1; c: { d: number }[] } }>; // "a" | "a.b" | "a.c" | "a.c[*].d"
7373
* ```
74-
* @template Target Destination type for which you want to pull a key
75-
* @template Joiner It means what symbol to connect when recursively spinning superimposed types.
76-
* @template IsSafe When a key is a combination type of a primitive type and an object, it means whether to perform a recursive search or not.
74+
* @template Target - The object type to extract nested keys from
75+
* @template Joiner - Defines the separator symbols for joining nested paths. `array` is used for array access (default `'[*]'`), `object` is used for object access (default `'.'`).
76+
* @template IsSafe - When `true`, stops at union types that mix primitives and objects without recursing into them. When `false`, recursively explores all branches of such union types.
77+
* @returns A union of all dot-notation key paths in `Target`
7778
*/
7879
export type DeepStrictObjectKeys<
7980
Target extends object,

src/types/DeepStrictObjectLastKeys.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ import type { IsUnion } from './IsUnion';
44
import type { ValueType } from './ValueType';
55

66
namespace DeepStrictObjectLastKeys {
7+
/**
8+
* @internal Recursively extracts only the leaf-level (last) keys from a nested object type.
9+
*
10+
* Unlike {@link DeepStrictObjectKeys.Infer}, this type skips intermediate keys and only
11+
* returns the deepest reachable paths. Union-typed properties are treated as leaf keys.
12+
*
13+
* @template T - The object type to extract leaf keys from
14+
* @template Joiner - Separator symbols for array and object paths
15+
* @template P - The current property keys being processed
16+
*/
717
export type Infer<
818
T extends object,
919
Joiner extends {
@@ -42,10 +52,15 @@ namespace DeepStrictObjectLastKeys {
4252
* - For arrays, it appends array indices (`[*]`) followed by the key of the element.
4353
* - For objects, it recursively traverses the nested structure and appends the last level keys.
4454
*
45-
* Example Usage:
55+
* @template T - The object type to extract leaf keys from
56+
* @template Joiner - Defines the separator symbols for joining nested paths. `array` is used for array access (default `'[*]'`), `object` is used for object access (default `'.'`).
57+
* @template P - The property keys of `T` to iterate over (defaults to `keyof T`)
58+
* @returns A union of dot-notation strings representing only the deepest (leaf) keys in `T`
59+
*
60+
* @example
4661
* ```ts
47-
* type Example1 = DeepStrictObjectLastKeys<{ a: { b: { c: number[] } } }>; // "a.b.c"
48-
* type Example2 = DeepStrictObjectLastKeys<{ a: { b: number[]; c: { d: string }[] } }>; // "a.b" | "a.c" | "a.c[*].d"
62+
* type Ex1 = DeepStrictObjectLastKeys<{ a: { b: { c: number[] } } }>; // "a.b.c"
63+
* type Ex2 = DeepStrictObjectLastKeys<{ a: { b: number[]; c: { d: string }[] } }>; // "a.b" | "a.c[*].d"
4964
* ```
5065
*/
5166
export type DeepStrictObjectLastKeys<

src/types/DeepStrictOmit.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ import type { DeepStrictObjectKeys } from './DeepStrictObjectKeys';
22
import type { GetElementMember } from './GetMember';
33

44
namespace DeepStrictOmit {
5+
/**
6+
* @internal Recursively omits keys from a non-array object type.
7+
*
8+
* Handles three cases for each property:
9+
* - Array of objects: recurses into array elements using {@link GetElementMember} to extract sub-keys
10+
* - Object (non-Date): recurses into the nested object
11+
* - Primitive / Date / array of primitives: preserves as-is
12+
*
13+
* Top-level keys that exactly match `K` are removed via the `as` clause.
14+
*
15+
* @template T - The object type to omit keys from
16+
* @template K - The dot-notation key paths to omit (must be valid keys of `T`)
17+
*/
518
export type Infer<T extends object, K extends DeepStrictObjectKeys<T>> = [K] extends [never]
619
? T
720
: {

src/types/DeepStrictUnbrand.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
namespace DeepStrictUnbrand {
2+
/**
3+
* @internal Union of all JavaScript primitive types.
4+
* Used to detect branded primitives (primitives intersected with `Record<any, any>`).
5+
*/
26
export type Primitive = string | number | boolean | symbol | null | undefined;
7+
8+
/**
9+
* @internal Strips branding from a single branded primitive type.
10+
*
11+
* A "branded primitive" is a type like `string & { __brand: 'email' }`.
12+
* This type pattern-matches the base primitive and returns it without the brand.
13+
*
14+
* @template T - A branded primitive type (must extend both `Primitive` and `Record<any, any>`)
15+
* @returns The underlying primitive type with branding removed
16+
*/
317
export type Infer<T extends Primitive & Record<any, any>> = T extends string & Record<any, any>
418
? string
519
: T extends number & Record<any, any>

0 commit comments

Comments
 (0)