refactor(stdlib): replace source any with unknown/generics

Type guards take `unknown`; walkers/comparators (get/set/isEqual) narrow via
casts; generic defaults and constraints tightened. Truly-idiomatic any-function
constraints and load-bearing type-level any are kept with explanatory comments.
This commit is contained in:
2026-06-15 16:54:50 +07:00
parent d6c6a62557
commit 425a7bc6e7
21 changed files with 124 additions and 107 deletions
+21 -21
View File
@@ -30,7 +30,7 @@ if (error) {
<!-- Hero --> <!-- Hero -->
<div class="prose-docs"> <div class="prose-docs">
<h1>@robonen/stdlib</h1> <h1>@robonen/stdlib</h1>
<p class="text-lg text-(--fg-muted)"> <p class="text-lg text-fg-muted">
A platform-independent standard library of tools, utilities, and helpers for TypeScript A platform-independent standard library of tools, utilities, and helpers for TypeScript
arrays, async, math, data structures, and patterns, all tree-shakeable and fully typed. arrays, async, math, data structures, and patterns, all tree-shakeable and fully typed.
</p> </p>
@@ -48,30 +48,30 @@ if (error) {
<!-- Feature highlights --> <!-- Feature highlights -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="rounded-lg border border-(--border) bg-(--bg-subtle) p-5"> <div class="rounded-lg border border-border bg-bg-subtle p-5">
<h3 class="text-sm font-semibold text-(--fg) mb-1.5">Fully typed</h3> <h3 class="text-sm font-semibold text-fg mb-1.5">Fully typed</h3>
<p class="text-sm text-(--fg-muted) leading-relaxed"> <p class="text-sm text-fg-muted leading-relaxed">
Generic signatures preserve element and key types end to end, so inference keeps Generic signatures preserve element and key types end to end, so inference keeps
working through <code>groupBy</code>, <code>partition</code>, <code>zip</code>, and friends. working through <code>groupBy</code>, <code>partition</code>, <code>zip</code>, and friends.
</p> </p>
</div> </div>
<div class="rounded-lg border border-(--border) bg-(--bg-subtle) p-5"> <div class="rounded-lg border border-border bg-bg-subtle p-5">
<h3 class="text-sm font-semibold text-(--fg) mb-1.5">Zero dependencies</h3> <h3 class="text-sm font-semibold text-fg mb-1.5">Zero dependencies</h3>
<p class="text-sm text-(--fg-muted) leading-relaxed"> <p class="text-sm text-fg-muted leading-relaxed">
No transitive dependencies and no platform assumptions. The same code runs in Node, No transitive dependencies and no platform assumptions. The same code runs in Node,
the browser, Deno, Bun, and edge runtimes. the browser, Deno, Bun, and edge runtimes.
</p> </p>
</div> </div>
<div class="rounded-lg border border-(--border) bg-(--bg-subtle) p-5"> <div class="rounded-lg border border-border bg-bg-subtle p-5">
<h3 class="text-sm font-semibold text-(--fg) mb-1.5">Tree-shakeable</h3> <h3 class="text-sm font-semibold text-fg mb-1.5">Tree-shakeable</h3>
<p class="text-sm text-(--fg-muted) leading-relaxed"> <p class="text-sm text-fg-muted leading-relaxed">
Each utility is a standalone export with no shared side effects. Import a single Each utility is a standalone export with no shared side effects. Import a single
function and ship only that function. function and ship only that function.
</p> </p>
</div> </div>
<div class="rounded-lg border border-(--border) bg-(--bg-subtle) p-5"> <div class="rounded-lg border border-border bg-bg-subtle p-5">
<h3 class="text-sm font-semibold text-(--fg) mb-1.5">Batteries included</h3> <h3 class="text-sm font-semibold text-fg mb-1.5">Batteries included</h3>
<p class="text-sm text-(--fg-muted) leading-relaxed"> <p class="text-sm text-fg-muted leading-relaxed">
Beyond array and math helpers you get data structures Beyond array and math helpers you get data structures
(<code>Deque</code>, <code>BinaryHeap</code>, <code>LinkedList</code>) and patterns (<code>Deque</code>, <code>BinaryHeap</code>, <code>LinkedList</code>) and patterns
(<code>StateMachine</code>, <code>PubSub</code>, <code>Command</code>). (<code>StateMachine</code>, <code>PubSub</code>, <code>Command</code>).
@@ -126,16 +126,16 @@ if (error) {
</div> </div>
<!-- Where to next --> <!-- Where to next -->
<div class="rounded-lg border border-(--border) bg-(--bg-elevated) p-5"> <div class="rounded-lg border border-border bg-bg-elevated p-5">
<h2 class="text-base font-semibold text-(--fg) mb-2">Where to next</h2> <h2 class="text-base font-semibold text-fg mb-2">Where to next</h2>
<p class="text-sm text-(--fg-muted) mb-3"> <p class="text-sm text-fg-muted mb-3">
Browse the full API reference below, or jump straight to a popular utility: Browse the full API reference below, or jump straight to a popular utility:
</p> </p>
<ul class="flex flex-wrap gap-2 m-0 p-0 list-none"> <ul class="flex flex-wrap gap-2 m-0 p-0 list-none">
<li> <li>
<NuxtLink <NuxtLink
to="/stdlib/group-by" to="/stdlib/group-by"
class="inline-flex items-center rounded-md border border-(--border) bg-(--bg-subtle) px-3 py-1.5 text-sm text-(--accent-text) hover:bg-(--bg-inset) focus:ring-(--ring)" class="inline-flex items-center rounded-md border border-border bg-bg-subtle px-3 py-1.5 text-sm text-accent-text hover:bg-bg-inset focus:ring-ring"
> >
groupBy groupBy
</NuxtLink> </NuxtLink>
@@ -143,7 +143,7 @@ if (error) {
<li> <li>
<NuxtLink <NuxtLink
to="/stdlib/try-it" to="/stdlib/try-it"
class="inline-flex items-center rounded-md border border-(--border) bg-(--bg-subtle) px-3 py-1.5 text-sm text-(--accent-text) hover:bg-(--bg-inset) focus:ring-(--ring)" class="inline-flex items-center rounded-md border border-border bg-bg-subtle px-3 py-1.5 text-sm text-accent-text hover:bg-bg-inset focus:ring-ring"
> >
tryIt tryIt
</NuxtLink> </NuxtLink>
@@ -151,7 +151,7 @@ if (error) {
<li> <li>
<NuxtLink <NuxtLink
to="/stdlib/retry" to="/stdlib/retry"
class="inline-flex items-center rounded-md border border-(--border) bg-(--bg-subtle) px-3 py-1.5 text-sm text-(--accent-text) hover:bg-(--bg-inset) focus:ring-(--ring)" class="inline-flex items-center rounded-md border border-border bg-bg-subtle px-3 py-1.5 text-sm text-accent-text hover:bg-bg-inset focus:ring-ring"
> >
retry retry
</NuxtLink> </NuxtLink>
@@ -159,7 +159,7 @@ if (error) {
<li> <li>
<NuxtLink <NuxtLink
to="/stdlib/clamp" to="/stdlib/clamp"
class="inline-flex items-center rounded-md border border-(--border) bg-(--bg-subtle) px-3 py-1.5 text-sm text-(--accent-text) hover:bg-(--bg-inset) focus:ring-(--ring)" class="inline-flex items-center rounded-md border border-border bg-bg-subtle px-3 py-1.5 text-sm text-accent-text hover:bg-bg-inset focus:ring-ring"
> >
clamp clamp
</NuxtLink> </NuxtLink>
@@ -167,7 +167,7 @@ if (error) {
<li> <li>
<NuxtLink <NuxtLink
to="/stdlib/debounce" to="/stdlib/debounce"
class="inline-flex items-center rounded-md border border-(--border) bg-(--bg-subtle) px-3 py-1.5 text-sm text-(--accent-text) hover:bg-(--bg-inset) focus:ring-(--ring)" class="inline-flex items-center rounded-md border border-border bg-bg-subtle px-3 py-1.5 text-sm text-accent-text hover:bg-bg-inset focus:ring-ring"
> >
debounce debounce
</NuxtLink> </NuxtLink>
+2 -2
View File
@@ -1,3 +1,3 @@
import { base, compose, imports, stylistic, typescript } from '@robonen/eslint'; import { base, compose, imports, stylistic, tests, typescript } from '@robonen/eslint';
export default compose(base, typescript, imports, stylistic); export default compose(base, typescript, imports, stylistic, tests);
+1 -1
View File
@@ -22,7 +22,7 @@ export function zip<A, B>(a: A[], b: B[]): Array<[A, B]>;
export function zip<A, B, C>(a: A[], b: B[], c: C[]): Array<[A, B, C]>; export function zip<A, B, C>(a: A[], b: B[], c: C[]): Array<[A, B, C]>;
export function zip<A, B, C, D>(a: A[], b: B[], c: C[], d: D[]): Array<[A, B, C, D]>; export function zip<A, B, C, D>(a: A[], b: B[], c: C[], d: D[]): Array<[A, B, C, D]>;
export function zip<A, B, C, D, E>(a: A[], b: B[], c: C[], d: D[], e: E[]): Array<[A, B, C, D, E]>; export function zip<A, B, C, D, E>(a: A[], b: B[], c: C[], d: D[], e: E[]): Array<[A, B, C, D, E]>;
export function zip(...arrays: any[][]): any[][] { export function zip(...arrays: unknown[][]): unknown[][] {
if (arrays.length === 0) if (arrays.length === 0)
return []; return [];
+4 -2
View File
@@ -6,7 +6,7 @@ export interface AsyncPoolOptions {
signal?: AbortSignal; signal?: AbortSignal;
} }
interface PoolEntry<T = any> { interface PoolEntry<T = unknown> {
task: (signal: AbortSignal) => Promise<T>; task: (signal: AbortSignal) => Promise<T>;
resolve: (value: T) => void; resolve: (value: T) => void;
reject: (reason: unknown) => void; reject: (reason: unknown) => void;
@@ -115,7 +115,9 @@ export class AsyncPool {
reject(this.signal.reason); reject(this.signal.reason);
return promise; return promise;
} }
const entry = { task, resolve, reject } as PoolEntry<T>; // Stored in a homogeneous queue: the per-call T is erased to PoolEntry<unknown>.
// run() resolves/rejects with unknown values, so this is sound at runtime.
const entry = { task, resolve, reject } as unknown as PoolEntry;
if (this.activeCount < this.limit) { if (this.activeCount < this.limit) {
this.run(entry); this.run(entry);
} }
+4 -4
View File
@@ -11,13 +11,13 @@ export interface RetryOptions {
export type RetryFunction<Return> = ( export type RetryFunction<Return> = (
args: { args: {
count: number; count: number;
stop: (error: any) => void; stop: (error: unknown) => void;
}, },
) => Promise<Return>; ) => Promise<Return>;
class RetryEarlyExitError { class RetryEarlyExitError {
cause: any; cause: unknown;
constructor(cause: any) { constructor(cause: unknown) {
this.cause = cause; this.cause = cause;
} }
} }
@@ -62,7 +62,7 @@ export async function retry<Return>(
const delayFn = isFunction(delay) ? delay : null; const delayFn = isFunction(delay) ? delay : null;
const delayMs = delayFn ? 0 : delay as number; const delayMs = delayFn ? 0 : delay as number;
const stop = (error?: any): never => { const stop = (error?: unknown): never => {
throw new RetryEarlyExitError(error); throw new RetryEarlyExitError(error);
}; };
+4 -4
View File
@@ -1,4 +1,4 @@
export type TryItReturn<Return> = Return extends PromiseLike<any> export type TryItReturn<Return> = Return extends PromiseLike<unknown>
? Promise<{ error: Error; data: undefined } | { error: undefined; data: Awaited<Return> }> ? Promise<{ error: Error; data: undefined } | { error: undefined; data: Awaited<Return> }>
: { error: Error; data: undefined } | { error: undefined; data: Return }; : { error: Error; data: undefined } | { error: undefined; data: Return };
@@ -7,11 +7,11 @@ function isThenable(value: unknown): value is PromiseLike<unknown> {
&& typeof (value as PromiseLike<unknown>).then === 'function'; && typeof (value as PromiseLike<unknown>).then === 'function';
} }
function onResolve(data: any) { function onResolve(data: unknown) {
return { error: undefined, data }; return { error: undefined, data };
} }
function onReject(error: any) { function onReject(error: unknown) {
return { error, data: undefined }; return { error, data: undefined };
} }
@@ -32,7 +32,7 @@ function onReject(error: any) {
* *
* @since 0.0.3 * @since 0.0.3
*/ */
export function tryIt<Args extends any[], Return>( export function tryIt<Args extends unknown[], Return>(
fn: (...args: Args) => Return, fn: (...args: Args) => Return,
) { ) {
return (...args: Args): TryItReturn<Return> => { return (...args: Args): TryItReturn<Return> => {
+8 -5
View File
@@ -7,7 +7,10 @@ export type ExtractFromObject<O extends Record<PropertyKey, unknown>, K>
? NonNullable<O>[K] ? NonNullable<O>[K]
: never; : never;
export type ExtractFromArray<A extends readonly any[], K> export type ExtractFromArray<A extends readonly unknown[], K>
// `any[]` (not `unknown[]`) is load-bearing: `any[] extends number[]` is true while
// `unknown[] extends number[]` is false. This distinguishes a general array (widen the
// element with `undefined`) from a fixed tuple (resolve the exact element at index K).
= any[] extends A = any[] extends A
? A extends ReadonlyArray<infer T> ? A extends ReadonlyArray<infer T>
? T | undefined ? T | undefined
@@ -22,7 +25,7 @@ export type ExtractFromCollection<O, K>
: K extends [infer Key, ...infer Rest] : K extends [infer Key, ...infer Rest]
? O extends Record<PropertyKey, unknown> ? O extends Record<PropertyKey, unknown>
? ExtractFromCollection<ExtractFromObject<O, Key>, Rest> ? ExtractFromCollection<ExtractFromObject<O, Key>, Rest>
: O extends readonly any[] : O extends readonly unknown[]
? ExtractFromCollection<ExtractFromArray<O, Key>, Rest> ? ExtractFromCollection<ExtractFromArray<O, Key>, Rest>
: never : never
: never; : never;
@@ -46,7 +49,7 @@ export type Get<O, K> = ExtractFromCollection<O, Path<K>>;
* @since 0.0.4 * @since 0.0.4
*/ */
export function get<O extends Collection, K extends string>(obj: O, path: K): Get<O, K> | undefined { export function get<O extends Collection, K extends string>(obj: O, path: K): Get<O, K> | undefined {
let value: any = obj; let value: unknown = obj;
let start = 0; let start = 0;
// Walk the path without allocating an intermediate array of segments. // Walk the path without allocating an intermediate array of segments.
@@ -58,9 +61,9 @@ export function get<O extends Collection, K extends string>(obj: O, path: K): Ge
if (value === null || value === undefined) if (value === null || value === undefined)
return undefined; return undefined;
value = value[path.slice(start, i)]; value = (value as Record<string, unknown>)[path.slice(start, i)];
start = i + 1; start = i + 1;
} }
return value; return value as Get<O, K> | undefined;
} }
+2 -2
View File
@@ -27,7 +27,7 @@ export function set<O extends Collection>(obj: O, path: string, value: unknown):
const keys = path.split('.'); const keys = path.split('.');
const lastKey = keys[keys.length - 1]!; const lastKey = keys[keys.length - 1]!;
let current: any = obj; let current = obj as Record<PropertyKey, unknown>;
for (let i = 0; i < keys.length - 1; i++) { for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i]!; const key = keys[i]!;
@@ -38,7 +38,7 @@ export function set<O extends Collection>(obj: O, path: string, value: unknown):
if (next === null || typeof next !== 'object') if (next === null || typeof next !== 'object')
current[key] = NUMERIC_SEGMENT.test(keys[i + 1]!) ? [] : {}; current[key] = NUMERIC_SEGMENT.test(keys[i + 1]!) ? [] : {};
current = current[key]; current = current[key] as Record<PropertyKey, unknown>;
} }
current[lastKey] = value; current[lastKey] = value;
+2 -2
View File
@@ -1,4 +1,4 @@
function equals(a: any, b: any, seen: WeakMap<object, unknown>): boolean { function equals(a: unknown, b: unknown, seen: WeakMap<object, unknown>): boolean {
if (a === b) if (a === b)
return true; return true;
@@ -65,7 +65,7 @@ function equals(a: any, b: any, seen: WeakMap<object, unknown>): boolean {
return false; return false;
for (const key of aKeys) { for (const key of aKeys) {
if (!Object.prototype.hasOwnProperty.call(b, key) || !equals(a[key], b[key], seen)) if (!Object.prototype.hasOwnProperty.call(b, key) || !equals((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key], seen))
return false; return false;
} }
+8 -8
View File
@@ -14,15 +14,15 @@ import type { AnyFunction } from '../../types';
* *
* @since 0.0.10 * @since 0.0.10
*/ */
export function compose<A extends any[], B>(ab: (...a: A) => B): (...a: A) => B; export function compose<A extends unknown[], B>(ab: (...a: A) => B): (...a: A) => B;
export function compose<A extends any[], B, C>(bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => C; export function compose<A extends unknown[], B, C>(bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => C;
export function compose<A extends any[], B, C, D>(cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => D; export function compose<A extends unknown[], B, C, D>(cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => D;
export function compose<A extends any[], B, C, D, E>(de: (d: D) => E, cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => E; export function compose<A extends unknown[], B, C, D, E>(de: (d: D) => E, cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => E;
export function compose<A extends any[], B, C, D, E, F>(ef: (e: E) => F, de: (d: D) => E, cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => F; export function compose<A extends unknown[], B, C, D, E, F>(ef: (e: E) => F, de: (d: D) => E, cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => F;
export function compose<A extends any[], B, C, D, E, F, G>(fg: (f: F) => G, ef: (e: E) => F, de: (d: D) => E, cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => G; export function compose<A extends unknown[], B, C, D, E, F, G>(fg: (f: F) => G, ef: (e: E) => F, de: (d: D) => E, cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => G;
export function compose<A extends any[], B, C, D, E, F, G, H>(gh: (g: G) => H, fg: (f: F) => G, ef: (e: E) => F, de: (d: D) => E, cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => H; export function compose<A extends unknown[], B, C, D, E, F, G, H>(gh: (g: G) => H, fg: (f: F) => G, ef: (e: E) => F, de: (d: D) => E, cd: (c: C) => D, bc: (b: B) => C, ab: (...a: A) => B): (...a: A) => H;
export function compose(...fns: AnyFunction[]): AnyFunction { export function compose(...fns: AnyFunction[]): AnyFunction {
return function (this: unknown, ...args: any[]) { return function (this: unknown, ...args: unknown[]) {
const last = fns.length - 1; const last = fns.length - 1;
if (last < 0) if (last < 0)
+8 -8
View File
@@ -14,15 +14,15 @@ import type { AnyFunction } from '../../types';
* *
* @since 0.0.10 * @since 0.0.10
*/ */
export function pipe<A extends any[], B>(ab: (...a: A) => B): (...a: A) => B; export function pipe<A extends unknown[], B>(ab: (...a: A) => B): (...a: A) => B;
export function pipe<A extends any[], B, C>(ab: (...a: A) => B, bc: (b: B) => C): (...a: A) => C; export function pipe<A extends unknown[], B, C>(ab: (...a: A) => B, bc: (b: B) => C): (...a: A) => C;
export function pipe<A extends any[], B, C, D>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D): (...a: A) => D; export function pipe<A extends unknown[], B, C, D>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D): (...a: A) => D;
export function pipe<A extends any[], B, C, D, E>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E): (...a: A) => E; export function pipe<A extends unknown[], B, C, D, E>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E): (...a: A) => E;
export function pipe<A extends any[], B, C, D, E, F>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F): (...a: A) => F; export function pipe<A extends unknown[], B, C, D, E, F>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F): (...a: A) => F;
export function pipe<A extends any[], B, C, D, E, F, G>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G): (...a: A) => G; export function pipe<A extends unknown[], B, C, D, E, F, G>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G): (...a: A) => G;
export function pipe<A extends any[], B, C, D, E, F, G, H>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G, gh: (g: G) => H): (...a: A) => H; export function pipe<A extends unknown[], B, C, D, E, F, G, H>(ab: (...a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G, gh: (g: G) => H): (...a: A) => H;
export function pipe(...fns: AnyFunction[]): AnyFunction { export function pipe(...fns: AnyFunction[]): AnyFunction {
return function (this: unknown, ...args: any[]) { return function (this: unknown, ...args: unknown[]) {
if (fns.length === 0) if (fns.length === 0)
return args[0]; return args[0];
@@ -124,6 +124,9 @@ export function createAsyncMachine<
export function createAsyncMachine(config: { export function createAsyncMachine(config: {
initial: string; initial: string;
context?: unknown; context?: unknown;
// Overload-implementation signature: the typed overloads above expose the real
// per-context API; `any` here accepts every concrete `AsyncStateNodeConfig<C>`
// (contravariant in `C`, so `unknown` would reject them).
states: Record<string, AsyncStateNodeConfig<any>>; states: Record<string, AsyncStateNodeConfig<any>>;
}): AsyncStateMachine { }): AsyncStateMachine {
return new AsyncStateMachine( return new AsyncStateMachine(
@@ -125,6 +125,9 @@ export function createMachine<
export function createMachine(config: { export function createMachine(config: {
initial: string; initial: string;
context?: unknown; context?: unknown;
// Overload-implementation signature: the typed overloads above expose the real
// per-context API; `any` here accepts every concrete `SyncStateNodeConfig<C>`
// (contravariant in `C`, so `unknown` would reject them).
states: Record<string, SyncStateNodeConfig<any>>; states: Record<string, SyncStateNodeConfig<any>>;
}): StateMachine { }): StateMachine {
return new StateMachine( return new StateMachine(
@@ -58,7 +58,7 @@ export type AsyncStateNodeConfig<Context> = StateNodeConfig<Context, MaybePromis
export type ExtractStates<T> = keyof T & string; export type ExtractStates<T> = keyof T & string;
export type ExtractEvents<T> = { export type ExtractEvents<T> = {
[K in keyof T]: T[K] extends { readonly on?: Readonly<Record<infer E extends string, any>> } [K in keyof T]: T[K] extends { readonly on?: Readonly<Record<infer E extends string, unknown>> }
? E ? E
: never; : never;
}[keyof T]; }[keyof T];
+3 -2
View File
@@ -14,7 +14,7 @@ export interface BinaryHeapOptions<T> {
* @param {number} b Second element * @param {number} b Second element
* @returns {number} Negative if a < b, positive if a > b, zero if equal * @returns {number} Negative if a < b, positive if a > b, zero if equal
*/ */
const defaultComparator: Comparator<any> = (a: number, b: number) => a - b; const defaultComparator: Comparator<number> = (a: number, b: number) => a - b;
/** /**
* @name BinaryHeap * @name BinaryHeap
@@ -49,7 +49,8 @@ export class BinaryHeap<T> implements BinaryHeapLike<T> {
* @param {BinaryHeapOptions<T>} [options] Heap configuration * @param {BinaryHeapOptions<T>} [options] Heap configuration
*/ */
constructor(initialValues?: T[] | T, options?: BinaryHeapOptions<T>) { constructor(initialValues?: T[] | T, options?: BinaryHeapOptions<T>) {
this.comparator = options?.comparator ?? defaultComparator; // Numeric default; cast bridges it to the caller's `T` when no comparator is given.
this.comparator = options?.comparator ?? (defaultComparator as Comparator<T>);
if (initialValues !== null && initialValues !== undefined) { if (initialValues !== null && initialValues !== undefined) {
const items = isArray(initialValues) ? initialValues : [initialValues]; const items = isArray(initialValues) ? initialValues : [initialValues];
+2 -2
View File
@@ -3,9 +3,9 @@
* @category Types * @category Types
* @description To string any value * @description To string any value
* *
* @param {any} value * @param {unknown} value
* @returns {string} * @returns {string}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const toString = (value: any): string => Object.prototype.toString.call(value); export const toString = (value: unknown): string => Object.prototype.toString.call(value);
-1
View File
@@ -38,7 +38,6 @@ describe('complex', () => {
}); });
it('true for class instances and null-prototype objects', () => { it('true for class instances and null-prototype objects', () => {
// eslint-disable-next-line @typescript-eslint/no-extraneous-class -- fixture for the instance check
class Foo {} class Foo {}
expect(isObject(new Foo())).toBe(true); expect(isObject(new Foo())).toBe(true);
+25 -25
View File
@@ -5,117 +5,117 @@ import { toString } from './casts';
* @category Types * @category Types
* @description Check if a value is an array * @description Check if a value is an array
* *
* @param {any} value * @param {unknown} value
* @returns {value is any[]} * @returns {value is T[]}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isArray = (value: any): value is any[] => Array.isArray(value); export const isArray = <T = unknown>(value: unknown): value is T[] => Array.isArray(value);
/** /**
* @name isObject * @name isObject
* @category Types * @category Types
* @description Check if a value is an object * @description Check if a value is an object
* *
* @param {any} value * @param {unknown} value
* @returns {value is object} * @returns {value is object}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isObject = (value: any): value is object => toString(value) === '[object Object]'; export const isObject = (value: unknown): value is object => toString(value) === '[object Object]';
/** /**
* @name isRegExp * @name isRegExp
* @category Types * @category Types
* @description Check if a value is a regexp * @description Check if a value is a regexp
* *
* @param {any} value * @param {unknown} value
* @returns {value is RegExp} * @returns {value is RegExp}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isRegExp = (value: any): value is RegExp => toString(value) === '[object RegExp]'; export const isRegExp = (value: unknown): value is RegExp => toString(value) === '[object RegExp]';
/** /**
* @name isDate * @name isDate
* @category Types * @category Types
* @description Check if a value is a date * @description Check if a value is a date
* *
* @param {any} value * @param {unknown} value
* @returns {value is Date} * @returns {value is Date}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isDate = (value: any): value is Date => toString(value) === '[object Date]'; export const isDate = (value: unknown): value is Date => toString(value) === '[object Date]';
/** /**
* @name isError * @name isError
* @category Types * @category Types
* @description Check if a value is an error * @description Check if a value is an error
* *
* @param {any} value * @param {unknown} value
* @returns {value is Error} * @returns {value is Error}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isError = (value: any): value is Error => toString(value) === '[object Error]'; export const isError = (value: unknown): value is Error => toString(value) === '[object Error]';
/** /**
* @name isPromise * @name isPromise
* @category Types * @category Types
* @description Check if a value is a promise * @description Check if a value is a promise
* *
* @param {any} value * @param {unknown} value
* @returns {value is Promise<any>} * @returns {value is Promise<unknown>}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isPromise = (value: any): value is Promise<any> => toString(value) === '[object Promise]'; export const isPromise = (value: unknown): value is Promise<unknown> => toString(value) === '[object Promise]';
/** /**
* @name isMap * @name isMap
* @category Types * @category Types
* @description Check if a value is a map * @description Check if a value is a map
* *
* @param {any} value * @param {unknown} value
* @returns {value is Map<any, any>} * @returns {value is Map<unknown, unknown>}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isMap = (value: any): value is Map<any, any> => toString(value) === '[object Map]'; export const isMap = (value: unknown): value is Map<unknown, unknown> => toString(value) === '[object Map]';
/** /**
* @name isSet * @name isSet
* @category Types * @category Types
* @description Check if a value is a set * @description Check if a value is a set
* *
* @param {any} value * @param {unknown} value
* @returns {value is Set<any>} * @returns {value is Set<unknown>}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isSet = (value: any): value is Set<any> => toString(value) === '[object Set]'; export const isSet = (value: unknown): value is Set<unknown> => toString(value) === '[object Set]';
/** /**
* @name isWeakMap * @name isWeakMap
* @category Types * @category Types
* @description Check if a value is a weakmap * @description Check if a value is a weakmap
* *
* @param {any} value * @param {unknown} value
* @returns {value is WeakMap<object, any>} * @returns {value is WeakMap<object, unknown>}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isWeakMap = (value: any): value is WeakMap<object, any> => toString(value) === '[object WeakMap]'; export const isWeakMap = (value: unknown): value is WeakMap<object, unknown> => toString(value) === '[object WeakMap]';
/** /**
* @name isWeakSet * @name isWeakSet
* @category Types * @category Types
* @description Check if a value is a weakset * @description Check if a value is a weakset
* *
* @param {any} value * @param {unknown} value
* @returns {value is WeakSet<object>} * @returns {value is WeakSet<object>}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isWeakSet = (value: any): value is WeakSet<object> => toString(value) === '[object WeakSet]'; export const isWeakSet = (value: unknown): value is WeakSet<object> => toString(value) === '[object WeakSet]';
+17 -16
View File
@@ -3,93 +3,94 @@
* @category Types * @category Types
* @description Check if a value is a boolean * @description Check if a value is a boolean
* *
* @param {any} value * @param {unknown} value
* @returns {value is boolean} * @returns {value is boolean}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isBoolean = (value: any): value is boolean => typeof value === 'boolean'; export const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean';
/** /**
* @name isFunction * @name isFunction
* @category Types * @category Types
* @description Check if a value is a function * @description Check if a value is a function
* *
* @param {any} value * @param {unknown} value
* @returns {value is Function} * @returns {value is Function}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isFunction = <T extends (...args: any[]) => any>(value: any): value is T => typeof value === 'function'; // `(...args: any[]) => any` is the idiomatic "any function" constraint here; `unknown` would reject legitimate function shapes at call sites.
export const isFunction = <T extends (...args: any[]) => any>(value: unknown): value is T => typeof value === 'function';
/** /**
* @name isNumber * @name isNumber
* @category Types * @category Types
* @description Check if a value is a number * @description Check if a value is a number
* *
* @param {any} value * @param {unknown} value
* @returns {value is number} * @returns {value is number}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isNumber = (value: any): value is number => typeof value === 'number'; export const isNumber = (value: unknown): value is number => typeof value === 'number';
/** /**
* @name isBigInt * @name isBigInt
* @category Types * @category Types
* @description Check if a value is a bigint * @description Check if a value is a bigint
* *
* @param {any} value * @param {unknown} value
* @returns {value is bigint} * @returns {value is bigint}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isBigInt = (value: any): value is bigint => typeof value === 'bigint'; export const isBigInt = (value: unknown): value is bigint => typeof value === 'bigint';
/** /**
* @name isString * @name isString
* @category Types * @category Types
* @description Check if a value is a string * @description Check if a value is a string
* *
* @param {any} value * @param {unknown} value
* @returns {value is string} * @returns {value is string}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isString = (value: any): value is string => typeof value === 'string'; export const isString = (value: unknown): value is string => typeof value === 'string';
/** /**
* @name isSymbol * @name isSymbol
* @category Types * @category Types
* @description Check if a value is a symbol * @description Check if a value is a symbol
* *
* @param {any} value * @param {unknown} value
* @returns {value is symbol} * @returns {value is symbol}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isSymbol = (value: any): value is symbol => typeof value === 'symbol'; export const isSymbol = (value: unknown): value is symbol => typeof value === 'symbol';
/** /**
* @name isUndefined * @name isUndefined
* @category Types * @category Types
* @description Check if a value is a undefined * @description Check if a value is a undefined
* *
* @param {any} value * @param {unknown} value
* @returns {value is undefined} * @returns {value is undefined}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isUndefined = (value: any): value is undefined => value === undefined; export const isUndefined = (value: unknown): value is undefined => value === undefined;
/** /**
* @name isNull * @name isNull
* @category Types * @category Types
* @description Check if a value is a null * @description Check if a value is a null
* *
* @param {any} value * @param {unknown} value
* @returns {value is null} * @returns {value is null}
* *
* @since 0.0.2 * @since 0.0.2
*/ */
export const isNull = (value: any): value is null => value === null; export const isNull = (value: unknown): value is null => value === null;
+4 -1
View File
@@ -1,7 +1,10 @@
/** /**
* A collection definition * A collection definition
*/ */
export type Collection = Record<PropertyKey, any> | any[]; // `any[]` is kept (not `unknown[]`): as the `O extends Collection` constraint in `get`/`set`,
// `unknown[]` would reject arrays whose elements sit in contravariant positions (e.g. the
// `CircularBuffer<PoolEntry>` used by `async/pool`), breaking compilation outside this file.
export type Collection = Record<PropertyKey, unknown> | any[];
/** /**
* Parse a collection path string into an array of keys * Parse a collection path string into an array of keys
+2
View File
@@ -1,6 +1,8 @@
/** /**
* Any function * Any function
*/ */
// `(...args: any[]) => any` is the idiomatic "any function" constraint; `unknown`
// would reject legitimate function shapes when used as `T extends AnyFunction`.
export type AnyFunction = (...args: any[]) => any; export type AnyFunction = (...args: any[]) => any;
/** /**