Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Refactor: type cleanup — type-fest helpers + buildRuleRecord#245

Merged
twschiller merged 3 commits into
mainfrom
refactor/type-fest-helpers
Jun 10, 2026
Merged

Refactor: type cleanup — type-fest helpers + buildRuleRecord#245
twschiller merged 3 commits into
mainfrom
refactor/type-fest-helpers

Conversation

@twschiller

@twschiller twschiller commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

What

Type-level cleanup from a survey of hand-rolled type gymnastics. Type-only, no runtime change. Two themes:

1. Adopt type-fest helpers

Adds type-fest as a dev dependency and replaces two hand-written patterns:

  • Entries<T>Object.entries(x) widens keys to string, 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.tsEntries<typeof DETECTION_KIND_TO_RULE_ID>
    • tab-tracker.tsEntries<Required<RuleCountMap>> (the Required<> drops undefined from the Partial record's value so the cross-frame sum still types as number)
  • IsEqual<A, B>message-schemas.ts had an AssertExtends helper applied in both directions to approximate structural equality between the zod inference and the hand-written wire type. IsEqual expresses that as one stricter assertion. tsc confirms the schema and DetectionPayload are still identical.

2. Centralize Object.fromEntries rule-record casts

type-fest has no FromEntries helper, so the four Object.fromEntries(...) as T casts can't be typed off-the-shelf. Two build the same Record<RuleId, boolean> shape, so they now route through a small buildRuleRecord helper in rule-metadata.ts:

  • storage.ts DEFAULT_STATES
  • rule-engine.ts ALL_DISABLED

The single unavoidable as (the string-key widening) now lives once inside the helper; call sites are cast-free, key coverage is guaranteed by RULE_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.
  • The other two fromEntries sites: availability.ts builds its map async over RULES (needs the rule object) and rule-options.ts produces the non-uniform RuleOptions shape — neither fits a Record<RuleId, V> builder.

Checks

  • bun run check
  • tsc --noEmit
  • affected suites (tab-tracker / lifecycle / message-schemas / storage / rule-engine / rule-metadata / catalog) ✅

🤖 Generated with Claude Code

…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]>
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agent-browser-shield-demo-site Ready Ready Preview, Comment Jun 10, 2026 12:12pm

Request Review

…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]>
@twschiller twschiller changed the title Refactor: adopt type-fest Entries/IsEqual helpers Refactor: type cleanup — type-fest helpers + buildRuleRecord Jun 10, 2026
`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]>
@twschiller twschiller merged commit 19ff8ec into main Jun 10, 2026
7 checks passed
@twschiller twschiller deleted the refactor/type-fest-helpers branch June 10, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant