denoland/deno · error · TypeError
Selector must specify either 'prefix' or both 'start' and…
Error message
Selector must specify either 'prefix' or both 'start' and 'end' key
What it means
Without a prefix, a selector must define a complete range: both start and end keys. Anything else — {start} alone, {end} alone, or an empty selector {} — throws TypeError("Selector must specify either 'prefix' or both 'start' and 'end' key"). The rationale: prefix bounds the range by itself, while a half-open pair would request an unbounded scan of the keyspace.
Solutions
- Validate the selector shape before calling list(): require prefix or both bounds.
- When a half-open range is wanted, synthesize the missing bound: start defaults to [] (very first key) or end to a sentinel key above your last real key.
- Type selectors as a discriminated union (prefix form vs range form) so TypeScript enforces the shape.
Example fix
// before
kv.list({ start: ["2026"] }); // throws
// after
kv.list({ start: ["2026"], end: ["2027"] });
// or scope with a prefix instead:
kv.list({ prefix: ["2026"] }); Defensive patterns
Strategy: type-guard
Validate before calling
function normalizeSelector(s: Record<string, undefined | Deno.KvKey>) {
const has = (k: string) => s[k] !== undefined;
if (has("prefix")) return { prefix: s.prefix! };
if (has("start") && has("end")) return { start: s.start!, end: s.end! };
// synthesize missing bounds for half-open ranges
if (has("start")) return { start: s.start!, end: ["\uffff"] };
if (has("end")) return { start: [], end: s.end! };
return { prefix: [] }; // full scan, explicitly
} Type guard
type ListSelector =
| { prefix: Deno.KvKey; start?: Deno.KvKey; end?: Deno.KvKey }
| { start: Deno.KvKey; end: Deno.KvKey };
const isCompleteSelector = (s: ListSelector): boolean =>
"prefix" in s || ("start" in s && "end" in s); Prevention
- Require prefix or both bounds before calling kv.list()
- Validate user-supplied filter shapes and synthesize missing bounds deliberately
- Use a discriminated-union type for selectors so TS rejects half-open forms
When it happens
Trigger: `kv.list({ start: ["a"] })`; `kv.list({ end: ["z"] })`; `kv.list({})`; selectors built from optional fields where all of prefix/start/end ended up absent (undefined values are ignored by the hasOwn checks).
Common situations: Filter UIs where both bounds are optional and the user supplies only one; spreading a config that may omit keys; porting from cursor-only iteration models that have no range concept.
Related errors
- Selector can not specify both 'start' and 'end' key when…
- batchSize must be a positive integer: received
- Cannot get cursor before first iteration
- Delay cannot be greater than 30 days: received
- Delay cannot be NaN
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/e94de3ac58d94f3a.
Report an issue: GitHub.
Appendix: source
Thrown at ext/kv/01_db.ts:888
}
if (prefix) {
if (start && end) {
throw new TypeError(
"Selector can not specify both 'start' and 'end' key when specifying 'prefix'",
);
}
if (start) {
this.#selector = { prefix, start };
} else if (end) {
this.#selector = { prefix, end };
} else {
this.#selector = { prefix };
}
} else {
if (start && end) {
this.#selector = { start, end };
} else {
throw new TypeError(
"Selector must specify either 'prefix' or both 'start' and 'end' key",
);
}
}
ObjectFreeze(this.#selector);
this.#pullBatch = pullBatch;
this.#limit = limit;
this.#reverse = reverse;
this.#consistency = consistency;
this.#batchSize = batchSize;
this.#cursorGen = cursor ? () => cursor : null;
}
get cursor(): string {
if (this.#cursorGen === null) {
throw new Error("Cannot get cursor before first iteration");
}
View on GitHub (pinned to 9ad36f7a2c)