{"version":3,"file":"filters.cjs","names":[],"sources":["../../../src/batteries/vector/filters.ts"],"sourcesContent":["/**\n * Filter types and evaluator for the vector battery.\n *\n * @module @nhtio/adk/batteries/vector/filters\n */\n\nimport { isObject } from '@nhtio/adk/guards'\nimport { validator } from '@nhtio/validation'\nimport type { VectorMetadata, VectorMetadataValue } from './types'\n\n/** A comparison operator usable in a {@link FilterCondition}. */\nexport type FilterOperator =\n  | 'eq'\n  | 'ne'\n  | 'gt'\n  | 'gte'\n  | 'lt'\n  | 'lte'\n  | 'in'\n  | 'nin'\n  | 'exists'\n  | 'contains'\n\n/** A raw, adapter-dialect filter expression: literal text plus positional bindings. */\nexport interface RawExpr {\n  /** The raw expression text. */\n  __raw: string\n  /** Positional binding values substituted into the expression. */\n  bindings: unknown[]\n}\n\n/**\n * Construct a {@link RawExpr} from literal text and optional positional `bindings`.\n *\n * @param sql - The raw expression text.\n * @param bindings - Positional binding values.\n */\nexport const raw = (sql: string, bindings: unknown[] = []): RawExpr => ({ __raw: sql, bindings })\n\n/** Type guard: `true` if `v` is a {@link RawExpr}. */\nexport const isRawExpr = (v: unknown): v is RawExpr =>\n  isObject(v) && typeof v.__raw === 'string' && Array.isArray(v.bindings)\n\n/** A single field comparison: `field op value`. */\nexport interface FilterCondition {\n  /** The metadata field path (dot-separated for nested fields). */\n  field: string\n  /** The comparison operator. */\n  op: FilterOperator\n  /** The value(s) compared against, or a {@link RawExpr}; omitted for `exists`. */\n  value?: VectorMetadataValue | VectorMetadataValue[] | RawExpr\n}\n/** A boolean combination of nested filters. Exactly one of `and`/`or`/`not` is meaningful. */\nexport interface FilterGroup {\n  /** All nested filters must match. */\n  and?: VectorFilter[]\n  /** At least one nested filter must match. */\n  or?: VectorFilter[]\n  /** The nested filter must not match. */\n  not?: VectorFilter\n}\n/** An adapter-dialect raw filter forwarded to the backend verbatim. */\nexport interface RawFilter {\n  /** The dialect the raw expression is written in (e.g. `'sql'`). */\n  $dialect: string\n  /** The raw expression — a string, or a structured dialect-specific payload. */\n  $raw: string | unknown\n  /** Positional binding values for the raw expression. */\n  $bindings?: unknown[]\n}\n/** Any filter node: a {@link FilterCondition}, a {@link FilterGroup}, or a {@link RawFilter}. */\nexport type VectorFilter = FilterCondition | FilterGroup | RawFilter\n\n/** Type guard: `true` if `f` is a {@link FilterCondition}. */\nexport const isFilterCondition = (f: VectorFilter): f is FilterCondition =>\n  typeof (f as FilterCondition).field === 'string' && typeof (f as FilterCondition).op === 'string'\n\n/** Type guard: `true` if `f` is a {@link RawFilter}. */\nexport const isRawFilter = (f: VectorFilter): f is RawFilter =>\n  typeof (f as RawFilter).$dialect === 'string'\n\n/** Type guard: `true` if `f` is a {@link FilterGroup} (neither a condition nor a raw filter). */\nexport const isFilterGroup = (f: VectorFilter): f is FilterGroup =>\n  !isFilterCondition(f) && !isRawFilter(f)\n\nconst rawFilterSchema = validator.object<RawFilter>({\n  $dialect: validator.string().required(),\n  // eslint-disable-next-line adk/require-validator-any-required -- value type-arg: $raw is any structured dialect payload; disposition is set by the .required() on the alternatives\n  $raw: validator.alternatives(validator.string(), validator.any()).required(),\n  // eslint-disable-next-line adk/require-validator-any-required -- item type-arg: bindings hold arbitrary values; disposition is set by .optional() on the array\n  $bindings: validator.array().items(validator.any()).optional(),\n})\n\nconst filterConditionSchema = validator.object<FilterCondition>({\n  field: validator.string().required(),\n  op: validator\n    .string()\n    .valid('eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'exists', 'contains')\n    .required(),\n  value: validator\n    // eslint-disable-next-line adk/require-validator-any-required -- value type-arg: a filter value is any scalar or array of any; disposition is set by .optional() below\n    .alternatives(validator.any(), validator.array().items(validator.any()))\n    .optional(),\n})\n\n/** Recursive validator schema for any {@link VectorFilter} (condition, raw, or nested group). */\nexport const vectorFilterSchema = validator\n  .alternatives(\n    filterConditionSchema,\n    rawFilterSchema,\n    validator\n      .object<FilterGroup>({\n        and: validator.array().items(validator.link('#vectorFilter')).optional(),\n        or: validator.array().items(validator.link('#vectorFilter')).optional(),\n        not: validator.link('#vectorFilter').optional(),\n      })\n      .unknown(false)\n  )\n  .id('vectorFilter')\n\nconst getField = (metadata: VectorMetadata, path: string): VectorMetadataValue | undefined => {\n  const keys = path.split('.')\n  let current: VectorMetadataValue | undefined = metadata\n  for (const key of keys) {\n    if (current === undefined || current === null) {\n      return undefined\n    }\n    if (typeof current !== 'object') {\n      return undefined\n    }\n    current = (current as Record<string, VectorMetadataValue>)[key]\n  }\n  return current\n}\n\n/**\n * Evaluate a {@link VectorFilter} against a record's metadata in memory — the reference semantics\n * adapters must match for client-side filtering and conformance.\n *\n * @param filter - The filter to evaluate.\n * @param metadata - The record metadata to test.\n * @returns `true` if the metadata satisfies the filter.\n * @throws when the filter (or a nested value) is a raw expression, which the in-memory evaluator\n *   cannot interpret.\n */\nexport const evaluateFilter = (filter: VectorFilter, metadata: VectorMetadata): boolean => {\n  if (isRawFilter(filter)) {\n    throw new Error('raw filters are not evaluable by the in-memory filter evaluator')\n  }\n\n  if (isFilterCondition(filter)) {\n    const { field, op, value } = filter\n    const fieldVal = getField(metadata, field)\n\n    if (isRawExpr(value)) {\n      throw new Error('raw expressions are not evaluable by the in-memory filter evaluator')\n    }\n\n    switch (op) {\n      case 'eq': {\n        return fieldVal === value\n      }\n\n      case 'ne': {\n        return fieldVal !== value\n      }\n\n      case 'gt': {\n        if (fieldVal === undefined || value === undefined) {\n          return false\n        }\n        if (typeof fieldVal === 'number' && typeof value === 'number') {\n          return fieldVal > value\n        }\n        if (typeof fieldVal === 'string' && typeof value === 'string') {\n          return fieldVal > value\n        }\n        return false\n      }\n\n      case 'gte': {\n        if (fieldVal === undefined || value === undefined) {\n          return false\n        }\n        if (typeof fieldVal === 'number' && typeof value === 'number') {\n          return fieldVal >= value\n        }\n        if (typeof fieldVal === 'string' && typeof value === 'string') {\n          return fieldVal >= value\n        }\n        return false\n      }\n\n      case 'lt': {\n        if (fieldVal === undefined || value === undefined) {\n          return false\n        }\n        if (typeof fieldVal === 'number' && typeof value === 'number') {\n          return fieldVal < value\n        }\n        if (typeof fieldVal === 'string' && typeof value === 'string') {\n          return fieldVal < value\n        }\n        return false\n      }\n\n      case 'lte': {\n        if (fieldVal === undefined || value === undefined) {\n          return false\n        }\n        if (typeof fieldVal === 'number' && typeof value === 'number') {\n          return fieldVal <= value\n        }\n        if (typeof fieldVal === 'string' && typeof value === 'string') {\n          return fieldVal <= value\n        }\n        return false\n      }\n\n      case 'in': {\n        if (!Array.isArray(value)) {\n          return false\n        }\n        return value.includes(fieldVal as never)\n      }\n\n      case 'nin': {\n        if (!Array.isArray(value)) {\n          return true\n        }\n        return !value.includes(fieldVal as never)\n      }\n\n      case 'exists': {\n        const checkExists = value === undefined || value === true\n        return checkExists ? fieldVal !== undefined : fieldVal === undefined\n      }\n\n      case 'contains': {\n        if (fieldVal === undefined) {\n          return false\n        }\n        if (Array.isArray(fieldVal)) {\n          return fieldVal.includes(value as never)\n        }\n        if (typeof fieldVal === 'string') {\n          return fieldVal.includes(String(value))\n        }\n        return false\n      }\n\n      default: {\n        return false\n      }\n    }\n  }\n\n  if (isFilterGroup(filter)) {\n    const { and, or, not } = filter\n\n    if (and !== undefined) {\n      return and.every((child) => evaluateFilter(child, metadata))\n    }\n\n    if (or !== undefined) {\n      return or.some((child) => evaluateFilter(child, metadata))\n    }\n\n    if (not !== undefined) {\n      return !evaluateFilter(not, metadata)\n    }\n\n    return true\n  }\n\n  return false\n}\n"],"mappings":"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"}