Changelog
[3.0.0] 2026-09-30 Changed (3) · Breaking (types only) (1) · Added (2) · Docs (1)
The runtime is byte-for-byte the same: no predicate returns a different value for any input. This is a major release because the types are now stricter, and stricter narrowing breaks a common pattern (see Breaking (types only)).
Changed
-
Twelve checkers are now TypeScript type predicates (
value is T), sotscnarrows in both branches. Every checker’s parameter isunknowninstead ofany.Function Predicate isStringvalue is stringisPlainObjectvalue is Record<string, unknown>isObjectvalue is objectisDatevalue is DateisRegexvalue is RegExpisPromisevalue is Promise<unknown>isIterablevalue is Iterable<unknown>isGeneratorvalue is IterableIterator<unknown>(duck-typed:next()plus self-iterable;Generator’sreturn/throware not verified)isScalarvalue is string | boolean | number | symbol | bigintisPrimitivevalue is string | boolean | number | bigintisFormElementvalue is HTMLFormElement(falls back toobjectwhen the DOM lib is not in the consumer’s compilation, so the typings never requirelib: ["dom"])isFormDatavalue is FormData(falls back toobjectwhenFormDatais not declared) -
Kept as plain
boolean, on purpose:isInt,isFloat,isNumeric,isJson,isUrl,isEmail,isValidId,isEmptyand the environment checks. These accept a subset of their base type, so a predicate would narrow the false branch wrongly: withn: number,if (isInt(n)) {} else { n }would makenneveralthough1.5takes the else branch.isNumericadditionally accepts numeric strings and single-element arrays (String([5]) === "5"), so neithernumbernornumber | `${number}`would be true.isEmptyis not a test for one type. -
Known limits of the predicates, documented in the README:
isObjectreturnsfalsefor functions althoughFunctionis assignable toobject;isRegexchecksconstructor.name === "RegExp", so a RegExp subclass under another class name returnsfalse.
Breaking (types only)
- A value typed
anyis narrowed by a type predicate. Code such asif (isPlainObject(data)) { data.user.name }(withdata: any, e.g. aJSON.parseresult) compiled before and now fails with'data.user' is of type 'unknown'; the same applies toisObject,isIterableandisPromiseonany. Type the value (const data: Payload), or narrow further after the guard. No runtime change, no change forunknown- or union-typed values.
Added
- Exported helper types:
ScalarValue,PrimitiveValue,FormElementType,FormDataType. - Type-level tests (
src/__tests__/types.test-d.ts,expectTypeOf, run byvitesttypecheck mode) and runtime narrowing tests (src/__tests__/guards.test.ts).npm run test:typesrunstsc --noEmit.
Docs
- README,
llms.txt,llms-full.txtand the skills describe the predicates and the boolean-only checkers. They previously claimedisString(or nothing) was a type guard.
Tests
102 runtime tests across 6 files + 6 type-level tests (types.test-d.ts) = 108[2.1.4] 2026-08-17 Security (1)
Security
isValidIdregex is now linear (src/index.ts:50). The previous pattern nested a quantified group inside another quantifier, giving a match cost that grows quadratically-to-exponentially in the input length — so a long crafted string passed toIs.validHtmlId(...)pinned the thread instead of returningfalse. A validator is exactly the surface that gets pointed at untrusted input, which is what makes this worth a release. The pattern is now/^[A-Za-z][\w\-:.]*$/: a single unambiguous character class with no nested quantifier, so there is nothing to backtrack over. Accepted and rejected values are unchanged.
[2.1.3] 2026-05-26 Fixed (18) · Added (5) · Changed (10) · Removed (2)
Fixed
isNumeric— stateful regex (src/index.ts:10). Dropped the/gflag soRegExp.prototype.testno longer advanceslastIndexbetween calls. Repeated calls on the same input now return a stabletrue.isInt— rejects negative integers (src/index.ts:16). Replaced the regex withNumber.isInteger(value)so signed integers are accepted.isInt— rejects integers in scientific notation (src/index.ts:16). Same fix —Number.isInteger(1e21)correctly returnstrue.isFloat— rejects negative floats / unescaped.in regex (src/index.ts:22). Regex is now/^-?\d+\.\d+$/(sign branch + escaped.), gated onNumber.isFinitesoNaN/Infinityare rejected.isObject,isRegex,isPlainObject,isPromise,isDate,isGenerator,isIterable— non-boolean falsy returns (multiple lines). Predicates now coerce withBoolean(...)/ explicitvalue !== nullchecks instead of leaking the raw falsy value (null,undefined,0,"").isPlainObject— throws onObject.create(null)(src/index.ts:39). Now short-circuits totruewhen the prototype isnull; otherwise falls back to theconstructor?.name === "Object"check with an optional chain.isPromise— doesn’t recognize subclasses (src/index.ts:79). Switched tovalue instanceof Promise, which accepts subclasses while still rejecting bare thenables.isGenerator— never matches a generator instance (src/index.ts:91). Replaced the constructor-name check with a duck-typed test: object +.nextfunction + self-iterable (value[Symbol.iterator]() === value).isEmpty— returnstruefor non-empty plain objects (src/index.ts:103). Added anObject.keys(value).length === 0fast-path for non-iterable plain objects, so{ a: 1 }is now correctly reported as non-empty.isEmpty— returnstruefornew Date()(src/index.ts:103). Constructed Date instances now short-circuit tofalse.isEmpty— returnstrueforNaN(src/index.ts:103).NaNis now treated as non-empty (it’s still a numeric value).isIterable— returns the empty string instead oftruefor""(src/index.ts:64). Replaced the truthy guard with explicit null/undefined checks so the empty string flows through to theSymbol.iteratortest.isUrl— accepts trailing dots and..(src/index.ts:142). Hostname is now split on.and rejected if any label is empty, sohttps://google.andhttps://google..comboth returnfalse.isEmail— matches single-element arrays (src/index.ts:160). Added atypeof value === "string"guard before the regex test, so arrays / non-strings can’t sneak through viaString()coercion.isSafari— false-positives on class-based DOM polyfills (src/index.ts:273). Replaced the/constructor/i.test(window.HTMLElement)heuristic with a userAgent-based detector that requiresSafariAND excludesChrome|Chromium|Edg|OPR|Android.isOpera— references bareoprglobal (src/index.ts:288). Switched towindow.opr?.addonsso the predicate no longer risks aReferenceErrorin strict mode.isBrowser— duplicated buggy logic forsafari/opera(src/index.ts:243). The function now delegates toisChrome(),isFirefox(),isSafari(),isOpera(),isIE(),isEdge()instead of inlining (and re-introducing) the same bugs.isMobile.any— circular self-reference throughIs(src/index.ts:232). ReplacedIs.mobile.X()calls with directisMobile.X()calls so the object no longer depends on its own export being initialized.
Added
- README.md — marketing-style index with a 30-second tour, a categorized predicate table, an
isEmptysemantics section, an SSR-safety note, and a curated list of known bugs. - CHANGELOG.md — this file.
- AI kit —
llms.txt,llms-full.txt, andskills/cards (README,overview,primitives,collections,formats,misc,environment,recipes). - Test suite — 89 passing tests across
primitives,collections,formats,misc, andenvironment. The suite uses Vitest underhappy-domso DOM-touching predicates (isFormElement,isFormData,isMobile.*,isMac,isBrowser,isChrome/isFirefox/isSafari/isOpera/isIE/isEdge) can be exercised with a configurablenavigator.userAgent. - CI — GitHub Actions matrix: Node 18 / 20 / 22 on Ubuntu plus Node 20 on Windows.
Changed
- Test runner: Jest → Vitest. Tests moved from
tests/tosrc/__tests__/(matches@mongez/atom’s layout) and are organized by category (primitives.test.ts,collections.test.ts,formats.test.ts,misc.test.ts,environment.test.ts). package.json:- Description rewritten to actually describe the package (“tree-shakable type & shape predicates”).
- Added
sideEffects: falseso bundlers can drop unused exports for real. - Keyword list expanded from 10 generic terms to a comprehensive index of every exported predicate.
- Removed Jest config block.
devDependencies: droppedjest,ts-jest,@types/jest,jest-esm-jsx-transform; bumpedtypescriptto^5.4; addedvitest@^2.1andhappy-dom@^15.
- Dropped dev dependencies:
jest,ts-jest,@types/jest,jest-esm-jsx-transform. The package no longer carries a Jest config. - Added dev dependencies:
vitest@^2.1,happy-dom@^15. - Bumped:
typescript@^4.5→^5.4.
Removed
tests/directory (replaced bysrc/__tests__/).- Stale devDeps listed above.
Tests
89 passing + 0 skipped = 89 total across 5 files