TypeScript paginated query type loses compile‑time size safety for array inputs
0 reputation · 04 Sept 2025, 18:02 UTC
0 reputation · 04 Sept 2025, 18:02 UTC
When building a paginated query helper in TypeScript, the goal is to retain a compile‑time guarantee that the returned page contains exactly the requested number of items, regardless of whether the source dataset is a fixed tuple or a runtime array.
Using a generic Page with a Slice utility works precisely when T is a tuple, because the length is known and the sliced type preserves the limit. If T is an array type (T[]), TypeScript cannot infer the exact length, so the data field falls back to T[] and the page‑size guarantee is lost. The unresolved decision is whether to require users to annotate datasets as tuples (or use branded length types) to keep exact pagination types, or to accept a less precise T[]-based API that sacrifices compile‑time safety.
Should the library enforce tuple‑based input to keep exact pagination types? Is there a way to retain size safety for array‑like inputs without forcing manual tuple conversion? Does accepting a T[]-based API compromise type safety enough to warrant a separate strict mode?
34775 reputation · 05 Sept 2025, 00:12 UTC
Enforce tuple-based input only when your callers already have tuple-typed data; otherwise the API becomes unusable for dynamic datasets. You cannot retain compile-time size safety for arbitrary runtime arrays without branded length types or explicit assertions. Accepting T[] is the pragmatic default, and a strict mode is justified only if it changes the accepted input type—for example, to tuples or branded arrays—rather than merely narrowing a generic parameter.
Likely explanation: TypeScript erases literal length information at the assignment boundary. Once a value is typed as T[], the compiler no longer tracks how many elements it contains, so a Slice<T, N> utility falls back to T[].
Confirmed behavior: as const and variadic tuple inference preserve length for literal arrays and parameters inferred as tuples, but neither reverses widening for a value already declared as T[]. A branded length type such as ArrayWithLength<T, N> can encode the size, yet it requires explicit construction or runtime validation and does not make ordinary arrays safe automatically.
as const or variadic tuple inference so the Page type preserves the exact size.T[] and return T[]. Move the guarantee to runtime validation: assert data.length === limit (or <= limit for the final page) before returning.number[].Build a minimal repro and run tsc --noEmit:
type Page<T, N extends number> = {
data: Slice<T, N>;
limit: N;
};
declare function paginate<T, N extends number>(
data: T,
limit: N
): Page<T, N>;
const literal = [1, 2, 3] as const;
const dynamic: number[] = [1, 2, 3];
paginate(literal, 2); // data is a tuple
paginate(dynamic, 2); // data widens to number[]
Inspect the inferred type of data in both calls. The tuple case preserves length; the array case does not. If you adopt branded lengths, verify that constructing from a plain array requires an explicit assertion or runtime check and that mismatched lengths are rejected.
Which TypeScript version are you targeting? Inference for variadic tuples and const type parameters has improved across minor releases. If you are on an older version, some tuple-preserving patterns may not infer as expected, which would change whether a strict overload is worth the complexity.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.