chroma-core/chroma · error · TypeError

${context} must be a rank expression

Error message

${context} must be a rank expression

What it means

Thrown by requireRank (rank.ts:437) when a value in a rank-operand position fails to convert via RankExpression.from. Because from() accepts RankExpression instances, plain numbers, and plain objects — and throws its own distinct TypeError for other types — this message fires specifically when the operand is null or undefined. The ${context} prefix names the exact slot: 'Sub left', 'Div right', 'Sum operand 0', 'ranks[1]', 'add operand 0', 'Abs', 'Exp', 'Log', etc.

Source

Thrown at clients/new-js/packages/chromadb/src/execution/expression/rank.ts:437

    throw new TypeError("Knn default must be a finite number");
  }

  return {
    query:
      Array.isArray(query) || typeof query === "string"
        ? query
        : deepClone(query),
    key,
    limit,
    defaultValue,
    returnRank: options.returnRank ?? false,
  };
};

const requireRank = (input: RankInput, context: string): RankExpression => {
  const result = RankExpression.from(input);
  if (!result) {
    throw new TypeError(`${context} must be a rank expression`);
  }
  return result;
};

export const Val = (value: number): RankExpression =>
  new ValueRankExpression(requireNumber(value, "Val requires a numeric value"));

export const Knn = (options: KnnOptions): RankExpression =>
  new KnnRankExpression(normalizeKnnOptions(options));

export interface RrfOptions {
  ranks: RankInput[];
  k?: number;
  weights?: number[];
  normalize?: boolean;
}

export const Rrf = ({

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Find the slot named in the message (e.g. 'Sub left' = first argument of Sub) and ensure it is a RankExpression, number, or plain-object literal
  2. Filter null/undefined out of operand arrays before spreading: parts.filter(p => p !== null && p !== undefined)
  3. Give optional components an explicit numeric default: maybeRank ?? 0

Example fix

// before
const rank = Sum(...components); // components may contain undefined

// after
const rank = Sum(
  ...components.filter((p): p is Exclude<typeof p, null | undefined> => p != null),
);
Defensive patterns

Strategy: validation

Validate before calling

const compact = (parts: RankInput[]): RankInput[] =>
  parts.filter((p): p is Exclude<RankInput, null | undefined> => p != null);
if (compact(parts).length === 0) throw new Error('no rank components');
const rank = Sum(...compact(parts));

Type guard

const isNonNullRankInput = (v: unknown): v is RankInput =>
  v instanceof RankExpression || typeof v === 'number' || typeof v === 'object';

Try / catch

try {
  const rank = Sub(left, right);
} catch (e) {
  if (e instanceof TypeError && e.message.includes('must be a rank expression')) {
    // message names the slot, e.g. 'Sub left' — fix that exact operand
    throw new Error(`bad rank operand: ${e.message}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Sub(null, Val(1)); expr.add(undefined); Mul(Val(2), null); Rrf({ ranks: [Val(1), null] }); Abs(undefined). Most common real shape: building operands from an array containing optional/missing entries — Sum(...parts) where parts includes null or undefined.

Common situations: Conditionally assembled rank expressions where one branch produced undefined (e.g. an if without else); map lookups (dictionary.get(key)) returning undefined; API responses where a scoring component is absent; TypeScript any-typed pipelines that defeat compile-time checks.

Related errors


AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16). Data as JSON: /api/errors/dbdc4b5bf0b10428. Report an issue: GitHub.