
Best Practices for Using is-kit in Real Projects
Put checks where trust changes, keep useful guards close to their purpose, and let ordinary TypeScript handle the rest.
Two jobs, two starting points
External data
Unknown → validate → use
Check what entered the application before trusting its shape.
Known data
Typed → refine → use
Keep the existing type and narrow only what you need.
Validate where trust changes
HTTP responses, storage, and messages can contain unexpected values. Keep them unknown until a guard accepts them; input as User performs no runtime check.
import { isString, oneOfValues, safeParse, struct } from 'is-kit';const isUser = struct({id: isString,name: isString,role: oneOfValues('admin', 'member'),});async function readUser(response: Response) {const input: unknown = await response.json();const result = safeParse(isUser, input);if (!result.valid) throw new Error('Invalid user response');return result.value;}
Reject a required response or choose a fallback for recoverable data. Handle decoding and transport failures too. Pass the accepted value onward; recheck if it can change or crosses another trust boundary. Storage, search parameters, and selected responses are shown in where to validate. See validating unknown values for the checks those examples use.
Keep guards near their purpose
A named guard gives a meaningful check one home for reuse and tests. Trivial one-off checks can stay inline. Place guards where their purpose is easiest to find:
| Guard | A useful home |
|---|---|
| Response shape | Beside the API function |
| Stored state | Near its reader |
| Shared domain rule | With the domain or shared guards |
A small utils/is.ts is fine to start; split it when unrelated guards become hard to find. A shared facade can help, but direct imports work too. The boundary matters more than the folder name.
Compose small checks. Use typedStruct to help detect drift from an existing type, or struct when the accepted boundary shape differs. See keeping guards in sync and validating generated API types.
Refine known values instead of validating again
If an internal value already has a useful type, narrow the property you need instead of validating the whole object again.
import { isString, refineKey } from 'is-kit';type Item = { id: number; value: string | number };declare const items: Item[];const hasStringValue = refineKey('value', isString);const textItems = items.filter(hasStringValue);// Each item retains its id and has a string value.
This assumes the input already satisfies Item; it does not validate an unknown object. See the property refinement guide for optional and nested properties.
Keep the toolkit small
Add guards where runtime uncertainty or reusable refinement calls for them. You do not need a guard for every TypeScript type. The library decisions behind this guidance are in the design principles and non-goals.