{"version":3,"file":"vector-mock.cjs","names":["readBody","flattenHeaders","http","createVectorRequestHandler"],"sources":["../src/vector-mock.ts"],"sourcesContent":["import * as http from \"node:http\";\nimport type { Mountable, JournalEntry } from \"./types.js\";\nimport type { Journal } from \"./journal.js\";\nimport type { MetricsRegistry } from \"./metrics.js\";\nimport type {\n  VectorMockOptions,\n  VectorCollection,\n  VectorEntry,\n  QueryResult,\n  VectorQuery,\n  QueryHandler,\n} from \"./vector-types.js\";\nimport { createVectorRequestHandler, type VectorState } from \"./vector-handler.js\";\nimport { flattenHeaders, readBody } from \"./helpers.js\";\n\nexport class VectorMock implements Mountable {\n  private collections: Map<string, VectorCollection> = new Map();\n  private queryHandlers: Map<string, QueryHandler> = new Map();\n  private server: http.Server | null = null;\n  private journal: Journal | null = null;\n  private registry: MetricsRegistry | null = null;\n  private options: VectorMockOptions;\n  private requestHandler: ReturnType<typeof createVectorRequestHandler>;\n\n  constructor(options?: VectorMockOptions) {\n    this.options = options ?? {};\n    this.requestHandler = this.buildHandler();\n  }\n\n  // ---- Configuration ----\n\n  addCollection(name: string, opts: { dimension: number }): this {\n    const collection: VectorCollection = {\n      name,\n      dimension: opts.dimension,\n      vectors: new Map(),\n    };\n    this.collections.set(name, collection);\n    this.requestHandler = this.buildHandler();\n    return this;\n  }\n\n  upsert(collection: string, vectors: VectorEntry[]): this {\n    let col = this.collections.get(collection);\n    if (!col) {\n      const dim = vectors.length > 0 ? vectors[0].values.length : 0;\n      col = { name: collection, dimension: dim, vectors: new Map() };\n      this.collections.set(collection, col);\n    }\n    for (const v of vectors) {\n      col.vectors.set(v.id, v);\n    }\n    this.requestHandler = this.buildHandler();\n    return this;\n  }\n\n  onQuery(\n    collection: string,\n    results: QueryResult[] | ((query: VectorQuery) => QueryResult[]),\n  ): this {\n    this.queryHandlers.set(collection, results);\n    this.requestHandler = this.buildHandler();\n    return this;\n  }\n\n  deleteCollection(name: string): this {\n    this.collections.delete(name);\n    this.queryHandlers.delete(name);\n    this.requestHandler = this.buildHandler();\n    return this;\n  }\n\n  // ---- Mountable interface ----\n\n  async handleRequest(\n    req: http.IncomingMessage,\n    res: http.ServerResponse,\n    pathname: string,\n  ): Promise<boolean> {\n    // Only consume the request body for methods that carry a body,\n    // avoiding double-consumption when the route doesn't match.\n    let parsed: Record<string, unknown> = {};\n    if (req.method !== \"GET\" && req.method !== \"DELETE\" && req.method !== \"HEAD\") {\n      const body = await readBody(req);\n      try {\n        if (body) parsed = JSON.parse(body);\n      } catch (parseErr) {\n        const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n        res.writeHead(400, { \"Content-Type\": \"application/json\" });\n        res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n        return true;\n      }\n    }\n\n    const handled = this.requestHandler(req, res, pathname, parsed);\n\n    // Record vector operation metric\n    if (handled && this.registry) {\n      const { operation, provider } = classifyVectorRequest(req.method ?? \"GET\", pathname);\n      this.registry.incrementCounter(\"aimock_vector_requests_total\", { operation, provider });\n    }\n\n    // Journal the request after the handler completes\n    if (handled && this.journal) {\n      this.journal.add({\n        method: req.method ?? \"GET\",\n        path: req.url ?? \"/\",\n        headers: flattenHeaders(req.headers),\n        body: null,\n        service: \"vector\",\n        response: { status: res.statusCode, fixture: null },\n      });\n    }\n\n    return handled;\n  }\n\n  health(): { status: string; collections: number } {\n    return {\n      status: \"ok\",\n      collections: this.collections.size,\n    };\n  }\n\n  setJournal(journal: Journal): void {\n    this.journal = journal;\n  }\n\n  setRegistry(registry: MetricsRegistry): void {\n    this.registry = registry;\n  }\n\n  // ---- Standalone mode ----\n\n  async start(): Promise<string> {\n    if (this.server) {\n      throw new Error(\"Server already started\");\n    }\n\n    const host = this.options.host ?? \"127.0.0.1\";\n    const port = this.options.port ?? 0;\n\n    return new Promise((resolve, reject) => {\n      const srv = http.createServer((req, res) => {\n        const chunks: Buffer[] = [];\n        req.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n        req.on(\"end\", () => {\n          const body = Buffer.concat(chunks).toString();\n          let parsed: Record<string, unknown> = {};\n          try {\n            if (body) parsed = JSON.parse(body);\n          } catch (parseErr) {\n            if (req.method !== \"GET\") {\n              const detail = parseErr instanceof Error ? parseErr.message : \"unknown\";\n              res.writeHead(400, { \"Content-Type\": \"application/json\" });\n              res.end(JSON.stringify({ error: `Malformed JSON body: ${detail}` }));\n              return;\n            }\n          }\n\n          const url = new URL(req.url ?? \"/\", `http://${host}`);\n\n          const handled = this.requestHandler(req, res, url.pathname, parsed);\n\n          if (handled && this.journal) {\n            this.journal.add({\n              method: req.method ?? \"GET\",\n              path: req.url ?? \"/\",\n              headers: flattenHeaders(req.headers),\n              body: null,\n              service: \"vector\",\n              response: { status: res.statusCode, fixture: null },\n            });\n          }\n          if (!handled) {\n            res.writeHead(404, { \"Content-Type\": \"application/json\" });\n            res.end(JSON.stringify({ error: \"Not found\" }));\n          }\n        });\n      });\n\n      srv.listen(port, host, () => {\n        this.server = srv;\n        const addr = srv.address();\n        if (typeof addr === \"object\" && addr !== null) {\n          resolve(`http://${host}:${addr.port}`);\n        } else {\n          resolve(`http://${host}:${port}`);\n        }\n      });\n\n      srv.on(\"error\", reject);\n    });\n  }\n\n  async stop(): Promise<void> {\n    if (!this.server) {\n      throw new Error(\"Server not started\");\n    }\n    const srv = this.server;\n    this.server = null;\n    await new Promise<void>((resolve, reject) => {\n      srv.close((err) => (err ? reject(err) : resolve()));\n    });\n  }\n\n  // ---- Inspection ----\n\n  getRequests(): JournalEntry[] {\n    if (!this.journal) return [];\n    return this.journal.getAll().filter((e) => e.service === \"vector\");\n  }\n\n  reset(): this {\n    this.collections.clear();\n    this.queryHandlers.clear();\n    this.requestHandler = this.buildHandler();\n    return this;\n  }\n\n  // ---- Internal ----\n\n  private buildHandler() {\n    const state: VectorState = {\n      collections: this.collections,\n      queryHandlers: this.queryHandlers,\n    };\n    return createVectorRequestHandler(state);\n  }\n}\n\n// ---- Helpers ----\n\n/**\n * Classify a vector request by operation and provider based on HTTP method and pathname.\n */\nfunction classifyVectorRequest(\n  method: string,\n  pathname: string,\n): { operation: string; provider: string } {\n  // Pinecone paths\n  if (pathname === \"/query\" && method === \"POST\") {\n    return { operation: \"query\", provider: \"pinecone\" };\n  }\n  if (pathname === \"/vectors/upsert\" && method === \"POST\") {\n    return { operation: \"upsert\", provider: \"pinecone\" };\n  }\n  if (pathname === \"/vectors/delete\" && method === \"POST\") {\n    return { operation: \"delete\", provider: \"pinecone\" };\n  }\n  if (pathname === \"/describe-index-stats\" && method === \"GET\") {\n    return { operation: \"describe\", provider: \"pinecone\" };\n  }\n\n  // Qdrant paths\n  if (/^\\/collections\\/[^/]+\\/points\\/search$/.test(pathname) && method === \"POST\") {\n    return { operation: \"query\", provider: \"qdrant\" };\n  }\n  if (/^\\/collections\\/[^/]+\\/points$/.test(pathname) && method === \"PUT\") {\n    return { operation: \"upsert\", provider: \"qdrant\" };\n  }\n  if (/^\\/collections\\/[^/]+\\/points\\/delete$/.test(pathname) && method === \"POST\") {\n    return { operation: \"delete\", provider: \"qdrant\" };\n  }\n\n  // ChromaDB paths\n  if (/^\\/api\\/v1\\/collections\\/[^/]+\\/query$/.test(pathname) && method === \"POST\") {\n    return { operation: \"query\", provider: \"chromadb\" };\n  }\n  if (/^\\/api\\/v1\\/collections\\/[^/]+\\/add$/.test(pathname) && method === \"POST\") {\n    return { operation: \"upsert\", provider: \"chromadb\" };\n  }\n  if (pathname === \"/api/v1/collections\" && method === \"GET\") {\n    return { operation: \"list\", provider: \"chromadb\" };\n  }\n  if (/^\\/api\\/v1\\/collections\\/[^/]+$/.test(pathname) && method === \"DELETE\") {\n    return { operation: \"delete\", provider: \"chromadb\" };\n  }\n\n  return { operation: \"unknown\", provider: \"unknown\" };\n}\n"],"mappings":"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"}