v1.13.0
What's new 🚀
Compose refinements while preserving their input domain
and, andAll, or, and oneOf now preserve the original input domain when composing refinements.
Previously, composing type predicates could widen the resulting function back to a guard accepting unknown. The returned predicate now remains a Refine<A, B>, retaining both its known input type and final narrowed output.
import { and, andAll, oneOf, or } from 'is-kit';
import type { Refine } from 'is-kit';
type AstNode =
| { kind: 'identifier'; text: string }
| { kind: 'literal'; value: string }
| { kind: 'call'; expression: AstNode };
type Identifier = Extract<AstNode, { kind: 'identifier' }>;
type Literal = Extract<AstNode, { kind: 'literal' }>;
type Call = Extract<AstNode, { kind: 'call' }>;
type IdentifierCall = Call & {
expression: Identifier;
};
type NamedIdentifierCall = IdentifierCall & {
expression: Identifier & { text: 'run' };
};
const isIdentifier = (node: AstNode): node is Identifier =>
node.kind === 'identifier';
const isLiteral = (node: AstNode): node is Literal =>
node.kind === 'literal';
const hasIdentifierExpression = (
node: Call
): node is IdentifierCall => isIdentifier(node.expression);
const hasRunExpression = (
node: IdentifierCall
): node is NamedIdentifierCall => node.expression.text === 'run';
const identifierCall = and(isCall, hasIdentifierExpression);
// Refine<AstNode, IdentifierCall>
const namedIdentifierCall = andAll(
isCall,
hasIdentifierExpression,
hasRunExpression
);
// Refine<AstNode, NamedIdentifierCall>
const identifierOrLiteral = or(isIdentifier, isLiteral);
// Refine<AstNode, Identifier | Literal>
const leafNode = oneOf(isIdentifier, isLiteral);
// Refine<AstNode, Identifier | Literal>The refinements can be reused directly with values whose domain is already known:
declare const node: AstNode;
if (namedIdentifierCall(node)) {
node.expression.text;
// 'run'
}Forward generic refinement chains to andAll
Higher-order helpers can now forward generically constrained refinements without losing their narrowing.
This includes:
- generically constrained individual refinements;
- homogeneous refinement arrays and rest tuples;
- progressive heterogeneous refinement chains;
- concrete tuples whose refinements accept a broader input domain.
import { andAll } from 'is-kit';
import type { Refine } from 'is-kit';
function composeChain<
A,
B extends A,
C extends B,
D extends C,
E extends D,
F extends Refine<A, B>,
Steps extends [
Refine<B, C>,
Refine<C, D>,
Refine<D, E>
]
>(precondition: F, steps: Steps) {
return andAll(precondition, ...steps);
// Refine<A, E>
}For a generic readonly tuple, pass the tuple directly instead of spreading it:
function composeReadonlyChain<
A,
B extends A,
C extends B,
D extends C,
E extends D,
F extends Refine<A, B>,
Steps extends readonly [
Refine<B, C>,
Refine<C, D>,
Refine<D, E>
]
>(precondition: F, steps: Steps) {
return andAll(precondition, steps);
// Refine<A, E>
}Existing spread calls remain supported:
const namedIdentifierCall = andAll(
isCall,
hasIdentifierExpression,
hasRunExpression
);Safer refinement constraints
Refinement combinators now reject ordinary boolean-returning functions where a type predicate is required.
andAll(isPositive);This prevents a boolean callback from being treated as proof of type narrowing. Invalid refinement chains whose output does not extend the preceding input are also rejected.
Improved public refinement types
Predicate<T> is now defined in terms of Refinement<unknown, T>, establishing Refinement<A, B> as the common representation for both unknown-input guards and known-domain refinements.
GuardedOf<F> can now extract the narrowed output from either form.
import type {
GuardedOf,
Predicate,
Refinement
} from 'is-kit';
type StringPredicate = Predicate<string>;
type StringRefinement = Refinement<string | number, string>;
type PredicateOutput = GuardedOf<StringPredicate>;
// string
type RefinementOutput = GuardedOf<StringRefinement>;
// string📄 Documentation improvements
The documentation is now available at is-kit.dev.
What's Changed
- docs(changelog): 1.12.1 by @github-actions[bot] in #269
- docs: add guide for keeping type guards in sync by @nyaomaru in #270
- docs: add guide for validating unknown values by @nyaomaru in #271
- docs: migrate documentation URLs to is-kit.dev by @nyaomaru in #272
- chore(docs): use PNG image for social previews by @nyaomaru in #273
- feat: support known-domain refinement composition by @nyaomaru in #274
- refactor(docs): extract shared guide components by @nyaomaru in #275
- Release: 1.13.0 by @github-actions[bot] in #276
Full Changelog: v1.12.1...v1.13.0