@o-lang/semantic-doc-search 1.0.27 → 1.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@o-lang/semantic-doc-search",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "O-lang Semantic Document Search Resolver with hybrid search, embeddings, rerank, and streaming.",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -1,31 +1,11 @@
1
- /**
2
- * Base Vector Adapter
3
- * All vector backends must extend this class.
4
- *
5
- * Responsibilities:
6
- * - dimension validation
7
- * - capability exposure
8
- * - ingestion guards
9
- */
10
-
11
1
  class VectorAdapter {
12
2
  constructor(config = {}) {
13
3
  this.backend = config.backend || "unknown";
14
4
  this.dimension = config.dimension || null;
15
-
16
- // Optional but recommended
17
- this.capabilities = config.capabilities || [];
18
- this.supportsHybrid = config.supportsHybrid ?? false;
19
- this.requiresIdempotentInsert = config.requiresIdempotentInsert ?? false;
20
5
  }
21
6
 
22
- /* ---------------- VALIDATION ---------------- */
23
-
24
7
  validateVector(vector) {
25
- if (!Array.isArray(vector)) {
26
- throw new Error("Vector must be an array");
27
- }
28
-
8
+ if (!Array.isArray(vector)) throw new Error("Vector must be an array");
29
9
  if (this.dimension && vector.length !== this.dimension) {
30
10
  throw new Error(
31
11
  `Vector dimension mismatch: expected ${this.dimension}, got ${vector.length}`
@@ -33,49 +13,16 @@ class VectorAdapter {
33
13
  }
34
14
  }
35
15
 
36
- ensureCapability(capability) {
37
- if (!this.capabilities.includes(capability)) {
38
- throw new Error(
39
- `Backend "${this.backend}" does not support capability: ${capability}`
40
- );
41
- }
42
- }
43
-
44
- /* ---------------- INGESTION GUARDS ---------------- */
45
-
46
- /**
47
- * Optional hook for hybrid-safe ingestion.
48
- * Adapters that require idempotent inserts SHOULD override this.
49
- */
50
- async ensureNotIndexed(/* fingerprint */) {
51
- if (this.requiresIdempotentInsert) {
52
- throw new Error(
53
- `ensureNotIndexed() must be implemented for backend "${this.backend}"`
54
- );
55
- }
56
- }
57
-
58
- /* ---------------- CORE OPS ---------------- */
59
-
60
- async upsert() {
61
- throw new Error("upsert() not implemented");
62
- }
63
-
64
- async query() {
65
- throw new Error("query() not implemented");
66
- }
67
-
68
- /* ---------------- HEALTH ---------------- */
16
+ async upsert() { throw new Error("upsert() not implemented"); }
17
+ async query() { throw new Error("query() not implemented"); }
18
+ async health() { return { backend: this.backend, status: "unknown" }; }
19
+ async close() {}
69
20
 
70
- async health() {
71
- return {
72
- backend: this.backend,
73
- status: "unknown",
74
- hybrid: this.supportsHybrid
75
- };
21
+ /** Checks if backend supports a capability */
22
+ supports(capability) {
23
+ const caps = this.constructor.capabilities?.()?.capabilities || [];
24
+ return caps.includes(capability);
76
25
  }
77
-
78
- async close() {}
79
26
  }
80
27
 
81
28
  module.exports = VectorAdapter;
@@ -1,45 +1,23 @@
1
1
  const VectorAdapter = require("./VectorAdapter");
2
- const vectorCapabilities = require("./vectorCapabilities");
2
+ const capabilities = require("./vectorCapabilities");
3
3
 
4
- /**
5
- * In-memory vector adapter
6
- * - Non-persistent
7
- * - Offline-safe
8
- * - No hybrid ingestion risk
9
- */
10
4
  class InMemoryAdapter extends VectorAdapter {
11
5
  constructor(config = {}) {
12
- super({
13
- ...config,
14
- backend: "memory",
15
- capabilities: vectorCapabilities.memory.capabilities,
16
- supportsHybrid: false,
17
- requiresIdempotentInsert: false
18
- });
19
-
6
+ super({ ...config, backend: "memory" });
20
7
  this.dimension = config.dimension || 384;
21
8
  this.store = [];
22
9
  }
23
10
 
24
- /* ---------------- INGEST ---------------- */
11
+ static capabilities() {
12
+ return capabilities.memory;
13
+ }
25
14
 
26
15
  async upsert({ id, vector, content, source, metadata = {} }) {
27
- this.ensureCapability("vector.insert");
28
16
  this.validateVector(vector);
29
-
30
- this.store.push({
31
- id,
32
- vector,
33
- content,
34
- source,
35
- metadata
36
- });
17
+ this.store.push({ id, vector, content, source, metadata });
37
18
  }
38
19
 
39
- /* ---------------- SEARCH ---------------- */
40
-
41
- async query(vector, { topK = 5, minScore = 0 } = {}) {
42
- this.ensureCapability("vector.search");
20
+ async query(vector, { topK = 5 } = {}) {
43
21
  this.validateVector(vector);
44
22
 
45
23
  return this.store
@@ -47,36 +25,18 @@ class InMemoryAdapter extends VectorAdapter {
47
25
  ...doc,
48
26
  score: cosineSimilarity(vector, doc.vector)
49
27
  }))
50
- .filter(doc => doc.score >= minScore)
51
28
  .sort((a, b) => b.score - a.score)
52
29
  .slice(0, topK);
53
30
  }
54
-
55
- /* ---------------- HEALTH ---------------- */
56
-
57
- async health() {
58
- return {
59
- backend: this.backend,
60
- status: "ok",
61
- vectors: this.store.length,
62
- offline: true
63
- };
64
- }
65
31
  }
66
32
 
67
- /* ---------------- UTIL ---------------- */
68
-
69
33
  function cosineSimilarity(a, b) {
70
- let dot = 0;
71
- let na = 0;
72
- let nb = 0;
73
-
34
+ let dot = 0, na = 0, nb = 0;
74
35
  for (let i = 0; i < a.length; i++) {
75
36
  dot += a[i] * b[i];
76
37
  na += a[i] ** 2;
77
38
  nb += b[i] ** 2;
78
39
  }
79
-
80
40
  return dot / (Math.sqrt(na) * Math.sqrt(nb));
81
41
  }
82
42
 
@@ -1,25 +1,14 @@
1
1
  const { Pool } = require("pg");
2
2
  const VectorAdapter = require("./VectorAdapter");
3
- const vectorCapabilities = require("./vectorCapabilities");
4
-
5
- /* ---------------- UTIL ---------------- */
3
+ const capabilities = require("./vectorCapabilities");
6
4
 
7
5
  function toPgVectorLiteral(vector) {
8
6
  return `[${vector.join(",")}]`;
9
7
  }
10
8
 
11
- /* ---------------- ADAPTER ---------------- */
12
-
13
9
  class PgVectorAdapter extends VectorAdapter {
14
10
  constructor(config = {}) {
15
- super({
16
- ...config,
17
- backend: "pgvector",
18
- capabilities: vectorCapabilities.pgvector.capabilities,
19
- supportsHybrid: true,
20
- requiresIdempotentInsert: true
21
- });
22
-
11
+ super({ ...config, backend: "pgvector" });
23
12
  this.dimension = config.dimension || 384;
24
13
 
25
14
  this.pool = new Pool({
@@ -27,77 +16,43 @@ class PgVectorAdapter extends VectorAdapter {
27
16
  });
28
17
  }
29
18
 
30
- /* ---------------- INGEST ---------------- */
19
+ static capabilities() {
20
+ return capabilities.pgvector;
21
+ }
31
22
 
32
23
  async upsert({ id, vector, content, source, metadata = {} }) {
33
- this.ensureCapability("vector.insert");
34
24
  this.validateVector(vector);
35
-
36
25
  const pgVector = toPgVectorLiteral(vector);
37
26
 
38
27
  await this.pool.query(
39
- `
40
- INSERT INTO doc_embeddings (id, embedding, content, source, metadata)
41
- VALUES ($1, $2::vector, $3, $4, $5::jsonb)
42
- ON CONFLICT (id) DO UPDATE
43
- SET embedding = EXCLUDED.embedding,
44
- content = EXCLUDED.content,
45
- source = EXCLUDED.source,
46
- metadata = EXCLUDED.metadata,
47
- updated_at = NOW()
48
- `,
28
+ `INSERT INTO doc_embeddings (id, embedding, content, source, metadata)
29
+ VALUES ($1, $2::vector, $3, $4, $5::jsonb)
30
+ ON CONFLICT (id) DO UPDATE
31
+ SET embedding = $2::vector,
32
+ content = $3,
33
+ source = $4,
34
+ metadata = $5::jsonb,
35
+ updated_at = NOW()`,
49
36
  [id, pgVector, content, source, JSON.stringify(metadata)]
50
37
  );
51
38
  }
52
39
 
53
- /* ---------------- SEARCH ---------------- */
54
-
55
- async query(vector, { topK = 5, minScore = 0 } = {}) {
56
- this.ensureCapability("vector.search");
40
+ async query(vector, { topK = 5 } = {}) {
57
41
  this.validateVector(vector);
58
-
59
42
  const pgVector = toPgVectorLiteral(vector);
60
43
 
61
44
  const res = await this.pool.query(
62
- `
63
- SELECT id,
64
- content,
65
- source,
66
- metadata,
67
- 1 - (embedding <=> $1::vector) AS score
68
- FROM doc_embeddings
69
- WHERE 1 - (embedding <=> $1::vector) >= $2
70
- ORDER BY embedding <=> $1::vector
71
- LIMIT $3
72
- `,
73
- [pgVector, minScore, topK]
45
+ `SELECT id, content, source, metadata,
46
+ 1 - (embedding <=> $1::vector) AS score
47
+ FROM doc_embeddings
48
+ ORDER BY embedding <=> $1::vector
49
+ LIMIT $2`,
50
+ [pgVector, topK]
74
51
  );
75
52
 
76
53
  return res.rows;
77
54
  }
78
55
 
79
- /* ---------------- HEALTH ---------------- */
80
-
81
- async health() {
82
- try {
83
- await this.pool.query("SELECT 1");
84
- return {
85
- backend: this.backend,
86
- status: "ok",
87
- offline: false,
88
- hybrid: true
89
- };
90
- } catch (err) {
91
- return {
92
- backend: this.backend,
93
- status: "error",
94
- error: err.message
95
- };
96
- }
97
- }
98
-
99
- /* ---------------- CLEANUP ---------------- */
100
-
101
56
  async close() {
102
57
  await this.pool.end();
103
58
  }
@@ -1,101 +1,40 @@
1
- /**
2
- * Pinecone Vector Store Adapter
3
- *
4
- * Requirements:
5
- * npm install @pinecone-database/pinecone
6
- *
7
- * Env:
8
- * PINECONE_API_KEY
9
- * PINECONE_INDEX=olang-docs
10
- */
1
+ let Pinecone;
11
2
 
12
- const { Pinecone } = require("@pinecone-database/pinecone");
13
- const VectorAdapter = require("./VectorAdapter");
14
- const vectorCapabilities = require("./vectorCapabilities");
15
-
16
- class PineconeAdapter extends VectorAdapter {
3
+ class PineconeAdapter {
17
4
  constructor(config = {}) {
18
- super({
19
- ...config,
20
- backend: "pinecone",
21
- capabilities: vectorCapabilities.pinecone.capabilities,
22
- supportsHybrid: true,
23
- requiresIdempotentInsert: true
24
- });
25
-
26
- this.dimension = config.dimension || 1536;
27
- this.indexName = config.PINECONE_INDEX || process.env.PINECONE_INDEX || "olang-docs";
5
+ if (!Pinecone) {
6
+ try {
7
+ Pinecone = require("@pinecone-database/pinecone");
8
+ } catch (err) {
9
+ throw new Error(
10
+ "Pinecone adapter requires '@pinecone-database/pinecone'. Please install it via npm."
11
+ );
12
+ }
13
+ }
28
14
 
29
- const apiKey = config.PINECONE_API_KEY || process.env.PINECONE_API_KEY;
30
- if (!apiKey) throw new Error("Missing PINECONE_API_KEY");
15
+ this.apiKey = process.env.PINECONE_API_KEY;
16
+ if (!this.apiKey) throw new Error("Missing PINECONE_API_KEY");
31
17
 
32
- this.client = new Pinecone({ apiKey });
33
- this.index = this.client.index(this.indexName);
18
+ const indexName = process.env.PINECONE_INDEX || "olang-docs";
19
+ this.client = new Pinecone({ apiKey: this.apiKey });
20
+ this.indexName = indexName;
34
21
  }
35
22
 
36
- /* ---------------- INGEST ---------------- */
37
-
38
- async upsert({ id, vector, content, source, metadata = {} }) {
39
- this.ensureCapability("vector.insert");
40
- this.validateVector(vector);
41
-
42
- await this.index.upsert([
43
- {
44
- id: id.toString(),
45
- values: vector,
46
- metadata: {
47
- content,
48
- source,
49
- ...metadata
50
- }
51
- }
52
- ]);
23
+ static capabilities() {
24
+ return { persistent: true, offline: false, distance: "cosine", maxDimension: 1536, capabilities: ["vector.insert", "vector.search"] };
53
25
  }
54
26
 
55
- /* ---------------- SEARCH ---------------- */
56
-
57
- async query(vector, { topK = 5, minScore = 0 } = {}) {
58
- this.ensureCapability("vector.search");
59
- this.validateVector(vector);
60
-
61
- const res = await this.index.query({
62
- vector,
63
- topK,
64
- includeMetadata: true
65
- });
66
-
67
- return res.matches
68
- .filter(m => m.score >= minScore)
69
- .map(m => ({
70
- id: m.id,
71
- score: m.score,
72
- content: m.metadata?.content,
73
- source: m.metadata?.source,
74
- metadata: m.metadata
75
- }));
27
+ async init() {
28
+ // ... your init logic here
76
29
  }
77
30
 
78
- /* ---------------- HEALTH ---------------- */
79
-
80
- async health() {
81
- try {
82
- await this.client.listIndexes();
83
- return {
84
- backend: this.backend,
85
- status: "ok",
86
- offline: false,
87
- hybrid: true
88
- };
89
- } catch (err) {
90
- return {
91
- backend: this.backend,
92
- status: "error",
93
- error: err.message
94
- };
95
- }
31
+ async upsert(id, vector, metadata) {
32
+ // ...
96
33
  }
97
34
 
98
- async close() {}
35
+ async search(queryVector, limit = 5) {
36
+ // ...
37
+ }
99
38
  }
100
39
 
101
40
  module.exports = PineconeAdapter;
@@ -1,3 +1,12 @@
1
+ const InMemoryAdapter = require("./inMemoryAdapter");
2
+ const PgVectorAdapter = require("./pgvectorAdapter");
3
+ const RedisAdapter = require("./redisAdapter");
4
+ const PineconeAdapter = require("./pineconeAdapter");
5
+
6
+ /**
7
+ * VectorRouter
8
+ * Dynamically selects vector backend at runtime
9
+ */
1
10
  class VectorRouter {
2
11
  static create(config = {}) {
3
12
  // 1. Explicit backend override (advanced users only)
@@ -7,36 +16,26 @@ class VectorRouter {
7
16
 
8
17
  // 2. Auto-detect pgvector (DEFAULT)
9
18
  if (config.POSTGRES_URL) {
10
- const PgVectorAdapter = require("./pgvectorAdapter");
11
19
  return new PgVectorAdapter(config);
12
20
  }
13
21
 
14
22
  // 3. Fallback to in-memory (safe default)
15
- const InMemoryAdapter = require("./inMemoryAdapter");
16
23
  return new InMemoryAdapter(config);
17
24
  }
18
25
 
19
26
  static _createExplicitBackend(backend, config) {
20
- switch (backend) {
21
- case "pgvector": {
22
- const PgVectorAdapter = require("./pgvectorAdapter");
27
+ switch (backend.toLowerCase()) {
28
+ case "pgvector":
23
29
  return new PgVectorAdapter(config);
24
- }
25
30
 
26
- case "memory": {
27
- const InMemoryAdapter = require("./inMemoryAdapter");
31
+ case "memory":
28
32
  return new InMemoryAdapter(config);
29
- }
30
33
 
31
- case "redis": {
32
- const RedisAdapter = require("./redisAdapter");
34
+ case "redis":
33
35
  return new RedisAdapter(config);
34
- }
35
36
 
36
- case "pinecone": {
37
- const PineconeAdapter = require("./pineconeAdapter");
37
+ case "pinecone":
38
38
  return new PineconeAdapter(config);
39
- }
40
39
 
41
40
  default:
42
41
  throw new Error(`Unknown vector backend: ${backend}`);