@stablemodels/qmd-cf 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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/dist/chunker.d.ts +11 -0
  3. package/dist/chunker.d.ts.map +1 -0
  4. package/dist/chunker.js +199 -0
  5. package/dist/chunker.js.map +1 -0
  6. package/dist/fts.d.ts +19 -0
  7. package/dist/fts.d.ts.map +1 -0
  8. package/dist/fts.js +109 -0
  9. package/dist/fts.js.map +1 -0
  10. package/dist/hash.d.ts +7 -0
  11. package/dist/hash.d.ts.map +1 -0
  12. package/dist/hash.js +14 -0
  13. package/dist/hash.js.map +1 -0
  14. package/dist/index.d.ts +56 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +57 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/qmd.d.ts +158 -0
  19. package/dist/qmd.d.ts.map +1 -0
  20. package/dist/qmd.js +462 -0
  21. package/dist/qmd.js.map +1 -0
  22. package/dist/rrf.d.ts +22 -0
  23. package/dist/rrf.d.ts.map +1 -0
  24. package/dist/rrf.js +92 -0
  25. package/dist/rrf.js.map +1 -0
  26. package/dist/schema.d.ts +14 -0
  27. package/dist/schema.d.ts.map +1 -0
  28. package/dist/schema.js +128 -0
  29. package/dist/schema.js.map +1 -0
  30. package/dist/testing.d.ts +77 -0
  31. package/dist/testing.d.ts.map +1 -0
  32. package/dist/testing.js +242 -0
  33. package/dist/testing.js.map +1 -0
  34. package/dist/types.d.ts +118 -0
  35. package/dist/types.d.ts.map +1 -0
  36. package/dist/types.js +9 -0
  37. package/dist/types.js.map +1 -0
  38. package/dist/vector.d.ts +38 -0
  39. package/dist/vector.d.ts.map +1 -0
  40. package/dist/vector.js +174 -0
  41. package/dist/vector.js.map +1 -0
  42. package/package.json +49 -0
  43. package/src/bun-sqlite.d.ts +17 -0
  44. package/src/chunker.ts +250 -0
  45. package/src/fts.ts +140 -0
  46. package/src/hash.ts +13 -0
  47. package/src/index.ts +72 -0
  48. package/src/qmd.ts +706 -0
  49. package/src/rrf.ts +115 -0
  50. package/src/schema.ts +147 -0
  51. package/src/testing.ts +303 -0
  52. package/src/types.ts +124 -0
  53. package/src/vector.ts +236 -0
package/dist/rrf.js ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Reciprocal Rank Fusion (RRF) — merge ranked result lists into a single ranking.
3
+ *
4
+ * RRF score for document d = Σ(weight_i / (k + rank_i + 1)) across all lists
5
+ * where rank_i is the 0-based position in list i.
6
+ *
7
+ * From qmd: k=60 is the standard constant. Higher k reduces the impact of
8
+ * being ranked #1 vs #5, making the fusion more conservative.
9
+ *
10
+ * Additionally applies a top-rank bonus (from qmd):
11
+ * - Rank #1 in any list: +0.05
12
+ * - Rank #2-3 in any list: +0.02
13
+ * This prevents exact matches from being diluted by expansion queries.
14
+ */
15
+ export function reciprocalRankFusion(ftsResults, vectorResults, options = {}) {
16
+ const k = options.k ?? 60;
17
+ const ftsWeight = options.ftsWeight ?? 1.0;
18
+ const vectorWeight = options.vectorWeight ?? 1.0;
19
+ const limit = options.limit ?? 10;
20
+ const scores = new Map();
21
+ // Process FTS results
22
+ for (let rank = 0; rank < ftsResults.length; rank++) {
23
+ const r = ftsResults[rank];
24
+ const contribution = ftsWeight / (k + rank + 1);
25
+ const entry = scores.get(r.docId);
26
+ if (entry) {
27
+ entry.rrfScore += contribution;
28
+ entry.topRank = Math.min(entry.topRank, rank);
29
+ entry.sources.add("fts");
30
+ entry.sourceScores.fts = r.score;
31
+ }
32
+ else {
33
+ scores.set(r.docId, {
34
+ rrfScore: contribution,
35
+ topRank: rank,
36
+ sources: new Set(["fts"]),
37
+ sourceScores: { fts: r.score },
38
+ bestResult: r,
39
+ });
40
+ }
41
+ }
42
+ // Process vector results
43
+ for (let rank = 0; rank < vectorResults.length; rank++) {
44
+ const r = vectorResults[rank];
45
+ const contribution = vectorWeight / (k + rank + 1);
46
+ const entry = scores.get(r.docId);
47
+ if (entry) {
48
+ entry.rrfScore += contribution;
49
+ entry.topRank = Math.min(entry.topRank, rank);
50
+ entry.sources.add("vector");
51
+ entry.sourceScores.vector = r.score;
52
+ // Keep the result with better snippet context
53
+ if (r.score > (entry.sourceScores.fts ?? 0)) {
54
+ entry.bestResult = r;
55
+ }
56
+ }
57
+ else {
58
+ scores.set(r.docId, {
59
+ rrfScore: contribution,
60
+ topRank: rank,
61
+ sources: new Set(["vector"]),
62
+ sourceScores: { vector: r.score },
63
+ bestResult: r,
64
+ });
65
+ }
66
+ }
67
+ // Apply top-rank bonus
68
+ for (const entry of scores.values()) {
69
+ if (entry.topRank === 0) {
70
+ entry.rrfScore += 0.05;
71
+ }
72
+ else if (entry.topRank <= 2) {
73
+ entry.rrfScore += 0.02;
74
+ }
75
+ }
76
+ // Sort by RRF score and return
77
+ return Array.from(scores.entries())
78
+ .sort((a, b) => b[1].rrfScore - a[1].rrfScore)
79
+ .slice(0, limit)
80
+ .map(([docId, entry]) => ({
81
+ docId,
82
+ score: entry.rrfScore,
83
+ snippet: entry.bestResult.snippet,
84
+ sources: Array.from(entry.sources),
85
+ sourceScores: entry.sourceScores,
86
+ title: entry.bestResult.title,
87
+ docType: entry.bestResult.docType,
88
+ namespace: entry.bestResult.namespace,
89
+ metadata: entry.bestResult.metadata,
90
+ }));
91
+ }
92
+ //# sourceMappingURL=rrf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rrf.js","sourceRoot":"","sources":["../src/rrf.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Initialize the FTS5 schema on a Durable Object's SQL storage.
3
+ *
4
+ * Tables created:
5
+ * - `qmd_documents` — document metadata (id, title, docType, namespace, metadata JSON, content_hash)
6
+ * - `qmd_chunks` — chunked content with parent doc reference
7
+ * - `qmd_chunks_fts` — FTS5 virtual table for full-text search over chunks
8
+ * - `qmd_contexts` — semantic context descriptions for path prefixes
9
+ * - `qmd_meta` — schema version tracking
10
+ *
11
+ * The FTS5 table is kept in sync via triggers on `qmd_chunks`.
12
+ */
13
+ export declare function initSchema(sql: SqlStorage, tokenizer?: string): void;
14
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,SAAc,GAAG,IAAI,CAoIzE"}
package/dist/schema.js ADDED
@@ -0,0 +1,128 @@
1
+ const CURRENT_VERSION = 2;
2
+ /**
3
+ * Initialize the FTS5 schema on a Durable Object's SQL storage.
4
+ *
5
+ * Tables created:
6
+ * - `qmd_documents` — document metadata (id, title, docType, namespace, metadata JSON, content_hash)
7
+ * - `qmd_chunks` — chunked content with parent doc reference
8
+ * - `qmd_chunks_fts` — FTS5 virtual table for full-text search over chunks
9
+ * - `qmd_contexts` — semantic context descriptions for path prefixes
10
+ * - `qmd_meta` — schema version tracking
11
+ *
12
+ * The FTS5 table is kept in sync via triggers on `qmd_chunks`.
13
+ */
14
+ export function initSchema(sql, tokenizer = "unicode61") {
15
+ // Create version tracking table first so we can check if already initialized
16
+ sql.exec(`
17
+ CREATE TABLE IF NOT EXISTS qmd_meta (
18
+ key TEXT PRIMARY KEY,
19
+ version INTEGER NOT NULL
20
+ )
21
+ `);
22
+ const existing = sql
23
+ .exec("SELECT version FROM qmd_meta LIMIT 1")
24
+ .toArray();
25
+ const currentVersion = existing.length > 0 ? existing[0].version : 0;
26
+ if (currentVersion >= CURRENT_VERSION) {
27
+ return; // Already at current version
28
+ }
29
+ // Version 0 -> 1: initial schema
30
+ if (currentVersion < 1) {
31
+ // Document metadata table
32
+ sql.exec(`
33
+ CREATE TABLE IF NOT EXISTS qmd_documents (
34
+ id TEXT PRIMARY KEY,
35
+ title TEXT,
36
+ doc_type TEXT,
37
+ namespace TEXT,
38
+ metadata TEXT,
39
+ created_at TEXT DEFAULT (datetime('now')),
40
+ updated_at TEXT DEFAULT (datetime('now'))
41
+ )
42
+ `);
43
+ // Chunk content table
44
+ sql.exec(`
45
+ CREATE TABLE IF NOT EXISTS qmd_chunks (
46
+ doc_id TEXT NOT NULL,
47
+ seq INTEGER NOT NULL,
48
+ content TEXT NOT NULL,
49
+ char_offset INTEGER NOT NULL DEFAULT 0,
50
+ PRIMARY KEY (doc_id, seq),
51
+ FOREIGN KEY (doc_id) REFERENCES qmd_documents(id) ON DELETE CASCADE
52
+ )
53
+ `);
54
+ // FTS5 virtual table — indexes chunk content with document title for boosted relevance
55
+ sql.exec(`
56
+ CREATE VIRTUAL TABLE IF NOT EXISTS qmd_chunks_fts USING fts5(
57
+ doc_id UNINDEXED,
58
+ seq UNINDEXED,
59
+ title,
60
+ content,
61
+ tokenize='${tokenizer}'
62
+ )
63
+ `);
64
+ // Triggers to keep FTS in sync with chunks table
65
+ sql.exec(`
66
+ CREATE TRIGGER IF NOT EXISTS qmd_chunks_ai AFTER INSERT ON qmd_chunks
67
+ BEGIN
68
+ INSERT INTO qmd_chunks_fts(doc_id, seq, title, content)
69
+ SELECT NEW.doc_id, NEW.seq, d.title, NEW.content
70
+ FROM qmd_documents d WHERE d.id = NEW.doc_id;
71
+ END
72
+ `);
73
+ sql.exec(`
74
+ CREATE TRIGGER IF NOT EXISTS qmd_chunks_ad AFTER DELETE ON qmd_chunks
75
+ BEGIN
76
+ DELETE FROM qmd_chunks_fts
77
+ WHERE doc_id = OLD.doc_id AND seq = OLD.seq;
78
+ END
79
+ `);
80
+ sql.exec(`
81
+ CREATE TRIGGER IF NOT EXISTS qmd_chunks_au AFTER UPDATE ON qmd_chunks
82
+ BEGIN
83
+ DELETE FROM qmd_chunks_fts
84
+ WHERE doc_id = OLD.doc_id AND seq = OLD.seq;
85
+ INSERT INTO qmd_chunks_fts(doc_id, seq, title, content)
86
+ SELECT NEW.doc_id, NEW.seq, d.title, NEW.content
87
+ FROM qmd_documents d WHERE d.id = NEW.doc_id;
88
+ END
89
+ `);
90
+ // Index for namespace-scoped lookups
91
+ sql.exec(`
92
+ CREATE INDEX IF NOT EXISTS idx_qmd_documents_namespace
93
+ ON qmd_documents(namespace)
94
+ `);
95
+ // Index for doc_type filtering
96
+ sql.exec(`
97
+ CREATE INDEX IF NOT EXISTS idx_qmd_documents_doc_type
98
+ ON qmd_documents(doc_type)
99
+ `);
100
+ }
101
+ // Version 1 -> 2: add content_hash column + contexts table
102
+ if (currentVersion < 2) {
103
+ // Add content_hash column for skip-on-unchanged indexing.
104
+ // For fresh installs (currentVersion === 0), the column doesn't exist yet on the just-created table.
105
+ // For upgrades from v1, ALTER TABLE adds the column to the existing table.
106
+ // Both paths use ALTER TABLE which is idempotent-safe with IF NOT EXISTS workaround.
107
+ const cols = sql
108
+ .exec("PRAGMA table_info(qmd_documents)")
109
+ .toArray()
110
+ .map((c) => c.name);
111
+ if (!cols.includes("content_hash")) {
112
+ sql.exec("ALTER TABLE qmd_documents ADD COLUMN content_hash TEXT");
113
+ }
114
+ // Semantic context descriptions for path prefixes
115
+ sql.exec(`
116
+ CREATE TABLE IF NOT EXISTS qmd_contexts (
117
+ prefix TEXT NOT NULL,
118
+ namespace TEXT NOT NULL DEFAULT '',
119
+ description TEXT NOT NULL,
120
+ PRIMARY KEY (prefix, namespace)
121
+ )
122
+ `);
123
+ sql.exec("CREATE INDEX IF NOT EXISTS idx_qmd_contexts_namespace ON qmd_contexts(namespace)");
124
+ }
125
+ // Record schema version
126
+ sql.exec("INSERT OR REPLACE INTO qmd_meta (key, version) VALUES ('schema', ?)", CURRENT_VERSION);
127
+ }
128
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,GAAe,EAAE,SAAS,GAAG,WAAW;IAClE,6EAA6E;IAC7E,GAAG,CAAC,IAAI,CAAC;;;;;EAKR,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,GAAG;SAClB,IAAI,CAAsB,sCAAsC,CAAC;SACjE,OAAO,EAAE,CAAC;IACZ,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAErE,IAAI,cAAc,IAAI,eAAe,EAAE,CAAC;QACvC,OAAO,CAAC,6BAA6B;IACtC,CAAC;IAED,iCAAiC;IACjC,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;QACxB,0BAA0B;QAC1B,GAAG,CAAC,IAAI,CAAC;;;;;;;;;;GAUR,CAAC,CAAC;QAEH,sBAAsB;QACtB,GAAG,CAAC,IAAI,CAAC;;;;;;;;;GASR,CAAC,CAAC;QAEH,uFAAuF;QACvF,GAAG,CAAC,IAAI,CAAC;;;;;;gBAMK,SAAS;;GAEtB,CAAC,CAAC;QAEH,iDAAiD;QACjD,GAAG,CAAC,IAAI,CAAC;;;;;;;GAOR,CAAC,CAAC;QAEH,GAAG,CAAC,IAAI,CAAC;;;;;;GAMR,CAAC,CAAC;QAEH,GAAG,CAAC,IAAI,CAAC;;;;;;;;;GASR,CAAC,CAAC;QAEH,qCAAqC;QACrC,GAAG,CAAC,IAAI,CAAC;;;GAGR,CAAC,CAAC;QAEH,+BAA+B;QAC/B,GAAG,CAAC,IAAI,CAAC;;;GAGR,CAAC,CAAC;IACJ,CAAC;IAED,2DAA2D;IAC3D,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;QACxB,0DAA0D;QAC1D,qGAAqG;QACrG,2EAA2E;QAC3E,qFAAqF;QACrF,MAAM,IAAI,GAAG,GAAG;aACd,IAAI,CAAmB,kCAAkC,CAAC;aAC1D,OAAO,EAAE;aACT,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YACpC,GAAG,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC;QACpE,CAAC;QAED,kDAAkD;QAClD,GAAG,CAAC,IAAI,CAAC;;;;;;;GAOR,CAAC,CAAC;QAEH,GAAG,CAAC,IAAI,CACP,kFAAkF,CAClF,CAAC;IACH,CAAC;IAED,wBAAwB;IACxB,GAAG,CAAC,IAAI,CACP,qEAAqE,EACrE,eAAe,CACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,77 @@
1
+ import type { EmbedFn } from "./types.js";
2
+ /**
3
+ * Cursor wrapping bun:sqlite results. Structurally compatible with
4
+ * Cloudflare's SqlStorageCursor.
5
+ */
6
+ declare class BunSqlCursor<T extends Record<string, SqlStorageValue>> {
7
+ private rows;
8
+ private index;
9
+ readonly columnNames: string[];
10
+ readonly rowsRead: number;
11
+ readonly rowsWritten: number;
12
+ constructor(rows: T[], columnNames: string[], rowsRead: number, rowsWritten: number);
13
+ toArray(): T[];
14
+ one(): T;
15
+ next(): {
16
+ done?: false;
17
+ value: T;
18
+ } | {
19
+ done: true;
20
+ value?: never;
21
+ };
22
+ raw<U extends SqlStorageValue[]>(): IterableIterator<U>;
23
+ [Symbol.iterator](): IterableIterator<T>;
24
+ }
25
+ /**
26
+ * In-memory SqlStorage backed by bun:sqlite.
27
+ *
28
+ * Provides real SQLite with FTS5 support, structurally compatible with
29
+ * Cloudflare Durable Object's `ctx.storage.sql` interface.
30
+ */
31
+ export declare class MockSqlStorage {
32
+ private db;
33
+ constructor();
34
+ exec<T extends Record<string, SqlStorageValue>>(query: string, ...bindings: any[]): BunSqlCursor<T>;
35
+ get databaseSize(): number;
36
+ get Cursor(): any;
37
+ get Statement(): any;
38
+ close(): void;
39
+ }
40
+ interface StoredVector {
41
+ id: string;
42
+ values: number[];
43
+ namespace?: string;
44
+ metadata?: Record<string, VectorizeVectorMetadata>;
45
+ }
46
+ /**
47
+ * In-memory Vectorize with brute-force cosine similarity.
48
+ *
49
+ * Structurally compatible with Cloudflare's Vectorize abstract class.
50
+ * Supports insert, upsert, query, queryById, getByIds, deleteByIds.
51
+ */
52
+ export declare class MockVectorize {
53
+ private vectors;
54
+ /** Inspect stored vectors for test assertions. */
55
+ get storedVectors(): Map<string, StoredVector>;
56
+ describe(): Promise<VectorizeIndexInfo>;
57
+ insert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;
58
+ upsert(vectors: VectorizeVector[]): Promise<VectorizeAsyncMutation>;
59
+ query(vector: number[] | Float32Array, options?: VectorizeQueryOptions): Promise<VectorizeMatches>;
60
+ queryById(vectorId: string, options?: VectorizeQueryOptions): Promise<VectorizeMatches>;
61
+ getByIds(ids: string[]): Promise<VectorizeVector[]>;
62
+ deleteByIds(ids: string[]): Promise<VectorizeAsyncMutation>;
63
+ /** Reset all stored vectors. */
64
+ clear(): void;
65
+ }
66
+ /**
67
+ * Create a deterministic mock embedding function.
68
+ *
69
+ * Generates consistent vectors based on character frequency distribution.
70
+ * Similar texts produce similar vectors, enabling meaningful cosine similarity
71
+ * in tests without calling a real embedding model.
72
+ *
73
+ * @param dims - Number of embedding dimensions (default: 8)
74
+ */
75
+ export declare function createMockEmbedFn(dims?: number): EmbedFn;
76
+ export {};
77
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C;;;GAGG;AACH,cAAM,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC;IAC3D,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,KAAK,CAAK;IAClB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAG5B,IAAI,EAAE,CAAC,EAAE,EACT,WAAW,EAAE,MAAM,EAAE,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM;IAQpB,OAAO,IAAI,CAAC,EAAE;IAId,GAAG,IAAI,CAAC;IAOR,IAAI,IAAI;QAAE,IAAI,CAAC,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,GAAG;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;IAOlE,GAAG,CAAC,CAAC,SAAS,eAAe,EAAE,KAAK,gBAAgB,CAAC,CAAC,CAAC;IAKvD,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC;CAGxC;AAID;;;;;GAKG;AACH,qBAAa,cAAc;IAC1B,OAAO,CAAC,EAAE,CAAW;;IAQrB,IAAI,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,EAC7C,KAAK,EAAE,MAAM,EACb,GAAG,QAAQ,EAAE,GAAG,EAAE,GAChB,YAAY,CAAC,CAAC,CAAC;IAoBlB,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,MAAM,IAAI,GAAG,CAEhB;IAED,IAAI,SAAS,IAAI,GAAG,CAEnB;IAED,KAAK,IAAI,IAAI;CAGb;AAID,UAAU,YAAY;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;CACnD;AAeD;;;;;GAKG;AACH,qBAAa,aAAa;IACzB,OAAO,CAAC,OAAO,CAAwC;IAEvD,kDAAkD;IAClD,IAAI,aAAa,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAE7C;IAEK,QAAQ,IAAI,OAAO,CAAC,kBAAkB,CAAC;IASvC,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAenE,MAAM,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAYnE,KAAK,CACV,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,EAC/B,OAAO,CAAC,EAAE,qBAAqB,GAC7B,OAAO,CAAC,gBAAgB,CAAC;IAwBtB,SAAS,CACd,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,qBAAqB,GAC7B,OAAO,CAAC,gBAAgB,CAAC;IAMtB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAYnD,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAOjE,gCAAgC;IAChC,KAAK,IAAI,IAAI;CAGb;AAID;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAI,GAAG,OAAO,CAenD"}
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Testing utilities for @stablemodels/qmd-cf.
3
+ *
4
+ * Provides mock implementations of Cloudflare's SqlStorage, Vectorize, and
5
+ * the EmbedFn type so consuming projects can test their Qmd integration
6
+ * without Cloudflare Workers or Vectorize services.
7
+ *
8
+ * MockSqlStorage wraps bun:sqlite's in-memory Database, giving you real FTS5
9
+ * execution while matching the Cloudflare DO SqlStorage interface.
10
+ *
11
+ * MockVectorize is an in-memory vector store with brute-force cosine similarity
12
+ * for testing the full hybrid search pipeline locally.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { Qmd } from "@stablemodels/qmd-cf";
17
+ * import { MockSqlStorage, MockVectorize, createMockEmbedFn } from "@stablemodels/qmd-cf/testing";
18
+ *
19
+ * // FTS-only testing
20
+ * const sql = new MockSqlStorage();
21
+ * const qmd = new Qmd(sql);
22
+ * await qmd.index({ id: "doc1", content: "Hello world" });
23
+ * const results = qmd.searchFts("hello");
24
+ *
25
+ * // Hybrid search testing
26
+ * const vectorize = new MockVectorize();
27
+ * const embedFn = createMockEmbedFn();
28
+ * const qmd = new Qmd(sql, { vectorize, embedFn });
29
+ * ```
30
+ */
31
+ import { Database } from "bun:sqlite";
32
+ // ─── BunSqlCursor ───────────────────────────────────────────────────
33
+ /**
34
+ * Cursor wrapping bun:sqlite results. Structurally compatible with
35
+ * Cloudflare's SqlStorageCursor.
36
+ */
37
+ class BunSqlCursor {
38
+ rows;
39
+ index = 0;
40
+ columnNames;
41
+ rowsRead;
42
+ rowsWritten;
43
+ constructor(rows, columnNames, rowsRead, rowsWritten) {
44
+ this.rows = rows;
45
+ this.columnNames = columnNames;
46
+ this.rowsRead = rowsRead;
47
+ this.rowsWritten = rowsWritten;
48
+ }
49
+ toArray() {
50
+ return this.rows;
51
+ }
52
+ one() {
53
+ if (this.rows.length !== 1) {
54
+ throw new Error(`Expected exactly one row, got ${this.rows.length}`);
55
+ }
56
+ return this.rows[0];
57
+ }
58
+ next() {
59
+ if (this.index < this.rows.length) {
60
+ return { value: this.rows[this.index++] };
61
+ }
62
+ return { done: true };
63
+ }
64
+ raw() {
65
+ const data = this.rows.map((row) => Object.values(row));
66
+ return data[Symbol.iterator]();
67
+ }
68
+ [Symbol.iterator]() {
69
+ return this.rows[Symbol.iterator]();
70
+ }
71
+ }
72
+ // ─── MockSqlStorage ─────────────────────────────────────────────────
73
+ /**
74
+ * In-memory SqlStorage backed by bun:sqlite.
75
+ *
76
+ * Provides real SQLite with FTS5 support, structurally compatible with
77
+ * Cloudflare Durable Object's `ctx.storage.sql` interface.
78
+ */
79
+ export class MockSqlStorage {
80
+ db;
81
+ constructor() {
82
+ this.db = new Database(":memory:");
83
+ this.db.exec("PRAGMA journal_mode=WAL");
84
+ this.db.exec("PRAGMA foreign_keys=ON");
85
+ }
86
+ exec(query, ...bindings) {
87
+ const stmt = this.db.prepare(query);
88
+ const trimmed = query.trimStart().toUpperCase();
89
+ const isSelect = trimmed.startsWith("SELECT") ||
90
+ trimmed.startsWith("WITH") ||
91
+ trimmed.startsWith("PRAGMA");
92
+ const isInsertReturning = trimmed.includes("RETURNING");
93
+ if (isSelect || isInsertReturning) {
94
+ const rows = stmt.all(...bindings);
95
+ const columnNames = rows.length > 0 ? Object.keys(rows[0]) : [];
96
+ return new BunSqlCursor(rows, columnNames, rows.length, 0);
97
+ }
98
+ const result = stmt.run(...bindings);
99
+ return new BunSqlCursor([], [], 0, result.changes);
100
+ }
101
+ get databaseSize() {
102
+ return 0;
103
+ }
104
+ get Cursor() {
105
+ return BunSqlCursor;
106
+ }
107
+ get Statement() {
108
+ return class {
109
+ };
110
+ }
111
+ close() {
112
+ this.db.close();
113
+ }
114
+ }
115
+ function cosineSimilarity(a, b) {
116
+ let dot = 0;
117
+ let normA = 0;
118
+ let normB = 0;
119
+ for (let i = 0; i < a.length; i++) {
120
+ dot += a[i] * b[i];
121
+ normA += a[i] * a[i];
122
+ normB += b[i] * b[i];
123
+ }
124
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
125
+ return denom === 0 ? 0 : dot / denom;
126
+ }
127
+ /**
128
+ * In-memory Vectorize with brute-force cosine similarity.
129
+ *
130
+ * Structurally compatible with Cloudflare's Vectorize abstract class.
131
+ * Supports insert, upsert, query, queryById, getByIds, deleteByIds.
132
+ */
133
+ export class MockVectorize {
134
+ vectors = new Map();
135
+ /** Inspect stored vectors for test assertions. */
136
+ get storedVectors() {
137
+ return this.vectors;
138
+ }
139
+ async describe() {
140
+ return {
141
+ vectorCount: this.vectors.size,
142
+ dimensions: 0,
143
+ processedUpToDatetime: 0,
144
+ processedUpToMutation: 0,
145
+ };
146
+ }
147
+ async insert(vectors) {
148
+ for (const v of vectors) {
149
+ if (this.vectors.has(v.id)) {
150
+ throw new Error(`Vector ${v.id} already exists`);
151
+ }
152
+ this.vectors.set(v.id, {
153
+ id: v.id,
154
+ values: Array.from(v.values),
155
+ namespace: v.namespace,
156
+ metadata: v.metadata,
157
+ });
158
+ }
159
+ return { mutationId: `mock-insert-${Date.now()}` };
160
+ }
161
+ async upsert(vectors) {
162
+ for (const v of vectors) {
163
+ this.vectors.set(v.id, {
164
+ id: v.id,
165
+ values: Array.from(v.values),
166
+ namespace: v.namespace,
167
+ metadata: v.metadata,
168
+ });
169
+ }
170
+ return { mutationId: `mock-upsert-${Date.now()}` };
171
+ }
172
+ async query(vector, options) {
173
+ const queryVec = Array.from(vector);
174
+ const topK = options?.topK ?? 5;
175
+ let candidates = Array.from(this.vectors.values());
176
+ if (options?.namespace) {
177
+ candidates = candidates.filter((v) => v.namespace === options.namespace);
178
+ }
179
+ const scored = candidates.map((v) => ({
180
+ id: v.id,
181
+ score: cosineSimilarity(queryVec, v.values),
182
+ namespace: v.namespace,
183
+ metadata: options?.returnMetadata === "all" ? v.metadata : undefined,
184
+ values: options?.returnValues ? v.values : undefined,
185
+ }));
186
+ scored.sort((a, b) => b.score - a.score);
187
+ const matches = scored.slice(0, topK);
188
+ return { matches, count: matches.length };
189
+ }
190
+ async queryById(vectorId, options) {
191
+ const vec = this.vectors.get(vectorId);
192
+ if (!vec)
193
+ return { matches: [], count: 0 };
194
+ return this.query(vec.values, options);
195
+ }
196
+ async getByIds(ids) {
197
+ return ids
198
+ .map((id) => this.vectors.get(id))
199
+ .filter((v) => v !== undefined)
200
+ .map((v) => ({
201
+ id: v.id,
202
+ values: v.values,
203
+ namespace: v.namespace,
204
+ metadata: v.metadata,
205
+ }));
206
+ }
207
+ async deleteByIds(ids) {
208
+ for (const id of ids) {
209
+ this.vectors.delete(id);
210
+ }
211
+ return { mutationId: `mock-delete-${Date.now()}` };
212
+ }
213
+ /** Reset all stored vectors. */
214
+ clear() {
215
+ this.vectors.clear();
216
+ }
217
+ }
218
+ // ─── Mock EmbedFn ───────────────────────────────────────────────────
219
+ /**
220
+ * Create a deterministic mock embedding function.
221
+ *
222
+ * Generates consistent vectors based on character frequency distribution.
223
+ * Similar texts produce similar vectors, enabling meaningful cosine similarity
224
+ * in tests without calling a real embedding model.
225
+ *
226
+ * @param dims - Number of embedding dimensions (default: 8)
227
+ */
228
+ export function createMockEmbedFn(dims = 8) {
229
+ return async (texts) => {
230
+ return texts.map((text) => {
231
+ const lower = text.toLowerCase();
232
+ const vec = new Array(dims).fill(0);
233
+ for (let i = 0; i < lower.length; i++) {
234
+ const code = lower.charCodeAt(i);
235
+ vec[code % dims] += 1;
236
+ }
237
+ const norm = Math.sqrt(vec.reduce((s, v) => s + v * v, 0));
238
+ return norm > 0 ? vec.map((v) => v / norm) : vec;
239
+ });
240
+ };
241
+ }
242
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}