|
@@ -4,7 +4,7 @@
|
|
|
* Usage:
|
|
* Usage:
|
|
|
* import { createStore } from '@tobilu/qmd'
|
|
* import { createStore } from '@tobilu/qmd'
|
|
|
*
|
|
*
|
|
|
- * const store = createStore({
|
|
|
|
|
|
|
+ * const store = await createStore({
|
|
|
* dbPath: './my-index.sqlite',
|
|
* dbPath: './my-index.sqlite',
|
|
|
* config: {
|
|
* config: {
|
|
|
* collections: {
|
|
* collections: {
|
|
@@ -13,15 +13,29 @@
|
|
|
* }
|
|
* }
|
|
|
* })
|
|
* })
|
|
|
*
|
|
*
|
|
|
- * const results = await store.query("how does auth work?")
|
|
|
|
|
- * store.close()
|
|
|
|
|
|
|
+ * const results = await store.search({ query: "how does auth work?" })
|
|
|
|
|
+ * await store.close()
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
import {
|
|
import {
|
|
|
createStore as createStoreInternal,
|
|
createStore as createStoreInternal,
|
|
|
hybridQuery,
|
|
hybridQuery,
|
|
|
structuredSearch,
|
|
structuredSearch,
|
|
|
|
|
+ DEFAULT_EMBED_MODEL,
|
|
|
|
|
+ reindexCollection,
|
|
|
|
|
+ generateEmbeddings,
|
|
|
listCollections as storeListCollections,
|
|
listCollections as storeListCollections,
|
|
|
|
|
+ syncConfigToDb,
|
|
|
|
|
+ getStoreCollections,
|
|
|
|
|
+ getStoreCollection,
|
|
|
|
|
+ getStoreGlobalContext,
|
|
|
|
|
+ getStoreContexts,
|
|
|
|
|
+ upsertStoreCollection,
|
|
|
|
|
+ deleteStoreCollection,
|
|
|
|
|
+ renameStoreCollection,
|
|
|
|
|
+ updateStoreContext,
|
|
|
|
|
+ removeStoreContext,
|
|
|
|
|
+ setStoreGlobalContext,
|
|
|
type Store as InternalStore,
|
|
type Store as InternalStore,
|
|
|
type DocumentResult,
|
|
type DocumentResult,
|
|
|
type DocumentNotFound,
|
|
type DocumentNotFound,
|
|
@@ -29,26 +43,29 @@ import {
|
|
|
type HybridQueryResult,
|
|
type HybridQueryResult,
|
|
|
type HybridQueryOptions,
|
|
type HybridQueryOptions,
|
|
|
type HybridQueryExplain,
|
|
type HybridQueryExplain,
|
|
|
- type StructuredSubSearch,
|
|
|
|
|
|
|
+ type ExpandedQuery,
|
|
|
type StructuredSearchOptions,
|
|
type StructuredSearchOptions,
|
|
|
type MultiGetResult,
|
|
type MultiGetResult,
|
|
|
type IndexStatus,
|
|
type IndexStatus,
|
|
|
type IndexHealthInfo,
|
|
type IndexHealthInfo,
|
|
|
- type ExpandedQuery,
|
|
|
|
|
type SearchHooks,
|
|
type SearchHooks,
|
|
|
|
|
+ type ReindexProgress,
|
|
|
|
|
+ type ReindexResult,
|
|
|
|
|
+ type EmbedProgress,
|
|
|
|
|
+ type EmbedResult,
|
|
|
} from "./store.js";
|
|
} from "./store.js";
|
|
|
|
|
+import {
|
|
|
|
|
+ LlamaCpp,
|
|
|
|
|
+} from "./llm.js";
|
|
|
import {
|
|
import {
|
|
|
setConfigSource,
|
|
setConfigSource,
|
|
|
loadConfig,
|
|
loadConfig,
|
|
|
addCollection as collectionsAddCollection,
|
|
addCollection as collectionsAddCollection,
|
|
|
removeCollection as collectionsRemoveCollection,
|
|
removeCollection as collectionsRemoveCollection,
|
|
|
renameCollection as collectionsRenameCollection,
|
|
renameCollection as collectionsRenameCollection,
|
|
|
- listCollections as collectionsListCollections,
|
|
|
|
|
addContext as collectionsAddContext,
|
|
addContext as collectionsAddContext,
|
|
|
removeContext as collectionsRemoveContext,
|
|
removeContext as collectionsRemoveContext,
|
|
|
setGlobalContext as collectionsSetGlobalContext,
|
|
setGlobalContext as collectionsSetGlobalContext,
|
|
|
- getGlobalContext as collectionsGetGlobalContext,
|
|
|
|
|
- listAllContexts as collectionsListAllContexts,
|
|
|
|
|
type Collection,
|
|
type Collection,
|
|
|
type CollectionConfig,
|
|
type CollectionConfig,
|
|
|
type NamedCollection,
|
|
type NamedCollection,
|
|
@@ -63,22 +80,97 @@ export type {
|
|
|
HybridQueryResult,
|
|
HybridQueryResult,
|
|
|
HybridQueryOptions,
|
|
HybridQueryOptions,
|
|
|
HybridQueryExplain,
|
|
HybridQueryExplain,
|
|
|
- StructuredSubSearch,
|
|
|
|
|
|
|
+ ExpandedQuery,
|
|
|
StructuredSearchOptions,
|
|
StructuredSearchOptions,
|
|
|
MultiGetResult,
|
|
MultiGetResult,
|
|
|
IndexStatus,
|
|
IndexStatus,
|
|
|
IndexHealthInfo,
|
|
IndexHealthInfo,
|
|
|
- ExpandedQuery,
|
|
|
|
|
SearchHooks,
|
|
SearchHooks,
|
|
|
|
|
+ ReindexProgress,
|
|
|
|
|
+ ReindexResult,
|
|
|
|
|
+ EmbedProgress,
|
|
|
|
|
+ EmbedResult,
|
|
|
Collection,
|
|
Collection,
|
|
|
CollectionConfig,
|
|
CollectionConfig,
|
|
|
NamedCollection,
|
|
NamedCollection,
|
|
|
ContextMap,
|
|
ContextMap,
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Progress info emitted during update() for each file processed.
|
|
|
|
|
+ */
|
|
|
|
|
+export type UpdateProgress = {
|
|
|
|
|
+ collection: string;
|
|
|
|
|
+ file: string;
|
|
|
|
|
+ current: number;
|
|
|
|
|
+ total: number;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Aggregated result from update() across all collections.
|
|
|
|
|
+ */
|
|
|
|
|
+export type UpdateResult = {
|
|
|
|
|
+ collections: number;
|
|
|
|
|
+ indexed: number;
|
|
|
|
|
+ updated: number;
|
|
|
|
|
+ unchanged: number;
|
|
|
|
|
+ removed: number;
|
|
|
|
|
+ needsEmbedding: number;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Options for the unified search() method.
|
|
|
|
|
+ */
|
|
|
|
|
+export interface SearchOptions {
|
|
|
|
|
+ /** Simple query string — will be auto-expanded via LLM */
|
|
|
|
|
+ query?: string;
|
|
|
|
|
+ /** Pre-expanded queries (from expandQuery) — skips auto-expansion */
|
|
|
|
|
+ queries?: ExpandedQuery[];
|
|
|
|
|
+ /** Domain intent hint — steers expansion and reranking */
|
|
|
|
|
+ intent?: string;
|
|
|
|
|
+ /** Rerank results using LLM (default: true) */
|
|
|
|
|
+ rerank?: boolean;
|
|
|
|
|
+ /** Filter to a specific collection */
|
|
|
|
|
+ collection?: string;
|
|
|
|
|
+ /** Filter to specific collections */
|
|
|
|
|
+ collections?: string[];
|
|
|
|
|
+ /** Max results (default: 10) */
|
|
|
|
|
+ limit?: number;
|
|
|
|
|
+ /** Minimum score threshold */
|
|
|
|
|
+ minScore?: number;
|
|
|
|
|
+ /** Include explain traces */
|
|
|
|
|
+ explain?: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Options for searchLex() — BM25 keyword search.
|
|
|
|
|
+ */
|
|
|
|
|
+export interface LexSearchOptions {
|
|
|
|
|
+ limit?: number;
|
|
|
|
|
+ collection?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Options for searchVector() — vector similarity search.
|
|
|
|
|
+ */
|
|
|
|
|
+export interface VectorSearchOptions {
|
|
|
|
|
+ limit?: number;
|
|
|
|
|
+ collection?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * Options for expandQuery() — manual query expansion.
|
|
|
|
|
+ */
|
|
|
|
|
+export interface ExpandQueryOptions {
|
|
|
|
|
+ intent?: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Options for creating a QMD store.
|
|
* Options for creating a QMD store.
|
|
|
- * You must provide `dbPath` and either `configPath` (YAML file) or `config` (inline).
|
|
|
|
|
|
|
+ *
|
|
|
|
|
+ * Provide `dbPath` and optionally `configPath` (YAML file) or `config` (inline).
|
|
|
|
|
+ * If neither configPath nor config is provided, the store reads from existing
|
|
|
|
|
+ * DB state (useful for reopening a previously-configured store).
|
|
|
*/
|
|
*/
|
|
|
export interface StoreOptions {
|
|
export interface StoreOptions {
|
|
|
/** Path to the SQLite database file */
|
|
/** Path to the SQLite database file */
|
|
@@ -92,6 +184,9 @@ export interface StoreOptions {
|
|
|
/**
|
|
/**
|
|
|
* The QMD SDK store — provides search, retrieval, collection management,
|
|
* The QMD SDK store — provides search, retrieval, collection management,
|
|
|
* context management, and indexing operations.
|
|
* context management, and indexing operations.
|
|
|
|
|
+ *
|
|
|
|
|
+ * All methods are async. The store manages its own LlamaCpp instance
|
|
|
|
|
+ * (lazy-loaded, auto-unloaded after inactivity) — no global singletons.
|
|
|
*/
|
|
*/
|
|
|
export interface QMDStore {
|
|
export interface QMDStore {
|
|
|
/** The underlying internal store (for advanced use) */
|
|
/** The underlying internal store (for advanced use) */
|
|
@@ -99,66 +194,86 @@ export interface QMDStore {
|
|
|
/** Path to the SQLite database */
|
|
/** Path to the SQLite database */
|
|
|
readonly dbPath: string;
|
|
readonly dbPath: string;
|
|
|
|
|
|
|
|
- // ── Search & Retrieval ──────────────────────────────────────────────
|
|
|
|
|
|
|
+ // ── Search ──────────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+ /** Full search: query expansion + multi-signal retrieval + LLM reranking */
|
|
|
|
|
+ search(options: SearchOptions): Promise<HybridQueryResult[]>;
|
|
|
|
|
|
|
|
- /** Hybrid search: BM25 + vector + query expansion + LLM reranking */
|
|
|
|
|
- query(query: string, options?: HybridQueryOptions): Promise<HybridQueryResult[]>;
|
|
|
|
|
|
|
+ /** BM25 keyword search (fast, no LLM) */
|
|
|
|
|
+ searchLex(query: string, options?: LexSearchOptions): Promise<SearchResult[]>;
|
|
|
|
|
|
|
|
- /** BM25 full-text keyword search (fast, no LLM) */
|
|
|
|
|
- search(query: string, options?: { limit?: number; collection?: string }): SearchResult[];
|
|
|
|
|
|
|
+ /** Vector similarity search (embedding model, no reranking) */
|
|
|
|
|
+ searchVector(query: string, options?: VectorSearchOptions): Promise<SearchResult[]>;
|
|
|
|
|
|
|
|
- /** Structured search with pre-expanded queries (for LLM callers) */
|
|
|
|
|
- structuredSearch(searches: StructuredSubSearch[], options?: StructuredSearchOptions): Promise<HybridQueryResult[]>;
|
|
|
|
|
|
|
+ /** Expand a query into typed sub-searches (lex/vec/hyde) for manual control */
|
|
|
|
|
+ expandQuery(query: string, options?: ExpandQueryOptions): Promise<ExpandedQuery[]>;
|
|
|
|
|
+
|
|
|
|
|
+ // ── Document Retrieval ──────────────────────────────────────────────
|
|
|
|
|
|
|
|
/** Get a single document by path or docid */
|
|
/** Get a single document by path or docid */
|
|
|
- get(pathOrDocid: string, options?: { includeBody?: boolean }): DocumentResult | DocumentNotFound;
|
|
|
|
|
|
|
+ get(pathOrDocid: string, options?: { includeBody?: boolean }): Promise<DocumentResult | DocumentNotFound>;
|
|
|
|
|
|
|
|
/** Get multiple documents by glob pattern or comma-separated list */
|
|
/** Get multiple documents by glob pattern or comma-separated list */
|
|
|
- multiGet(pattern: string, options?: { includeBody?: boolean; maxBytes?: number }): { docs: MultiGetResult[]; errors: string[] };
|
|
|
|
|
|
|
+ multiGet(pattern: string, options?: { includeBody?: boolean; maxBytes?: number }): Promise<{ docs: MultiGetResult[]; errors: string[] }>;
|
|
|
|
|
|
|
|
// ── Collection Management ───────────────────────────────────────────
|
|
// ── Collection Management ───────────────────────────────────────────
|
|
|
|
|
|
|
|
/** Add or update a collection */
|
|
/** Add or update a collection */
|
|
|
- addCollection(name: string, opts: { path: string; pattern?: string; ignore?: string[] }): void;
|
|
|
|
|
|
|
+ addCollection(name: string, opts: { path: string; pattern?: string; ignore?: string[] }): Promise<void>;
|
|
|
|
|
|
|
|
/** Remove a collection */
|
|
/** Remove a collection */
|
|
|
- removeCollection(name: string): boolean;
|
|
|
|
|
|
|
+ removeCollection(name: string): Promise<boolean>;
|
|
|
|
|
|
|
|
/** Rename a collection */
|
|
/** Rename a collection */
|
|
|
- renameCollection(oldName: string, newName: string): boolean;
|
|
|
|
|
|
|
+ renameCollection(oldName: string, newName: string): Promise<boolean>;
|
|
|
|
|
|
|
|
/** List all collections with document stats */
|
|
/** List all collections with document stats */
|
|
|
- listCollections(): { name: string; pwd: string; glob_pattern: string; doc_count: number; active_count: number; last_modified: string | null }[];
|
|
|
|
|
|
|
+ listCollections(): Promise<{ name: string; pwd: string; glob_pattern: string; doc_count: number; active_count: number; last_modified: string | null }[]>;
|
|
|
|
|
|
|
|
// ── Context Management ──────────────────────────────────────────────
|
|
// ── Context Management ──────────────────────────────────────────────
|
|
|
|
|
|
|
|
/** Add context for a path within a collection */
|
|
/** Add context for a path within a collection */
|
|
|
- addContext(collectionName: string, pathPrefix: string, contextText: string): boolean;
|
|
|
|
|
|
|
+ addContext(collectionName: string, pathPrefix: string, contextText: string): Promise<boolean>;
|
|
|
|
|
|
|
|
/** Remove context from a collection path */
|
|
/** Remove context from a collection path */
|
|
|
- removeContext(collectionName: string, pathPrefix: string): boolean;
|
|
|
|
|
|
|
+ removeContext(collectionName: string, pathPrefix: string): Promise<boolean>;
|
|
|
|
|
|
|
|
/** Set global context (applies to all collections) */
|
|
/** Set global context (applies to all collections) */
|
|
|
- setGlobalContext(context: string | undefined): void;
|
|
|
|
|
|
|
+ setGlobalContext(context: string | undefined): Promise<void>;
|
|
|
|
|
|
|
|
/** Get global context */
|
|
/** Get global context */
|
|
|
- getGlobalContext(): string | undefined;
|
|
|
|
|
|
|
+ getGlobalContext(): Promise<string | undefined>;
|
|
|
|
|
|
|
|
/** List all contexts across all collections */
|
|
/** List all contexts across all collections */
|
|
|
- listContexts(): Array<{ collection: string; path: string; context: string }>;
|
|
|
|
|
|
|
+ listContexts(): Promise<Array<{ collection: string; path: string; context: string }>>;
|
|
|
|
|
+
|
|
|
|
|
+ // ── Indexing ────────────────────────────────────────────────────────
|
|
|
|
|
+
|
|
|
|
|
+ /** Re-index collections by scanning the filesystem */
|
|
|
|
|
+ update(options?: {
|
|
|
|
|
+ collections?: string[];
|
|
|
|
|
+ onProgress?: (info: UpdateProgress) => void;
|
|
|
|
|
+ }): Promise<UpdateResult>;
|
|
|
|
|
+
|
|
|
|
|
+ /** Generate vector embeddings for documents that need them */
|
|
|
|
|
+ embed(options?: {
|
|
|
|
|
+ force?: boolean;
|
|
|
|
|
+ model?: string;
|
|
|
|
|
+ onProgress?: (info: EmbedProgress) => void;
|
|
|
|
|
+ }): Promise<EmbedResult>;
|
|
|
|
|
|
|
|
// ── Index Health ────────────────────────────────────────────────────
|
|
// ── Index Health ────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
/** Get index status (document counts, collections, embedding state) */
|
|
/** Get index status (document counts, collections, embedding state) */
|
|
|
- getStatus(): IndexStatus;
|
|
|
|
|
|
|
+ getStatus(): Promise<IndexStatus>;
|
|
|
|
|
|
|
|
/** Get index health info (stale embeddings, etc.) */
|
|
/** Get index health info (stale embeddings, etc.) */
|
|
|
- getIndexHealth(): IndexHealthInfo;
|
|
|
|
|
|
|
+ getIndexHealth(): Promise<IndexHealthInfo>;
|
|
|
|
|
|
|
|
// ── Lifecycle ───────────────────────────────────────────────────────
|
|
// ── Lifecycle ───────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
- /** Close the database connection */
|
|
|
|
|
- close(): void;
|
|
|
|
|
|
|
+ /** Close the store and release all resources (LLM models, DB connection) */
|
|
|
|
|
+ close(): Promise<void>;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -167,13 +282,13 @@ export interface QMDStore {
|
|
|
* @example
|
|
* @example
|
|
|
* ```typescript
|
|
* ```typescript
|
|
|
* // With a YAML config file
|
|
* // With a YAML config file
|
|
|
- * const store = createStore({
|
|
|
|
|
|
|
+ * const store = await createStore({
|
|
|
* dbPath: './index.sqlite',
|
|
* dbPath: './index.sqlite',
|
|
|
* configPath: './qmd.yml',
|
|
* configPath: './qmd.yml',
|
|
|
* })
|
|
* })
|
|
|
*
|
|
*
|
|
|
* // With inline config (no files needed besides the DB)
|
|
* // With inline config (no files needed besides the DB)
|
|
|
- * const store = createStore({
|
|
|
|
|
|
|
+ * const store = await createStore({
|
|
|
* dbPath: './index.sqlite',
|
|
* dbPath: './index.sqlite',
|
|
|
* config: {
|
|
* config: {
|
|
|
* collections: {
|
|
* collections: {
|
|
@@ -182,66 +297,190 @@ export interface QMDStore {
|
|
|
* }
|
|
* }
|
|
|
* })
|
|
* })
|
|
|
*
|
|
*
|
|
|
- * const results = await store.query("authentication flow")
|
|
|
|
|
- * store.close()
|
|
|
|
|
|
|
+ * const results = await store.search({ query: "authentication flow" })
|
|
|
|
|
+ * await store.close()
|
|
|
* ```
|
|
* ```
|
|
|
*/
|
|
*/
|
|
|
-export function createStore(options: StoreOptions): QMDStore {
|
|
|
|
|
|
|
+export async function createStore(options: StoreOptions): Promise<QMDStore> {
|
|
|
if (!options.dbPath) {
|
|
if (!options.dbPath) {
|
|
|
throw new Error("dbPath is required");
|
|
throw new Error("dbPath is required");
|
|
|
}
|
|
}
|
|
|
- if (!options.configPath && !options.config) {
|
|
|
|
|
- throw new Error("Either configPath or config is required");
|
|
|
|
|
- }
|
|
|
|
|
if (options.configPath && options.config) {
|
|
if (options.configPath && options.config) {
|
|
|
throw new Error("Provide either configPath or config, not both");
|
|
throw new Error("Provide either configPath or config, not both");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Inject config source into collections module
|
|
|
|
|
- setConfigSource({
|
|
|
|
|
- configPath: options.configPath,
|
|
|
|
|
- config: options.config,
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- // Create the internal store
|
|
|
|
|
|
|
+ // Create the internal store (opens DB, creates tables)
|
|
|
const internal = createStoreInternal(options.dbPath);
|
|
const internal = createStoreInternal(options.dbPath);
|
|
|
|
|
+ const db = internal.db;
|
|
|
|
|
+
|
|
|
|
|
+ // Track whether we have a YAML config path for write-through
|
|
|
|
|
+ const hasYamlConfig = !!options.configPath;
|
|
|
|
|
+
|
|
|
|
|
+ // Sync config into SQLite store_collections
|
|
|
|
|
+ if (options.configPath) {
|
|
|
|
|
+ // YAML mode: inject config source for write-through, sync to DB
|
|
|
|
|
+ setConfigSource({ configPath: options.configPath });
|
|
|
|
|
+ const config = loadConfig();
|
|
|
|
|
+ syncConfigToDb(db, config);
|
|
|
|
|
+ } else if (options.config) {
|
|
|
|
|
+ // Inline config mode: inject config source for mutations, sync to DB
|
|
|
|
|
+ setConfigSource({ config: options.config });
|
|
|
|
|
+ syncConfigToDb(db, options.config);
|
|
|
|
|
+ }
|
|
|
|
|
+ // else: DB-only mode — no external config, use existing store_collections
|
|
|
|
|
+
|
|
|
|
|
+ // Create a per-store LlamaCpp instance — lazy-loads models on first use,
|
|
|
|
|
+ // auto-unloads after 5 min inactivity to free VRAM.
|
|
|
|
|
+ const llm = new LlamaCpp({
|
|
|
|
|
+ inactivityTimeoutMs: 5 * 60 * 1000,
|
|
|
|
|
+ disposeModelsOnInactivity: true,
|
|
|
|
|
+ });
|
|
|
|
|
+ internal.llm = llm;
|
|
|
|
|
|
|
|
const store: QMDStore = {
|
|
const store: QMDStore = {
|
|
|
internal,
|
|
internal,
|
|
|
dbPath: internal.dbPath,
|
|
dbPath: internal.dbPath,
|
|
|
|
|
|
|
|
- // Search & Retrieval
|
|
|
|
|
- query: (q, opts) => hybridQuery(internal, q, opts),
|
|
|
|
|
- search: (q, opts) => internal.searchFTS(q, opts?.limit, opts?.collection),
|
|
|
|
|
- structuredSearch: (searches, opts) => structuredSearch(internal, searches, opts),
|
|
|
|
|
- get: (pathOrDocid, opts) => internal.findDocument(pathOrDocid, opts),
|
|
|
|
|
- multiGet: (pattern, opts) => internal.findDocuments(pattern, opts),
|
|
|
|
|
|
|
+ // Search
|
|
|
|
|
+ search: async (opts) => {
|
|
|
|
|
+ if (!opts.query && !opts.queries) {
|
|
|
|
|
+ throw new Error("search() requires either 'query' or 'queries'");
|
|
|
|
|
+ }
|
|
|
|
|
+ // Normalize collection/collections
|
|
|
|
|
+ const collections = [
|
|
|
|
|
+ ...(opts.collection ? [opts.collection] : []),
|
|
|
|
|
+ ...(opts.collections ?? []),
|
|
|
|
|
+ ];
|
|
|
|
|
+ const skipRerank = opts.rerank === false;
|
|
|
|
|
+
|
|
|
|
|
+ if (opts.queries) {
|
|
|
|
|
+ // Pre-expanded queries — use structuredSearch
|
|
|
|
|
+ return structuredSearch(internal, opts.queries, {
|
|
|
|
|
+ collections: collections.length > 0 ? collections : undefined,
|
|
|
|
|
+ limit: opts.limit,
|
|
|
|
|
+ minScore: opts.minScore,
|
|
|
|
|
+ explain: opts.explain,
|
|
|
|
|
+ intent: opts.intent,
|
|
|
|
|
+ skipRerank,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Simple query string — use hybridQuery (expand + search + rerank)
|
|
|
|
|
+ return hybridQuery(internal, opts.query!, {
|
|
|
|
|
+ collection: collections[0],
|
|
|
|
|
+ limit: opts.limit,
|
|
|
|
|
+ minScore: opts.minScore,
|
|
|
|
|
+ explain: opts.explain,
|
|
|
|
|
+ intent: opts.intent,
|
|
|
|
|
+ skipRerank,
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ searchLex: async (q, opts) => internal.searchFTS(q, opts?.limit, opts?.collection),
|
|
|
|
|
+ searchVector: async (q, opts) => internal.searchVec(q, DEFAULT_EMBED_MODEL, opts?.limit, opts?.collection),
|
|
|
|
|
+ expandQuery: async (q, opts) => internal.expandQuery(q, undefined, opts?.intent),
|
|
|
|
|
+ get: async (pathOrDocid, opts) => internal.findDocument(pathOrDocid, opts),
|
|
|
|
|
+ multiGet: async (pattern, opts) => internal.findDocuments(pattern, opts),
|
|
|
|
|
+
|
|
|
|
|
+ // Collection Management — write to SQLite + write-through to YAML/inline if configured
|
|
|
|
|
+ addCollection: async (name, opts) => {
|
|
|
|
|
+ upsertStoreCollection(db, name, { path: opts.path, pattern: opts.pattern, ignore: opts.ignore });
|
|
|
|
|
+ if (hasYamlConfig || options.config) {
|
|
|
|
|
+ collectionsAddCollection(name, opts.path, opts.pattern);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ removeCollection: async (name) => {
|
|
|
|
|
+ const result = deleteStoreCollection(db, name);
|
|
|
|
|
+ if (hasYamlConfig || options.config) {
|
|
|
|
|
+ collectionsRemoveCollection(name);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ },
|
|
|
|
|
+ renameCollection: async (oldName, newName) => {
|
|
|
|
|
+ const result = renameStoreCollection(db, oldName, newName);
|
|
|
|
|
+ if (hasYamlConfig || options.config) {
|
|
|
|
|
+ collectionsRenameCollection(oldName, newName);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ },
|
|
|
|
|
+ listCollections: async () => storeListCollections(db),
|
|
|
|
|
+
|
|
|
|
|
+ // Context Management — write to SQLite + write-through to YAML/inline if configured
|
|
|
|
|
+ addContext: async (collectionName, pathPrefix, contextText) => {
|
|
|
|
|
+ const result = updateStoreContext(db, collectionName, pathPrefix, contextText);
|
|
|
|
|
+ if (hasYamlConfig || options.config) {
|
|
|
|
|
+ collectionsAddContext(collectionName, pathPrefix, contextText);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ },
|
|
|
|
|
+ removeContext: async (collectionName, pathPrefix) => {
|
|
|
|
|
+ const result = removeStoreContext(db, collectionName, pathPrefix);
|
|
|
|
|
+ if (hasYamlConfig || options.config) {
|
|
|
|
|
+ collectionsRemoveContext(collectionName, pathPrefix);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ },
|
|
|
|
|
+ setGlobalContext: async (context) => {
|
|
|
|
|
+ setStoreGlobalContext(db, context);
|
|
|
|
|
+ if (hasYamlConfig || options.config) {
|
|
|
|
|
+ collectionsSetGlobalContext(context);
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+ getGlobalContext: async () => getStoreGlobalContext(db),
|
|
|
|
|
+ listContexts: async () => getStoreContexts(db),
|
|
|
|
|
+
|
|
|
|
|
+ // Indexing — reads collections from SQLite
|
|
|
|
|
+ update: async (updateOpts) => {
|
|
|
|
|
+ const collections = getStoreCollections(db);
|
|
|
|
|
+ const filtered = updateOpts?.collections
|
|
|
|
|
+ ? collections.filter(c => updateOpts.collections!.includes(c.name))
|
|
|
|
|
+ : collections;
|
|
|
|
|
+
|
|
|
|
|
+ internal.clearCache();
|
|
|
|
|
+
|
|
|
|
|
+ let totalIndexed = 0, totalUpdated = 0, totalUnchanged = 0, totalRemoved = 0;
|
|
|
|
|
+
|
|
|
|
|
+ for (const col of filtered) {
|
|
|
|
|
+ const result = await reindexCollection(internal, col.path, col.pattern || "**/*.md", col.name, {
|
|
|
|
|
+ ignorePatterns: col.ignore,
|
|
|
|
|
+ onProgress: updateOpts?.onProgress
|
|
|
|
|
+ ? (info) => updateOpts.onProgress!({ collection: col.name, ...info })
|
|
|
|
|
+ : undefined,
|
|
|
|
|
+ });
|
|
|
|
|
+ totalIndexed += result.indexed;
|
|
|
|
|
+ totalUpdated += result.updated;
|
|
|
|
|
+ totalUnchanged += result.unchanged;
|
|
|
|
|
+ totalRemoved += result.removed;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ collections: filtered.length,
|
|
|
|
|
+ indexed: totalIndexed,
|
|
|
|
|
+ updated: totalUpdated,
|
|
|
|
|
+ unchanged: totalUnchanged,
|
|
|
|
|
+ removed: totalRemoved,
|
|
|
|
|
+ needsEmbedding: internal.getHashesNeedingEmbedding(),
|
|
|
|
|
+ };
|
|
|
|
|
+ },
|
|
|
|
|
|
|
|
- // Collection Management
|
|
|
|
|
- addCollection: (name, opts) => {
|
|
|
|
|
- collectionsAddCollection(name, opts.path, opts.pattern);
|
|
|
|
|
|
|
+ embed: async (embedOpts) => {
|
|
|
|
|
+ return generateEmbeddings(internal, {
|
|
|
|
|
+ force: embedOpts?.force,
|
|
|
|
|
+ model: embedOpts?.model,
|
|
|
|
|
+ onProgress: embedOpts?.onProgress,
|
|
|
|
|
+ });
|
|
|
},
|
|
},
|
|
|
- removeCollection: (name) => collectionsRemoveCollection(name),
|
|
|
|
|
- renameCollection: (oldName, newName) => collectionsRenameCollection(oldName, newName),
|
|
|
|
|
- listCollections: () => storeListCollections(internal.db),
|
|
|
|
|
-
|
|
|
|
|
- // Context Management
|
|
|
|
|
- addContext: (collectionName, pathPrefix, contextText) =>
|
|
|
|
|
- collectionsAddContext(collectionName, pathPrefix, contextText),
|
|
|
|
|
- removeContext: (collectionName, pathPrefix) =>
|
|
|
|
|
- collectionsRemoveContext(collectionName, pathPrefix),
|
|
|
|
|
- setGlobalContext: (context) => collectionsSetGlobalContext(context),
|
|
|
|
|
- getGlobalContext: () => collectionsGetGlobalContext(),
|
|
|
|
|
- listContexts: () => collectionsListAllContexts(),
|
|
|
|
|
|
|
|
|
|
// Index Health
|
|
// Index Health
|
|
|
- getStatus: () => internal.getStatus(),
|
|
|
|
|
- getIndexHealth: () => internal.getIndexHealth(),
|
|
|
|
|
|
|
+ getStatus: async () => internal.getStatus(),
|
|
|
|
|
+ getIndexHealth: async () => internal.getIndexHealth(),
|
|
|
|
|
|
|
|
// Lifecycle
|
|
// Lifecycle
|
|
|
- close: () => {
|
|
|
|
|
|
|
+ close: async () => {
|
|
|
|
|
+ await llm.dispose();
|
|
|
internal.close();
|
|
internal.close();
|
|
|
- setConfigSource(undefined); // Reset config source
|
|
|
|
|
|
|
+ if (hasYamlConfig || options.config) {
|
|
|
|
|
+ setConfigSource(undefined); // Reset config source
|
|
|
|
|
+ }
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
|
|
|