@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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 stablemodels
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,11 @@
1
+ import type { Chunk } from "./types.js";
2
+ /**
3
+ * Chunk a document into overlapping segments, seeking intelligent break points.
4
+ *
5
+ * Uses a scored break point system (from qmd) that pre-scans the entire document
6
+ * for structural markers (headings, code fences, paragraphs, etc.) and picks the
7
+ * highest-scoring break point within a window around the target cut position.
8
+ * Avoids splitting inside fenced code blocks.
9
+ */
10
+ export declare function chunkText(docId: string, content: string, maxChars?: number, overlapChars?: number): Chunk[];
11
+ //# sourceMappingURL=chunker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunker.d.ts","sourceRoot":"","sources":["../src/chunker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAyBxC;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACxB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,GAAE,MAA2B,EACrC,YAAY,GAAE,MAA8B,GAC1C,KAAK,EAAE,CA2DT"}
@@ -0,0 +1,199 @@
1
+ const DEFAULT_CHUNK_SIZE = 3200; // ~800 tokens at ~4 chars/token
2
+ const DEFAULT_CHUNK_OVERLAP = 480; // 15% overlap
3
+ /** Break point scores — spread wide so headings decisively win over paragraphs. */
4
+ const BREAK_SCORES = {
5
+ h1: 100,
6
+ h2: 90,
7
+ h3: 80,
8
+ h4: 70,
9
+ h5: 60,
10
+ h6: 50,
11
+ code_fence: 80,
12
+ hr: 60,
13
+ paragraph: 20,
14
+ list_item: 5,
15
+ newline: 1,
16
+ };
17
+ /**
18
+ * Chunk a document into overlapping segments, seeking intelligent break points.
19
+ *
20
+ * Uses a scored break point system (from qmd) that pre-scans the entire document
21
+ * for structural markers (headings, code fences, paragraphs, etc.) and picks the
22
+ * highest-scoring break point within a window around the target cut position.
23
+ * Avoids splitting inside fenced code blocks.
24
+ */
25
+ export function chunkText(docId, content, maxChars = DEFAULT_CHUNK_SIZE, overlapChars = DEFAULT_CHUNK_OVERLAP) {
26
+ if (content.length === 0) {
27
+ return [];
28
+ }
29
+ // Short content: single chunk, no splitting needed
30
+ if (content.length <= maxChars) {
31
+ return [{ docId, seq: 0, text: content, charOffset: 0 }];
32
+ }
33
+ const breakPoints = scanBreakPoints(content);
34
+ const codeFences = findCodeFences(content);
35
+ const chunks = [];
36
+ let pos = 0;
37
+ let seq = 0;
38
+ while (pos < content.length) {
39
+ const remaining = content.length - pos;
40
+ if (remaining <= maxChars) {
41
+ chunks.push({ docId, seq, text: content.slice(pos), charOffset: pos });
42
+ break;
43
+ }
44
+ const targetEnd = pos + maxChars;
45
+ const cutoff = findBestCutoff(content, breakPoints, codeFences, targetEnd, maxChars);
46
+ // Ensure we make forward progress
47
+ const endPos = cutoff > pos ? cutoff : pos + maxChars;
48
+ chunks.push({
49
+ docId,
50
+ seq,
51
+ text: content.slice(pos, endPos),
52
+ charOffset: pos,
53
+ });
54
+ // Advance position, subtracting overlap
55
+ const advance = endPos - pos - overlapChars;
56
+ pos += Math.max(advance, 1);
57
+ // Don't start the next chunk inside a code fence — skip to fence end
58
+ for (const [fStart, fEnd] of codeFences) {
59
+ if (pos > fStart && pos < fEnd) {
60
+ pos = fEnd;
61
+ break;
62
+ }
63
+ }
64
+ seq++;
65
+ }
66
+ return chunks;
67
+ }
68
+ /**
69
+ * Pre-scan the document for structural break points with scores.
70
+ *
71
+ * Returns break points sorted by offset. Each offset points to the first
72
+ * character of the new section (i.e., right after the structural marker).
73
+ */
74
+ function scanBreakPoints(text) {
75
+ const points = [];
76
+ const lines = text.split("\n");
77
+ let offset = 0;
78
+ for (let i = 0; i < lines.length; i++) {
79
+ const line = lines[i];
80
+ const lineStart = offset;
81
+ const nextLineStart = offset + line.length + 1; // +1 for the \n
82
+ // Headings (must be at start of line)
83
+ if (line.startsWith("###### ")) {
84
+ points.push({ offset: lineStart, score: BREAK_SCORES.h6 });
85
+ }
86
+ else if (line.startsWith("##### ")) {
87
+ points.push({ offset: lineStart, score: BREAK_SCORES.h5 });
88
+ }
89
+ else if (line.startsWith("#### ")) {
90
+ points.push({ offset: lineStart, score: BREAK_SCORES.h4 });
91
+ }
92
+ else if (line.startsWith("### ")) {
93
+ points.push({ offset: lineStart, score: BREAK_SCORES.h3 });
94
+ }
95
+ else if (line.startsWith("## ")) {
96
+ points.push({ offset: lineStart, score: BREAK_SCORES.h2 });
97
+ }
98
+ else if (line.startsWith("# ")) {
99
+ points.push({ offset: lineStart, score: BREAK_SCORES.h1 });
100
+ }
101
+ // Code fences (``` at start of line) — break before the fence
102
+ if (line.startsWith("```")) {
103
+ points.push({ offset: lineStart, score: BREAK_SCORES.code_fence });
104
+ }
105
+ // Horizontal rules (---, ***, ___ with optional spaces)
106
+ if (/^(\s*[-*_]\s*){3,}$/.test(line)) {
107
+ points.push({ offset: lineStart, score: BREAK_SCORES.hr });
108
+ }
109
+ // Paragraph boundary (empty line followed by content)
110
+ if (line === "" && i > 0) {
111
+ points.push({ offset: nextLineStart, score: BREAK_SCORES.paragraph });
112
+ }
113
+ // List items
114
+ if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(line)) {
115
+ points.push({ offset: lineStart, score: BREAK_SCORES.list_item });
116
+ }
117
+ // Every newline is a minimal break point
118
+ if (i < lines.length - 1) {
119
+ points.push({ offset: nextLineStart, score: BREAK_SCORES.newline });
120
+ }
121
+ offset = nextLineStart;
122
+ }
123
+ return points;
124
+ }
125
+ /**
126
+ * Find matched code fence (```) ranges. Returns [start, end] pairs
127
+ * where start is the offset of the opening fence and end is the offset
128
+ * just after the closing fence line's newline.
129
+ */
130
+ function findCodeFences(text) {
131
+ const ranges = [];
132
+ const lines = text.split("\n");
133
+ let offset = 0;
134
+ let fenceStart = null;
135
+ for (const line of lines) {
136
+ if (line.startsWith("```")) {
137
+ if (fenceStart === null) {
138
+ fenceStart = offset;
139
+ }
140
+ else {
141
+ // Close the fence — end is after this line
142
+ ranges.push([fenceStart, offset + line.length + 1]);
143
+ fenceStart = null;
144
+ }
145
+ }
146
+ offset += line.length + 1;
147
+ }
148
+ return ranges;
149
+ }
150
+ /**
151
+ * Check if an offset falls inside any code fence range.
152
+ */
153
+ function isInsideCodeFence(offset, codeFences) {
154
+ for (const [start, end] of codeFences) {
155
+ // Inside means strictly between the opening and closing fence lines.
156
+ // Breaking AT the start of a fence (before it) is fine.
157
+ if (offset > start && offset < end)
158
+ return true;
159
+ }
160
+ return false;
161
+ }
162
+ /**
163
+ * Find the best break point near the target cut position.
164
+ *
165
+ * Searches a window from 50% to 100% of maxChars around the chunk start.
166
+ * Applies squared distance decay so breaks closer to the target are preferred.
167
+ * Rejects candidates inside code fences.
168
+ */
169
+ function findBestCutoff(text, breakPoints, codeFences, targetEnd, maxChars) {
170
+ const windowStart = targetEnd - Math.floor(maxChars * 0.5);
171
+ const windowEnd = targetEnd;
172
+ const windowSize = windowEnd - windowStart;
173
+ let bestScore = -1;
174
+ let bestOffset = targetEnd;
175
+ for (const bp of breakPoints) {
176
+ if (bp.offset < windowStart || bp.offset > windowEnd)
177
+ continue;
178
+ if (isInsideCodeFence(bp.offset, codeFences))
179
+ continue;
180
+ // Squared distance decay: prefer breaks closer to targetEnd
181
+ const dist = Math.abs(bp.offset - targetEnd);
182
+ const normalizedDist = dist / windowSize;
183
+ const multiplier = 1.0 - normalizedDist * normalizedDist * 0.7;
184
+ const weightedScore = bp.score * multiplier;
185
+ if (weightedScore > bestScore) {
186
+ bestScore = weightedScore;
187
+ bestOffset = bp.offset;
188
+ }
189
+ }
190
+ // Fallback: if no structural break points found, try word boundary (last space)
191
+ if (bestScore < 0) {
192
+ const lastSpace = text.lastIndexOf(" ", targetEnd);
193
+ if (lastSpace >= windowStart) {
194
+ return lastSpace + 1;
195
+ }
196
+ }
197
+ return bestOffset;
198
+ }
199
+ //# sourceMappingURL=chunker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunker.js","sourceRoot":"","sources":["../src/chunker.ts"],"names":[],"mappings":"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"}
package/dist/fts.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { FtsResult, SearchOptions } from "./types.js";
2
+ /**
3
+ * Build an FTS5 query string from a natural language query.
4
+ *
5
+ * Strategy (from qmd):
6
+ * - Single term: prefix match ("term"*)
7
+ * - Multi-term: exact phrase OR NEAR(terms, 10) OR individual terms ORed
8
+ *
9
+ * This gives good recall while still ranking exact matches highest via BM25.
10
+ */
11
+ export declare function buildFts5Query(query: string): string;
12
+ /**
13
+ * Execute a full-text search using FTS5 BM25 ranking.
14
+ *
15
+ * BM25 weights: title gets 10x boost, content gets 1x.
16
+ * Results are deduplicated by document (keeping the best-scoring chunk).
17
+ */
18
+ export declare function searchFts(sql: SqlStorage, query: string, options?: SearchOptions): FtsResult[];
19
+ //# sourceMappingURL=fts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fts.d.ts","sourceRoot":"","sources":["../src/fts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAqBpD;AAsBD;;;;;GAKG;AACH,wBAAgB,SAAS,CACxB,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GACzB,SAAS,EAAE,CA2Eb"}
package/dist/fts.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Build an FTS5 query string from a natural language query.
3
+ *
4
+ * Strategy (from qmd):
5
+ * - Single term: prefix match ("term"*)
6
+ * - Multi-term: exact phrase OR NEAR(terms, 10) OR individual terms ORed
7
+ *
8
+ * This gives good recall while still ranking exact matches highest via BM25.
9
+ */
10
+ export function buildFts5Query(query) {
11
+ const terms = query
12
+ .trim()
13
+ .split(/\s+/)
14
+ .filter((t) => t.length > 0)
15
+ // Strip characters that break FTS5 syntax
16
+ .map((t) => t.replace(/['"(){}[\]*^~:]/g, ""));
17
+ if (terms.length === 0)
18
+ return '""';
19
+ if (terms.length === 1) {
20
+ // Single term: prefix match
21
+ return `"${terms[0]}" *`;
22
+ }
23
+ // Multi-term: combine strategies for best recall
24
+ const phrase = `"${terms.join(" ")}"`;
25
+ const near = `NEAR(${terms.map((t) => `"${t}"`).join(" ")}, 10)`;
26
+ const orTerms = terms.map((t) => `"${t}"`).join(" OR ");
27
+ return `(${phrase}) OR (${near}) OR (${orTerms})`;
28
+ }
29
+ /**
30
+ * Normalize a raw BM25 score to (0, 1].
31
+ * SQLite FTS5 bm25() returns negative values where lower (more negative) = better match.
32
+ * We convert to: score = 1 / (1 + abs(raw))
33
+ */
34
+ function normalizeBm25(raw) {
35
+ return Math.abs(raw) / (1 + Math.abs(raw));
36
+ }
37
+ /**
38
+ * Execute a full-text search using FTS5 BM25 ranking.
39
+ *
40
+ * BM25 weights: title gets 10x boost, content gets 1x.
41
+ * Results are deduplicated by document (keeping the best-scoring chunk).
42
+ */
43
+ export function searchFts(sql, query, options = {}) {
44
+ const ftsQuery = buildFts5Query(query);
45
+ const limit = options.limit ?? 10;
46
+ // Build WHERE clauses for optional filters
47
+ const filters = [];
48
+ const bindings = [ftsQuery];
49
+ if (options.docType) {
50
+ filters.push("d.doc_type = ?");
51
+ bindings.push(options.docType);
52
+ }
53
+ if (options.namespace) {
54
+ if (options.namespace.includes("*")) {
55
+ const prefix = options.namespace.replace(/\*+$/, "").replace(/\/+$/, "");
56
+ filters.push("d.namespace LIKE ?");
57
+ bindings.push(`${prefix}/%`);
58
+ }
59
+ else {
60
+ filters.push("d.namespace = ?");
61
+ bindings.push(options.namespace);
62
+ }
63
+ }
64
+ const whereClause = filters.length > 0 ? `AND ${filters.join(" AND ")}` : "";
65
+ // BM25 weights: doc_id (unindexed, 0), seq (unindexed, 0), title (10.0), content (1.0)
66
+ const rows = sql
67
+ .exec(`
68
+ SELECT
69
+ f.doc_id,
70
+ f.seq,
71
+ f.content,
72
+ bm25(qmd_chunks_fts, 0, 0, 10.0, 1.0) as rank,
73
+ d.title,
74
+ d.doc_type,
75
+ d.namespace,
76
+ d.metadata
77
+ FROM qmd_chunks_fts f
78
+ JOIN qmd_documents d ON d.id = f.doc_id
79
+ WHERE qmd_chunks_fts MATCH ?
80
+ ${whereClause}
81
+ ORDER BY rank
82
+ LIMIT ?
83
+ `, ...bindings,
84
+ // Fetch extra to allow dedup
85
+ limit * 3)
86
+ .toArray();
87
+ // Deduplicate by doc_id, keeping the best-scoring chunk
88
+ const seen = new Map();
89
+ for (const row of rows) {
90
+ const score = normalizeBm25(row.rank);
91
+ const existing = seen.get(row.doc_id);
92
+ if (!existing || score > existing.score) {
93
+ seen.set(row.doc_id, {
94
+ docId: row.doc_id,
95
+ score,
96
+ snippet: row.content,
97
+ seq: row.seq,
98
+ title: row.title,
99
+ docType: row.doc_type,
100
+ namespace: row.namespace,
101
+ metadata: row.metadata ? JSON.parse(row.metadata) : null,
102
+ });
103
+ }
104
+ }
105
+ return Array.from(seen.values())
106
+ .sort((a, b) => b.score - a.score)
107
+ .slice(0, limit);
108
+ }
109
+ //# sourceMappingURL=fts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fts.js","sourceRoot":"","sources":["../src/fts.ts"],"names":[],"mappings":"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"}
package/dist/hash.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * FNV-1a 32-bit hash — fast, deterministic, non-cryptographic.
3
+ * Used to detect content changes for skip-on-unchanged indexing.
4
+ * Returns an 8-character lowercase hex string.
5
+ */
6
+ export declare function fnv1a32(input: string): string;
7
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO7C"}
package/dist/hash.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * FNV-1a 32-bit hash — fast, deterministic, non-cryptographic.
3
+ * Used to detect content changes for skip-on-unchanged indexing.
4
+ * Returns an 8-character lowercase hex string.
5
+ */
6
+ export function fnv1a32(input) {
7
+ let hash = 0x811c9dc5; // FNV offset basis
8
+ for (let i = 0; i < input.length; i++) {
9
+ hash ^= input.charCodeAt(i);
10
+ hash = Math.imul(hash, 0x01000193); // FNV prime
11
+ }
12
+ return (hash >>> 0).toString(16).padStart(8, "0");
13
+ }
14
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,KAAa;IACpC,IAAI,IAAI,GAAG,UAAU,CAAC,CAAC,mBAAmB;IAC1C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,YAAY;IACjD,CAAC;IACD,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @stablemodels/qmd-cf — Hybrid full-text + vector search for Cloudflare Durable Objects.
3
+ *
4
+ * A DO-native reimagination of qmd (https://github.com/tobi/qmd).
5
+ *
6
+ * FTS5 runs co-located in the Durable Object's SQLite for zero-latency BM25 keyword search.
7
+ * Optionally, Cloudflare Vectorize adds semantic vector search, fused via Reciprocal Rank Fusion.
8
+ *
9
+ * Cloudflare platform types (SqlStorage, Vectorize, VectorizeVector, etc.) are
10
+ * ambient from @cloudflare/workers-types — consumers access them directly.
11
+ *
12
+ * @example FTS-only (zero external dependencies)
13
+ * ```ts
14
+ * import { Qmd } from "@stablemodels/qmd-cf";
15
+ *
16
+ * export class MyDurableObject extends DurableObject {
17
+ * qmd: Qmd;
18
+ *
19
+ * constructor(ctx: DurableObjectState, env: Env) {
20
+ * super(ctx, env);
21
+ * this.qmd = new Qmd(ctx.storage.sql);
22
+ * }
23
+ *
24
+ * async search(query: string) {
25
+ * return this.qmd.search(query);
26
+ * }
27
+ * }
28
+ * ```
29
+ *
30
+ * @example Hybrid FTS + Vector search
31
+ * ```ts
32
+ * import { Qmd } from "@stablemodels/qmd-cf";
33
+ *
34
+ * export class MyDurableObject extends DurableObject {
35
+ * qmd: Qmd;
36
+ *
37
+ * constructor(ctx: DurableObjectState, env: Env) {
38
+ * super(ctx, env);
39
+ * this.qmd = new Qmd(ctx.storage.sql, {
40
+ * vectorize: env.VECTORIZE,
41
+ * embedFn: (texts) =>
42
+ * env.AI.run("@cf/baai/bge-m3", { text: texts })
43
+ * .then(r => r.data),
44
+ * });
45
+ * }
46
+ * }
47
+ * ```
48
+ */
49
+ export { Qmd } from "./qmd.js";
50
+ export type { Document, Chunk, FtsResult, VectorResult, SearchResult, SearchOptions, HybridSearchOptions, QmdConfig, IndexStats, EmbedFn, } from "./types.js";
51
+ export { chunkText } from "./chunker.js";
52
+ export { buildFts5Query } from "./fts.js";
53
+ export { fnv1a32 } from "./hash.js";
54
+ export { reciprocalRankFusion } from "./rrf.js";
55
+ export { formatDocForEmbedding, formatQueryForEmbedding } from "./vector.js";
56
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAGH,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/B,YAAY,EACX,QAAQ,EACR,KAAK,EACL,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,SAAS,EACT,UAAU,EACV,OAAO,GACP,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @stablemodels/qmd-cf — Hybrid full-text + vector search for Cloudflare Durable Objects.
3
+ *
4
+ * A DO-native reimagination of qmd (https://github.com/tobi/qmd).
5
+ *
6
+ * FTS5 runs co-located in the Durable Object's SQLite for zero-latency BM25 keyword search.
7
+ * Optionally, Cloudflare Vectorize adds semantic vector search, fused via Reciprocal Rank Fusion.
8
+ *
9
+ * Cloudflare platform types (SqlStorage, Vectorize, VectorizeVector, etc.) are
10
+ * ambient from @cloudflare/workers-types — consumers access them directly.
11
+ *
12
+ * @example FTS-only (zero external dependencies)
13
+ * ```ts
14
+ * import { Qmd } from "@stablemodels/qmd-cf";
15
+ *
16
+ * export class MyDurableObject extends DurableObject {
17
+ * qmd: Qmd;
18
+ *
19
+ * constructor(ctx: DurableObjectState, env: Env) {
20
+ * super(ctx, env);
21
+ * this.qmd = new Qmd(ctx.storage.sql);
22
+ * }
23
+ *
24
+ * async search(query: string) {
25
+ * return this.qmd.search(query);
26
+ * }
27
+ * }
28
+ * ```
29
+ *
30
+ * @example Hybrid FTS + Vector search
31
+ * ```ts
32
+ * import { Qmd } from "@stablemodels/qmd-cf";
33
+ *
34
+ * export class MyDurableObject extends DurableObject {
35
+ * qmd: Qmd;
36
+ *
37
+ * constructor(ctx: DurableObjectState, env: Env) {
38
+ * super(ctx, env);
39
+ * this.qmd = new Qmd(ctx.storage.sql, {
40
+ * vectorize: env.VECTORIZE,
41
+ * embedFn: (texts) =>
42
+ * env.AI.run("@cf/baai/bge-m3", { text: texts })
43
+ * .then(r => r.data),
44
+ * });
45
+ * }
46
+ * }
47
+ * ```
48
+ */
49
+ // Main class
50
+ export { Qmd } from "./qmd.js";
51
+ // Utilities (useful for custom pipelines)
52
+ export { chunkText } from "./chunker.js";
53
+ export { buildFts5Query } from "./fts.js";
54
+ export { fnv1a32 } from "./hash.js";
55
+ export { reciprocalRankFusion } from "./rrf.js";
56
+ export { formatDocForEmbedding, formatQueryForEmbedding } from "./vector.js";
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,aAAa;AACb,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAgB/B,0CAA0C;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}