mcmodding-mcp 0.1.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.
Files changed (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +324 -0
  3. package/dist/db-versioning.d.ts +31 -0
  4. package/dist/db-versioning.d.ts.map +1 -0
  5. package/dist/db-versioning.js +206 -0
  6. package/dist/db-versioning.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +202 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/indexer/chunker.d.ts +32 -0
  12. package/dist/indexer/chunker.d.ts.map +1 -0
  13. package/dist/indexer/chunker.js +157 -0
  14. package/dist/indexer/chunker.js.map +1 -0
  15. package/dist/indexer/crawler.d.ts +28 -0
  16. package/dist/indexer/crawler.d.ts.map +1 -0
  17. package/dist/indexer/crawler.js +550 -0
  18. package/dist/indexer/crawler.js.map +1 -0
  19. package/dist/indexer/embeddings.d.ts +56 -0
  20. package/dist/indexer/embeddings.d.ts.map +1 -0
  21. package/dist/indexer/embeddings.js +200 -0
  22. package/dist/indexer/embeddings.js.map +1 -0
  23. package/dist/indexer/sitemap.d.ts +35 -0
  24. package/dist/indexer/sitemap.d.ts.map +1 -0
  25. package/dist/indexer/sitemap.js +263 -0
  26. package/dist/indexer/sitemap.js.map +1 -0
  27. package/dist/indexer/store.d.ts +237 -0
  28. package/dist/indexer/store.d.ts.map +1 -0
  29. package/dist/indexer/store.js +857 -0
  30. package/dist/indexer/store.js.map +1 -0
  31. package/dist/indexer/types.d.ts +77 -0
  32. package/dist/indexer/types.d.ts.map +1 -0
  33. package/dist/indexer/types.js +2 -0
  34. package/dist/indexer/types.js.map +1 -0
  35. package/dist/indexer/updater.d.ts +52 -0
  36. package/dist/indexer/updater.d.ts.map +1 -0
  37. package/dist/indexer/updater.js +263 -0
  38. package/dist/indexer/updater.js.map +1 -0
  39. package/dist/services/concept-service.d.ts +49 -0
  40. package/dist/services/concept-service.d.ts.map +1 -0
  41. package/dist/services/concept-service.js +554 -0
  42. package/dist/services/concept-service.js.map +1 -0
  43. package/dist/services/example-service.d.ts +52 -0
  44. package/dist/services/example-service.d.ts.map +1 -0
  45. package/dist/services/example-service.js +302 -0
  46. package/dist/services/example-service.js.map +1 -0
  47. package/dist/services/search-service.d.ts +54 -0
  48. package/dist/services/search-service.d.ts.map +1 -0
  49. package/dist/services/search-service.js +519 -0
  50. package/dist/services/search-service.js.map +1 -0
  51. package/dist/services/search-utils.d.ts +34 -0
  52. package/dist/services/search-utils.d.ts.map +1 -0
  53. package/dist/services/search-utils.js +239 -0
  54. package/dist/services/search-utils.js.map +1 -0
  55. package/dist/tools/explainConcept.d.ts +9 -0
  56. package/dist/tools/explainConcept.d.ts.map +1 -0
  57. package/dist/tools/explainConcept.js +93 -0
  58. package/dist/tools/explainConcept.js.map +1 -0
  59. package/dist/tools/getExample.d.ts +20 -0
  60. package/dist/tools/getExample.d.ts.map +1 -0
  61. package/dist/tools/getExample.js +88 -0
  62. package/dist/tools/getExample.js.map +1 -0
  63. package/dist/tools/getMinecraftVersion.d.ts +6 -0
  64. package/dist/tools/getMinecraftVersion.d.ts.map +1 -0
  65. package/dist/tools/getMinecraftVersion.js +57 -0
  66. package/dist/tools/getMinecraftVersion.js.map +1 -0
  67. package/dist/tools/searchDocs.d.ts +15 -0
  68. package/dist/tools/searchDocs.d.ts.map +1 -0
  69. package/dist/tools/searchDocs.js +144 -0
  70. package/dist/tools/searchDocs.js.map +1 -0
  71. package/package.json +111 -0
  72. package/scripts/postinstall.js +859 -0
@@ -0,0 +1,857 @@
1
+ import Database from 'better-sqlite3';
2
+ const SCHEMA_VERSION = 1;
3
+ export class DocumentStore {
4
+ db;
5
+ constructor(dbPath) {
6
+ this.db = new Database(dbPath);
7
+ this.initializeSchema();
8
+ }
9
+ initializeSchema() {
10
+ this.db.exec(`
11
+ -- Metadata table for versioning
12
+ CREATE TABLE IF NOT EXISTS metadata (
13
+ key TEXT PRIMARY KEY,
14
+ value TEXT NOT NULL
15
+ );
16
+
17
+ -- Documents table
18
+ CREATE TABLE IF NOT EXISTS documents (
19
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
20
+ url TEXT UNIQUE NOT NULL,
21
+ title TEXT NOT NULL,
22
+ content TEXT NOT NULL,
23
+ raw_html TEXT,
24
+ category TEXT NOT NULL,
25
+ loader TEXT NOT NULL,
26
+ minecraft_version TEXT,
27
+ hash TEXT NOT NULL,
28
+ indexed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
29
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
30
+ );
31
+
32
+ -- Sections table for structured content
33
+ CREATE TABLE IF NOT EXISTS sections (
34
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
35
+ document_id INTEGER NOT NULL,
36
+ heading TEXT NOT NULL,
37
+ level INTEGER NOT NULL,
38
+ content TEXT NOT NULL,
39
+ order_num INTEGER NOT NULL,
40
+ FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE
41
+ );
42
+
43
+ -- Code blocks table
44
+ CREATE TABLE IF NOT EXISTS code_blocks (
45
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
46
+ section_id INTEGER NOT NULL,
47
+ language TEXT NOT NULL,
48
+ code TEXT NOT NULL,
49
+ caption TEXT,
50
+ FOREIGN KEY (section_id) REFERENCES sections(id) ON DELETE CASCADE
51
+ );
52
+
53
+ -- Chunks table for optimized search
54
+ CREATE TABLE IF NOT EXISTS chunks (
55
+ id TEXT PRIMARY KEY,
56
+ document_id INTEGER NOT NULL,
57
+ chunk_type TEXT NOT NULL,
58
+ content TEXT NOT NULL,
59
+ section_heading TEXT,
60
+ section_level INTEGER,
61
+ code_language TEXT,
62
+ order_num INTEGER NOT NULL,
63
+ word_count INTEGER NOT NULL,
64
+ has_code BOOLEAN NOT NULL,
65
+ FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE
66
+ );
67
+
68
+ -- Embeddings table for semantic search
69
+ CREATE TABLE IF NOT EXISTS embeddings (
70
+ chunk_id TEXT PRIMARY KEY,
71
+ embedding BLOB NOT NULL,
72
+ dimension INTEGER NOT NULL,
73
+ model TEXT NOT NULL,
74
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
75
+ FOREIGN KEY (chunk_id) REFERENCES chunks(id) ON DELETE CASCADE
76
+ );
77
+
78
+ -- Full-text search indexes
79
+ CREATE VIRTUAL TABLE IF NOT EXISTS documents_fts USING fts5(
80
+ title,
81
+ content,
82
+ category,
83
+ content='documents',
84
+ content_rowid='id'
85
+ );
86
+
87
+ CREATE VIRTUAL TABLE IF NOT EXISTS chunks_fts USING fts5(
88
+ content,
89
+ section_heading,
90
+ content='chunks',
91
+ content_rowid='rowid'
92
+ );
93
+
94
+ -- Indexes for performance
95
+ CREATE INDEX IF NOT EXISTS idx_documents_loader ON documents(loader);
96
+ CREATE INDEX IF NOT EXISTS idx_documents_category ON documents(category);
97
+ CREATE INDEX IF NOT EXISTS idx_documents_hash ON documents(hash);
98
+ CREATE INDEX IF NOT EXISTS idx_documents_version ON documents(minecraft_version);
99
+ CREATE INDEX IF NOT EXISTS idx_chunks_document ON chunks(document_id);
100
+ CREATE INDEX IF NOT EXISTS idx_chunks_type ON chunks(chunk_type);
101
+ CREATE INDEX IF NOT EXISTS idx_embeddings_model ON embeddings(model);
102
+
103
+ -- Triggers for FTS sync
104
+ CREATE TRIGGER IF NOT EXISTS documents_ai AFTER INSERT ON documents BEGIN
105
+ INSERT INTO documents_fts(rowid, title, content, category)
106
+ VALUES (new.id, new.title, new.content, new.category);
107
+ END;
108
+
109
+ CREATE TRIGGER IF NOT EXISTS documents_ad AFTER DELETE ON documents BEGIN
110
+ DELETE FROM documents_fts WHERE rowid = old.id;
111
+ END;
112
+
113
+ CREATE TRIGGER IF NOT EXISTS documents_au AFTER UPDATE ON documents BEGIN
114
+ UPDATE documents_fts SET
115
+ title = new.title,
116
+ content = new.content,
117
+ category = new.category
118
+ WHERE rowid = new.id;
119
+ END;
120
+
121
+ CREATE TRIGGER IF NOT EXISTS chunks_ai AFTER INSERT ON chunks BEGIN
122
+ INSERT INTO chunks_fts(rowid, content, section_heading)
123
+ VALUES (new.rowid, new.content, new.section_heading);
124
+ END;
125
+
126
+ CREATE TRIGGER IF NOT EXISTS chunks_ad AFTER DELETE ON chunks BEGIN
127
+ DELETE FROM chunks_fts WHERE rowid = old.rowid;
128
+ END;
129
+ `);
130
+ this.initializeMetadata();
131
+ }
132
+ initializeMetadata() {
133
+ const stmt = this.db.prepare('INSERT OR IGNORE INTO metadata (key, value) VALUES (?, ?)');
134
+ stmt.run('schema_version', SCHEMA_VERSION.toString());
135
+ stmt.run('index_version', '0.1.0');
136
+ stmt.run('last_updated', new Date().toISOString());
137
+ }
138
+ needsUpdate(url, hash) {
139
+ const stmt = this.db.prepare('SELECT hash FROM documents WHERE url = ?');
140
+ const result = stmt.get(url);
141
+ return !result || result.hash !== hash;
142
+ }
143
+ storeDocument(doc) {
144
+ const insert = this.db.prepare(`
145
+ INSERT OR REPLACE INTO documents (url, title, content, raw_html, category, loader, hash, minecraft_version, updated_at)
146
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
147
+ `);
148
+ const result = insert.run(doc.url, doc.title, doc.content, doc.rawHtml, doc.category, doc.loader, doc.hash, doc.minecraftVersion || null);
149
+ const documentId = result.lastInsertRowid;
150
+ this.db.prepare('DELETE FROM sections WHERE document_id = ?').run(documentId);
151
+ for (const section of doc.sections) {
152
+ this.storeSection(documentId, section);
153
+ }
154
+ return documentId;
155
+ }
156
+ storeSection(documentId, section) {
157
+ const insert = this.db.prepare(`
158
+ INSERT INTO sections (document_id, heading, level, content, order_num)
159
+ VALUES (?, ?, ?, ?, ?)
160
+ `);
161
+ const result = insert.run(documentId, section.heading, section.level, section.content, section.order);
162
+ const sectionId = result.lastInsertRowid;
163
+ const codeInsert = this.db.prepare(`
164
+ INSERT INTO code_blocks (section_id, language, code, caption)
165
+ VALUES (?, ?, ?, ?)
166
+ `);
167
+ for (const block of section.codeBlocks) {
168
+ codeInsert.run(sectionId, block.language, block.code, block.caption || null);
169
+ }
170
+ return sectionId;
171
+ }
172
+ storeChunks(chunks, documentId) {
173
+ this.db.prepare('DELETE FROM chunks WHERE document_id = ?').run(documentId);
174
+ const insert = this.db.prepare(`
175
+ INSERT INTO chunks (
176
+ id, document_id, chunk_type, content, section_heading, section_level,
177
+ code_language, order_num, word_count, has_code
178
+ )
179
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
180
+ `);
181
+ for (const chunk of chunks) {
182
+ insert.run(chunk.id, documentId, chunk.chunkType, chunk.content, chunk.sectionHeading || null, chunk.sectionLevel || null, chunk.codeLanguage || null, chunk.order, chunk.metadata.wordCount, chunk.metadata.hasCode ? 1 : 0);
183
+ }
184
+ }
185
+ searchChunks(query, category, loader, limit = 10) {
186
+ let sql = `
187
+ SELECT
188
+ c.id,
189
+ c.content,
190
+ c.chunk_type,
191
+ c.section_heading,
192
+ c.code_language,
193
+ d.url,
194
+ d.title,
195
+ d.category,
196
+ d.loader,
197
+ chunks_fts.rank
198
+ FROM chunks_fts
199
+ JOIN chunks c ON chunks_fts.rowid = c.rowid
200
+ JOIN documents d ON c.document_id = d.id
201
+ WHERE chunks_fts MATCH ?
202
+ `;
203
+ const params = [query];
204
+ if (category && category !== 'all') {
205
+ sql += ' AND d.category = ?';
206
+ params.push(category);
207
+ }
208
+ if (loader) {
209
+ sql += ' AND d.loader = ?';
210
+ params.push(loader);
211
+ }
212
+ sql += ` ORDER BY chunks_fts.rank LIMIT ?`;
213
+ params.push(limit);
214
+ const stmt = this.db.prepare(sql);
215
+ return stmt.all(...params);
216
+ }
217
+ searchDocuments(query, category, loader, limit = 10) {
218
+ let sql = `
219
+ SELECT
220
+ d.id,
221
+ d.url,
222
+ d.title,
223
+ d.content,
224
+ d.category,
225
+ d.loader,
226
+ documents_fts.rank
227
+ FROM documents_fts
228
+ JOIN documents d ON documents_fts.rowid = d.id
229
+ WHERE documents_fts MATCH ?
230
+ `;
231
+ const params = [query];
232
+ if (category && category !== 'all') {
233
+ sql += ' AND d.category = ?';
234
+ params.push(category);
235
+ }
236
+ if (loader) {
237
+ sql += ' AND d.loader = ?';
238
+ params.push(loader);
239
+ }
240
+ sql += ` ORDER BY documents_fts.rank LIMIT ?`;
241
+ params.push(limit);
242
+ const stmt = this.db.prepare(sql);
243
+ return stmt.all(...params);
244
+ }
245
+ getStats() {
246
+ const docCount = this.db.prepare('SELECT COUNT(*) as count FROM documents').get();
247
+ const sectionCount = this.db.prepare('SELECT COUNT(*) as count FROM sections').get();
248
+ const codeCount = this.db.prepare('SELECT COUNT(*) as count FROM code_blocks').get();
249
+ const loaderCounts = this.db
250
+ .prepare('SELECT loader, COUNT(*) as count FROM documents GROUP BY loader')
251
+ .all();
252
+ const lastUpdated = this.db
253
+ .prepare("SELECT value FROM metadata WHERE key = 'last_updated'")
254
+ .get();
255
+ const version = this.db
256
+ .prepare("SELECT value FROM metadata WHERE key = 'index_version'")
257
+ .get();
258
+ const loaders = {
259
+ fabric: 0,
260
+ neoforge: 0,
261
+ shared: 0,
262
+ };
263
+ for (const item of loaderCounts) {
264
+ if (item.loader in loaders) {
265
+ loaders[item.loader] = item.count;
266
+ }
267
+ }
268
+ return {
269
+ totalDocuments: docCount.count,
270
+ totalSections: sectionCount.count,
271
+ totalCodeBlocks: codeCount.count,
272
+ lastUpdated: new Date(lastUpdated.value),
273
+ version: version.value,
274
+ loaders,
275
+ };
276
+ }
277
+ updateTimestamp() {
278
+ this.db
279
+ .prepare("UPDATE metadata SET value = ? WHERE key = 'last_updated'")
280
+ .run(new Date().toISOString());
281
+ }
282
+ getAllUrls() {
283
+ const results = this.db.prepare('SELECT url FROM documents').all();
284
+ return results.map((r) => r.url);
285
+ }
286
+ storeEmbeddings(embeddings, model) {
287
+ const insert = this.db.prepare(`
288
+ INSERT OR REPLACE INTO embeddings (chunk_id, embedding, dimension, model)
289
+ VALUES (?, ?, ?, ?)
290
+ `);
291
+ for (const item of embeddings) {
292
+ const buffer = Buffer.from(new Float32Array(item.embedding).buffer);
293
+ insert.run(item.chunkId, buffer, item.embedding.length, model);
294
+ }
295
+ }
296
+ getEmbedding(chunkId) {
297
+ const stmt = this.db.prepare('SELECT embedding, dimension FROM embeddings WHERE chunk_id = ?');
298
+ const result = stmt.get(chunkId);
299
+ if (!result)
300
+ return undefined;
301
+ const float32Array = new Float32Array(result.embedding.buffer, result.embedding.byteOffset, result.embedding.byteLength / Float32Array.BYTES_PER_ELEMENT);
302
+ return Array.from(float32Array);
303
+ }
304
+ getAllEmbeddings() {
305
+ const stmt = this.db.prepare('SELECT chunk_id, embedding, dimension FROM embeddings');
306
+ const results = stmt.all();
307
+ return results.map((row) => {
308
+ const float32Array = new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / Float32Array.BYTES_PER_ELEMENT);
309
+ return {
310
+ chunkId: row.chunk_id,
311
+ embedding: Array.from(float32Array),
312
+ };
313
+ });
314
+ }
315
+ getEmbeddingsBatch(batchSize = 500, offset = 0) {
316
+ const stmt = this.db.prepare('SELECT chunk_id, embedding, dimension FROM embeddings LIMIT ? OFFSET ?');
317
+ const results = stmt.all(batchSize, offset);
318
+ return results.map((row) => {
319
+ const float32Array = new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / Float32Array.BYTES_PER_ELEMENT);
320
+ return {
321
+ chunkId: row.chunk_id,
322
+ embedding: Array.from(float32Array),
323
+ };
324
+ });
325
+ }
326
+ getEmbeddingCount() {
327
+ const stmt = this.db.prepare('SELECT COUNT(*) as count FROM embeddings');
328
+ const result = stmt.get();
329
+ return result.count;
330
+ }
331
+ deleteEmbeddings(documentId) {
332
+ this.db
333
+ .prepare(`
334
+ DELETE FROM embeddings
335
+ WHERE chunk_id IN (
336
+ SELECT id FROM chunks WHERE document_id = ?
337
+ )
338
+ `)
339
+ .run(documentId);
340
+ }
341
+ getDocumentsByVersion(version) {
342
+ const stmt = this.db.prepare(`
343
+ SELECT * FROM documents WHERE minecraft_version = ?
344
+ `);
345
+ return stmt.all(version);
346
+ }
347
+ getAllVersions() {
348
+ const stmt = this.db.prepare(`
349
+ SELECT DISTINCT minecraft_version FROM documents
350
+ WHERE minecraft_version IS NOT NULL
351
+ ORDER BY minecraft_version DESC
352
+ `);
353
+ const results = stmt.all();
354
+ return results.map((r) => r.minecraft_version);
355
+ }
356
+ searchByVersion(query, version, category, loader, limit = 10) {
357
+ let sql = `
358
+ SELECT
359
+ c.id,
360
+ c.content,
361
+ c.chunk_type,
362
+ c.section_heading,
363
+ c.code_language,
364
+ d.url,
365
+ d.title,
366
+ d.category,
367
+ d.loader,
368
+ d.minecraft_version,
369
+ chunks_fts.rank
370
+ FROM chunks_fts
371
+ JOIN chunks c ON chunks_fts.rowid = c.rowid
372
+ JOIN documents d ON c.document_id = d.id
373
+ WHERE chunks_fts MATCH ? AND d.minecraft_version = ?
374
+ `;
375
+ const params = [query, version];
376
+ if (category && category !== 'all') {
377
+ sql += ' AND d.category = ?';
378
+ params.push(category);
379
+ }
380
+ if (loader) {
381
+ sql += ' AND d.loader = ?';
382
+ params.push(loader);
383
+ }
384
+ sql += ` ORDER BY chunks_fts.rank LIMIT ?`;
385
+ params.push(limit);
386
+ const stmt = this.db.prepare(sql);
387
+ return stmt.all(...params);
388
+ }
389
+ getEmbeddingStats() {
390
+ const total = this.db.prepare('SELECT COUNT(*) as count FROM embeddings').get();
391
+ const models = this.db
392
+ .prepare('SELECT model, COUNT(*) as count FROM embeddings GROUP BY model')
393
+ .all();
394
+ return {
395
+ totalEmbeddings: total.count,
396
+ models,
397
+ };
398
+ }
399
+ searchCodeExamples(topic, options = {}) {
400
+ const { language, minecraftVersion, loader, category, limit = 10 } = options;
401
+ const searchQuery = topic.trim();
402
+ let sql = `
403
+ SELECT
404
+ c.id,
405
+ c.content,
406
+ c.chunk_type,
407
+ c.section_heading,
408
+ c.section_level,
409
+ c.code_language,
410
+ c.has_code,
411
+ d.id as document_id,
412
+ d.url,
413
+ d.title,
414
+ d.category,
415
+ d.loader,
416
+ d.minecraft_version,
417
+ chunks_fts.rank
418
+ FROM chunks_fts
419
+ JOIN chunks c ON chunks_fts.rowid = c.rowid
420
+ JOIN documents d ON c.document_id = d.id
421
+ WHERE chunks_fts MATCH ?
422
+ AND c.has_code = 1
423
+ `;
424
+ const params = [searchQuery];
425
+ if (language) {
426
+ sql += ' AND c.code_language = ?';
427
+ params.push(language);
428
+ }
429
+ if (minecraftVersion) {
430
+ sql += ' AND d.minecraft_version = ?';
431
+ params.push(minecraftVersion);
432
+ }
433
+ if (loader) {
434
+ sql += ' AND d.loader = ?';
435
+ params.push(loader);
436
+ }
437
+ if (category && category !== 'all') {
438
+ sql += ' AND d.category = ?';
439
+ params.push(category);
440
+ }
441
+ sql += ` ORDER BY chunks_fts.rank LIMIT ?`;
442
+ params.push(limit);
443
+ const stmt = this.db.prepare(sql);
444
+ return stmt.all(...params);
445
+ }
446
+ getCodeBlocksWithContext(documentId) {
447
+ const sql = `
448
+ SELECT
449
+ cb.id,
450
+ cb.language,
451
+ cb.code,
452
+ cb.caption,
453
+ s.heading as section_heading,
454
+ s.level as section_level,
455
+ s.content as section_content,
456
+ d.id as document_id,
457
+ d.title as document_title,
458
+ d.url as document_url,
459
+ d.category,
460
+ d.loader,
461
+ d.minecraft_version
462
+ FROM code_blocks cb
463
+ JOIN sections s ON cb.section_id = s.id
464
+ JOIN documents d ON s.document_id = d.id
465
+ WHERE d.id = ?
466
+ ORDER BY s.order_num, cb.id
467
+ `;
468
+ const stmt = this.db.prepare(sql);
469
+ return stmt.all(documentId);
470
+ }
471
+ getCodeBlocksByLanguage(language, options = {}) {
472
+ const { loader, minecraftVersion, limit = 20 } = options;
473
+ let sql = `
474
+ SELECT
475
+ cb.id,
476
+ cb.language,
477
+ cb.code,
478
+ cb.caption,
479
+ s.heading as section_heading,
480
+ s.level as section_level,
481
+ s.content as section_content,
482
+ d.id as document_id,
483
+ d.title as document_title,
484
+ d.url as document_url,
485
+ d.category,
486
+ d.loader,
487
+ d.minecraft_version
488
+ FROM code_blocks cb
489
+ JOIN sections s ON cb.section_id = s.id
490
+ JOIN documents d ON s.document_id = d.id
491
+ WHERE cb.language = ?
492
+ `;
493
+ const params = [language];
494
+ if (loader) {
495
+ sql += ' AND d.loader = ?';
496
+ params.push(loader);
497
+ }
498
+ if (minecraftVersion) {
499
+ sql += ' AND d.minecraft_version = ?';
500
+ params.push(minecraftVersion);
501
+ }
502
+ sql += ' ORDER BY d.id, s.order_num LIMIT ?';
503
+ params.push(limit);
504
+ const stmt = this.db.prepare(sql);
505
+ return stmt.all(...params);
506
+ }
507
+ searchSections(query, options = {}) {
508
+ const { loader, minecraftVersion, category, limit = 10 } = options;
509
+ let sql = `
510
+ SELECT
511
+ s.id,
512
+ s.heading,
513
+ s.level,
514
+ s.content,
515
+ s.order_num,
516
+ d.id as document_id,
517
+ d.title as document_title,
518
+ d.url as document_url,
519
+ d.category,
520
+ d.loader,
521
+ d.minecraft_version
522
+ FROM sections s
523
+ JOIN documents d ON s.document_id = d.id
524
+ WHERE s.heading LIKE ? OR s.content LIKE ?
525
+ `;
526
+ const searchPattern = `%${query}%`;
527
+ const params = [searchPattern, searchPattern];
528
+ if (loader) {
529
+ sql += ' AND d.loader = ?';
530
+ params.push(loader);
531
+ }
532
+ if (minecraftVersion) {
533
+ sql += ' AND d.minecraft_version = ?';
534
+ params.push(minecraftVersion);
535
+ }
536
+ if (category && category !== 'all') {
537
+ sql += ' AND d.category = ?';
538
+ params.push(category);
539
+ }
540
+ sql += ' ORDER BY s.order_num LIMIT ?';
541
+ params.push(limit);
542
+ const stmt = this.db.prepare(sql);
543
+ return stmt.all(...params);
544
+ }
545
+ getAvailableLanguages() {
546
+ const sql = `
547
+ SELECT code_language as language, COUNT(*) as count
548
+ FROM chunks
549
+ WHERE has_code = 1 AND code_language IS NOT NULL
550
+ GROUP BY code_language
551
+ ORDER BY count DESC
552
+ `;
553
+ const stmt = this.db.prepare(sql);
554
+ return stmt.all();
555
+ }
556
+ findSimilarChunks(embedding, options = {}) {
557
+ const { limit = 10, loader, minecraftVersion, category } = options;
558
+ let sql = `
559
+ SELECT
560
+ e.chunk_id,
561
+ e.embedding,
562
+ c.content,
563
+ c.chunk_type,
564
+ c.section_heading,
565
+ c.section_level,
566
+ c.code_language,
567
+ c.has_code,
568
+ d.id as document_id,
569
+ d.url,
570
+ d.title,
571
+ d.category,
572
+ d.loader,
573
+ d.minecraft_version
574
+ FROM embeddings e
575
+ JOIN chunks c ON e.chunk_id = c.id
576
+ JOIN documents d ON c.document_id = d.id
577
+ WHERE 1=1
578
+ `;
579
+ const params = [];
580
+ if (loader) {
581
+ sql += ' AND d.loader = ?';
582
+ params.push(loader);
583
+ }
584
+ if (category && category !== 'all') {
585
+ sql += ' AND d.category = ?';
586
+ params.push(category);
587
+ }
588
+ if (minecraftVersion) {
589
+ if (minecraftVersion.includes('%')) {
590
+ sql += ' AND d.minecraft_version LIKE ?';
591
+ params.push(minecraftVersion);
592
+ }
593
+ else {
594
+ sql += ' AND d.minecraft_version = ?';
595
+ params.push(minecraftVersion);
596
+ }
597
+ }
598
+ const stmt = this.db.prepare(sql);
599
+ const candidates = stmt.all(...params);
600
+ const results = candidates.map((candidate) => {
601
+ const vector = new Float32Array(candidate.embedding.buffer, candidate.embedding.byteOffset, candidate.embedding.byteLength / 4);
602
+ const similarity = this.cosineSimilarity(embedding, vector);
603
+ return { ...candidate, similarity };
604
+ });
605
+ return results.sort((a, b) => b.similarity - a.similarity).slice(0, limit);
606
+ }
607
+ cosineSimilarity(a, b) {
608
+ let dotProduct = 0;
609
+ let normA = 0;
610
+ let normB = 0;
611
+ for (let i = 0; i < a.length; i++) {
612
+ dotProduct += a[i] * b[i];
613
+ normA += a[i] * a[i];
614
+ normB += b[i] * b[i];
615
+ }
616
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
617
+ }
618
+ searchChunksAdvanced(ftsQuery, likePatterns, options = {}) {
619
+ const { hasCode = true, language, loader, minecraftVersion, category, limit = 30 } = options;
620
+ let results = [];
621
+ if (ftsQuery && ftsQuery.length > 0) {
622
+ try {
623
+ let sql = `
624
+ SELECT
625
+ c.id,
626
+ c.content,
627
+ c.chunk_type,
628
+ c.section_heading,
629
+ c.section_level,
630
+ c.code_language,
631
+ c.has_code,
632
+ d.id as document_id,
633
+ d.url,
634
+ d.title,
635
+ d.category,
636
+ d.loader,
637
+ d.minecraft_version
638
+ FROM chunks_fts
639
+ JOIN chunks c ON chunks_fts.rowid = c.rowid
640
+ JOIN documents d ON c.document_id = d.id
641
+ WHERE chunks_fts MATCH ?
642
+ `;
643
+ const params = [ftsQuery];
644
+ if (hasCode) {
645
+ sql += ' AND c.has_code = 1';
646
+ }
647
+ if (language) {
648
+ sql += ' AND c.code_language = ?';
649
+ params.push(language);
650
+ }
651
+ if (loader) {
652
+ sql += ' AND d.loader = ?';
653
+ params.push(loader);
654
+ }
655
+ if (minecraftVersion) {
656
+ sql += ' AND d.minecraft_version = ?';
657
+ params.push(minecraftVersion);
658
+ }
659
+ if (category && category !== 'all') {
660
+ sql += ' AND d.category = ?';
661
+ params.push(category);
662
+ }
663
+ sql += ` ORDER BY rank LIMIT ?`;
664
+ params.push(limit);
665
+ const stmt = this.db.prepare(sql);
666
+ results = stmt.all(...params);
667
+ }
668
+ catch {
669
+ console.error('[store] FTS query failed, trying LIKE fallback');
670
+ }
671
+ }
672
+ if (results.length === 0 && likePatterns.length > 0) {
673
+ const likeConditions = likePatterns
674
+ .slice(0, 3)
675
+ .map(() => '(c.content LIKE ? OR c.section_heading LIKE ?)')
676
+ .join(' OR ');
677
+ let sql = `
678
+ SELECT
679
+ c.id,
680
+ c.content,
681
+ c.chunk_type,
682
+ c.section_heading,
683
+ c.section_level,
684
+ c.code_language,
685
+ c.has_code,
686
+ d.id as document_id,
687
+ d.url,
688
+ d.title,
689
+ d.category,
690
+ d.loader,
691
+ d.minecraft_version
692
+ FROM chunks c
693
+ JOIN documents d ON c.document_id = d.id
694
+ WHERE (${likeConditions})
695
+ `;
696
+ const params = [];
697
+ for (const pattern of likePatterns.slice(0, 3)) {
698
+ params.push(pattern, pattern);
699
+ }
700
+ if (hasCode) {
701
+ sql += ' AND c.has_code = 1';
702
+ }
703
+ if (language) {
704
+ sql += ' AND c.code_language = ?';
705
+ params.push(language);
706
+ }
707
+ if (loader) {
708
+ sql += ' AND d.loader = ?';
709
+ params.push(loader);
710
+ }
711
+ if (minecraftVersion) {
712
+ sql += ' AND d.minecraft_version = ?';
713
+ params.push(minecraftVersion);
714
+ }
715
+ if (category && category !== 'all') {
716
+ sql += ' AND d.category = ?';
717
+ params.push(category);
718
+ }
719
+ sql += ` LIMIT ?`;
720
+ params.push(limit);
721
+ const stmt = this.db.prepare(sql);
722
+ results = stmt.all(...params);
723
+ }
724
+ return results;
725
+ }
726
+ searchDocumentsLike(patterns, options = {}) {
727
+ const { loader, minecraftVersion, category, limit = 20 } = options;
728
+ if (patterns.length === 0)
729
+ return [];
730
+ const likeConditions = patterns
731
+ .slice(0, 3)
732
+ .map(() => '(d.title LIKE ? OR d.content LIKE ?)')
733
+ .join(' OR ');
734
+ let sql = `
735
+ SELECT
736
+ d.id,
737
+ d.url,
738
+ d.title,
739
+ d.content,
740
+ d.category,
741
+ d.loader,
742
+ d.minecraft_version
743
+ FROM documents d
744
+ WHERE (${likeConditions})
745
+ `;
746
+ const params = [];
747
+ for (const pattern of patterns.slice(0, 3)) {
748
+ params.push(pattern, pattern);
749
+ }
750
+ if (loader) {
751
+ sql += ' AND d.loader = ?';
752
+ params.push(loader);
753
+ }
754
+ if (minecraftVersion) {
755
+ sql += ' AND d.minecraft_version = ?';
756
+ params.push(minecraftVersion);
757
+ }
758
+ if (category && category !== 'all') {
759
+ sql += ' AND d.category = ?';
760
+ params.push(category);
761
+ }
762
+ sql += ` LIMIT ?`;
763
+ params.push(limit);
764
+ const stmt = this.db.prepare(sql);
765
+ return stmt.all(...params);
766
+ }
767
+ searchCodeBlocksByPatterns(codePatterns, options = {}) {
768
+ const { language, loader, minecraftVersion, limit = 20 } = options;
769
+ if (codePatterns.length === 0)
770
+ return [];
771
+ const likeConditions = codePatterns
772
+ .slice(0, 5)
773
+ .map(() => 'cb.code LIKE ?')
774
+ .join(' OR ');
775
+ let sql = `
776
+ SELECT
777
+ cb.id,
778
+ cb.language,
779
+ cb.code,
780
+ cb.caption,
781
+ s.heading as section_heading,
782
+ s.level as section_level,
783
+ s.content as section_content,
784
+ d.id as document_id,
785
+ d.title as document_title,
786
+ d.url as document_url,
787
+ d.category,
788
+ d.loader,
789
+ d.minecraft_version
790
+ FROM code_blocks cb
791
+ JOIN sections s ON cb.section_id = s.id
792
+ JOIN documents d ON s.document_id = d.id
793
+ WHERE (${likeConditions})
794
+ `;
795
+ const params = codePatterns.slice(0, 5).map((p) => `%${p}%`);
796
+ if (language) {
797
+ sql += ' AND cb.language = ?';
798
+ params.push(language);
799
+ }
800
+ if (loader) {
801
+ sql += ' AND d.loader = ?';
802
+ params.push(loader);
803
+ }
804
+ if (minecraftVersion) {
805
+ sql += ' AND d.minecraft_version = ?';
806
+ params.push(minecraftVersion);
807
+ }
808
+ sql += ` ORDER BY d.id, s.order_num LIMIT ?`;
809
+ params.push(limit);
810
+ const stmt = this.db.prepare(sql);
811
+ return stmt.all(...params);
812
+ }
813
+ getAllCodeBlocksWithContext(options = {}) {
814
+ const { language, loader, minecraftVersion, limit = 100 } = options;
815
+ let sql = `
816
+ SELECT
817
+ cb.id,
818
+ cb.language,
819
+ cb.code,
820
+ cb.caption,
821
+ s.heading as section_heading,
822
+ s.level as section_level,
823
+ s.content as section_content,
824
+ d.id as document_id,
825
+ d.title as document_title,
826
+ d.url as document_url,
827
+ d.category,
828
+ d.loader,
829
+ d.minecraft_version
830
+ FROM code_blocks cb
831
+ JOIN sections s ON cb.section_id = s.id
832
+ JOIN documents d ON s.document_id = d.id
833
+ WHERE 1=1
834
+ `;
835
+ const params = [];
836
+ if (language) {
837
+ sql += ' AND cb.language = ?';
838
+ params.push(language);
839
+ }
840
+ if (loader) {
841
+ sql += ' AND d.loader = ?';
842
+ params.push(loader);
843
+ }
844
+ if (minecraftVersion) {
845
+ sql += ' AND d.minecraft_version = ?';
846
+ params.push(minecraftVersion);
847
+ }
848
+ sql += ` ORDER BY d.id, s.order_num LIMIT ?`;
849
+ params.push(limit);
850
+ const stmt = this.db.prepare(sql);
851
+ return stmt.all(...params);
852
+ }
853
+ close() {
854
+ this.db.close();
855
+ }
856
+ }
857
+ //# sourceMappingURL=store.js.map