Refactor: type cleanup — type-fest helpers + buildRuleRecord#245
Merged
Conversation
…ype gymnastics) Add `type-fest` as a dev dependency and use two of its helpers in place of hand-written equivalents: - `Entries<T>` replaces the `Object.entries(x) as [K, V][]` tuple assertions in `lifecycle.ts` and `tab-tracker.ts`. The entry shape is now derived from the source type (`Required<RuleCountMap>` keeps the value `number`, matching the cross-frame sum), so it can't silently drift from the map it iterates. - `IsEqual<A, B>` replaces the bidirectional `AssertExtends` drift guard in `message-schemas.ts`, collapsing the two-direction parity check into one structural-equality assertion between the zod inference and the wire type. Type-only changes; `bun run check`, tsc, and the affected test suites pass. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…eRecord `type-fest` has no `FromEntries` helper, so the four `Object.fromEntries(...) as T` casts can't be typed off-the-shelf. Two of them build the same shape — `Record<RuleId, boolean>` (`RuleStates`) — so introduce a small `buildRuleRecord` helper in `rule-metadata.ts` (next to `RULE_IDS`, the canonical id list) and route those two through it: - `storage.ts` DEFAULT_STATES - `rule-engine.ts` ALL_DISABLED The single unavoidable `as` (the `string`-key widening of `Object.fromEntries`) now lives once inside the helper. Call sites become cast-free, key coverage is guaranteed by `RULE_IDS`, and the value type is inferred + checked against the annotated target rather than asserted. The other two fromEntries sites are intentionally left as-is: `availability.ts` builds its map async over `RULES` (needs the rule object, not just the id) and `rule-options.ts` produces the non-uniform `RuleOptions` shape — neither fits a `Record<RuleId, V>` builder. `bun run check`, tsc, and the affected suites (storage / rule-engine / rule-metadata / catalog) pass. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
`parse-config.test.ts` mocks `rules/rule-metadata` with a factory, and `storage.ts` (pulled in transitively) now calls `buildRuleRecord` at module-init to build its default state. The mock didn't export the helper, so the suite failed to load with `buildRuleRecord is not a function` — only when the full suite runs together (targeted runs that excluded this file passed). Add a stub `buildRuleRecord` to the mock factory that mirrors the real helper over the small mocked id set. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Type-level cleanup from a survey of hand-rolled type gymnastics. Type-only, no runtime change. Two themes:
1. Adopt
type-festhelpersAdds
type-festas a dev dependency and replaces two hand-written patterns:Entries<T>—Object.entries(x)widens keys tostring, so the code asserted the tuple shape by hand (as [K, V][]), which can silently drift from the map it iterates. Now derived from the source type:lifecycle.ts→Entries<typeof DETECTION_KIND_TO_RULE_ID>tab-tracker.ts→Entries<Required<RuleCountMap>>(theRequired<>dropsundefinedfrom thePartialrecord's value so the cross-frame sum still types asnumber)IsEqual<A, B>—message-schemas.tshad anAssertExtendshelper applied in both directions to approximate structural equality between the zod inference and the hand-written wire type.IsEqualexpresses that as one stricter assertion. tsc confirms the schema andDetectionPayloadare still identical.2. Centralize
Object.fromEntriesrule-record caststype-festhas noFromEntrieshelper, so the fourObject.fromEntries(...) as Tcasts can't be typed off-the-shelf. Two build the sameRecord<RuleId, boolean>shape, so they now route through a smallbuildRuleRecordhelper inrule-metadata.ts:storage.tsDEFAULT_STATESrule-engine.tsALL_DISABLEDThe single unavoidable
as(thestring-key widening) now lives once inside the helper; call sites are cast-free, key coverage is guaranteed byRULE_IDS, and the value type is inferred + checked.Deliberately not done
PartialRecord/FromEntries— neither exists in type-fest v5.Partial<Record<…>>is already idiomatic; nothing to swap.fromEntriessites:availability.tsbuilds its map async overRULES(needs the rule object) andrule-options.tsproduces the non-uniformRuleOptionsshape — neither fits aRecord<RuleId, V>builder.Checks
bun run check✅tsc --noEmit✅🤖 Generated with Claude Code