From ddcc0c723d6c9657a6f61894ae2a5aad2b74bfd2 Mon Sep 17 00:00:00 2001 From: David Snelling Date: Mon, 29 Jun 2026 10:29:20 -0700 Subject: [PATCH] refactor(8.0): remove the 4 deprecated query-operator aliases (clean break) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- docs/QUERY_OPERATORS.md | 16 +++++----- docs/api/README.md | 4 +-- docs/vfs/PROJECTION_STRATEGY_API.md | 18 +++++------ docs/vfs/SEMANTIC_VFS.md | 2 +- src/db/whereMatcher.ts | 10 ++----- src/utils/metadataFilter.ts | 30 +++++++------------ src/utils/metadataIndex.ts | 15 +++------- .../projections/TemporalProjection.ts | 10 +++---- tests/unit/db/whereMatcher.test.ts | 8 ++--- 9 files changed, 45 insertions(+), 68 deletions(-) diff --git a/docs/QUERY_OPERATORS.md b/docs/QUERY_OPERATORS.md index 16482de1..b1db7850 100644 --- a/docs/QUERY_OPERATORS.md +++ b/docs/QUERY_OPERATORS.md @@ -10,8 +10,8 @@ All operators work with `find({ where: { ... } })` and filter on **metadata fiel | Operator | Alias | Description | Example | |----------|-------|-------------|---------| -| `equals` | `eq`, `is` | Exact match | `{ status: { equals: 'active' } }` | -| `notEquals` | `ne`, `isNot` | Not equal | `{ status: { notEquals: 'deleted' } }` | +| `eq` | `equals` | Exact match | `{ status: { eq: 'active' } }` | +| `ne` | `notEquals` | Not equal | `{ status: { ne: 'deleted' } }` | **Shorthand:** A bare value is treated as `equals`: @@ -27,10 +27,10 @@ brain.find({ where: { status: { equals: 'active' } } }) | Operator | Alias | Description | Example | |----------|-------|-------------|---------| -| `greaterThan` | `gt` | Greater than | `{ age: { greaterThan: 18 } }` | -| `greaterEqual` | `gte` | Greater or equal | `{ score: { greaterEqual: 90 } }` | -| `lessThan` | `lt` | Less than | `{ price: { lessThan: 100 } }` | -| `lessEqual` | `lte` | Less or equal | `{ rating: { lessEqual: 3 } }` | +| `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] } }` | ```typescript @@ -160,9 +160,9 @@ Brainy's MetadataIndex supports a subset of operators natively for O(1) field lo | `equals` / `eq` | Yes | Yes | | `notEquals` / `ne` | — | Yes | | `greaterThan` / `gt` | Yes | Yes | -| `greaterEqual` / `gte` | Yes | Yes | +| `greaterThanOrEqual` / `gte` | Yes | Yes | | `lessThan` / `lt` | Yes | Yes | -| `lessEqual` / `lte` | Yes | Yes | +| `lessThanOrEqual` / `lte` | Yes | Yes | | `between` | Yes | Yes | | `oneOf` / `in` | Yes | Yes | | `noneOf` | — | Yes | diff --git a/docs/api/README.md b/docs/api/README.md index 5af84dec..4ca84364 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -413,9 +413,9 @@ Brainy uses clean, readable operators (BFO — Brainy Field Operators): | `equals` / `eq` | Exact match | `{age: {equals: 25}}` | | `notEquals` / `ne` | Not equal | `{status: {notEquals: 'deleted'}}` | | `greaterThan` / `gt` | Greater than | `{age: {greaterThan: 18}}` | -| `greaterEqual` / `gte` | Greater or equal | `{score: {greaterEqual: 90}}` | +| `gte` / `greaterThanOrEqual` | Greater or equal | `{score: {gte: 90}}` | | `lessThan` / `lt` | Less than | `{price: {lessThan: 100}}` | -| `lessEqual` / `lte` | Less or equal | `{rating: {lessEqual: 3}}` | +| `lte` / `lessThanOrEqual` | Less or equal | `{rating: {lte: 3}}` | | `between` | Inclusive range | `{year: {between: [2020, 2025]}}` | | `oneOf` / `in` | In array | `{color: {oneOf: ['red', 'blue']}}` | | `noneOf` | Not in array | `{status: {noneOf: ['deleted']}}` | diff --git a/docs/vfs/PROJECTION_STRATEGY_API.md b/docs/vfs/PROJECTION_STRATEGY_API.md index ab66e7c7..380862e1 100644 --- a/docs/vfs/PROJECTION_STRATEGY_API.md +++ b/docs/vfs/PROJECTION_STRATEGY_API.md @@ -289,7 +289,7 @@ export class SizeProjection extends BaseProjectionStrategy { where: { vfsType: 'file', size: { - greaterEqual: min, + gte: min, lessThan: max } }, @@ -305,7 +305,7 @@ export class SizeProjection extends BaseProjectionStrategy { where: { vfsType: 'file', size: { - greaterEqual: min, + gte: min, lessThan: max } }, @@ -359,7 +359,7 @@ export class StatusProjection extends BaseProjectionStrategy { const results = await brain.find({ where: { vfsType: 'file', - modified: { greaterEqual: oneDayAgo }, + modified: { gte: oneDayAgo }, reviewStatus: { missing: true } // No review status set }, limit: 1000 @@ -387,7 +387,7 @@ export class StatusProjection extends BaseProjectionStrategy { vfsType: 'file', anyOf: [ { reviewStatus: { exists: true } }, - { modified: { greaterEqual: Date.now() - 86400000 } } + { modified: { gte: Date.now() - 86400000 } } ] }, limit @@ -415,7 +415,7 @@ Projection strategies use **Brainy Field Operators** (BFO), not MongoDB-style op { size: { $gte: 1000, $lte: 5000 } } // ✅ BFO style (CORRECT) -{ size: { greaterEqual: 1000, lessEqual: 5000 } } +{ size: { gte: 1000, lte: 5000 } } ``` ### Logical Operators @@ -451,9 +451,9 @@ Projection strategies use **Brainy Field Operators** (BFO), not MongoDB-style op // Comparison { field: value } // Exact match { field: { greaterThan: 10 } } // > -{ field: { greaterEqual: 10 } } // >= +{ field: { gte: 10 } } // >= { field: { lessThan: 10 } } // < -{ field: { lessEqual: 10 } } // <= +{ field: { lte: 10 } } // <= { field: { not: value } } // != // Logical @@ -485,7 +485,7 @@ All metadata fields are automatically indexed. Use direct equality or range quer ```typescript // ✅ Fast: Direct index lookup (O(log n)) { priority: 'high' } -{ size: { greaterEqual: 1000 } } +{ size: { gte: 1000 } } // ⚠️ Slower: Must scan results { path: { matches: /complex-regex/ } } @@ -668,7 +668,7 @@ async resolve(brain, vfs, period: string) { const results = await brain.find({ where: { - modified: { greaterEqual: since } + modified: { gte: since } } }) return this.extractIds(results) diff --git a/docs/vfs/SEMANTIC_VFS.md b/docs/vfs/SEMANTIC_VFS.md index 40807d3c..9298c822 100644 --- a/docs/vfs/SEMANTIC_VFS.md +++ b/docs/vfs/SEMANTIC_VFS.md @@ -127,7 +127,7 @@ await vfs.readFile('/as-of/2024-03-15/auth.ts') // the path only resolves if auth.ts was modified that day ``` -**How it works:** Tracks the `modified` timestamp on every file and runs a range query (`greaterEqual`/`lessEqual`) over one 24-hour window for O(log n) performance. The VFS does not store historical file contents — `/as-of/` filters by *when a file last changed*; reads return the current bytes. For point-in-time state, use the Db API (`brain.asOf(generation)`). +**How it works:** Tracks the `modified` timestamp on every file and runs a range query (`gte`/`lte`) over one 24-hour window for O(log n) performance. The VFS does not store historical file contents — `/as-of/` filters by *when a file last changed*; reads return the current bytes. For point-in-time state, use the Db API (`brain.asOf(generation)`). **Status:** ✅ Fully implemented and tested at 10K file scale diff --git a/src/db/whereMatcher.ts b/src/db/whereMatcher.ts index 51c635a7..c5469209 100644 --- a/src/db/whereMatcher.ts +++ b/src/db/whereMatcher.ts @@ -39,9 +39,9 @@ export class UnsupportedWhereOperatorError extends Error { constructor(operator: string) { super( `The where-operator '${operator}' is not supported for historical/speculative ` + - `in-memory evaluation. Supported: eq/equals/is, ne/notEquals/isNot, in/oneOf, ` + - `gt/greaterThan, gte/greaterThanOrEqual/greaterEqual, lt/lessThan, ` + - `lte/lessThanOrEqual/lessEqual, between, contains, exists, missing, ` + + `in-memory evaluation. Supported: eq/equals, ne/notEquals, in/oneOf, ` + + `gt/greaterThan, gte/greaterThanOrEqual, lt/lessThan, ` + + `lte/lessThanOrEqual, between, contains, exists, missing, ` + `plus allOf/anyOf/not.` ) this.name = 'UnsupportedWhereOperatorError' @@ -150,12 +150,10 @@ function fieldConditionMatches(value: unknown, condition: unknown): boolean { for (const [op, operand] of Object.entries(condition as Record)) { let matches: boolean switch (op) { - case 'is': case 'equals': case 'eq': matches = eqMatches(value, operand) break - case 'isNot': case 'notEquals': case 'ne': matches = !eqMatches(value, operand) @@ -170,7 +168,6 @@ function fieldConditionMatches(value: unknown, condition: unknown): boolean { matches = cmp !== null && cmp > 0 break } - case 'greaterEqual': case 'greaterThanOrEqual': case 'gte': { const cmp = compare(value, operand) @@ -183,7 +180,6 @@ function fieldConditionMatches(value: unknown, condition: unknown): boolean { matches = cmp !== null && cmp < 0 break } - case 'lessEqual': case 'lessThanOrEqual': case 'lte': { const cmp = compare(value, operand) diff --git a/src/utils/metadataFilter.ts b/src/utils/metadataFilter.ts index f38df4f5..ed0bb1c1 100644 --- a/src/utils/metadataFilter.ts +++ b/src/utils/metadataFilter.ts @@ -11,17 +11,21 @@ import { SearchResult, HNSWNoun, HNSWNounWithMetadata } from '../coreTypes.js' * Designed for performance, clarity, and patent independence */ export interface BrainyFieldOperators { - // Equality operators + // Equality operators (canonical + long-form aliases) + eq?: any equals?: any + ne?: any notEquals?: any - is?: any - isNot?: any - - // Comparison operators + + // Comparison operators (canonical + long-form aliases) greaterThan?: any - greaterEqual?: any + gt?: any + greaterThanOrEqual?: any + gte?: any lessThan?: any - lessEqual?: any + lt?: any + lessThanOrEqual?: any + lte?: any between?: [any, any] // Array/Set operators @@ -45,14 +49,6 @@ export interface BrainyFieldOperators { allOf?: MetadataFilter[] anyOf?: MetadataFilter[] not?: MetadataFilter - - // Short aliases for common operations - eq?: any - ne?: any - gt?: any - gte?: any - lt?: any - lte?: any } /** @@ -88,12 +84,10 @@ function matchesQuery(value: any, query: any): boolean { switch (op) { // Equality operators case 'equals': - case 'is': case 'eq': if (value !== operand) return false break case 'notEquals': - case 'isNot': case 'ne': // Special handling: if value is undefined and operand is not undefined, // they are not equal (so the condition passes) @@ -108,7 +102,6 @@ function matchesQuery(value: any, query: any): boolean { case 'gt': if (typeof value !== 'number' || typeof operand !== 'number' || !(value > operand)) return false break - case 'greaterEqual': case 'gte': if (typeof value !== 'number' || typeof operand !== 'number' || !(value >= operand)) return false break @@ -116,7 +109,6 @@ function matchesQuery(value: any, query: any): boolean { case 'lt': if (typeof value !== 'number' || typeof operand !== 'number' || !(value < operand)) return false break - case 'lessEqual': case 'lte': if (typeof value !== 'number' || typeof operand !== 'number' || !(value <= operand)) return false break diff --git a/src/utils/metadataIndex.ts b/src/utils/metadataIndex.ts index 59259150..676c3b2e 100644 --- a/src/utils/metadataIndex.ts +++ b/src/utils/metadataIndex.ts @@ -1760,7 +1760,6 @@ export class MetadataIndexManager implements MetadataIndexProvider { } break case 'equals': - case 'is': case 'eq': criteria.push({ field: key, values: [operand] }) break @@ -1770,8 +1769,6 @@ export class MetadataIndexManager implements MetadataIndexProvider { break case 'greaterThan': case 'lessThan': - case 'greaterEqual': - case 'lessEqual': case 'between': // Range queries will be handled separately // Sorted index will be created/loaded when needed in getIdsForRange @@ -1881,16 +1878,14 @@ export class MetadataIndexManager implements MetadataIndexProvider { fieldResults = [] switch (op) { // ===== EQUALITY OPERATORS ===== - // Canonical: 'eq' | Alias: 'equals' | Deprecated: 'is' - case 'is': // DEPRECATED: Use 'eq' instead + // Canonical: 'eq' | Alias: 'equals' case 'equals': // Alias for 'eq' case 'eq': fieldResults = await this.getIds(field, operand) break // ===== NEGATION OPERATORS ===== - // Canonical: 'ne' | Alias: 'notEquals' | Deprecated: 'isNot' - case 'isNot': // DEPRECATED: Use 'ne' instead + // Canonical: 'ne' | Alias: 'notEquals' case 'notEquals': // Alias for 'ne' case 'ne': { // For notEquals, we need all IDs EXCEPT those matching the value @@ -1931,8 +1926,7 @@ export class MetadataIndexManager implements MetadataIndexProvider { break // ===== GREATER THAN OR EQUAL OPERATORS ===== - // Canonical: 'gte' | Alias: 'greaterThanOrEqual' | Deprecated: 'greaterEqual' - case 'greaterEqual': // DEPRECATED: Use 'gte' instead + // Canonical: 'gte' | Alias: 'greaterThanOrEqual' case 'greaterThanOrEqual': // Alias for 'gte' case 'gte': fieldResults = await this.getIdsForRange(field, operand, undefined, true, true) @@ -1946,8 +1940,7 @@ export class MetadataIndexManager implements MetadataIndexProvider { break // ===== LESS THAN OR EQUAL OPERATORS ===== - // Canonical: 'lte' | Alias: 'lessThanOrEqual' | Deprecated: 'lessEqual' - case 'lessEqual': // DEPRECATED: Use 'lte' instead + // Canonical: 'lte' | Alias: 'lessThanOrEqual' case 'lessThanOrEqual': // Alias for 'lte' case 'lte': fieldResults = await this.getIdsForRange(field, undefined, operand, true, true) diff --git a/src/vfs/semantic/projections/TemporalProjection.ts b/src/vfs/semantic/projections/TemporalProjection.ts index 41b29b68..f002a07e 100644 --- a/src/vfs/semantic/projections/TemporalProjection.ts +++ b/src/vfs/semantic/projections/TemporalProjection.ts @@ -37,8 +37,8 @@ export class TemporalProjection extends BaseProjectionStrategy { where: { vfsType: 'file', modified: { - greaterEqual: startOfDay.getTime(), // BFO operator - lessEqual: endOfDay.getTime() // BFO operator + gte: startOfDay.getTime(), // BFO operator + lte: endOfDay.getTime() // BFO operator } }, limit: 1000 @@ -74,8 +74,8 @@ export class TemporalProjection extends BaseProjectionStrategy { where: { vfsType: 'file', modified: { - greaterEqual: startOfDay.getTime(), - lessEqual: endOfDay.getTime() + gte: startOfDay.getTime(), + lte: endOfDay.getTime() } }, limit: 1000 @@ -93,7 +93,7 @@ export class TemporalProjection extends BaseProjectionStrategy { const results = await brain.find({ where: { vfsType: 'file', - modified: { greaterEqual: oneDayAgo } + modified: { gte: oneDayAgo } }, limit }) diff --git a/tests/unit/db/whereMatcher.test.ts b/tests/unit/db/whereMatcher.test.ts index 42f88a7a..2223117c 100644 --- a/tests/unit/db/whereMatcher.test.ts +++ b/tests/unit/db/whereMatcher.test.ts @@ -80,10 +80,9 @@ describe('db/whereMatcher — operators', () => { expect(whereMatches(e, { status: 'closed' })).toBe(false) }) - it('eq / equals / is aliases', () => { + it('eq / equals aliases', () => { expect(whereMatches(e, { amount: { eq: 250 } })).toBe(true) expect(whereMatches(e, { amount: { equals: 250 } })).toBe(true) - expect(whereMatches(e, { amount: { is: 250 } })).toBe(true) expect(whereMatches(e, { amount: { eq: 99 } })).toBe(false) }) @@ -93,10 +92,9 @@ describe('db/whereMatcher — operators', () => { expect(whereMatches(e, { tags: 'missing-tag' })).toBe(false) }) - it('ne / notEquals / isNot aliases', () => { + it('ne / notEquals aliases', () => { expect(whereMatches(e, { status: { ne: 'closed' } })).toBe(true) expect(whereMatches(e, { status: { notEquals: 'open' } })).toBe(false) - expect(whereMatches(e, { status: { isNot: 'open' } })).toBe(false) }) it('in / oneOf set membership', () => { @@ -109,12 +107,10 @@ describe('db/whereMatcher — operators', () => { expect(whereMatches(e, { amount: { greaterThan: 250 } })).toBe(false) expect(whereMatches(e, { amount: { gte: 250 } })).toBe(true) expect(whereMatches(e, { amount: { greaterThanOrEqual: 251 } })).toBe(false) - expect(whereMatches(e, { amount: { greaterEqual: 250 } })).toBe(true) expect(whereMatches(e, { amount: { lt: 251 } })).toBe(true) expect(whereMatches(e, { amount: { lessThan: 250 } })).toBe(false) expect(whereMatches(e, { amount: { lte: 250 } })).toBe(true) expect(whereMatches(e, { amount: { lessThanOrEqual: 249 } })).toBe(false) - expect(whereMatches(e, { amount: { lessEqual: 250 } })).toBe(true) }) it('string range comparison is lexicographic', () => {