Skip to content
Merged
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
30 changes: 6 additions & 24 deletions docs/feature-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This document tracks suggested additions for `@nevware21/ts-utils`.
- Request: Identify what additions could be added to ts-utils
- Requested by: Maintainer discussion
- Priority: High
- Target Version: 0.14.0
- Scope: Array, object, string, iterator, typing, and documentation improvements

### Objective
Expand Down Expand Up @@ -48,17 +49,7 @@ Notes:
- Iterator helpers are intentionally listed as utility suggestions here rather than standard-language mappings.
- Implementations should include ES5 polyfills where applicable for v0.x/v1.x compatibility

### A. Typing Improvements (High Value)

- `ReadonlyRecord<K, V>` helper type alias for API ergonomics
- `DeepPartial<T>` utility type
- `DeepReadonly<T>` utility type
- `Mutable<T>` utility type for controlled writable transformations
- `DeepRequired<T>` utility type for normalized configuration and defaults handling
- `ValueOf<T>` helper type for extracting union values from object maps
- `NonEmptyArray<T>` helper type for APIs that require at least one entry

### B. Object Utilities (Medium Value)
### A. Object Utilities (Medium Value)

- `objPick` / `objOmit`
- `objMapValues`
Expand All @@ -72,7 +63,7 @@ Notes:
- maintain plain-object safety patterns
- avoid behavior changes to existing deep copy helpers

### C. String Utilities (Medium Value)
### B. String Utilities (Medium Value)

- `strStartsWithAny` / `strEndsWithAny`
- `strWrap` / `strUnwrap`
Expand All @@ -83,31 +74,22 @@ Notes:
- prefer helpers that avoid locale-sensitive behavior unless explicitly documented
- keep semantics predictable for ES5 runtimes and string coercion patterns

### D. Iterator and Collection Helpers (Medium Value)
### C. Iterator and Collection Helpers (Medium Value)

- `iterMap`, `iterFilter`, `iterTake` – Iterator transformation helpers
- `iterReduce`, `iterSome`, `iterEvery` – Iterator reduction/testing
- `iterToArray` for predictable materialization of iterables / iterators
- `arrToMap` helpers with stable key selection
- lightweight set operations for iterables

### E. Reliability and Tooling (High Value)
### D. Reliability and Tooling (High Value)

- keep bundle-size thresholds justified with measured report
- require test parity for polyfill vs native behavior
- ensure newly exported functions are reflected in README utility matrix
- add targeted coverage checks for newly introduced leaf utilities
- document explicit null / undefined coercion or throw behavior for new helpers

## Acceptance Criteria for this Request

- [x] Identify additions by category
- [x] Prioritize additions by value and fit
- [ ] Create follow-up issue list for proposed items
- [ ] Add ownership and target milestone per item

## Next Actions

1. Open GitHub issues for sections A-D candidates.
2. Add milestone tags for upcoming releases.
3. Keep this document focused on proposed additions only.
1. Keep this document focused on proposed additions only.
35 changes: 35 additions & 0 deletions docs/usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This guide provides practical examples for using the @nevware21/ts-utils library
- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Type Checking Functions](#type-checking-functions)
- [Type Utility Aliases](#type-utility-aliases)
- [Array Operations](#array-operations)
- [Object Manipulations](#object-manipulations)
- [String Functions](#string-functions)
Expand Down Expand Up @@ -70,6 +71,40 @@ if (isTruthy(value)) {
}
```

### Type Utility Aliases

Type-only aliases help define stronger contracts for APIs and config models:

```typescript
import type {
DeepPartial,
DeepReadonly,
DeepRequired,
Mutable,
NonEmptyArray,
ReadonlyRecord,
ValueOf
} from "@nevware21/ts-utils";

type ThemeMap = ReadonlyRecord<"light" | "dark", { primary: string }>;
type ThemeName = ValueOf<{ light: "light"; dark: "dark" }>;

type User = {
readonly id: string;
profile?: {
email?: string;
tags?: string[];
};
};

type UserPatch = DeepPartial<User>;
type FrozenUser = DeepReadonly<User>;
type NormalizedUser = DeepRequired<User>;
type WritableUser = Mutable<User>;

const batch: NonEmptyArray<User> = [{ id: "u1" }];
```

### Array Operations

Array helper functions provide polyfill support and consistent API across environments:
Expand Down
47 changes: 46 additions & 1 deletion lib/src/array/groupBy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,53 @@ import { isSymbol } from "../symbol/symbol";
import { arrForEach } from "./forEach";

/**
* Callback function type for arrGroupBy
* Callback function type used by {@link arrGroupBy} to derive a group key for each element.
* The function is called once per element and must return a `string`, `number`, or `symbol`
* that identifies which group the element belongs to. Elements that map to the same key are
* collected into the same array in the result object.
* @since 0.14.0
* @group Array
* @group ArrayLike
* @typeParam T - Identifies the base type of array elements
* @param value - The current element of the array being processed.
* @param index - The zero-based index of the current element in the array.
* @param array - The array (or array-like object) that {@link arrGroupBy} was called on.
* @returns A `string`, `number`, or `symbol` that identifies the group for the current element.
* @example
* ```ts
* // Group numbers as "even" or "odd"
* const parity: ArrGroupByCallbackFn<number> = (n) => n % 2 === 0 ? "even" : "odd";
*
* arrGroupBy([1, 2, 3, 4, 5], parity);
* // { odd: [1, 3, 5], even: [2, 4] }
* ```
* @example
* ```ts
* // Group objects by a property value
* interface Person { name: string; dept: string; }
*
* const byDept: ArrGroupByCallbackFn<Person> = (p) => p.dept;
*
* const people: Person[] = [
* { name: "Alice", dept: "eng" },
* { name: "Bob", dept: "hr" },
* { name: "Carol", dept: "eng" }
* ];
*
* arrGroupBy(people, byDept);
* // {
* // eng: [{ name: "Alice", dept: "eng" }, { name: "Carol", dept: "eng" }],
* // hr: [{ name: "Bob", dept: "hr" }]
* // }
* ```
* @example
* ```ts
* // Use the element index to create fixed-size buckets
* const bucket: ArrGroupByCallbackFn<string> = (_v, idx) => idx % 3;
*
* arrGroupBy(["a", "b", "c", "d", "e"], bucket);
* // { 0: ["a", "d"], 1: ["b", "e"], 2: ["c"] }
* ```
*/
export type ArrGroupByCallbackFn<T> = (value: T, index: number, array: ArrayLike<T>) => string | number | symbol;

Expand Down
46 changes: 41 additions & 5 deletions lib/src/funcs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,63 @@
* Licensed under the MIT license.
*/

/**
* Extracts a union of all keys of `T` whose values are functions. This is
* useful for constraining a parameter or property to only name a method that
* actually exists on a given type.
* @since 0.9.8
* @group Funcs
* @typeParam T - The object type to inspect.
* @example
* ```ts
* interface MyApi {
* fetch(url: string): Promise<Response>;
* retry(url: string): Promise<Response>;
* baseUrl: string;
* }
*
* type ApiMethods = TypeFuncNames<MyApi>;
* // => "fetch" | "retry" ("baseUrl" is excluded because it is not a function)
* ```
*/
export type TypeFuncNames<T> = {
[key in keyof T]: T[key] extends Function ? key : never
}[keyof T];

/**
* The Definition of how proxy functions should be applied to target objects
* Describes how a single function from a host object `H` should be proxied
* onto a target object `T`. Used as an element of the `funcDefs` array passed
* to {@link createProxyFuncs}.
* @since 0.9.8
* @group Funcs
* @typeParam T - The target object type that will receive the proxied function.
* @typeParam H - The host object type that owns the original function.
* @example
* ```ts
* interface Host { greet(): string; farewell(): string; }
* interface Target { hello?(): string; farewell?(): string; }
*
* const defs: ProxyFunctionDef<Target, Host>[] = [
* { n: "greet", as: "hello" }, // host.greet → target.hello
* { n: "farewell" }, // host.farewell → target.farewell
* ];
* ```
*/
export type ProxyFunctionDef<T, H> = {
/**
* Identifies the host function name
* Identifies the host function name to proxy.
*/
n: TypeFuncNames<H>,

/**
* Use this name as on the target for the proxied function, defaults to the same
* as the host function when not defined.
* Use this name on the target for the proxied function. Defaults to the
* same name as the host function when not defined.
*/
as?: TypeFuncNames<T>,

/**
* If the target already includes the function should it be replaced, defaults to false.
* When `false` (the default) an existing function on the target will
* **not** be replaced. Set to `true` to always overwrite.
*/
rp?: boolean
}
Loading
Loading