{"version":3,"file":"conformance.mjs","names":[],"sources":["../../../src/batteries/vector/conformance/index.ts"],"sourcesContent":["/**\n * Shared conformance suite for vector-store adapters. Drive any adapter through\n * {@link runVectorStoreConformance} to verify it honours the same contract all shipped adapters do.\n * Public, deep-import-only (`@nhtio/adk/batteries/vector/conformance`). The suite has no test-runner\n * dependency: callers invoke it and await the returned promise from any test framework.\n *\n * @module @nhtio/adk/batteries/vector/conformance\n */\n\nimport type { CallableVectorStore } from '../contract'\n\n/** A deterministic stub encoder producing a 3-dim vector from simple text features. */\nexport const stubEncoder = async (\n  texts: string[],\n  _kind: 'query' | 'document'\n): Promise<number[][]> =>\n  texts.map((tx) => [tx.length, tx.includes('cat') ? 1 : 0, tx.includes('dog') ? 1 : 0])\n\n/**\n * A dimension-padding encoder factory: wraps {@link stubEncoder} and zero-extends to `dim`. Used by\n * backends that enforce a minimum dimension (e.g. Cloudflare Vectorize requires 32–1536).\n */\nexport const paddedStubEncoder =\n  (dim: number) =>\n  async (texts: string[], kind: 'query' | 'document'): Promise<number[][]> => {\n    const base = await stubEncoder(texts, kind)\n    return base.map((v) => padVector(v, dim))\n  }\n\n// Zero-extend (or truncate) a vector to exactly `dim` entries. dim===3 (the default) returns the\n// vector unchanged, so existing callers are byte-for-byte identical.\nconst padVector = (v: number[], dim: number): number[] => {\n  if (v.length === dim) return v\n  if (v.length > dim) return v.slice(0, dim)\n  return [...v, ...new Array(dim - v.length).fill(0)]\n}\n\n/**\n * Drive a vector-store adapter through the shared contract suite.\n *\n * @remarks\n * `makeStore` must return an already-connected store whose `'docs'` collection is created at the\n * matching dimension; it is re-invoked per test so retries are self-contained. `dim` defaults to 3\n * — pass a larger value for backends with a dimension floor (the harness pads every test vector to\n * `dim`). `opts.retry` / `opts.timeout` are forwarded to every `it()` (defaults: retry 0, 5s) and\n * exist for aggressively eventually-consistent managed backends whose read-after-write can flap for\n * seconds — a retried attempt re-runs `makeStore()` against an index that has had more time to\n * settle, without weakening any assertion.\n *\n * @param label - Human-readable label for the suite (the adapter name).\n * @param makeStore - Factory returning a fresh, connected store with the `'docs'` collection.\n * @param dim - Vector dimensionality the harness pads to (default 3).\n * @param opts - Per-`it` retry/timeout overrides.\n */\nconst assert: (condition: boolean, message: string) => asserts condition = (condition, message) => {\n  if (!condition) throw new Error(message)\n}\nconst equal = (actual: unknown, expected: unknown): boolean => {\n  if (actual === expected) return true\n  if (\n    actual === null ||\n    expected === null ||\n    typeof actual !== 'object' ||\n    typeof expected !== 'object'\n  )\n    return false\n  if (Array.isArray(actual) !== Array.isArray(expected)) return false\n  const actualRecord = actual as Record<string, unknown>\n  const expectedRecord = expected as Record<string, unknown>\n  const keysA = Object.keys(actualRecord)\n  const keysB = Object.keys(expectedRecord)\n  if (keysA.length !== keysB.length) return false\n  return keysA.every(\n    (key) => Object.hasOwn(expectedRecord, key) && equal(actualRecord[key], expectedRecord[key])\n  )\n}\nconst match = (actual: unknown, expected: unknown): boolean => {\n  if (expected === null || typeof expected !== 'object') return equal(actual, expected)\n  if (actual === null || typeof actual !== 'object') return false\n  return Object.entries(expected).every(([key, value]) =>\n    match((actual as Record<string, unknown>)[key], value)\n  )\n}\nconst expectValue = (actual: unknown) => ({\n  toBe: (expected: unknown) =>\n    assert(equal(actual, expected), `expected ${String(actual)} to be ${String(expected)}`),\n  toEqual: (expected: unknown) => assert(equal(actual, expected), 'values were not equal'),\n  toMatchObject: (expected: unknown) =>\n    assert(match(actual, expected), 'value did not match expected object'),\n  toBeDefined: () => assert(actual !== undefined, 'value was undefined'),\n  toBeUndefined: () => assert(actual === undefined, 'value was defined'),\n  toHaveLength: (length: number) =>\n    assert((actual as { length: number }).length === length, 'unexpected length'),\n  rejects: {\n    toThrow: async () => {\n      try {\n        await (actual as Promise<unknown>)\n      } catch {\n        return\n      }\n      throw new Error('expected promise to reject')\n    },\n  },\n})\n\n/**\n * Run the framework-free vector-store conformance suite against a consumer implementation.\n *\n * Each check creates a fresh store through `makeStore`; the returned promise rejects when any\n * contract assertion fails. Consumers can await this function from any test runner without\n * importing a test framework from the conformance module.\n *\n * @param label - Human-readable name used to identify the implementation under test.\n * @param makeStore - Factory returning a fresh, connected store with the `docs` collection.\n * @param dim - Vector dimensionality used by the suite (defaults to 3).\n * @param opts - Retry count and per-attempt timeout in milliseconds for eventually consistent stores.\n */\nexport const runVectorStoreConformance = async (\n  label: string,\n  makeStore: () => Promise<CallableVectorStore>,\n  dim = 3,\n  opts: { retry?: number; timeout?: number } = {}\n): Promise<void> => {\n  const p = (v: number[]): number[] => padVector(v, dim)\n  const pending: Array<() => Promise<void>> = []\n  const retry = opts.retry ?? 0\n  const timeout = opts.timeout ?? 5000\n  const runCase = async (test: () => Promise<void>): Promise<void> => {\n    let last: unknown\n    for (let attempt = 0; attempt <= retry; attempt++) {\n      let timedOut = false\n      const operation = Promise.resolve().then(test)\n      let timer: ReturnType<typeof setTimeout> | undefined\n      const deadline = new Promise<never>((_, reject) => {\n        timer = setTimeout(() => {\n          timedOut = true\n          reject(new Error(`conformance check timed out after ${timeout}ms`))\n        }, timeout)\n      })\n      try {\n        await Promise.race([operation, deadline])\n        return\n      } catch (error) {\n        // A timeout cannot cancel an arbitrary backend operation. Drain it before retrying so a\n        // late write/read from this attempt cannot race the fresh store made by the next attempt.\n        if (timedOut) {\n          last = error\n          break\n        }\n        last = error\n      } finally {\n        if (timer !== undefined) clearTimeout(timer)\n      }\n    }\n    throw last\n  }\n  const it = (_name: string, _io: unknown, test: () => Promise<void>) => {\n    pending.push(() => runCase(test))\n  }\n  const describe = (_name: string, suite: () => void) => suite()\n  const expect = expectValue\n  const io = undefined\n  describe('conformance: ' + label, () => {\n    it('upserts vectors and searches by nearVector with [0,1] score', io, async () => {\n      const vs = await makeStore()\n      await vs('docs').upsert([\n        { id: '1', vector: p([3, 1, 0]), metadata: { kind: 'animal' } },\n        { id: '2', vector: p([3, 0, 1]), metadata: { kind: 'animal' } },\n      ])\n      const res = await vs('docs')\n        .nearVector(p([3, 1, 0]))\n        .select('id', 'score')\n        .limit(2)\n      expect(res.length).toBe(2)\n      expect(res[0].id).toBeDefined()\n      expect(typeof res[0].score).toBe('number')\n      expect(res[0].score! >= 0 && res[0].score! <= 1).toBe(true)\n    })\n    it('encodes text on upsert+nearText when an encoder is configured', io, async () => {\n      const vs = await makeStore()\n      await vs('docs').upsert([{ id: 'c', document: 'cat', metadata: {} }])\n      const res = await vs('docs').nearText('cat').select('id').limit(1)\n      expect(res.length).toBe(1)\n      expect(res[0].id).toBe('c')\n    })\n    it('requires .select() on reads', io, async () => {\n      const vs = await makeStore()\n      await expect(\n        (async () => {\n          await vs('docs').nearVector(p([1, 1, 1]))\n        })()\n      ).rejects.toThrow()\n    })\n    it('filter-scan (no near) returns rows without score', io, async () => {\n      const vs = await makeStore()\n      await vs('docs').upsert([{ id: '1', vector: p([1, 0, 0]), metadata: { kind: 'x' } }])\n      const res = await vs('docs').where('kind', 'x').select('id', 'metadata').limit(10)\n      expect(res.length).toBe(1)\n      expect(res[0].score).toBeUndefined()\n    })\n    it('nested filter A AND (B OR C) selects the right subset', io, async () => {\n      const vs = await makeStore()\n      // Every record carries all three fields so the test probes nested AND/OR routing, not\n      // backend-specific missing-field semantics (e.g. Chroma's $or excludes records missing a\n      // referenced key, where the JS evaluator treats that clause as merely false).\n      await vs('docs').upsert([\n        { id: 'a', vector: p([1, 0, 0]), metadata: { kind: 'doc', year: 2024, pinned: false } }, // kept (year arm)\n        { id: 'b', vector: p([0, 1, 0]), metadata: { kind: 'doc', year: 2010, pinned: false } }, // dropped (neither arm)\n        { id: 'c', vector: p([0, 0, 1]), metadata: { kind: 'doc', year: 2010, pinned: true } }, // kept (pinned arm)\n        { id: 'd', vector: p([1, 1, 0]), metadata: { kind: 'other', year: 2024, pinned: true } }, // dropped (kind mismatch)\n      ])\n      // kind = 'doc' AND (year >= 2024 OR pinned = true)\n      const res = await vs('docs')\n        .where('kind', 'doc')\n        .andWhere((qb) => qb.where('year', '>=', 2024).orWhere('pinned', true))\n        .select('id')\n        .limit(10)\n      expect(res.map((r) => r.id).sort()).toEqual(['a', 'c'])\n    })\n    it('projection: vector excluded unless selected', io, async () => {\n      const vs = await makeStore()\n      await vs('docs').upsert([{ id: '1', vector: p([1, 0, 0]) }])\n      const noVec = await vs('docs')\n        .nearVector(p([1, 0, 0]))\n        .select('id')\n        .limit(1)\n      expect(noVec[0].vector).toBeUndefined()\n      const withVec = await vs('docs')\n        .nearVector(p([1, 0, 0]))\n        .select('id', 'vector')\n        .limit(1)\n      expect(Array.isArray(withVec[0].vector)).toBe(true)\n    })\n    it('deletes by id', io, async () => {\n      const vs = await makeStore()\n      await vs('docs').upsert([\n        { id: '1', vector: p([1, 0, 0]) },\n        { id: '2', vector: p([0, 1, 0]) },\n      ])\n      await vs('docs').whereIn('id', ['1']).delete()\n      const all = await vs('docs').select('id').limit(10)\n      expect(all.length).toBe(1)\n    })\n    it('throws transaction-unsupported when capabilities.transactions is false', io, async () => {\n      const vs = await makeStore()\n      if (!vs.capabilities.transactions) {\n        await expect(vs.transaction(async () => {})).rejects.toThrow()\n      }\n    })\n  })\n  for (const fn of pending) {\n    await fn()\n  }\n}\n"],"mappings":"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"}