Skip to content

v1.14.0

Choose a tag to compare

@nyaomaru nyaomaru released this 29 Aug 07:25
· 140 commits to main since this release
Immutable release. Only release title and notes can be modified.
ec88dbf

What's new 🚀

Refine nested properties with reusable type guards

is-kit now provides three helpers for lifting an existing refinement onto an object property or array element:

  • refineKey: refine a required property
  • refineDefinedKey: refine an optional property when defined and make it required in the narrowed type
  • refineIndex: require and refine a defined own array element

They work with any compatible TypeScript type guard, including the TypeScript Compiler API, without adding a runtime or peer dependency on TypeScript.

import * as ts from 'typescript';
import {
  and,
  refineDefinedKey,
  refineIndex,
  refineKey
} from 'is-kit';

const isIdentifierCall = and(
  ts.isCallExpression,
  refineKey('expression', ts.isIdentifier)
);

const isVariableWithCallInitializer = and(
  ts.isVariableDeclaration,
  refineDefinedKey('initializer', ts.isCallExpression)
);

const isCallWithStringFirstArgument = and(
  ts.isCallExpression,
  refineKey('arguments', refineIndex(0, ts.isStringLiteral))
);

declare const node: ts.Node;

if (isIdentifierCall(node)) {
  node.expression.text;
  // node.expression is ts.Identifier
}

if (isVariableWithCallInitializer(node)) {
  node.initializer.expression;
  // node.initializer is present and is ts.CallExpression
}

if (isCallWithStringFirstArgument(node)) {
  node.arguments[0].text;
  // node.arguments[0] is present and is ts.StringLiteral
}

The helpers preserve the original parent type while narrowing only the property or element checked at runtime.

refineDefinedKey returns false for missing or undefined properties without passing those values to the supplied refinement. refineIndex does the same for out-of-bounds indices, sparse holes, inherited indexed values, and explicitly undefined elements.

For these property-refinement helpers, keys must identify one concrete property, and indices must be non-negative integer literals. Broad keys, unions, template-literal patterns, and branded multi-value key domains are rejected at compile time so one runtime lookup cannot incorrectly narrow multiple locations.

Safer narrowing for dynamic keys

hasKey, hasKeys, equalsKey, and narrowKeyTo now provide key-specific narrowing only when the key identifies one concrete property.

Union and broad keys remain usable as runtime checks, but safely fall back to object-only narrowing—or preserve the base guard type for narrowKeyTo because checking one runtime-selected key cannot prove that every possible key was checked.

The published declarations are tested with TypeScript 5.7 through 7.0.

What's Changed

  • docs(changelog): 1.13.0 by @github-actions[bot] in #277
  • fix(ci): use OIDC for npm publishing by @nyaomaru in #278
  • docs: add guide thumbnail by @nyaomaru in #279
  • feat: add property refinement helpers by @nyaomaru in #280
  • docs: document property refinements and lazy by @nyaomaru in #281
  • test: add TypeScript compatibility matrix by @nyaomaru in #282
  • fix: require own elements in refineIndex by @nyaomaru in #283
  • fix: prevent key helpers from over-narrowing union key by @nyaomaru in #284
  • refactor: separate key guards from property refinements by @nyaomaru in #285
  • fix(docs): use guide images in social metadata by @nyaomaru in #286
  • Release: 1.14.0 by @github-actions[bot] in #287

Full Changelog: v1.13.0...v1.14.0