doc2vec 2.3.0 → 2.5.0

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/dist/database.js CHANGED
@@ -44,7 +44,7 @@ const sqliteVec = __importStar(require("sqlite-vec"));
44
44
  const js_client_rest_1 = require("@qdrant/js-client-rest");
45
45
  const utils_1 = require("./utils");
46
46
  class DatabaseManager {
47
- static async initDatabase(config, parentLogger) {
47
+ static async initDatabase(config, parentLogger, embeddingDimension) {
48
48
  const logger = parentLogger.child('database');
49
49
  const dbConfig = config.database_config;
50
50
  if (dbConfig.type === 'sqlite') {
@@ -53,10 +53,10 @@ class DatabaseManager {
53
53
  logger.info(`Opening SQLite database at ${dbPath}`);
54
54
  const db = new better_sqlite3_1.default(dbPath, { allowExtension: true });
55
55
  sqliteVec.load(db);
56
- logger.debug(`Creating vec_items table if it doesn't exist`);
56
+ logger.debug(`Creating vec_items table if it doesn't exist (dimension: ${embeddingDimension})`);
57
57
  db.exec(`
58
58
  CREATE VIRTUAL TABLE IF NOT EXISTS vec_items USING vec0(
59
- embedding FLOAT[3072],
59
+ embedding FLOAT[${embeddingDimension}],
60
60
  product_name TEXT,
61
61
  version TEXT,
62
62
  branch TEXT,
@@ -81,7 +81,7 @@ class DatabaseManager {
81
81
  const collectionName = params.collection_name || `${config.product_name.toLowerCase().replace(/\s+/g, '_')}_${config.version}`;
82
82
  logger.info(`Connecting to Qdrant at ${qdrantUrl}:${qdrantPort}, collection: ${collectionName}`);
83
83
  const qdrantClient = new js_client_rest_1.QdrantClient({ url: qdrantUrl, apiKey: process.env.QDRANT_API_KEY, port: qdrantPort });
84
- await this.createCollectionQdrant(qdrantClient, collectionName, logger);
84
+ await this.createCollectionQdrant(qdrantClient, collectionName, logger, embeddingDimension);
85
85
  logger.info(`Qdrant connection established successfully`);
86
86
  return { client: qdrantClient, collectionName, type: 'qdrant' };
87
87
  }
@@ -91,7 +91,7 @@ class DatabaseManager {
91
91
  throw new Error(errMsg);
92
92
  }
93
93
  }
94
- static async createCollectionQdrant(qdrantClient, collectionName, logger) {
94
+ static async createCollectionQdrant(qdrantClient, collectionName, logger, embeddingDimension) {
95
95
  try {
96
96
  logger.debug(`Checking if collection ${collectionName} exists`);
97
97
  const collections = await qdrantClient.getCollections();
@@ -100,10 +100,10 @@ class DatabaseManager {
100
100
  logger.info(`Collection ${collectionName} already exists`);
101
101
  return;
102
102
  }
103
- logger.info(`Creating new collection ${collectionName}`);
103
+ logger.info(`Creating new collection ${collectionName} with dimension ${embeddingDimension}`);
104
104
  await qdrantClient.createCollection(collectionName, {
105
105
  vectors: {
106
- size: 3072,
106
+ size: embeddingDimension,
107
107
  distance: "Cosine",
108
108
  },
109
109
  });
@@ -181,7 +181,7 @@ class DatabaseManager {
181
181
  }
182
182
  return defaultValue;
183
183
  }
184
- static async setMetadataValue(dbConnection, key, value, logger) {
184
+ static async setMetadataValue(dbConnection, key, value, logger, embeddingDimension) {
185
185
  try {
186
186
  if (dbConnection.type === 'sqlite') {
187
187
  const stmt = dbConnection.db.prepare(`
@@ -193,8 +193,7 @@ class DatabaseManager {
193
193
  }
194
194
  else if (dbConnection.type === 'qdrant') {
195
195
  const metadataUUID = utils_1.Utils.generateMetadataUUID(key);
196
- const dummyEmbeddingSize = 3072;
197
- const dummyEmbedding = new Array(dummyEmbeddingSize).fill(0);
196
+ const dummyEmbedding = new Array(embeddingDimension).fill(0);
198
197
  const metadataPoint = {
199
198
  id: metadataUUID,
200
199
  vector: dummyEmbedding,
@@ -258,7 +257,7 @@ class DatabaseManager {
258
257
  logger.info(`No saved run date found for ${repo}, using default: ${defaultDate}`);
259
258
  return defaultDate;
260
259
  }
261
- static async updateLastRunDate(dbConnection, repo, logger) {
260
+ static async updateLastRunDate(dbConnection, repo, logger, embeddingDimension) {
262
261
  const now = new Date().toISOString();
263
262
  try {
264
263
  if (dbConnection.type === 'sqlite') {
@@ -276,8 +275,7 @@ class DatabaseManager {
276
275
  const metadataKey = `last_run_${repo.replace('/', '_')}`;
277
276
  logger.debug(`Using UUID: ${metadataUUID} for metadata`);
278
277
  // Generate a dummy embedding (all zeros)
279
- const dummyEmbeddingSize = 3072; // Same size as your content embeddings
280
- const dummyEmbedding = new Array(dummyEmbeddingSize).fill(0);
278
+ const dummyEmbedding = new Array(embeddingDimension).fill(0);
281
279
  // Create a point with special metadata payload
282
280
  const metadataPoint = {
283
281
  id: metadataUUID,
@@ -535,7 +533,7 @@ class DatabaseManager {
535
533
  {
536
534
  key: 'url',
537
535
  match: {
538
- text: url
536
+ value: url // Exact match — must match getChunkHashesByUrlQdrant's filter
539
537
  }
540
538
  }
541
539
  ],
@@ -555,6 +553,96 @@ class DatabaseManager {
555
553
  logger.error(`Error deleting chunks from Qdrant for URL ${url}:`, error);
556
554
  }
557
555
  }
556
+ /**
557
+ * Fetch all distinct URLs stored under a given URL prefix.
558
+ * Used to pre-seed the crawl queue on re-runs so link discovery isn't lost
559
+ * when pages are skipped via ETag matching.
560
+ */
561
+ static getStoredUrlsByPrefixSQLite(db, urlPrefix) {
562
+ const stmt = db.prepare('SELECT DISTINCT url FROM vec_items WHERE url LIKE ? || \'%\'');
563
+ const rows = stmt.all(urlPrefix);
564
+ return rows.map(r => r.url);
565
+ }
566
+ static async getStoredUrlsByPrefixQdrant(db, urlPrefix) {
567
+ const { client, collectionName } = db;
568
+ try {
569
+ const response = await client.scroll(collectionName, {
570
+ limit: 10000,
571
+ with_payload: { include: ['url'] },
572
+ with_vector: false,
573
+ filter: {
574
+ must: [
575
+ {
576
+ key: 'url',
577
+ match: { text: urlPrefix }
578
+ }
579
+ ],
580
+ must_not: [
581
+ {
582
+ key: 'is_metadata',
583
+ match: { value: true }
584
+ }
585
+ ]
586
+ }
587
+ });
588
+ const urls = new Set();
589
+ for (const point of response.points) {
590
+ const url = point.payload?.url;
591
+ if (url && url.startsWith(urlPrefix)) {
592
+ urls.add(url);
593
+ }
594
+ }
595
+ return Array.from(urls);
596
+ }
597
+ catch (error) {
598
+ return [];
599
+ }
600
+ }
601
+ /**
602
+ * Fetch all stored chunk content hashes for a given URL.
603
+ * Returns a sorted array of hash strings for comparison.
604
+ */
605
+ static getChunkHashesByUrlSQLite(db, url) {
606
+ const stmt = db.prepare('SELECT hash FROM vec_items WHERE url = ?');
607
+ const rows = stmt.all(url);
608
+ return rows.map(r => r.hash).sort();
609
+ }
610
+ /**
611
+ * Fetch all stored chunk content hashes for a given URL from Qdrant.
612
+ * Returns a sorted array of hash strings for comparison.
613
+ */
614
+ static async getChunkHashesByUrlQdrant(db, url) {
615
+ const { client, collectionName } = db;
616
+ try {
617
+ const response = await client.scroll(collectionName, {
618
+ limit: 10000,
619
+ with_payload: { include: ['hash'] },
620
+ with_vector: false,
621
+ filter: {
622
+ must: [
623
+ {
624
+ key: 'url',
625
+ match: { value: url }
626
+ }
627
+ ],
628
+ must_not: [
629
+ {
630
+ key: 'is_metadata',
631
+ match: { value: true }
632
+ }
633
+ ]
634
+ }
635
+ });
636
+ return response.points
637
+ .map((point) => point.payload?.hash)
638
+ .filter(Boolean)
639
+ .sort();
640
+ }
641
+ catch (error) {
642
+ // On error, return empty array so the caller treats it as "changed" (safe default)
643
+ return [];
644
+ }
645
+ }
558
646
  static removeObsoleteFilesSQLite(db, processedFiles, pathConfig, logger) {
559
647
  const getChunksForPathStmt = db.prepare(`
560
648
  SELECT chunk_id, url FROM vec_items