Releases: SrHenry/type-utils
v0.8.4 (2026-06-04)
What's Changed
New Features
isNativeSchema(value)— type guard that checks if a value is a native type-utilsTypeGuard<T>, useful for distinguishing from external Standard Schema validatorsGetSchema<T>— type helper that maps a TypeScript type to the correspondingFluentSchemaresult typeisStandardSchemanow accepts functions with~standardpropertyis()andensureInterface()now use native-first routing for type-utils guards
Bug Fixes
Schema.validatorREADME section now shows actual.validator()usage examplesBooleanSchemacorrectly returnsFluentSchema<boolean>GetSchemahandlesvoid,never, andreadonlyarray typesnormalizeSchemaearly-exits for native schemas, preventing recursion inrecord()- Circular dependency resolved in
isNativeSchema
Improvements
- Schema types refactored into dedicated files (V1/V2/V3 namespaces, individual schema type files)
- V3 wildcard re-export replaced with explicit allowlist to prevent leaking internal types
- Schema types,
ValidateReturn, and rule types re-exported from types hub
Dependencies
- Vitest 3.2.1 → 4.1.0
- @biomejs/biome 2.4.15 → 2.4.16
- tsx 4.22.3 → 4.22.4
- webpack-cli 7.0.2 → 7.0.3
Full Changelog: v0.8.3...v0.8.4
v0.8.3
v0.8.2 (2026-05-27)
Bug Fixes
- fix(validator): resolve inline rule overload in
createInlineRule(#43) - fix(schema): preserve optional schema metadata (#44)
- fix(schema): preserve array element type inference (#46)
- refactor(validator): reduce normalize() cognitive complexity (#45)
- refactor(schema): align
_fn/OptionalizedArrayoverloads withArraySchema(#49)
Internal
- Move TypeScript and
@types/nodeto dev/peer deps to improve Socket supply chain score - Extract helpers from
normalize()to reduce cognitive complexity - Add unit tests for
copyStructMetadata,createInlineRuleoverloads, and array inference
Full Changelog: v0.8.1...v0.8.2
v0.8.1 (2026-05-26)
What's Changed
Features
- Schema: Add
.toStandardSchema()to 6 missing schemas (Standard Schema interop completeness) (#30) - Release: Add
--rc,--beta,--alphaprerelease flags - Release: Add
--bumpflag for auto version calculation - Release: Add release automation script and pipeline docs
- Build: Move TypeScript and
@types/nodeto dev/peer deps to improve socket supply chain score
Bug Fixes
- Schema:
primitive()andenumvalidation now correctly handlenull(#32)
Pipeline & Internal
- Modularize workflows into subdirectories
- Add AGENTS.md with AI harness session behavior and PR workflow
- TTY detection,
NO_COLORsupport, and summary for all test runs - Fix ANSI escape and harness adapter abstraction in release script
- Polish
--helptext alignment in release script
Dependencies
Full Changelog: v0.8.0...v0.8.1
v0.8.0 (2026-05-25)
What's Changed
Three major features land in this release:
🔄 Pipeline Refactor
Pipeline internals replaced decorator-based architecture with PipelineBox/AsyncPipelineBox classes. New helpers: callWith() (reverse-apply), apply() (partial application), tap()/tapAsync() (side-effects). enpipe is now deprecated — use callWith/apply for clearer type-safe transforms. inject() removed. GetPipeline<T> type removed — use Pipe<T>.
🧪 Vitest Migration
Full migration from Jest/ts-jest to Vitest with globals mode. 318 tests passing. Build fix: test files and vitest globals excluded from production tsconfigs (TS 6 compatibility).
🔧 Biome Lint Pipeline
Biome + Prettier enforced across codebase. 525 lint warnings → 0. Precommit hook now runs: build:clean && check:fix && test && circular-dependencies.
⚡ SchemaValidator Refactor
Extracted validate() monolith (760+ lines, complexity 150) into dedicated handlers: validateObject, validateRecord, validateIntersection, validateUnion, validateDefault, validateWithoutMetadata.
🔧 Other Changes
- feat(pipeline): add callWith reverse-apply helper, remove inject b08ffd6
- feat(pipeline): add tap/tapAsync, restructure into layers, fix types and casts 9d0f9ce
- refactor(pipeline): replace decorator-based internals with PipelineBox classes, add apply helper 0d6041f
- deprecate(pipeline): mark enpipe as deprecated, recommend pipe/callWith/apply 20a4373
- docs(README): rewrite pipeline section with callWith/apply sub-sections e4f284f
- feat: migrate test suite from jest/ts-jest to vitest 004ffe4
- feat: add Biome lint + Prettier format pipeline ed88069
- chore: reduce biome lint warnings from 525 to 167 55b665a
- chore: eliminate all remaining biome lint warnings (167 → 0) 4d7a6d9
- refactor(validator): extract SchemaValidator.validate() cases into dedicated functions a0b6299
- fix(build): exclude test files from production tsconfigs a1da192
- style: clean up asTypeGuard.ts formatting, make random.ts param schemas lazy d541cc6
- chore: remove completed SchemaValidator refactor from TODO f451250
- docs(README): add Schema.bigint/tuple/record/asUndefined, BigInt rules, Standard Schema interop, Tag type, badges 671ec00
- chore: bump version to 0.8.0 36035d3
🧪 Testing
318 tests pass, typecheck clean, 0 circular dependencies, 0 lint warnings.
⚠️ Breaking Changes (experimental API)
inject()removed — usecallWith()GetPipeline<T>type removed — usePipe<T>PipelineBoxis now a standalone class (not decorator-based)AsyncPipelineBoxno longer has.catch()— use.depipe().catch()
📋 Migration Notes
- Replace
.pipe(fn => fn(value))with.pipe(callWith(value)) - Replace
.pipe(enpipe(fn, ...args))with.pipe(apply(fn, ...args)) - Replace
inject()usage withcallWith()or plain transforms
Full Changelog: v0.7.1...v0.8.0
v0.7.1 (2026-05-24)
What's Changed
-
fix(rules): eliminate handler(void 0) spec-check via CUSTOM_RULE_BRAND
isCustomHandlercalledhandler(void 0)at registration time to spec-check rule handlers, breaking the type contract and forcing users to guard againstundefinedon destructuring/property-access handlers. Replaced withCUSTOM_RULE_BRANDruntime symbol for brand-checking, extendedCustomtuple to 4 elements[name, args, handler, formator], and deletedisCustomHandlerentirely. Includes regression tests ensuring handler is never called with undefined. c90bb45 -
refactor(helpers): rewrite random.ts param schemas with createInlineRule and match dispatch
ReplaceasTypeGuardvalidators withcreateInlineRule(nullParam, arrayLike, minMax), replace if/else dispatch withmatch().with()pattern, convert param schemas to lazy factories, extract pure helper functions. 5f1da5850fd8cb3b7d9cf0ff2e9e97b9e97f4ad3 -
fix(deps): resolve fast-uri ReDoS vulnerability via webpack/ajv transitive dep
Bumpfast-uriresolution to ^3.1.2, patching ReDoS vulnerabilities reported in Dependabot alerts #36 and #37. Also bumps: jest 30.3.0 → 30.4.2, @types/node 25.6.0 → 25.6.2, @sinonjs/fake-timers ^15.0.0 → ^15.4.0. d9b3ac8 -
style: format all src files with prettier e0041c7
🔀 Merged Pull Requests
- build(deps): bump @types/node from 25.6.2 to 25.9.0 (#24)
- build(deps-dev): bump ts-jest from 29.4.9 to 29.4.10 (#25)
- build(deps-dev): bump tsx from 4.21.0 to 4.22.3 (#23)
Full Changelog: v0.7.0...v0.7.1
v0.7.0 (2026-04-27)
What's Changed
Two major features land in this release: full Standard Schema V1 interoperability and ESM migration with dual CJS+ESM output.
🔄 Standard Schema V1 Interoperability
Implements the Standard Schema V1 spec — the common interface created by the authors of Zod, Valibot, and ArkType that allows any tool accepting "a schema" to work with any conforming library interchangeably.
Producer direction — all TypeGuards and schemas now expose ~standard automatically:
import { string, object, number } from '@srhenry/type-utils'
const user = object({ name: string(), age: number() })
// Any Standard Schema consumer can use this
const result = user['~standard'].validate({ name: 'Alice', age: 30 })
// → { success: true, value: { name: 'Alice', age: 30 } }Consumer direction — is(), ensureInterface(), and match() accept external Standard Schemas directly:
import { is, ensureInterface } from '@srhenry/type-utils'
import { z } from 'zod'
const zodUser = z.object({ name: z.string(), age: z.number() })
is({ name: 'Alice', age: 30 }, zodUser) // true
ensureInterface({ name: 'Alice', age: 30 }, zodUser) // passesComposition widening — all 6 composition schemas now accept StandardSchemaV1<T,T> alongside TypeGuard<T>:
object({ name: zodString, age: number() }) // mix freely
array(zodString) // Standard Schema as element
tuple(zodString, zodNumber) // Standard Schema in tuples
or(zodString, zodNumber) // union with Standard Schema
and(zodNameSchema, zodAgeSchema) // intersection
record(string(), zodNumber) // record valuesAdapters — explicit conversion when you need it:
import { toStandardSchema, fromStandardSchema } from '@srhenry/type-utils/standard-schema'
// TypeGuard → StandardSchemaV1
const std = toStandardSchema(string())
// StandardSchemaV1 → TypeGuard
const guard = fromStandardSchema(zodSchema)Fluent .toStandardSchema() — available on all schema instances:
const std = string().toStandardSchema()
const stdObj = object({ name: string() }).toStandardSchema()New files
| File | Purpose |
|---|---|
types.ts |
Full StandardSchemaV1 namespace inlined from spec — no @standard-schema/spec dependency |
isStandardSchema.ts |
Runtime guard; returns false for functions to prevent infinite recursion |
attachStandardSchema.ts |
Attaches ~standard via Object.defineProperty (non-enumerable) |
toStandardSchema.ts |
TypeGuard<T> → StandardSchemaV1<T,T> adapter |
fromStandardSchema.ts |
StandardSchemaV1<Input,Output> → TypeGuard<Input> adapter; throws on async |
normalizeSchema.ts |
Composition boundary helper — converts StandardSchemaV1 → TypeGuard with probe-based optional detection, synthetic CustomStruct metadata, and vendor-prefixed error messages |
pathConverter.ts |
Converts internal ValidationError.path strings ↔ Standard Schema Issue.path format |
Design decisions
~standardis non-enumerable — keepsObject.keys(),JSON.stringify(), and spread operators clean on TypeGuardsisStandardSchemareturnsfalsefor functions — prevents infinite recursion when TypeGuards (which are functions with~standardattached) are passed to widenedensureInterface- Single attachment point —
attachStandardSchema(guard)called insidesetStructMetadata, so all schemas automatically get~standard - Sync-only —
fromStandardSchema(),is(),ensureInterface(), andmatch()throwTypeErrorfor async schemas, per spec guidance - Input === Output — producer always sets
Input = Output = T; transform support deferred as future work
📦 ESM Migration & Dual CJS+ESM Output
The package is now fully ESM-native with dual output:
"type": "module"inpackage.json- Conditional
exportsmap withtypes/import/requirefor all subpaths - Separate ESM and CJS build pipelines (
tsconfig.esm.json,tsconfig.cjs.json) .tsimport extensions across all 264 source files (TypeScript 6.0allowImportingTsExtensions)- Jest configured for ESM module resolution
reflect-metadata removed — replaced with a custom WeakMap<object, Map<string|symbol, unknown>> metadata store and Symbol.for() keys. Eliminates the CJS-only runtime dependency and global Reflect side effects.
No breaking changes for downstream consumers — the conditional exports map ensures import resolves to ESM and require resolves to CJS automatically.
🔧 Other Changes
- Added
asEnum().toStandardSchema()method (was the only schema missing it) setStructMetadatagainedCustomStructoverload for external schema normalizationValidatorMap<T>widened to acceptStandardSchemaV1<T[K],T[K]>alongsideTypeGuard<T[K]>- New
NormalizedValidatorMap<T>internal type forBaseValidatorcompatibility TypeGuardTupleUnwrapupdated to unwrapStandardSchemaV1<infer U, any>in addition toTypeGuard<infer U>- Fixed pre-existing bug:
./schemas→./schemainpackage.jsonexports map - Dev dependency bumps:
jest30.3.0,ts-jest29.4.9,webpack5.106.2,webpack-cli7.0.2,@types/node25.6.0 - Security: bumped transient dependencies to mitigate CVE-2026-27903 (ReDoS) and CVE-2026-33672
🧪 Testing
259 tests pass (14 new composition widening + 46 Standard Schema + 199 existing), typecheck clean.
⚠️ Known Limitations
- Async Standard Schema not supported —
fromStandardSchema(),is(),ensureInterface(), andmatch()throwTypeErrorfor async schemas. Async adapters (fromStandardSchemaAsync,isAsync, etc.) are deferred as future work. - Transforms not supported — Standard Schema's Input ≠ Output mode is not yet supported. Our producer always sets
Input = Output = T. match().with()type-level widening deferred — runtime works, but TypeScript overloads for.with()are not yet widened to acceptStandardSchemaV1<Pattern>in the type system.- UMD builds have pre-existing type errors in
omit.tsandreplaceSchemaTree.ts— separate fix needed.
📋 Migration Notes
- No breaking API changes. Existing code continues to work unchanged.
- The
reflect-metadatapolyfill is no longer loaded at runtime. If downstream code was relying onReflect.defineMetadata/getMetadata/hasMetadatabeing available globally (internal only), it will need its ownreflect-metadataimport. - Standard Schema interop is opt-in — you must explicitly access
~standardor use the new adapters.
Full Changelog: v0.6.5...v0.7.0
v0.6.5 (2026-02-05)
What's Changed
- hotfix: 🚑 add custom keyMetadata type (kind: 'string') validation resolver to account for
record()call with no params. ab7c3b8
Full Changelog: v0.6.4...v0.6.5
v0.6.4 (2026-02-04)
What's Changed
- fix: 🐛 add custom rule type annotation in schemas + fix optional schema propagated type param 3061093
Full Changelog: v0.6.3...v0.6.4
v0.6.3 (2026-02-04)
What's Changed
- hotfix: 🚑 .exec method of matcher returning wrong type when expression mapper returns another function, resolving to the inner function return type instead of propagating the function returned from the expression mapper 7eae293
Full Changelog: v0.6.2...v0.6.3