Repository navigation
Description
challenge / use case
A feed of loosely typed items is routinely narrowed with a type guard before the next step reads it, but filter()
accepts only a boolean predicate and reports the input type unchanged:
export function filter<V>(predicate: (item: NoInfer<V>) => Awaitable<boolean>): Task<V>;A guard such as (value: unknown): value is Record<PropertyKey, unknown> is accepted, as it returns a boolean, but
the task downstream still sees unknown, so the mapper that follows fails to type-check:
pipe(
(items(scripts))
(map(node => parse(string(node)))) // unknown
(filter(isEvent)) // still unknown
(map(render)) // TS2345: unknown is not assignable to Record<PropertyKey, unknown>
);Observed on @metreeca/flow 0.12.0 while reading schema.org blocks out of a page in ec2u/pipe
(packages/loader/src/events/tasks/read.ts). Array.prototype.filter narrows on a guard, so a pipeline reads as
less typed than the array code it replaces.
proposal
Add a guard overload to filter(), keeping the boolean one as it stands:
export function filter<V, R extends V>(guard: (item: NoInfer<V>) => item is R): Task<V, R>;
export function filter<V>(predicate: (item: NoInfer<V>) => Awaitable<boolean>): Task<V>;A guard is synchronous by nature (item is R cannot be awaited), so the overload takes no Awaitable. The body is
unchanged: only the reported type differs.
alternatives
-
Splitting the item into a zero-or-one feed and splicing it back, which is what the pipeline above does today:
(flat(map(node => items(events(parse(string(node))))))) // events(): unknown → readonly Record[]
It types correctly and streams, but states a filter as a flat map and needs a helper returning an array where a
predicate would do. -
Casting after the filter, which the project's conventions rule out.
notes
when() has the same shape (test: (value: NoInfer<V>) => boolean), and its pass branch would benefit from the
same narrowing; left out of this request to keep it to one task.
Activity
feat: narrow filter and when items through type guards
knoan commentedon Oct 26, 2026
Solution
Added type guard overloads to
filter()andwhen(), so a guard narrows the item type downstream instead of leaving it unchanged.Changes
filter(): a type guard narrows the retained items to the type it admits; plain predicates keep the item typewhen(): a type guard narrows the items routed to the first task; the second task receives them unchangedRelated
release/0.13, shipping in 0.13.0