brainy/docs/QUERY_OPERATORS.md
David Snelling ddcc0c723d refactor(8.0): remove the 4 deprecated query-operator aliases (clean break)
8.0 keeps the canonical operators (eq/ne/gt/gte/lt/lte) and their clean
long-form aliases (equals/notEquals/greaterThan/greaterThanOrEqual/lessThan/
lessThanOrEqual), and drops the four redundant deprecated spellings:
  is → eq,  isNot → ne,  greaterEqual → gte,  lessEqual → lte

Removed from every evaluator (metadataIndex criteria + range switches,
metadataFilter, the db whereMatcher egress path) and from the
BrainyFieldOperators type, the unsupported-operator error message, the docs
(QUERY_OPERATORS / api README / VFS projection + semantic guides), and the
whereMatcher alias tests. Also migrated Brainy's own internal use — the VFS
TemporalProjection queried with greaterEqual/lessEqual, which would have
silently broken — to gte/lte.

Full gate green: build, unit 1512, integration 607.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 10:29:20 -07:00

8 KiB

Query Operators (BFO)

Brainy Field Operators — the complete reference for where filters in find().

All operators work with find({ where: { ... } }) and filter on metadata fields (not data).


Equality

Operator Alias Description Example
eq equals Exact match { status: { eq: 'active' } }
ne notEquals Not equal { status: { ne: 'deleted' } }

Shorthand: A bare value is treated as equals:

// These are equivalent:
brain.find({ where: { status: 'active' } })
brain.find({ where: { status: { equals: 'active' } } })

Comparison

Operator Alias Description Example
gt greaterThan Greater than { age: { gt: 18 } }
gte greaterThanOrEqual Greater or equal { score: { gte: 90 } }
lt lessThan Less than { price: { lt: 100 } }
lte lessThanOrEqual Less or equal { rating: { lte: 3 } }
between Inclusive range [min, max] { year: { between: [2020, 2025] } }
// Range query
const recent = await brain.find({
  where: {
    createdAt: { between: [Date.now() - 86400000, Date.now()] }
  }
})

Array / Set

Operator Alias Description Example
oneOf in Value is one of the given options { color: { oneOf: ['red', 'blue'] } }
noneOf Value is NOT one of the given options { status: { noneOf: ['deleted', 'archived'] } }
contains Array field contains value { tags: { contains: 'ai' } }
excludes Array field does NOT contain value { tags: { excludes: 'spam' } }
hasAll Array field contains ALL listed values { skills: { hasAll: ['js', 'ts'] } }
// Find entities tagged with 'ai'
const aiEntities = await brain.find({
  where: { tags: { contains: 'ai' } }
})

// Find entities of specific types
const people = await brain.find({
  where: { noun: { oneOf: ['Person', 'Agent'] } }
})

Existence

Operator Description Example
exists: true Field exists (has any value) { email: { exists: true } }
exists: false Field does NOT exist { email: { exists: false } }
missing: true Field does NOT exist (alias for exists: false) { email: { missing: true } }
missing: false Field exists (alias for exists: true) { email: { missing: false } }
// Find entities that have an email field
const withEmail = await brain.find({
  where: { email: { exists: true } }
})

Pattern (In-Memory Only)

These operators work via the in-memory filter path. They are applied after the indexed query, so use them with other indexed operators for best performance.

Operator Description Example
matches Regex or string pattern match { name: { matches: /^Dr\./ } }
startsWith String prefix { name: { startsWith: 'John' } }
endsWith String suffix { email: { endsWith: '@gmail.com' } }
const doctors = await brain.find({
  where: {
    type: NounType.Person,           // Indexed — fast
    name: { startsWith: 'Dr.' }     // In-memory — applied after
  }
})

Logical

Combine multiple conditions:

Operator Description Example
allOf ALL sub-filters must match (AND) { allOf: [{ status: 'active' }, { role: 'admin' }] }
anyOf ANY sub-filter must match (OR) { anyOf: [{ role: 'admin' }, { role: 'owner' }] }
not Invert a filter { not: { status: 'deleted' } }
// Complex OR query
const adminsOrOwners = await brain.find({
  where: {
    anyOf: [
      { role: 'admin' },
      { role: 'owner' }
    ]
  }
})

// NOT query
const notDeleted = await brain.find({
  where: {
    not: { status: 'deleted' }
  }
})

// Combined AND + OR
const results = await brain.find({
  where: {
    allOf: [
      { department: 'engineering' },
      { anyOf: [
        { level: 'senior' },
        { yearsExperience: { greaterThan: 5 } }
      ]}
    ]
  }
})

Indexed vs In-Memory Operators

Brainy's MetadataIndex supports a subset of operators natively for O(1) field lookups. Other operators fall back to in-memory filtering.

Operator MetadataIndex (Indexed) In-Memory Fallback
equals / eq Yes Yes
notEquals / ne Yes
greaterThan / gt Yes Yes
greaterThanOrEqual / gte Yes Yes
lessThan / lt Yes Yes
lessThanOrEqual / lte Yes Yes
between Yes Yes
oneOf / in Yes Yes
noneOf Yes
contains Yes Yes
exists / missing Yes Yes
matches Yes
startsWith Yes
endsWith Yes
allOf Partial Yes
anyOf Partial Yes
not Yes

Performance tip: Combine indexed operators (equals, greaterThan, oneOf, between, contains, exists) with pattern operators for optimal speed — the index narrows results first, then patterns filter in memory.


Practical Examples

Filter by entity type

// Using the type shorthand (recommended)
brain.find({ type: NounType.Person })

// Using where.noun directly
brain.find({ where: { noun: NounType.Person } })

// Multiple types
brain.find({ type: [NounType.Person, NounType.Agent] })

Filter by subtype

subtype is a top-level standard field — takes the column-store fast path, not the metadata fallback. Pair with type for the typical "Person who is an employee" query:

// Equality on subtype:
brain.find({ type: NounType.Person, subtype: 'employee' })

// Set membership:
brain.find({ type: NounType.Person, subtype: ['employee', 'contractor'] })

// Operator-form predicates use `where`:
brain.find({
  type: NounType.Person,
  where: { subtype: { exists: true } }
})

See the Subtypes & Facets guide for the full surface.

Filter relationships by subtype (7.30+)

Verbs are first-class peers — related() and graph traversal both honor subtype filters on the fast path:

// Filter relationships by VerbType subtype
const direct = await brain.related({
  from: ceoId,
  type: VerbType.ReportsTo,
  subtype: 'direct'
})

// Set membership on verb subtype
const all = await brain.related({
  from: ceoId,
  type: VerbType.ReportsTo,
  subtype: ['direct', 'dotted-line']
})

// Graph traversal — subtype filters traversal edges (depth-1 in 7.30 JS path;
// multi-hop subtype filtering lands on Cortex native)
const reports = await brain.find({
  connected: {
    from: ceoId,
    via: VerbType.ReportsTo,
    subtype: 'direct',
    depth: 1
  }
})

Combine semantic search with filters

const results = await brain.find({
  query: 'machine learning engineer',     // Semantic search (on data)
  type: NounType.Person,                  // Type filter (indexed)
  where: {
    department: 'engineering',            // Exact match (indexed)
    yearsExperience: { greaterThan: 3 }   // Range filter (indexed)
  },
  limit: 10
})

Temporal queries

const lastWeek = Date.now() - 7 * 24 * 60 * 60 * 1000
const recentEntities = await brain.find({
  where: {
    createdAt: { greaterThan: lastWeek }
  },
  orderBy: 'createdAt',
  order: 'desc',
  limit: 50
})

Graph + metadata combination

const results = await brain.find({
  connected: {
    from: teamLeadId,
    via: VerbType.WorksWith,
    depth: 2
  },
  where: {
    role: { oneOf: ['engineer', 'designer'] },
    active: true
  }
})

See Also