@aigne/afs-index 1.12.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/backend/d1-index-backend.cjs +656 -0
- package/dist/backend/d1-index-backend.d.cts +149 -0
- package/dist/backend/d1-index-backend.d.cts.map +1 -0
- package/dist/backend/d1-index-backend.d.mts +149 -0
- package/dist/backend/d1-index-backend.d.mts.map +1 -0
- package/dist/backend/d1-index-backend.mjs +657 -0
- package/dist/backend/d1-index-backend.mjs.map +1 -0
- package/dist/backend/d1-vector-backend.cjs +83 -0
- package/dist/backend/d1-vector-backend.d.cts +21 -0
- package/dist/backend/d1-vector-backend.d.cts.map +1 -0
- package/dist/backend/d1-vector-backend.d.mts +21 -0
- package/dist/backend/d1-vector-backend.d.mts.map +1 -0
- package/dist/backend/d1-vector-backend.mjs +84 -0
- package/dist/backend/d1-vector-backend.mjs.map +1 -0
- package/dist/backend/memory-backend.cjs +304 -0
- package/dist/backend/memory-backend.d.cts +66 -0
- package/dist/backend/memory-backend.d.cts.map +1 -0
- package/dist/backend/memory-backend.d.mts +66 -0
- package/dist/backend/memory-backend.d.mts.map +1 -0
- package/dist/backend/memory-backend.mjs +305 -0
- package/dist/backend/memory-backend.mjs.map +1 -0
- package/dist/backend/prefix-range.cjs +25 -0
- package/dist/backend/prefix-range.mjs +25 -0
- package/dist/backend/prefix-range.mjs.map +1 -0
- package/dist/backend/schema.cjs +187 -0
- package/dist/backend/schema.mjs +188 -0
- package/dist/backend/schema.mjs.map +1 -0
- package/dist/backend/sqlite-backend.cjs +889 -0
- package/dist/backend/sqlite-backend.d.cts +96 -0
- package/dist/backend/sqlite-backend.d.cts.map +1 -0
- package/dist/backend/sqlite-backend.d.mts +96 -0
- package/dist/backend/sqlite-backend.d.mts.map +1 -0
- package/dist/backend/sqlite-backend.mjs +890 -0
- package/dist/backend/sqlite-backend.mjs.map +1 -0
- package/dist/backend/types.d.cts +209 -0
- package/dist/backend/types.d.cts.map +1 -0
- package/dist/backend/types.d.mts +209 -0
- package/dist/backend/types.d.mts.map +1 -0
- package/dist/backend/upstash-vector-backend.cjs +98 -0
- package/dist/backend/upstash-vector-backend.d.cts +21 -0
- package/dist/backend/upstash-vector-backend.d.cts.map +1 -0
- package/dist/backend/upstash-vector-backend.d.mts +21 -0
- package/dist/backend/upstash-vector-backend.d.mts.map +1 -0
- package/dist/backend/upstash-vector-backend.mjs +98 -0
- package/dist/backend/upstash-vector-backend.mjs.map +1 -0
- package/dist/backend/vector-backend.d.cts +29 -0
- package/dist/backend/vector-backend.d.cts.map +1 -0
- package/dist/backend/vector-backend.d.mts +29 -0
- package/dist/backend/vector-backend.d.mts.map +1 -0
- package/dist/dirty-queue.d.cts +41 -0
- package/dist/dirty-queue.d.cts.map +1 -0
- package/dist/dirty-queue.d.mts +41 -0
- package/dist/dirty-queue.d.mts.map +1 -0
- package/dist/embed.cjs +45 -0
- package/dist/embed.d.cts +20 -0
- package/dist/embed.d.cts.map +1 -0
- package/dist/embed.d.mts +20 -0
- package/dist/embed.d.mts.map +1 -0
- package/dist/embed.mjs +46 -0
- package/dist/embed.mjs.map +1 -0
- package/dist/extraction/defaults.cjs +242 -0
- package/dist/extraction/defaults.d.cts +14 -0
- package/dist/extraction/defaults.d.cts.map +1 -0
- package/dist/extraction/defaults.d.mts +14 -0
- package/dist/extraction/defaults.d.mts.map +1 -0
- package/dist/extraction/defaults.mjs +242 -0
- package/dist/extraction/defaults.mjs.map +1 -0
- package/dist/extraction/field-map.cjs +162 -0
- package/dist/extraction/field-map.d.cts +70 -0
- package/dist/extraction/field-map.d.cts.map +1 -0
- package/dist/extraction/field-map.d.mts +70 -0
- package/dist/extraction/field-map.d.mts.map +1 -0
- package/dist/extraction/field-map.mjs +157 -0
- package/dist/extraction/field-map.mjs.map +1 -0
- package/dist/extraction/manager.cjs +74 -0
- package/dist/extraction/manager.d.cts +47 -0
- package/dist/extraction/manager.d.cts.map +1 -0
- package/dist/extraction/manager.d.mts +47 -0
- package/dist/extraction/manager.d.mts.map +1 -0
- package/dist/extraction/manager.mjs +74 -0
- package/dist/extraction/manager.mjs.map +1 -0
- package/dist/extraction/path-anchors.cjs +86 -0
- package/dist/extraction/path-anchors.d.cts +72 -0
- package/dist/extraction/path-anchors.d.cts.map +1 -0
- package/dist/extraction/path-anchors.d.mts +72 -0
- package/dist/extraction/path-anchors.d.mts.map +1 -0
- package/dist/extraction/path-anchors.mjs +86 -0
- package/dist/extraction/path-anchors.mjs.map +1 -0
- package/dist/follower-scheduler.cjs +92 -0
- package/dist/follower-scheduler.d.cts +58 -0
- package/dist/follower-scheduler.d.cts.map +1 -0
- package/dist/follower-scheduler.d.mts +58 -0
- package/dist/follower-scheduler.d.mts.map +1 -0
- package/dist/follower-scheduler.mjs +92 -0
- package/dist/follower-scheduler.mjs.map +1 -0
- package/dist/follower.cjs +577 -0
- package/dist/follower.d.cts +241 -0
- package/dist/follower.d.cts.map +1 -0
- package/dist/follower.d.mts +241 -0
- package/dist/follower.d.mts.map +1 -0
- package/dist/follower.mjs +571 -0
- package/dist/follower.mjs.map +1 -0
- package/dist/index-provider.cjs +1166 -0
- package/dist/index-provider.d.cts +175 -0
- package/dist/index-provider.d.cts.map +1 -0
- package/dist/index-provider.d.mts +175 -0
- package/dist/index-provider.d.mts.map +1 -0
- package/dist/index-provider.mjs +1167 -0
- package/dist/index-provider.mjs.map +1 -0
- package/dist/index.cjs +53 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.mjs +20 -0
- package/dist/keys/exact.cjs +20 -0
- package/dist/keys/exact.mjs +20 -0
- package/dist/keys/exact.mjs.map +1 -0
- package/dist/keys/location.cjs +13 -0
- package/dist/keys/location.mjs +13 -0
- package/dist/keys/location.mjs.map +1 -0
- package/dist/keys/normalized.cjs +62 -0
- package/dist/keys/normalized.mjs +63 -0
- package/dist/keys/normalized.mjs.map +1 -0
- package/dist/keys/person.cjs +15 -0
- package/dist/keys/person.mjs +15 -0
- package/dist/keys/person.mjs.map +1 -0
- package/dist/keys/registry.cjs +45 -0
- package/dist/keys/registry.d.cts +15 -0
- package/dist/keys/registry.d.cts.map +1 -0
- package/dist/keys/registry.d.mts +15 -0
- package/dist/keys/registry.d.mts.map +1 -0
- package/dist/keys/registry.mjs +46 -0
- package/dist/keys/registry.mjs.map +1 -0
- package/dist/keys/time.cjs +128 -0
- package/dist/keys/time.mjs +128 -0
- package/dist/keys/time.mjs.map +1 -0
- package/dist/keys/topic.cjs +23 -0
- package/dist/keys/topic.mjs +23 -0
- package/dist/keys/topic.mjs.map +1 -0
- package/dist/manifest-index.cjs +29 -0
- package/dist/manifest-index.d.cts +24 -0
- package/dist/manifest-index.d.cts.map +1 -0
- package/dist/manifest-index.d.mts +24 -0
- package/dist/manifest-index.d.mts.map +1 -0
- package/dist/manifest-index.mjs +28 -0
- package/dist/manifest-index.mjs.map +1 -0
- package/dist/offline-rebuild.cjs +48 -0
- package/dist/offline-rebuild.d.cts +57 -0
- package/dist/offline-rebuild.d.cts.map +1 -0
- package/dist/offline-rebuild.d.mts +57 -0
- package/dist/offline-rebuild.d.mts.map +1 -0
- package/dist/offline-rebuild.mjs +49 -0
- package/dist/offline-rebuild.mjs.map +1 -0
- package/dist/query/engine.cjs +433 -0
- package/dist/query/engine.d.cts +34 -0
- package/dist/query/engine.d.cts.map +1 -0
- package/dist/query/engine.d.mts +34 -0
- package/dist/query/engine.d.mts.map +1 -0
- package/dist/query/engine.mjs +433 -0
- package/dist/query/engine.mjs.map +1 -0
- package/dist/query/limits.cjs +12 -0
- package/dist/query/limits.mjs +12 -0
- package/dist/query/limits.mjs.map +1 -0
- package/dist/text/cjk.cjs +30 -0
- package/dist/text/cjk.d.cts +24 -0
- package/dist/text/cjk.d.cts.map +1 -0
- package/dist/text/cjk.d.mts +24 -0
- package/dist/text/cjk.d.mts.map +1 -0
- package/dist/text/cjk.mjs +30 -0
- package/dist/text/cjk.mjs.map +1 -0
- package/dist/text/fts.cjs +244 -0
- package/dist/text/fts.mjs +245 -0
- package/dist/text/fts.mjs.map +1 -0
- package/dist/text/host.cjs +39 -0
- package/dist/text/host.mjs +38 -0
- package/dist/text/host.mjs.map +1 -0
- package/dist/types.cjs +44 -0
- package/dist/types.d.cts +393 -0
- package/dist/types.d.cts.map +1 -0
- package/dist/types.d.mts +393 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +42 -0
- package/dist/types.mjs.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/text/cjk.ts
|
|
2
|
+
/**
|
|
3
|
+
* CJK text segmentation for FTS5 compatibility.
|
|
4
|
+
*
|
|
5
|
+
* FTS5's unicode61 tokenizer treats consecutive CJK characters as a single token,
|
|
6
|
+
* which prevents character-level matching. This module inserts spaces between CJK
|
|
7
|
+
* characters so FTS5 indexes them individually.
|
|
8
|
+
*
|
|
9
|
+
* Design note: Character-level segmentation is intentional for a recall-optimized
|
|
10
|
+
* memory system. Word-level (Intl.Segmenter) was tested and caused -10%~-46%
|
|
11
|
+
* regression due to vocabulary mismatch between query and corpus word boundaries.
|
|
12
|
+
* Character-level gives broader matching at the cost of precision — the right
|
|
13
|
+
* trade-off when results are top-K ranked and embeddings handle semantic matching.
|
|
14
|
+
*
|
|
15
|
+
* Example: "空调使用模式" → "空 调 使 用 模 式"
|
|
16
|
+
*/
|
|
17
|
+
/** CJK Unified Ideographs + Extension A + Compatibility + Hiragana + Katakana */
|
|
18
|
+
const CJK_CHAR = /[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff\u3040-\u309f\u30a0-\u30ff]/g;
|
|
19
|
+
/**
|
|
20
|
+
* Insert spaces around each CJK character so FTS5 treats them as individual tokens.
|
|
21
|
+
* Non-CJK text passes through unchanged.
|
|
22
|
+
*/
|
|
23
|
+
function segmentCJK(text) {
|
|
24
|
+
if (!text) return text;
|
|
25
|
+
return text.replace(CJK_CHAR, " $& ").replace(/\s+/g, " ").trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { segmentCJK };
|
|
30
|
+
//# sourceMappingURL=cjk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cjk.mjs","names":[],"sources":["../../src/text/cjk.ts"],"sourcesContent":["/**\n * CJK text segmentation for FTS5 compatibility.\n *\n * FTS5's unicode61 tokenizer treats consecutive CJK characters as a single token,\n * which prevents character-level matching. This module inserts spaces between CJK\n * characters so FTS5 indexes them individually.\n *\n * Design note: Character-level segmentation is intentional for a recall-optimized\n * memory system. Word-level (Intl.Segmenter) was tested and caused -10%~-46%\n * regression due to vocabulary mismatch between query and corpus word boundaries.\n * Character-level gives broader matching at the cost of precision — the right\n * trade-off when results are top-K ranked and embeddings handle semantic matching.\n *\n * Example: \"空调使用模式\" → \"空 调 使 用 模 式\"\n */\n\n/** CJK Unified Ideographs + Extension A + Compatibility + Hiragana + Katakana */\nconst CJK_CHAR = /[\\u4e00-\\u9fff\\u3400-\\u4dbf\\uf900-\\ufaff\\u3040-\\u309f\\u30a0-\\u30ff]/g;\n\n/**\n * Insert spaces around each CJK character so FTS5 treats them as individual tokens.\n * Non-CJK text passes through unchanged.\n */\nexport function segmentCJK(text: string): string {\n if (!text) return text;\n return text.replace(CJK_CHAR, \" $& \").replace(/\\s+/g, \" \").trim();\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,MAAM,WAAW;;;;;AAMjB,SAAgB,WAAW,MAAsB;AAC/C,KAAI,CAAC,KAAM,QAAO;AAClB,QAAO,KAAK,QAAQ,UAAU,OAAO,CAAC,QAAQ,QAAQ,IAAI,CAAC,MAAM"}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
const require_cjk = require('./cjk.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/text/fts.ts
|
|
4
|
+
/**
|
|
5
|
+
* FTS query transformation for FTS5 compatibility.
|
|
6
|
+
*
|
|
7
|
+
* Decision lock (#933, owner ruling 2026-07-03 on PR #946): the central
|
|
8
|
+
* transform produces an AND query, not OR. FTS5's default MATCH semantics
|
|
9
|
+
* is AND-all; broadening every query to OR-any silently drops precision
|
|
10
|
+
* for every consumer (anchor+FTS combined queries, aside's tag/keyword
|
|
11
|
+
* filters) in exchange for recall that only one caller (memory) ever
|
|
12
|
+
* wanted — and memory gets that recall from its own metaFilter + anchor
|
|
13
|
+
* layers, not from FTS breadth. Transformation order is non-negotiable:
|
|
14
|
+
*
|
|
15
|
+
* 1. Strip bare AND/OR/NOT first — so legacy "空 OR 调" pre-segmented output
|
|
16
|
+
* from memory degrades gracefully (strip OR → re-join with a plain
|
|
17
|
+
* space = idempotent). Reversing the order turns pre-segmented strings
|
|
18
|
+
* into malformed FTS5 on D1.
|
|
19
|
+
* 2. CJK segmentation — insert spaces between consecutive CJK characters so
|
|
20
|
+
* FTS5 unicode61 indexes them individually (symmetric with write-side).
|
|
21
|
+
* 3. Stopword filtering — common English/Chinese function words carry no
|
|
22
|
+
* retrieval signal; under AND semantics they only narrow the result set
|
|
23
|
+
* for no benefit (a query padded with "tell me about X" would otherwise
|
|
24
|
+
* require "tell", "me", "about" to all literally appear in the summary).
|
|
25
|
+
* Ported verbatim from the memory provider's private STOPWORDS list
|
|
26
|
+
* (`providers/core/memory/src/index.ts`, removed there by #933 — this
|
|
27
|
+
* transform is now the single place FTS query text is built).
|
|
28
|
+
* 4. Join all surviving tokens with a single space — FTS5's implicit MATCH
|
|
29
|
+
* semantics is AND, so a space-separated token list is already an AND
|
|
30
|
+
* query. Single-token queries pass through untouched. If every token is
|
|
31
|
+
* filtered as a stopword, fall back to the boolean-stripped text from
|
|
32
|
+
* step 1 (not the fully raw input) so a bare-AND/OR/NOT never reaches
|
|
33
|
+
* FTS5 even in this fallback path.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Common English/Chinese stopwords, filtered out before the FTS5 MATCH
|
|
37
|
+
* query is built. Ported verbatim from the memory provider's private
|
|
38
|
+
* STOPWORDS list (pre-#933) — the index provider is now the single owner
|
|
39
|
+
* of FTS query transformation, so this list lives here instead.
|
|
40
|
+
*/
|
|
41
|
+
const STOPWORDS = new Set([
|
|
42
|
+
"a",
|
|
43
|
+
"an",
|
|
44
|
+
"the",
|
|
45
|
+
"is",
|
|
46
|
+
"are",
|
|
47
|
+
"was",
|
|
48
|
+
"were",
|
|
49
|
+
"be",
|
|
50
|
+
"been",
|
|
51
|
+
"am",
|
|
52
|
+
"do",
|
|
53
|
+
"does",
|
|
54
|
+
"did",
|
|
55
|
+
"will",
|
|
56
|
+
"would",
|
|
57
|
+
"could",
|
|
58
|
+
"should",
|
|
59
|
+
"can",
|
|
60
|
+
"may",
|
|
61
|
+
"might",
|
|
62
|
+
"shall",
|
|
63
|
+
"have",
|
|
64
|
+
"has",
|
|
65
|
+
"had",
|
|
66
|
+
"having",
|
|
67
|
+
"i",
|
|
68
|
+
"me",
|
|
69
|
+
"my",
|
|
70
|
+
"we",
|
|
71
|
+
"our",
|
|
72
|
+
"you",
|
|
73
|
+
"your",
|
|
74
|
+
"he",
|
|
75
|
+
"she",
|
|
76
|
+
"it",
|
|
77
|
+
"they",
|
|
78
|
+
"them",
|
|
79
|
+
"its",
|
|
80
|
+
"his",
|
|
81
|
+
"her",
|
|
82
|
+
"their",
|
|
83
|
+
"this",
|
|
84
|
+
"that",
|
|
85
|
+
"these",
|
|
86
|
+
"those",
|
|
87
|
+
"in",
|
|
88
|
+
"on",
|
|
89
|
+
"at",
|
|
90
|
+
"to",
|
|
91
|
+
"for",
|
|
92
|
+
"of",
|
|
93
|
+
"with",
|
|
94
|
+
"by",
|
|
95
|
+
"from",
|
|
96
|
+
"as",
|
|
97
|
+
"into",
|
|
98
|
+
"about",
|
|
99
|
+
"between",
|
|
100
|
+
"through",
|
|
101
|
+
"during",
|
|
102
|
+
"and",
|
|
103
|
+
"or",
|
|
104
|
+
"but",
|
|
105
|
+
"not",
|
|
106
|
+
"if",
|
|
107
|
+
"then",
|
|
108
|
+
"so",
|
|
109
|
+
"than",
|
|
110
|
+
"too",
|
|
111
|
+
"very",
|
|
112
|
+
"just",
|
|
113
|
+
"how",
|
|
114
|
+
"what",
|
|
115
|
+
"when",
|
|
116
|
+
"where",
|
|
117
|
+
"who",
|
|
118
|
+
"which",
|
|
119
|
+
"why",
|
|
120
|
+
"all",
|
|
121
|
+
"each",
|
|
122
|
+
"some",
|
|
123
|
+
"any",
|
|
124
|
+
"no",
|
|
125
|
+
"there",
|
|
126
|
+
"here",
|
|
127
|
+
"up",
|
|
128
|
+
"out",
|
|
129
|
+
"also",
|
|
130
|
+
"tell",
|
|
131
|
+
"的",
|
|
132
|
+
"了",
|
|
133
|
+
"在",
|
|
134
|
+
"是",
|
|
135
|
+
"我",
|
|
136
|
+
"有",
|
|
137
|
+
"和",
|
|
138
|
+
"就",
|
|
139
|
+
"不",
|
|
140
|
+
"人",
|
|
141
|
+
"都",
|
|
142
|
+
"一",
|
|
143
|
+
"上",
|
|
144
|
+
"也",
|
|
145
|
+
"很",
|
|
146
|
+
"到",
|
|
147
|
+
"说",
|
|
148
|
+
"要",
|
|
149
|
+
"去",
|
|
150
|
+
"你",
|
|
151
|
+
"会",
|
|
152
|
+
"着",
|
|
153
|
+
"看",
|
|
154
|
+
"好",
|
|
155
|
+
"这",
|
|
156
|
+
"他",
|
|
157
|
+
"她",
|
|
158
|
+
"它",
|
|
159
|
+
"们",
|
|
160
|
+
"么",
|
|
161
|
+
"吗",
|
|
162
|
+
"吧",
|
|
163
|
+
"啊",
|
|
164
|
+
"呢",
|
|
165
|
+
"嗯",
|
|
166
|
+
"哦",
|
|
167
|
+
"那",
|
|
168
|
+
"把",
|
|
169
|
+
"被",
|
|
170
|
+
"让",
|
|
171
|
+
"给",
|
|
172
|
+
"跟",
|
|
173
|
+
"用",
|
|
174
|
+
"帮",
|
|
175
|
+
"请",
|
|
176
|
+
"下",
|
|
177
|
+
"能",
|
|
178
|
+
"哪",
|
|
179
|
+
"什",
|
|
180
|
+
"怎",
|
|
181
|
+
"为",
|
|
182
|
+
"还",
|
|
183
|
+
"或",
|
|
184
|
+
"但",
|
|
185
|
+
"然",
|
|
186
|
+
"所",
|
|
187
|
+
"因",
|
|
188
|
+
"如",
|
|
189
|
+
"虽",
|
|
190
|
+
"只",
|
|
191
|
+
"已",
|
|
192
|
+
"正",
|
|
193
|
+
"一个",
|
|
194
|
+
"没有",
|
|
195
|
+
"自己",
|
|
196
|
+
"什么",
|
|
197
|
+
"可以",
|
|
198
|
+
"怎么",
|
|
199
|
+
"怎样",
|
|
200
|
+
"如何",
|
|
201
|
+
"哪个",
|
|
202
|
+
"哪些",
|
|
203
|
+
"为什么",
|
|
204
|
+
"什么时候",
|
|
205
|
+
"多少",
|
|
206
|
+
"关于",
|
|
207
|
+
"还是",
|
|
208
|
+
"或者",
|
|
209
|
+
"以及",
|
|
210
|
+
"但是",
|
|
211
|
+
"然后",
|
|
212
|
+
"所以",
|
|
213
|
+
"因为",
|
|
214
|
+
"如果",
|
|
215
|
+
"虽然",
|
|
216
|
+
"只是",
|
|
217
|
+
"已经",
|
|
218
|
+
"正在",
|
|
219
|
+
"一下",
|
|
220
|
+
"回忆"
|
|
221
|
+
]);
|
|
222
|
+
/**
|
|
223
|
+
* Transform a natural-language query into a safe FTS5 MATCH expression.
|
|
224
|
+
*
|
|
225
|
+
* - Strips isolated AND/OR/NOT keywords (they cause D1 syntax errors at
|
|
226
|
+
* phrase boundaries, e.g. trailing "空调 AND").
|
|
227
|
+
* - Applies CJK character-level segmentation symmetric with the write side.
|
|
228
|
+
* - Filters common stopwords (no retrieval signal, only narrows AND recall).
|
|
229
|
+
* - Joins remaining tokens using the specified mode:
|
|
230
|
+
* - "and" (default): space-separated — FTS5 implicit AND. Good for precision.
|
|
231
|
+
* - "or": " OR "-joined — broadens recall. Use for memory/similarity search.
|
|
232
|
+
* Single-token queries are returned as-is regardless of mode.
|
|
233
|
+
*/
|
|
234
|
+
function transformFtsQuery(text, mode = "and") {
|
|
235
|
+
const stripped = text.replace(/\b(AND|OR|NOT)\b/gi, " ").replace(/\s+/g, " ").trim();
|
|
236
|
+
if (!stripped) return "";
|
|
237
|
+
const tokens = require_cjk.segmentCJK(stripped).toLowerCase().split(/[\s,;.!?,。!?;:、]+/).filter((t) => t.length > 0 && !STOPWORDS.has(t));
|
|
238
|
+
if (tokens.length === 0) return stripped;
|
|
239
|
+
if (tokens.length === 1) return tokens[0];
|
|
240
|
+
return mode === "or" ? tokens.join(" OR ") : tokens.join(" ");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
//#endregion
|
|
244
|
+
exports.transformFtsQuery = transformFtsQuery;
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { segmentCJK } from "./cjk.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/text/fts.ts
|
|
4
|
+
/**
|
|
5
|
+
* FTS query transformation for FTS5 compatibility.
|
|
6
|
+
*
|
|
7
|
+
* Decision lock (#933, owner ruling 2026-07-03 on PR #946): the central
|
|
8
|
+
* transform produces an AND query, not OR. FTS5's default MATCH semantics
|
|
9
|
+
* is AND-all; broadening every query to OR-any silently drops precision
|
|
10
|
+
* for every consumer (anchor+FTS combined queries, aside's tag/keyword
|
|
11
|
+
* filters) in exchange for recall that only one caller (memory) ever
|
|
12
|
+
* wanted — and memory gets that recall from its own metaFilter + anchor
|
|
13
|
+
* layers, not from FTS breadth. Transformation order is non-negotiable:
|
|
14
|
+
*
|
|
15
|
+
* 1. Strip bare AND/OR/NOT first — so legacy "空 OR 调" pre-segmented output
|
|
16
|
+
* from memory degrades gracefully (strip OR → re-join with a plain
|
|
17
|
+
* space = idempotent). Reversing the order turns pre-segmented strings
|
|
18
|
+
* into malformed FTS5 on D1.
|
|
19
|
+
* 2. CJK segmentation — insert spaces between consecutive CJK characters so
|
|
20
|
+
* FTS5 unicode61 indexes them individually (symmetric with write-side).
|
|
21
|
+
* 3. Stopword filtering — common English/Chinese function words carry no
|
|
22
|
+
* retrieval signal; under AND semantics they only narrow the result set
|
|
23
|
+
* for no benefit (a query padded with "tell me about X" would otherwise
|
|
24
|
+
* require "tell", "me", "about" to all literally appear in the summary).
|
|
25
|
+
* Ported verbatim from the memory provider's private STOPWORDS list
|
|
26
|
+
* (`providers/core/memory/src/index.ts`, removed there by #933 — this
|
|
27
|
+
* transform is now the single place FTS query text is built).
|
|
28
|
+
* 4. Join all surviving tokens with a single space — FTS5's implicit MATCH
|
|
29
|
+
* semantics is AND, so a space-separated token list is already an AND
|
|
30
|
+
* query. Single-token queries pass through untouched. If every token is
|
|
31
|
+
* filtered as a stopword, fall back to the boolean-stripped text from
|
|
32
|
+
* step 1 (not the fully raw input) so a bare-AND/OR/NOT never reaches
|
|
33
|
+
* FTS5 even in this fallback path.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Common English/Chinese stopwords, filtered out before the FTS5 MATCH
|
|
37
|
+
* query is built. Ported verbatim from the memory provider's private
|
|
38
|
+
* STOPWORDS list (pre-#933) — the index provider is now the single owner
|
|
39
|
+
* of FTS query transformation, so this list lives here instead.
|
|
40
|
+
*/
|
|
41
|
+
const STOPWORDS = new Set([
|
|
42
|
+
"a",
|
|
43
|
+
"an",
|
|
44
|
+
"the",
|
|
45
|
+
"is",
|
|
46
|
+
"are",
|
|
47
|
+
"was",
|
|
48
|
+
"were",
|
|
49
|
+
"be",
|
|
50
|
+
"been",
|
|
51
|
+
"am",
|
|
52
|
+
"do",
|
|
53
|
+
"does",
|
|
54
|
+
"did",
|
|
55
|
+
"will",
|
|
56
|
+
"would",
|
|
57
|
+
"could",
|
|
58
|
+
"should",
|
|
59
|
+
"can",
|
|
60
|
+
"may",
|
|
61
|
+
"might",
|
|
62
|
+
"shall",
|
|
63
|
+
"have",
|
|
64
|
+
"has",
|
|
65
|
+
"had",
|
|
66
|
+
"having",
|
|
67
|
+
"i",
|
|
68
|
+
"me",
|
|
69
|
+
"my",
|
|
70
|
+
"we",
|
|
71
|
+
"our",
|
|
72
|
+
"you",
|
|
73
|
+
"your",
|
|
74
|
+
"he",
|
|
75
|
+
"she",
|
|
76
|
+
"it",
|
|
77
|
+
"they",
|
|
78
|
+
"them",
|
|
79
|
+
"its",
|
|
80
|
+
"his",
|
|
81
|
+
"her",
|
|
82
|
+
"their",
|
|
83
|
+
"this",
|
|
84
|
+
"that",
|
|
85
|
+
"these",
|
|
86
|
+
"those",
|
|
87
|
+
"in",
|
|
88
|
+
"on",
|
|
89
|
+
"at",
|
|
90
|
+
"to",
|
|
91
|
+
"for",
|
|
92
|
+
"of",
|
|
93
|
+
"with",
|
|
94
|
+
"by",
|
|
95
|
+
"from",
|
|
96
|
+
"as",
|
|
97
|
+
"into",
|
|
98
|
+
"about",
|
|
99
|
+
"between",
|
|
100
|
+
"through",
|
|
101
|
+
"during",
|
|
102
|
+
"and",
|
|
103
|
+
"or",
|
|
104
|
+
"but",
|
|
105
|
+
"not",
|
|
106
|
+
"if",
|
|
107
|
+
"then",
|
|
108
|
+
"so",
|
|
109
|
+
"than",
|
|
110
|
+
"too",
|
|
111
|
+
"very",
|
|
112
|
+
"just",
|
|
113
|
+
"how",
|
|
114
|
+
"what",
|
|
115
|
+
"when",
|
|
116
|
+
"where",
|
|
117
|
+
"who",
|
|
118
|
+
"which",
|
|
119
|
+
"why",
|
|
120
|
+
"all",
|
|
121
|
+
"each",
|
|
122
|
+
"some",
|
|
123
|
+
"any",
|
|
124
|
+
"no",
|
|
125
|
+
"there",
|
|
126
|
+
"here",
|
|
127
|
+
"up",
|
|
128
|
+
"out",
|
|
129
|
+
"also",
|
|
130
|
+
"tell",
|
|
131
|
+
"的",
|
|
132
|
+
"了",
|
|
133
|
+
"在",
|
|
134
|
+
"是",
|
|
135
|
+
"我",
|
|
136
|
+
"有",
|
|
137
|
+
"和",
|
|
138
|
+
"就",
|
|
139
|
+
"不",
|
|
140
|
+
"人",
|
|
141
|
+
"都",
|
|
142
|
+
"一",
|
|
143
|
+
"上",
|
|
144
|
+
"也",
|
|
145
|
+
"很",
|
|
146
|
+
"到",
|
|
147
|
+
"说",
|
|
148
|
+
"要",
|
|
149
|
+
"去",
|
|
150
|
+
"你",
|
|
151
|
+
"会",
|
|
152
|
+
"着",
|
|
153
|
+
"看",
|
|
154
|
+
"好",
|
|
155
|
+
"这",
|
|
156
|
+
"他",
|
|
157
|
+
"她",
|
|
158
|
+
"它",
|
|
159
|
+
"们",
|
|
160
|
+
"么",
|
|
161
|
+
"吗",
|
|
162
|
+
"吧",
|
|
163
|
+
"啊",
|
|
164
|
+
"呢",
|
|
165
|
+
"嗯",
|
|
166
|
+
"哦",
|
|
167
|
+
"那",
|
|
168
|
+
"把",
|
|
169
|
+
"被",
|
|
170
|
+
"让",
|
|
171
|
+
"给",
|
|
172
|
+
"跟",
|
|
173
|
+
"用",
|
|
174
|
+
"帮",
|
|
175
|
+
"请",
|
|
176
|
+
"下",
|
|
177
|
+
"能",
|
|
178
|
+
"哪",
|
|
179
|
+
"什",
|
|
180
|
+
"怎",
|
|
181
|
+
"为",
|
|
182
|
+
"还",
|
|
183
|
+
"或",
|
|
184
|
+
"但",
|
|
185
|
+
"然",
|
|
186
|
+
"所",
|
|
187
|
+
"因",
|
|
188
|
+
"如",
|
|
189
|
+
"虽",
|
|
190
|
+
"只",
|
|
191
|
+
"已",
|
|
192
|
+
"正",
|
|
193
|
+
"一个",
|
|
194
|
+
"没有",
|
|
195
|
+
"自己",
|
|
196
|
+
"什么",
|
|
197
|
+
"可以",
|
|
198
|
+
"怎么",
|
|
199
|
+
"怎样",
|
|
200
|
+
"如何",
|
|
201
|
+
"哪个",
|
|
202
|
+
"哪些",
|
|
203
|
+
"为什么",
|
|
204
|
+
"什么时候",
|
|
205
|
+
"多少",
|
|
206
|
+
"关于",
|
|
207
|
+
"还是",
|
|
208
|
+
"或者",
|
|
209
|
+
"以及",
|
|
210
|
+
"但是",
|
|
211
|
+
"然后",
|
|
212
|
+
"所以",
|
|
213
|
+
"因为",
|
|
214
|
+
"如果",
|
|
215
|
+
"虽然",
|
|
216
|
+
"只是",
|
|
217
|
+
"已经",
|
|
218
|
+
"正在",
|
|
219
|
+
"一下",
|
|
220
|
+
"回忆"
|
|
221
|
+
]);
|
|
222
|
+
/**
|
|
223
|
+
* Transform a natural-language query into a safe FTS5 MATCH expression.
|
|
224
|
+
*
|
|
225
|
+
* - Strips isolated AND/OR/NOT keywords (they cause D1 syntax errors at
|
|
226
|
+
* phrase boundaries, e.g. trailing "空调 AND").
|
|
227
|
+
* - Applies CJK character-level segmentation symmetric with the write side.
|
|
228
|
+
* - Filters common stopwords (no retrieval signal, only narrows AND recall).
|
|
229
|
+
* - Joins remaining tokens using the specified mode:
|
|
230
|
+
* - "and" (default): space-separated — FTS5 implicit AND. Good for precision.
|
|
231
|
+
* - "or": " OR "-joined — broadens recall. Use for memory/similarity search.
|
|
232
|
+
* Single-token queries are returned as-is regardless of mode.
|
|
233
|
+
*/
|
|
234
|
+
function transformFtsQuery(text, mode = "and") {
|
|
235
|
+
const stripped = text.replace(/\b(AND|OR|NOT)\b/gi, " ").replace(/\s+/g, " ").trim();
|
|
236
|
+
if (!stripped) return "";
|
|
237
|
+
const tokens = segmentCJK(stripped).toLowerCase().split(/[\s,;.!?,。!?;:、]+/).filter((t) => t.length > 0 && !STOPWORDS.has(t));
|
|
238
|
+
if (tokens.length === 0) return stripped;
|
|
239
|
+
if (tokens.length === 1) return tokens[0];
|
|
240
|
+
return mode === "or" ? tokens.join(" OR ") : tokens.join(" ");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
//#endregion
|
|
244
|
+
export { transformFtsQuery };
|
|
245
|
+
//# sourceMappingURL=fts.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fts.mjs","names":[],"sources":["../../src/text/fts.ts"],"sourcesContent":["/**\n * FTS query transformation for FTS5 compatibility.\n *\n * Decision lock (#933, owner ruling 2026-07-03 on PR #946): the central\n * transform produces an AND query, not OR. FTS5's default MATCH semantics\n * is AND-all; broadening every query to OR-any silently drops precision\n * for every consumer (anchor+FTS combined queries, aside's tag/keyword\n * filters) in exchange for recall that only one caller (memory) ever\n * wanted — and memory gets that recall from its own metaFilter + anchor\n * layers, not from FTS breadth. Transformation order is non-negotiable:\n *\n * 1. Strip bare AND/OR/NOT first — so legacy \"空 OR 调\" pre-segmented output\n * from memory degrades gracefully (strip OR → re-join with a plain\n * space = idempotent). Reversing the order turns pre-segmented strings\n * into malformed FTS5 on D1.\n * 2. CJK segmentation — insert spaces between consecutive CJK characters so\n * FTS5 unicode61 indexes them individually (symmetric with write-side).\n * 3. Stopword filtering — common English/Chinese function words carry no\n * retrieval signal; under AND semantics they only narrow the result set\n * for no benefit (a query padded with \"tell me about X\" would otherwise\n * require \"tell\", \"me\", \"about\" to all literally appear in the summary).\n * Ported verbatim from the memory provider's private STOPWORDS list\n * (`providers/core/memory/src/index.ts`, removed there by #933 — this\n * transform is now the single place FTS query text is built).\n * 4. Join all surviving tokens with a single space — FTS5's implicit MATCH\n * semantics is AND, so a space-separated token list is already an AND\n * query. Single-token queries pass through untouched. If every token is\n * filtered as a stopword, fall back to the boolean-stripped text from\n * step 1 (not the fully raw input) so a bare-AND/OR/NOT never reaches\n * FTS5 even in this fallback path.\n */\nimport { segmentCJK } from \"./cjk.js\";\n\n/**\n * Common English/Chinese stopwords, filtered out before the FTS5 MATCH\n * query is built. Ported verbatim from the memory provider's private\n * STOPWORDS list (pre-#933) — the index provider is now the single owner\n * of FTS query transformation, so this list lives here instead.\n */\nconst STOPWORDS = new Set([\n // English\n \"a\",\n \"an\",\n \"the\",\n \"is\",\n \"are\",\n \"was\",\n \"were\",\n \"be\",\n \"been\",\n \"am\",\n \"do\",\n \"does\",\n \"did\",\n \"will\",\n \"would\",\n \"could\",\n \"should\",\n \"can\",\n \"may\",\n \"might\",\n \"shall\",\n \"have\",\n \"has\",\n \"had\",\n \"having\",\n \"i\",\n \"me\",\n \"my\",\n \"we\",\n \"our\",\n \"you\",\n \"your\",\n \"he\",\n \"she\",\n \"it\",\n \"they\",\n \"them\",\n \"its\",\n \"his\",\n \"her\",\n \"their\",\n \"this\",\n \"that\",\n \"these\",\n \"those\",\n \"in\",\n \"on\",\n \"at\",\n \"to\",\n \"for\",\n \"of\",\n \"with\",\n \"by\",\n \"from\",\n \"as\",\n \"into\",\n \"about\",\n \"between\",\n \"through\",\n \"during\",\n \"and\",\n \"or\",\n \"but\",\n \"not\",\n \"if\",\n \"then\",\n \"so\",\n \"than\",\n \"too\",\n \"very\",\n \"just\",\n \"how\",\n \"what\",\n \"when\",\n \"where\",\n \"who\",\n \"which\",\n \"why\",\n \"all\",\n \"each\",\n \"some\",\n \"any\",\n \"no\",\n \"there\",\n \"here\",\n \"up\",\n \"out\",\n \"also\",\n \"tell\",\n // Chinese single-character particles/function words\n // (after CJK segmentation, multi-char stopwords become individual chars)\n \"的\",\n \"了\",\n \"在\",\n \"是\",\n \"我\",\n \"有\",\n \"和\",\n \"就\",\n \"不\",\n \"人\",\n \"都\",\n \"一\",\n \"上\",\n \"也\",\n \"很\",\n \"到\",\n \"说\",\n \"要\",\n \"去\",\n \"你\",\n \"会\",\n \"着\",\n \"看\",\n \"好\",\n \"这\",\n \"他\",\n \"她\",\n \"它\",\n \"们\",\n \"么\",\n \"吗\",\n \"吧\",\n \"啊\",\n \"呢\",\n \"嗯\",\n \"哦\",\n \"那\",\n \"把\",\n \"被\",\n \"让\",\n \"给\",\n \"跟\",\n \"用\",\n \"帮\",\n \"请\",\n \"下\",\n \"能\",\n \"哪\",\n \"什\",\n \"怎\",\n \"为\",\n \"还\",\n \"或\",\n \"但\",\n \"然\",\n \"所\",\n \"因\",\n \"如\",\n \"虽\",\n \"只\",\n \"已\",\n \"正\",\n // Multi-char CJK stopwords (still useful before segmentation in some paths)\n \"一个\",\n \"没有\",\n \"自己\",\n \"什么\",\n \"可以\",\n \"怎么\",\n \"怎样\",\n \"如何\",\n \"哪个\",\n \"哪些\",\n \"为什么\",\n \"什么时候\",\n \"多少\",\n \"关于\",\n \"还是\",\n \"或者\",\n \"以及\",\n \"但是\",\n \"然后\",\n \"所以\",\n \"因为\",\n \"如果\",\n \"虽然\",\n \"只是\",\n \"已经\",\n \"正在\",\n \"一下\",\n \"回忆\",\n]);\n\n/**\n * Transform a natural-language query into a safe FTS5 MATCH expression.\n *\n * - Strips isolated AND/OR/NOT keywords (they cause D1 syntax errors at\n * phrase boundaries, e.g. trailing \"空调 AND\").\n * - Applies CJK character-level segmentation symmetric with the write side.\n * - Filters common stopwords (no retrieval signal, only narrows AND recall).\n * - Joins remaining tokens using the specified mode:\n * - \"and\" (default): space-separated — FTS5 implicit AND. Good for precision.\n * - \"or\": \" OR \"-joined — broadens recall. Use for memory/similarity search.\n * Single-token queries are returned as-is regardless of mode.\n */\nexport function transformFtsQuery(text: string, mode: \"and\" | \"or\" = \"and\"): string {\n // Step 1: strip standalone AND / OR / NOT (word-boundary anchored, case-insensitive)\n const stripped = text\n .replace(/\\b(AND|OR|NOT)\\b/gi, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n if (!stripped) return \"\";\n\n // Step 2: CJK segmentation (symmetric with write-side segmentCJK in backends)\n const segmented = segmentCJK(stripped);\n\n // Step 3: tokenise, lowercase, drop stopwords\n const tokens = segmented\n .toLowerCase()\n .split(/[\\s,;.!?,。!?;:、]+/)\n .filter((t) => t.length > 0 && !STOPWORDS.has(t));\n\n // Every token was a stopword (e.g. \"the is a an\") — fall back to the\n // boolean-stripped text rather than silently emptying the query. Mirrors\n // the prior memory `toFtsOrQuery` fallback (`return text`), but returns\n // the step-1 output (not the fully raw input) so a bare AND/OR/NOT can't\n // slip back in through this path.\n if (tokens.length === 0) return stripped;\n\n // Step 4: single token passes through; otherwise join per mode\n if (tokens.length === 1) return tokens[0]!;\n return mode === \"or\" ? tokens.join(\" OR \") : tokens.join(\" \");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,MAAM,YAAY,IAAI,IAAI;CAExB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;;;;;;;;;;;;AAcF,SAAgB,kBAAkB,MAAc,OAAqB,OAAe;CAElF,MAAM,WAAW,KACd,QAAQ,sBAAsB,IAAI,CAClC,QAAQ,QAAQ,IAAI,CACpB,MAAM;AACT,KAAI,CAAC,SAAU,QAAO;CAMtB,MAAM,SAHY,WAAW,SAAS,CAInC,aAAa,CACb,MAAM,oBAAoB,CAC1B,QAAQ,MAAM,EAAE,SAAS,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC;AAOnD,KAAI,OAAO,WAAW,EAAG,QAAO;AAGhC,KAAI,OAAO,WAAW,EAAG,QAAO,OAAO;AACvC,QAAO,SAAS,OAAO,OAAO,KAAK,OAAO,GAAG,OAAO,KAAK,IAAI"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/text/host.ts
|
|
3
|
+
/**
|
|
4
|
+
* Host normalization — the single source of truth for turning a raw hostname
|
|
5
|
+
* into a canonical anchor/FTS token, shared by BOTH sides of the index so the
|
|
6
|
+
* same host always collapses to the same key:
|
|
7
|
+
*
|
|
8
|
+
* - write side: `host()` extraction (extraction/field-map.ts) + FTS summary
|
|
9
|
+
* - query side: anchor key generation (keys/normalized.ts, `stripWwwPrefix`)
|
|
10
|
+
*
|
|
11
|
+
* Without a shared normalizer the `www.` prefix drifts between write and query
|
|
12
|
+
* (issue #1249): `site: host($.url)` indexes `www.example.com` while a `site:`
|
|
13
|
+
* query for `example.com` never matches — two distinct `idx_anchor_key` rows.
|
|
14
|
+
*
|
|
15
|
+
* Scope (issue #1249): strip a single leading `www.` label only. Numbered
|
|
16
|
+
* variants (`www2.`) and other host aliases are intentionally NOT stripped —
|
|
17
|
+
* they usually address distinct hosts, so collapsing them would over-match.
|
|
18
|
+
*/
|
|
19
|
+
/** Leading `www.` label stripped so `www.example.com` ≡ `example.com`. */
|
|
20
|
+
const WWW_PREFIX_RE = /^www\./;
|
|
21
|
+
/**
|
|
22
|
+
* Strip a leading `www.` prefix from an already-lowercased host string.
|
|
23
|
+
* Case-preserving otherwise — callers that need lowercasing use
|
|
24
|
+
* {@link normalizeHost} instead.
|
|
25
|
+
*/
|
|
26
|
+
function stripWwwPrefix(host) {
|
|
27
|
+
return host.replace(WWW_PREFIX_RE, "");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Canonicalize a hostname: trim, lowercase, then strip a leading `www.`.
|
|
31
|
+
* `WWW.Example.com`, `www.example.com`, and `example.com` all → `example.com`.
|
|
32
|
+
*/
|
|
33
|
+
function normalizeHost(host) {
|
|
34
|
+
return stripWwwPrefix(host.trim().toLowerCase());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
exports.normalizeHost = normalizeHost;
|
|
39
|
+
exports.stripWwwPrefix = stripWwwPrefix;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/text/host.ts
|
|
2
|
+
/**
|
|
3
|
+
* Host normalization — the single source of truth for turning a raw hostname
|
|
4
|
+
* into a canonical anchor/FTS token, shared by BOTH sides of the index so the
|
|
5
|
+
* same host always collapses to the same key:
|
|
6
|
+
*
|
|
7
|
+
* - write side: `host()` extraction (extraction/field-map.ts) + FTS summary
|
|
8
|
+
* - query side: anchor key generation (keys/normalized.ts, `stripWwwPrefix`)
|
|
9
|
+
*
|
|
10
|
+
* Without a shared normalizer the `www.` prefix drifts between write and query
|
|
11
|
+
* (issue #1249): `site: host($.url)` indexes `www.example.com` while a `site:`
|
|
12
|
+
* query for `example.com` never matches — two distinct `idx_anchor_key` rows.
|
|
13
|
+
*
|
|
14
|
+
* Scope (issue #1249): strip a single leading `www.` label only. Numbered
|
|
15
|
+
* variants (`www2.`) and other host aliases are intentionally NOT stripped —
|
|
16
|
+
* they usually address distinct hosts, so collapsing them would over-match.
|
|
17
|
+
*/
|
|
18
|
+
/** Leading `www.` label stripped so `www.example.com` ≡ `example.com`. */
|
|
19
|
+
const WWW_PREFIX_RE = /^www\./;
|
|
20
|
+
/**
|
|
21
|
+
* Strip a leading `www.` prefix from an already-lowercased host string.
|
|
22
|
+
* Case-preserving otherwise — callers that need lowercasing use
|
|
23
|
+
* {@link normalizeHost} instead.
|
|
24
|
+
*/
|
|
25
|
+
function stripWwwPrefix(host) {
|
|
26
|
+
return host.replace(WWW_PREFIX_RE, "");
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Canonicalize a hostname: trim, lowercase, then strip a leading `www.`.
|
|
30
|
+
* `WWW.Example.com`, `www.example.com`, and `example.com` all → `example.com`.
|
|
31
|
+
*/
|
|
32
|
+
function normalizeHost(host) {
|
|
33
|
+
return stripWwwPrefix(host.trim().toLowerCase());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { normalizeHost, stripWwwPrefix };
|
|
38
|
+
//# sourceMappingURL=host.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.mjs","names":[],"sources":["../../src/text/host.ts"],"sourcesContent":["/**\n * Host normalization — the single source of truth for turning a raw hostname\n * into a canonical anchor/FTS token, shared by BOTH sides of the index so the\n * same host always collapses to the same key:\n *\n * - write side: `host()` extraction (extraction/field-map.ts) + FTS summary\n * - query side: anchor key generation (keys/normalized.ts, `stripWwwPrefix`)\n *\n * Without a shared normalizer the `www.` prefix drifts between write and query\n * (issue #1249): `site: host($.url)` indexes `www.example.com` while a `site:`\n * query for `example.com` never matches — two distinct `idx_anchor_key` rows.\n *\n * Scope (issue #1249): strip a single leading `www.` label only. Numbered\n * variants (`www2.`) and other host aliases are intentionally NOT stripped —\n * they usually address distinct hosts, so collapsing them would over-match.\n */\n\n/** Leading `www.` label stripped so `www.example.com` ≡ `example.com`. */\nconst WWW_PREFIX_RE = /^www\\./;\n\n/**\n * Strip a leading `www.` prefix from an already-lowercased host string.\n * Case-preserving otherwise — callers that need lowercasing use\n * {@link normalizeHost} instead.\n */\nexport function stripWwwPrefix(host: string): string {\n return host.replace(WWW_PREFIX_RE, \"\");\n}\n\n/**\n * Canonicalize a hostname: trim, lowercase, then strip a leading `www.`.\n * `WWW.Example.com`, `www.example.com`, and `example.com` all → `example.com`.\n */\nexport function normalizeHost(host: string): string {\n return stripWwwPrefix(host.trim().toLowerCase());\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,MAAM,gBAAgB;;;;;;AAOtB,SAAgB,eAAe,MAAsB;AACnD,QAAO,KAAK,QAAQ,eAAe,GAAG;;;;;;AAOxC,SAAgB,cAAc,MAAsB;AAClD,QAAO,eAAe,KAAK,MAAM,CAAC,aAAa,CAAC"}
|
package/dist/types.cjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/types.ts
|
|
3
|
+
/**
|
|
4
|
+
* Query recall strategy for `.actions/query` / `QueryEngine.query`. Per-mode
|
|
5
|
+
* semantics live in `query/engine.ts`:
|
|
6
|
+
* - "anchor" — pure anchor-based query
|
|
7
|
+
* - "fts" — pure full-text search
|
|
8
|
+
* - "combined" — RRF fusion of anchor + FTS ranked lists
|
|
9
|
+
* - "semantic" — RRF fusion + embedding fallback
|
|
10
|
+
*
|
|
11
|
+
* `SEARCH_MODES` is the single source of truth — the `.actions/query` zod enum,
|
|
12
|
+
* `QueryInput.mode`, and the `AFS_INDEX_SEARCH_MODE` env coercion all derive
|
|
13
|
+
* from it, so a new mode is added in exactly one place.
|
|
14
|
+
*/
|
|
15
|
+
const SEARCH_MODES = [
|
|
16
|
+
"anchor",
|
|
17
|
+
"fts",
|
|
18
|
+
"combined",
|
|
19
|
+
"semantic"
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Default query mode when a caller doesn't pass `mode` and the deployment
|
|
23
|
+
* didn't configure `AFSIndexOptions.defaultSearchMode`. Kept at the historical
|
|
24
|
+
* `.actions/query` default so an unconfigured deployment is byte-identical.
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_SEARCH_MODE = "combined";
|
|
27
|
+
/**
|
|
28
|
+
* Coerce an untrusted string (typically the `AFS_INDEX_SEARCH_MODE` env var)
|
|
29
|
+
* into a `SearchMode`. Source-agnostic — receives an already-read string and
|
|
30
|
+
* never touches `process.env`/`env`, so it behaves identically in Cloudflare
|
|
31
|
+
* Workers and Node (same discipline as `parseBool` from `@aigne/afs/utils/env`).
|
|
32
|
+
* lower-case + trim + allow-list; anything unrecognized (including
|
|
33
|
+
* `undefined`/empty) falls back to `DEFAULT_SEARCH_MODE` and never throws —
|
|
34
|
+
* mirrors the CF `resolveVectorIndexBackend` shape for `AFS_INDEX_VECTOR_BACKEND`.
|
|
35
|
+
*/
|
|
36
|
+
function coerceSearchMode(value) {
|
|
37
|
+
const v = value?.toLowerCase().trim();
|
|
38
|
+
return SEARCH_MODES.includes(v ?? "") ? v : DEFAULT_SEARCH_MODE;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//#endregion
|
|
42
|
+
exports.DEFAULT_SEARCH_MODE = DEFAULT_SEARCH_MODE;
|
|
43
|
+
exports.SEARCH_MODES = SEARCH_MODES;
|
|
44
|
+
exports.coerceSearchMode = coerceSearchMode;
|