Karakeep 搜索查询语言完整指南从基础语法到源码级实现解析【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarderKarakeepHoarder是一款可自托管的收藏一切应用支持链接、笔记与图片书签并提供基于 AI 的自动打标签与全文搜索能力。本文以仓库中 version-v0.28.0 版本文档 为骨架系统讲解其搜索查询语言Search Query Language的全部限定符Qualifier、布尔组合语法与全文搜索用法并深入对应解析器与查询执行源码帮助你精确检索书签库甚至构建动态智能列表Smart List。一、搜索查询语言概览Karakeep 提供了一套专用的搜索查询语言用于过滤和查找书签。与单纯的关键词搜索不同它允许你通过结构化限定符如is:fav、#tag、after:2023-01-01组合出精确的检索条件同时保留普通文本的全文搜索能力。整套语言由前端与后端共享的解析器实现同一套语法同时服务于搜索框与智能列表保证行为一致。二、基础语法搜索查询语言遵循一套简单而一致的语法规则空格分隔多个条件多个条件之间用空格隔开隐含逻辑 AND与关系显式布尔逻辑使用and/or关键字显式表达与 / 或逻辑取反限定符在限定符前加-前缀表示取反negate例如-is:archived表示未归档的书签括号分组使用圆括号()对条件进行分组以控制优先级注意分组本身不能被取反即-(...)不合法。补充在更新版本的文档与当前仓库源码中取反符号除-外还支持!作为等价别名如!is:archived标签限定符也支持tag:作为#的等价写法详见后文源码解析。三、完整限定符参考表以下是 v0.28.0 文档中给出的全部限定符及其说明限定符说明用法示例is:fav已收藏的书签is:favis:archived已归档的书签-is:archivedis:tagged带有一个或多个标签的书签is:taggedis:inlist位于一个或多个列表中的书签is:inlistis:link、is:text、is:media类型为链接、文本或媒体的书签is:linkurl:value匹配 URL 包含指定子串的书签url:example.comtitle:value匹配标题包含指定子串的书签title:example支持用引号包裹带空格的标题title:my title#tag匹配带有指定标签的书签#important支持用引号包裹带空格的标签#work in progresslist:name匹配位于指定列表中的书签list:reading支持用引号包裹带空格的列表名list:to reviewafter:date创建日期在指定日期YYYY-MM-DD当天或之后的书签after:2023-01-01before:date创建日期在指定日期YYYY-MM-DD当天或之前的书签before:2023-12-31feed:name从特定 RSS 订阅源导入的书签feed:Hackernewsage:time-range按书签创建距今的时间范围匹配。用/表示书签的最大 / 最小年龄。支持单位d天、w周、m月、y年age:1d、age:2w、age:6m、age:3y限定符详解与注意事项is:*系列用于按状态或类型过滤。is:archived与is:fav常与-配合使用如-is:archived找出所有未归档书签is:tagged/is:inlist判断书签是否至少关联了一个标签或列表is:link/is:text/is:media对应书签的三种存储类型。url:与title:执行的是子串匹配而非精确匹配因此url:example.com能命中所有 URL 中包含该片段的书签当值中包含空格时必须使用双引号。#tag标签匹配。标签名默认支持连字符等字符如#my-tag含空格时用引号包裹#work in progress。after:/before:日期格式严格为YYYY-MM-DD语义为闭区间当天或之后/之前。age:相对时间过滤age:1d表示最近 1 天之内创建age:3y表示创建超过 3 年。注意这里/描述的是书签年龄的大小关系与after/before的绝对日期形成互补。官方示例# 查找 2023 年收藏且打了 important 标签的书签 is:fav after:2023-01-01 before:2023-12-31 #important # 查找已归档、且要么在 reading 列表中、要么打了 work 标签的书签 is:archived and (list:reading or #work) # 查找没有标签或不在任何列表中的书签 -is:tagged or -is:inlist # 查找标题中包含 React 的书签 title:React四、组合条件布尔逻辑与分组多个条件可以自由组合。语法层面的核心规则是空格分隔 隐式 ANDand/or关键字 显式布尔运算圆括号可改变求值优先级限定符前加-实现取反。# 查找 2023 年收藏且打了 important 标签的书签 is:fav after:2023-01-01 before:2023-12-31 #important # 查找已归档、且要么在 reading 列表中、要么打了 work 标签的书签 is:archived and (list:reading or #work) # 查找既未收藏也未归档的书签 -is:fav -is:archived从实际解析结果看见 searchQueryParser.test.ts 中的复杂查询用例(is:fav is:archived) or (#my-tag)会被解析为or节点下挂一个and节点与一个标签匹配节点而(is:fav or is:archived) and #my-tag则相反证明括号确实参与构造了嵌套的匹配树而非简单的线性拼接。五、文本搜索全文搜索任何不属于限定符的文本都会被当作全文搜索内容处理# 在书签内容中搜索 machine learning machine learning # 文本搜索与限定符组合 machine learning is:fav文本与限定符可以交错出现。从 searchQueryParser.test.ts 的用例可见查询hello is:fav world is:archived mixed world #my-tag test会被拆分为纯文本hello world mixed world test与三个 matcherfavourited、archived、tagName的组合两者互不干扰。这意味着你可以在一次搜索中同时享受结构化过滤与全文检索。六、源码级解析解析器如何工作搜索查询语言并非简单的字符串匹配而是一套由typescript-parsec实现的词法 语法解析器位于 packages/shared/searchQueryParser.ts并被 Web 端、移动端与智能列表三方共用。词法分析Lexer解析器首先按优先级顺序将输入字符串切分为 token见 searchQueryParser.tsconst lexerRules: [RegExp, TokenType][] [ [/^\sand/i, TokenType.And], [/^\sor/i, TokenType.Or], [/^#/, TokenType.Hash], [/^(is|url|list|after|before|age|feed|title|tag|source):/, TokenType.Qualifier], [/^([^])/, TokenType.StringLiteral], [/^\(/, TokenType.LParen], [/^\)/, TokenType.RParen], [/^\s/, TokenType.Space], [/^-/, TokenType.Minus], [/^!/, TokenType.Exclamation], [/^[^ )(]/, TokenType.Ident], // 兜底规则匹配大量普通字符 ] as const;值得注意的细节and/or匹配不区分大小写/i标志且要求前面带空白限定符白名单包含is、url、list、after、before、age、feed、title、tag、source十个前缀——其中tag:是#的等价写法source:是 v0.28.0 文档未列出、但当前源码已实现的限定符双引号字符串被单独识别为StringLiteral解析时会剥去引号-与!都被视为取反符号Minus/Exclamation因此两种写法行为完全一致。语法分析与 Matcher 树词法 token 随后被送入递归下降文法EXP/MATCHERsearchQueryParser.ts每个匹配条件被编译成一个Matcher对象。is:*与各冒号限定符分别映射为不同类型的 matcheris:fav→{ type: favourited, favourited: true }is:archived→{ type: archived, archived: true }is:tagged→{ type: tagged, tagged: true }is:inlist→{ type: inlist, inList: true }is:link/is:text/is:media→{ type: type, typeName: LINK | TEXT | ASSET }url:→{ type: url, url }title:→{ type: title, title }#/tag:→{ type: tagName, tagName }list:→{ type: listName, listName }feed:→{ type: rssFeedName, feedName }source:→{ type: source, source }after:/before:→{ type: dateAfter | dateBefore, date }age:→{ type: age, relativeDate: { direction, amount, unit } }所有 matcher 的类型定义集中在 packages/shared/types/search.ts其中and/or会递归地组合子 matcher形成一棵匹配树export type Matcher | NonRecursiveMatcher | { type: and; matchers: Matcher[] } | { type: or; matchers: Matcher[] };解析完成后还会调用flattenAndsAndOrs对同类型节点做扁平化合并searchQueryParser.ts例如(is:fav is:archived) #my-tag会被合并为单个and节点下挂三个 matcher。三种解析结果状态parseSearchQuery返回result字段取值full | partial | invalidsearchQueryParser.tsfull整个查询被完整解析partial解析器无法消费全部输入例如用户正在输入、查询尚未写完此时返回已解析的 matcher 与剩余文本invalid语法不合法整个查询降级为纯文本处理。这一设计让搜索框可以在输入过程中实时给出部分匹配结果体验流畅而未知的限定符不会报错会被当作普通文本回退处理测试用例is:fav is:helloworld验证了这一点见 searchQueryParser.test.ts。相对时间解析age:限定符由 packages/shared/utils/relativeDateUtils.ts 负责。正则^([])(\d)([dwmy])$严格限定格式表示更新newer年龄小于表示更旧older年龄大于单位映射为d→day、w→week、m→month、y→year。toAbsoluteDate再将相对时间换算为绝对日期用于数据库查询。七、源码级执行Matcher 如何变成 SQL解析出的 Matcher 树并不会直接用于前端过滤而是被转换为 SQL 查询。核心实现在 packages/trpc/lib/search.ts 的getBookmarkIdsFromMatcher中其内部通过getIds对每种 matcher 类型生成对应的 drizzle-orm 查询tagName使用EXISTS/NOT EXISTS子查询关联tagsOnBookmarks与bookmarkTags表按标签名精确匹配search.tstagged用exists/notExists判断书签是否关联了任意标签and/or节点分别通过intersect求 ID 交集对应 AND与union求 ID 并集对应 OR在内存中合并各子查询结果search.ts。搜索接口bookmarks.searchBookmarks在 packages/trpc/routers/bookmarks.ts 中定义前端通过 apps/web/lib/hooks/bookmark-search.ts 调用并支持三种搜索模式fts全文检索、semantic语义搜索与hybrid混合。值得注意的是当查询完全由限定符组成如is:fav没有可嵌入的文本时前端会自动回退到全文检索模式见 bookmark-search.ts。八、进阶实战用查询语言驱动智能列表搜索查询语言并不只用于搜索框——Karakeep 的**智能列表Smart List**直接复用同一套语法。在 packages/trpc/models/lists.ts 中SmartList将用户保存的查询字符串通过parseSearchQuery解析要求结果为full否则报 Invalid smart list query再调用getBookmarkIdsFromMatcher实时计算列表内容lists.ts。这意味着你可以把常用搜索保存为持久化的智能列表例如# 一个自动汇总最近一周收藏内容的智能列表查询 is:fav age:1w # 一个聚合所有未归档、带 todo 标签链接的智能列表查询 -is:archived #todo is:link由于智能列表与搜索框共享解析器与执行链路两者的行为完全一致查询语法可以无缝迁移。九、常见组合速查需求查询最近一个月收藏且未读未归档-is:archived age:1m2023 年收藏的已归档书签is:archived after:2023-01-01 before:2023-12-31在 reading 列表或打了 work 标签list:reading or #work既没打标签也不在任何列表-is:tagged -is:inlist标题含 React 的链接类型书签title:React is:link来自 Hackernews 订阅源的书签feed:Hackernews十、测试验证与进一步阅读该查询语言的行为有完整的单元测试保障覆盖简单is:*查询、字符串限定符、!取反别名、tag:别名、日期/年龄查询、复杂布尔组合、纯文本与限定符混排、未知限定符回退、部分解析等场景见 packages/shared/searchQueryParser.test.ts。如需深入了解执行层的 SQL 生成可阅读 packages/trpc/lib/search.ts 及其测试 packages/trpc/lib/tests/search.test.ts。若想进一步掌握相关概念可参阅仓库内的 书签使用指南、标签说明 与 列表说明它们与本搜索语言共同构成 Karakeep 的检索与组织体系。【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarder创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考