Skip to content

fix(kida): type toAccessor by what it actually returns - #208

Merged
dangreen merged 1 commit into
mainfrom
fix/kida-to-accessor-any-fn
Aug 24, 2026
Merged

fix(kida): type toAccessor by what it actually returns#208
dangreen merged 1 commit into
mainfrom
fix/kida-to-accessor-any-fn

Conversation

@dangreen

Copy link
Copy Markdown
Member

toAccessor and toAccessorOrSignal decide what to do with a typeof test:

export function toAccessor(source: unknown) {
  return isAccessor(source)
    ? source
    : () => source
}

isAccessor is typeof value === 'function'. It cannot tell an accessor from a callback, and it does not try — either one is handed back untouched.

The types said otherwise. They tested AnyAccessor, which is Accessor<any> — a zero-argument function. A callback takes an argument, so it is not assignable, so it fell through to the wrapping branch:

const wrapped = toAccessor((event: Event) => String(event.type))
// typed   Accessor<(event: Event) => string>
// is      (event: Event) => string

toAccessorOrSignal was wrong the same way, and worse — it claimed a WritableSignal of the callback.

Both now test AnyFn, which is what the runtime tests:

export type ToAccessor<T> = [T] extends [AnyFn]
  ? T
  : Accessor<Exclude<T, AnyFn>> | Extract<T, AnyFn>

Nothing else changes. A plain value still becomes Accessor<T>, an accessor or a signal still comes back as itself; only the case that was already lying is corrected.

Types only — every bundle in the chain is byte-identical, and no size pin moves. Checked across all eleven packages: lint, tsc --noEmit and the unit suites are green.

This matters beyond the two functions: nanoviews' props$ reads props in accessor form with the same typeof rule, and it types its result with ToAccessor. Before this fix, a callback prop taken as $onSelect would have been typed as an accessor wrapping the callback rather than as the callback itself.

`ToAccessor` and `ToAccessorOrSignal` tested `AnyAccessor`, so a function that takes an argument fell through to the wrapping branch: `toAccessor(event => …)` was typed `Accessor<(event) => …>` while it returned the callback itself, and `toAccessorOrSignal` claimed a `WritableSignal` of it.

The test behind both is `isAccessor`, which is `typeof value === 'function'`. It cannot tell a callback from an accessor and hands either one back untouched, so the types now test `AnyFn` and say the same.
@codecov

codecov Bot commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.33%. Comparing base (2891ec4) to head (d600d91).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #208   +/-   ##
=======================================
  Coverage   85.33%   85.33%           
=======================================
  Files         139      139           
  Lines        3143     3143           
  Branches      591      591           
=======================================
  Hits         2682     2682           
  Misses        332      332           
  Partials      129      129           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dangreen
dangreen merged commit 024e812 into main Aug 24, 2026
10 checks passed
@dangreen
dangreen deleted the fix/kida-to-accessor-any-fn branch August 24, 2026 15:44
@github-actions github-actions Bot mentioned this pull request Aug 24, 2026
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