Loading docs…
Loading docs…
Helpers for key presence, literal narrowing, and lifting child refinements onto existing parent types. Use hasKey/hasKeys to check required own properties, narrowKeyTo for literal values, and the refine* helpers for known properties and array elements.
Narrow an unknown value to an object that owns a specific key. Handy before custom refinements or discriminated-union checks. A singleton literal key narrows that property; a multi-value key domain narrows only to object.
import { hasKey } from 'is-kit';const hasKind = hasKey('kind');declare const input: unknown;if (hasKind(input)) {// input: Record<'kind', unknown>input.kind;}
Narrow an unknown value to an object that owns all specified keys. Useful as a compact pre-check before discriminated-union refinements. Key-specific narrowing requires every argument to be a singleton literal key.
import { hasKeys } from 'is-kit';const hasKindAndId = hasKeys('kind', 'id');declare const input: unknown;if (hasKindAndId(input)) {// input: Record<'kind' | 'id', unknown>input.kind;input.id;}
Build reusable guards that narrow a property to specific literal values. A union or broad key remains callable as a runtime check but preserves only the base guard type.
import { narrowKeyTo, or, struct, isString, isNumber, oneOfValues } from 'is-kit';type User = { id: string; age: number; role: 'admin' | 'guest' | 'trial' };const isUser = struct({id: isString,age: isNumber,role: oneOfValues('admin', 'guest', 'trial'),});// Build role-specific guards that also narrow the 'role' field to literalsconst byRole = narrowKeyTo(isUser, 'role');const isAdmin = byRole('admin'); // Readonly<User> & { role: 'admin' }const isGuest = byRole('guest'); // Readonly<User> & { role: 'guest' }const isTrial = byRole('trial'); // Readonly<User> & { role: 'trial' }// Compose as usualconst isGuestOrTrial = or(isGuest, isTrial);declare const input: unknown;if (isGuestOrTrial(input)) {// input.role is narrowed to 'guest' | 'trial'}
Apply a refinement to one required property and preserve the narrowed property on its existing parent type. The property is read once and passed to the refinement once. Normal property access allows inherited values and accessors.
import { isString, refineKey } from 'is-kit';const hasStringValue = refineKey('value', isString);declare let item: {readonly value: string | number;readonly id: number;};if (hasStringValue(item)) {item.value.toUpperCase(); // value: stringitem.id.toFixed(); // unrelated properties are preserved}
Require and refine one optional property. A missing property or an undefined value returns false without invoking the supplied refinement. A defined inherited value is accepted through normal property access.
import { isString, refineDefinedKey } from 'is-kit';const hasDefinedStringLabel = refineDefinedKey('label', isString);declare let item: {readonly label?: string | number | undefined;};if (hasDefinedStringLabel(item)) {item.label.toUpperCase(); // label is present and string}
Require and refine one own element of a readonly array. Out-of-bounds, sparse, inherited, and undefined elements return false before the supplied refinement runs.
import { isString, refineIndex } from 'is-kit';const hasStringAtZero = refineIndex(0, isString);declare const values: readonly (string | number)[];if (hasStringAtZero(values)) {values[0].toUpperCase(); // values[0]: string}
Precise property narrowing requires each key argument to identify one concrete runtime location. The presence and equality helpers keep broad keys, unions, template-literal patterns, and branded multi-value domains usable, but safely omit key-specific narrowing.
The refine* helpers reject those multi-value domains because their known-input contract must carry one checked child value back onto one concrete location of the parent type.
For reusable required, optional, indexed, nested, and literal property examples, continue with Refine properties on existing TypeScript types. The TypeScript Compiler API guideuses the same generic pattern as an advanced example.