denoland/deno · error · TypeError

Selector can not specify both 'start' and 'end' key when…

Error message

Selector can not specify both 'start' and 'end' key when specifying 'prefix'

What it means

A Kv list selector may combine prefix with at most one range bound: {prefix, start?} or {prefix, end?}. Specifying prefix together with both start and end throws TypeError("Selector can not specify both 'start' and 'end' key when specifying 'prefix'") — the prefix already fixes the bounds of the range, so a third constraint is contradictory.

Solutions

  1. Drop the prefix and keep { start, end } when you need an explicit range.
  2. Keep the prefix with exactly one bound: { prefix, end } pages up to a boundary; { prefix, start } pages from one.
  3. Build selectors through a helper that enforces at most one bound alongside prefix.

Example fix

// before
kv.list({ prefix: ["users"], start: ["users", "a"], end: ["users", "z"] });

// after
kv.list({ start: ["users", "a"], end: ["users", "z"] });
// or, if scoping to the prefix is what you want:
kv.list({ prefix: ["users"], end: ["users", "z"] });
Defensive patterns

Strategy: validation

Validate before calling

function makeSelector(s: Record<string, unknown>) {
  const has = (k: string) => Object.hasOwn(s, k) && s[k] !== undefined;
  if (has("prefix") && has("start") && has("end")) {
    const { start, end, ...rest } = s; // drop prefix instead: full range wins
    return { start, end };
  }
  return s;
}
kv.list(makeSelector(rawSelector));

Type guard

type SafeSelector =
  | { prefix: Deno.KvKey; start?: Deno.KvKey }
  | { prefix: Deno.KvKey; end?: Deno.KvKey }
  | { start: Deno.KvKey; end: Deno.KvKey };

const isSafeSelector = (s: SafeSelector): boolean =>
  !("prefix" in s && "start" in s && "end" in s);

Prevention

When it happens

Trigger: `kv.list({ prefix: ["users"], start: ["users", "a"], end: ["users", "z"] })` — selectors assembled generically from whatever fields are present, or migrated from a {start, end} selector to prefix without removing the old bounds.

Common situations: Generic query builders attaching every provided option; config objects reused across queries; refactors that add a prefix for scoping but leave legacy bounds in place.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/72d5e0982848458a. Report an issue: GitHub.

Appendix: source

Thrown at ext/kv/01_db.ts:873

      ) => Promise<Deno.KvEntry<unknown>[]>;
    },
  ) {
    super();
    let prefix: Deno.KvKey | undefined;
    let start: Deno.KvKey | undefined;
    let end: Deno.KvKey | undefined;
    if (ObjectHasOwn(selector, "prefix") && selector.prefix !== undefined) {
      prefix = ObjectFreeze(ArrayPrototypeSlice(selector.prefix));
    }
    if (ObjectHasOwn(selector, "start") && selector.start !== undefined) {
      start = ObjectFreeze(ArrayPrototypeSlice(selector.start));
    }
    if (ObjectHasOwn(selector, "end") && selector.end !== undefined) {
      end = ObjectFreeze(ArrayPrototypeSlice(selector.end));
    }
    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",
        );
      }

View on GitHub (pinned to 9ad36f7a2c)