feat(guards): add @packrat/guards runtime type guard package#2038
Conversation
Introduce a shared guards package so every app in the monorepo has one canonical import path for runtime type narrowing instead of reaching into radash directly, copying assertion helpers into per-app typeAssertions.ts, or leaning on `as` casts at API → store boundaries. ## What's in it - **radash re-exports**: isString, isNumber, isDate, isObject, isArray, isBoolean, isEmpty, isEqual, isFloat, isFunction, isInt, isPrimitive, isPromise, isSymbol. - **assertions** (throw on failure, narrow via `asserts`): assertDefined, assertNonNull, assertPresent, assertIsString, assertIsNumber, assertIsBoolean, assertAllDefined. - **narrow** (return T | undefined instead of throwing): asString, asNumber, asBoolean, asDate, asStringRecord, nullToUndefined. - **enum** (string literal union validation): makeEnumGuard, assertEnum. ## Wiring - New workspace package at packages/guards (private, bun workspaces picks it up via packages/*). - tsconfig paths entries for @packrat/guards and @packrat/guards/*. - radash as a direct dependency. ## Not yet A follow-up pass will migrate existing scattered `as` casts and per-app typeAssertions.ts copies over to @packrat/guards. Keeping this commit scoped to the new package so the blast radius is zero.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Adds a new internal workspace package, @packrat/guards, to centralize runtime type guards / assertions / narrowing helpers behind a single import path across the monorepo (reducing ad-hoc radash imports, local typeAssertions.ts copies, and unsafe casts).
Changes:
- Added
packages/guardsworkspace with assertions, enum guards, and narrowing helpers, plus radash re-exports. - Wired TS path aliases for
@packrat/guardsin the roottsconfig.json. - Updated
bun.lockto include the new workspace (and additional dependency graph changes).
Reviewed changes
Copilot reviewed 5 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Adds path aliases for @packrat/guards and subpaths. |
| packages/guards/src/index.ts | Public entrypoint re-exporting radash guards + local helpers. |
| packages/guards/src/assertions.ts | New asserts-based runtime assertions. |
| packages/guards/src/narrow.ts | New asX / narrowing helpers for API→store mapping. |
| packages/guards/src/enum.ts | New helpers for validating string literal unions at runtime. |
| packages/guards/package.json | Declares new private workspace package and radash dependency. |
| bun.lock | Adds @packrat/guards workspace and updates dependency graph. |
| export { | ||
| isArray, | ||
| isDate, | ||
| isEmpty, | ||
| isEqual, | ||
| isFloat, | ||
| isFunction, | ||
| isInt, | ||
| isNumber, | ||
| isObject, | ||
| isPrimitive, | ||
| isPromise, | ||
| isString, | ||
| isSymbol, | ||
| } from 'radash'; |
There was a problem hiding this comment.
PR description lists isBoolean as a radash re-export, but it is not currently exported from @packrat/guards. Either add it to this re-export list (if available in radash) or update the exported surface/docs so consumers don't hit missing import errors.
| /** | ||
| * Returns a `Record<string, string>` from an unknown value, keeping only | ||
| * string-valued entries. Returns `{}` if the input isn't a plain object. | ||
| */ | ||
| export const asStringRecord = (value: unknown): Record<string, string> => { | ||
| if (value === null || typeof value !== 'object') return {}; | ||
| const out: Record<string, string> = {}; | ||
| for (const [key, val] of Object.entries(value as Record<string, unknown>)) { |
There was a problem hiding this comment.
asStringRecord claims it returns {} when the input isn't a plain object, but the current check only excludes null and non-objects—arrays (and other non-plain objects) will be treated as objects and may produce unexpected keys. Tighten the guard to reject arrays / non-plain objects before iterating entries.
| /** | |
| * Returns a `Record<string, string>` from an unknown value, keeping only | |
| * string-valued entries. Returns `{}` if the input isn't a plain object. | |
| */ | |
| export const asStringRecord = (value: unknown): Record<string, string> => { | |
| if (value === null || typeof value !== 'object') return {}; | |
| const out: Record<string, string> = {}; | |
| for (const [key, val] of Object.entries(value as Record<string, unknown>)) { | |
| const isPlainObject = (value: unknown): value is Record<string, unknown> => { | |
| if (value === null || typeof value !== 'object' || Array.isArray(value)) { | |
| return false; | |
| } | |
| const prototype = Object.getPrototypeOf(value); | |
| return prototype === Object.prototype || prototype === null; | |
| }; | |
| /** | |
| * Returns a `Record<string, string>` from an unknown value, keeping only | |
| * string-valued entries. Returns `{}` if the input isn't a plain object. | |
| */ | |
| export const asStringRecord = (value: unknown): Record<string, string> => { | |
| if (!isPlainObject(value)) return {}; | |
| const out: Record<string, string> = {}; | |
| for (const [key, val] of Object.entries(value)) { |
| ".": "./src/index.ts" | ||
| }, | ||
| "dependencies": { | ||
| "radash": "^12.1.0" |
There was a problem hiding this comment.
radash is declared as ^12.1.0 here while other workspaces already depend on ^12.1.1. Consider aligning the version range to reduce lockfile churn and avoid accidental multi-version installs.
| "radash": "^12.1.0" | |
| "radash": "^12.1.1" |
feat(guards): add @packrat/guards runtime type guard package
Summary
New
@packrat/guardsworkspace package providing one canonical import path for runtime type narrowing across the monorepo. Adding this so we can stop reaching intoradashdirectly from every file, stop copyingtypeAssertions.tsinto every app, and stop usingascasts at API → store boundaries.What's in it
radash re-exports (one import path instead of many):
isString,isNumber,isDate,isObject,isArray,isBoolean,isEmpty,isEqual,isFloat,isFunction,isInt,isPrimitive,isPromise,isSymbol.assertions (throw on failure, narrow via
asserts):assertDefined,assertNonNull,assertPresent,assertIsString,assertIsNumber,assertIsBoolean,assertAllDefined.narrow (return
T | undefinedinstead of throwing — for API→store mapping):asString,asNumber,asBoolean,asDate,asStringRecord,nullToUndefined.enum (string literal union validation):
makeEnumGuard,assertEnum.Wiring
packages/guards/. Bun workspaces picks it up viapackages/*.tsconfig.jsonpath entries for@packrat/guardsand@packrat/guards/*.radashas a direct dependency.Not yet — follow-up
This PR is scoped to just the package. Migrating existing call sites is a separate effort:
apps/expo/utils/typeAssertions.tsandpackages/api/src/utils/typeAssertions.tsconsumers to@packrat/guards, then delete the per-app copies.as string | undefinedcasts inpackages/api/src/services/r2-bucket.ts(already done in chore: enable Biome useMaxParams rule (max: 3) as error #1953 using radash directly — can re-point to@packrat/guardsafter this lands).isWeightUnitguard inapps/expo/features/pack-templates/hooks/useGenerateTemplateFromOnlineContent.ts(added in chore: enable Biome useMaxParams rule (max: 3) as error #1953) tomakeEnumGuard(WEIGHT_UNITS).Test plan
bun installpicks up the new workspacebun run check-typespasses with a smoke-test import of every exported symbol🤖 Generated with Claude Code