Skip to content

feat(query): port TextQuery per-token and per-field weights (#17)#32

Draft
ajGingrich wants to merge 7 commits into
feat/textquery-stopwords-16from
feat/textquery-weights-17
Draft

feat(query): port TextQuery per-token and per-field weights (#17)#32
ajGingrich wants to merge 7 commits into
feat/textquery-stopwords-16from
feat/textquery-weights-17

Conversation

@ajGingrich
Copy link
Copy Markdown
Collaborator

Closes #17.

Summary

Brings TS TextQuery to parity with Python redisvl on text weighting:

  • textFieldName now accepts string | Record<string, number>.
  • New textWeights?: Record<string, number> for per-token bias.
  • Renders Redis Search => { $weight: N } syntax (dialect 2).
  • All state stays readonly — no mutators (intentional divergence from Python; documented).

Stacked on #31

This PR is based on feat/textquery-stopwords-16 (PR #31, stopwords port). The base branch is set to that PR so GitHub displays a clean diff. Do not merge until #31 lands. I will rebase onto main and undraft once that happens.

Behavior

Input Renders
textFieldName: 'd' @d:(quick | fox) (unchanged)
textFieldName: { title: 5 } @title:(quick | fox) => { $weight: 5 }
textFieldName: { title: 3, body: 1 } (@title:(quick | fox) => { $weight: 3 } | @body:(quick | fox))
textWeights: { apple: 2 } @d:(apple=>{$weight:2} | pear)

Tokens are lowercased before textWeights lookup (Python parity, leverages PR #31's normalization pipeline). Parsers use Object.create(null) so adversarial query tokens like constructor or __proto__ cannot resolve through the prototype chain.

What's covered

  • src/query/text.ts — widened config, parsers, render logic, JSDoc.
  • tests/unit/query/text.test.ts — new tests for field weights, text weights, validation, render output, Python-compat textFieldName getter, prototype-pollution regression.
  • tests/integration/query-types.test.ts — multi-field weighted ranking against a real Redis container.
  • website/docs/user-guide/filters-and-queries.md — weighting subsection.

Out of scope (intentional)

Verification

npm run lint                   # clean (only pre-existing huggingface warnings)
npm run type-check             # ok
npm run type-check:tests       # ok
npm run test:unit              # 486/486
npm test                       # all green (4 file-level vitest errors from .worktrees/ are pre-existing — not from this branch)
cd website && npm run build    # ok

Test plan

Reference

Python redisvl: redisvl/query/query.py:1273-1589.

🤖 Generated with Claude Code

- Widen textFieldName to string | Record<string, number>
- Expose fieldWeights as a frozen readonly record
- Validate weights are finite numbers > 0
- Reject empty records, arrays, non-string keys

Renders unchanged for now; multi-field rendering follows.
- Emit one clause per field, OR-joined when multi-field
- Omit => { $weight } when weight is exactly 1.0
- Wrap multi-field clauses in parens
- Preserve filter AND-wrapping
- Accept textWeights record; lowercase + trim keys at construction
- Reject keys with inner whitespace, negative/NaN/Infinity weights
- Allow weight of 0 for tokens (suppresses scoring contribution)
- Render matched tokens as token=>{$weight:N} inside the OR list
- Match keys case-insensitively against normalized query tokens
- Return bare string when single field has weight 1.0
- Return shallow copy of fieldWeights record otherwise
- Mirror Python query.py:1510-1521 for cross-language compatibility
- Update class JSDoc with per-field and per-token weight examples
- Remove stale "not yet ported" note
- Add user-guide subsection covering both forms with rendered output
- Document intentional mutator divergence from Python
- Seed two docs with matching terms in different text fields
- Query with per-field weights heavily favoring one field
- Assert the doc whose match is in the higher-weighted field ranks first
- Add JSDoc to fieldWeights and textWeights field declarations covering
  frozen-at-construction, insertion-order render semantics, and null-prototype
- Surface a clearer "textFieldName is required" error when the property is
  omitted or null (previously fell through to the generic shape error)
- Tighten the missing-textFieldName test to assert the message
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant