用一杯咖啡的时间学会 FilterQL:给数据做“精准筛选”的迷你语言
❝
把 1000 条电影记录变成“2000 年后、评分 8.5 以上、按评分倒序”的 10 部佳片,只需要一行:
(genre == Action || genre == Comedy) && year >= 2000 && rating >= 8.5 | SORT rating desc | LIMIT 10
❞
如果你写过 SQL 的 WHERE
、用过 JavaScript 的 array.filter()
,或者只是想把 API 返回的一大坨 JSON 迅速缩小到“我真正关心的那一小撮”,FilterQL 就是为你准备的“口袋瑞士军刀”。
它只有两项资产:
-
一个仅 3 KB 左右的 TypeScript 库,安装即用; -
一份开放的语法规范,任何语言都能照着实现。
下面用问答方式带你完整体验:从安装、语法、实战到扩展,一个不落。
目录
-
一句话认识 FilterQL -
安装与 30 秒上手 -
语法速查表(会用即走) -
真实场景:CLI 工具里的“万能过滤器” -
在浏览器或 Node 里写代码:细节拆解 -
定义字段(schema) -
写查询(query) -
管道操作(排序、截取)
-
-
常见疑问 FAQ -
高级:自定义操作 ROUND、FILTER 扩展 -
语言规范极简导航 -
小结与下一步
1. 一句话认识 FilterQL
FilterQL = 轻量级 WHERE 子句 + 链式数据管道
-
语法接近日常英语:
title == "Inception" && year >= 2010
-
自带排序、分页:
rating >= 8.5 | SORT rating desc | LIMIT 10
-
不挑数据源:数组、JSON、CSV 转成对象数组都能用。
2. 安装与 30 秒上手
安装
# 推荐用 Bun
bun add filterql
# 或 npm / pnpm / yarn
npm install filterql
30 秒上手脚本
// demo.ts
import { FilterQL } from "filterql"
// 1. 描述数据字段
const schema = {
title: { type: "string", alias: "t" },
year: { type: "number", alias: "y" },
rating: { type: "number" },
genre: { type: "string" },
watched:{ type: "boolean", alias: "w" }
}
// 2. 创建实例
const fql = new FilterQL({ schema })
// 3. 随便来几条数据
const movies = [
{ title: "The Matrix", year: 1999, rating: 8.7, genre: "Action", watched: true },
{ title: "Inception", year: 2010, rating: 8.8, genre: "Sci-Fi", watched: false },
{ title: "The Dark Knight", year: 2008, rating: 9.0, genre: "Action", watched: true }
]
// 4. 查询:想看已看过的动作片
const result = fql.filter(movies, "genre == Action && watched | SORT rating desc")
console.table(result)
运行:
bun demo.ts
输出:
┌---------┬-------------------┬------┬--------┬--------┬---------┐
│ (index) │ title │ year │ rating │ genre │ watched │
├---------┼-------------------┼------┼--------┼--------┼---------┤
│ 0 │ "The Dark Knight" │ 2008 │ 9 │"Action"│ true │
│ 1 │ "The Matrix" │ 1999 │ 8.7 │"Action"│ true │
└---------┴-------------------┴------┴--------┴--------┴---------┘
3. 语法速查表
目的 | 写法示例 | 备注 |
---|---|---|
等于 | title == "Inception" |
区分大小写 |
不区分大小写 | title i== "inception" |
前缀 i |
包含 | title *= "cep" |
子串匹配 |
开头 | title ^= "The" |
|
结尾 | title $= "Knight" |
|
正则 | title ~= "In.*ion" |
标准正则 |
大于等于 | rating >= 8.5 |
数字、日期都行 |
逻辑与 | && |
|
逻辑或 | ` | |
取反 | ! |
!watched 等价 watched == false |
布尔简写 | watched |
直接写字段名 = 为 true |
全匹配 | * |
不筛选,只做后续排序/截取 |
排序 | SORT field [asc/desc] |
默认升序 |
截取 | LIMIT 10 |
取前 10 条 |
4. 真实场景:CLI 工具里的“万能过滤器”
想象你正在写一个 movie-cli
命令行,用户想这样用:
movie-cli '(genre == Action || genre == Comedy) && year >= 2000 && rating >= 8.5'
核心代码(仅 10 行):
// movie-cli.ts
import { FilterQL } from "filterql"
const data = await (await fetch("https://api.example.com/movies")).json()
const schema = {/* 同上 */}
const query = process.argv[2] // 接收用户输入
const fql = new FilterQL({ schema })
console.table(fql.filter(data, query))
就这么简单,用户立刻拥有“类 SQL”的即席查询能力。
5. 在浏览器或 Node 里写代码:细节拆解
5.1 定义字段(schema)
const schema = {
title: { type: "string", alias: "t" }, // 支持别名
year: { type: "number", alias: "y" },
rating: { type: "number" },
genre: { type: "string" },
watched:{ type: "boolean", alias: "w" }
}
-
type
只能是"string"
|"number"
|"boolean"
-
alias
可选,写查询时更短:y >= 2000
代替year >= 2000
-
数据里有多余字段完全没关系——FilterQL 只认 schema 声明的。
5.2 写查询(query)
-
单条件
"rating >= 8.5"
-
多条件
(genre == Action || genre == Comedy) && year >= 2000
-
使用别名
y >= 2000 && r >= 8.5
-
布尔字段
watched && !downloaded
-
空值检查
rating != ""
(把 null / undefined / “” 视为空)
5.3 管道操作(排序、截取)
语法:
查询 | 操作1 [参数] | 操作2 [参数]
内置操作只有两条,但已覆盖 80% 需求:
-
SORT
SORT rating desc
降序
SORT year asc
升序(默认) -
LIMIT
LIMIT 20
取前 20 条
链式示例:
year >= 2000 | SORT rating desc | LIMIT 10
6. 常见疑问 FAQ
问题 | 回答 |
---|---|
大小写敏感吗? | 默认敏感,用 i== / i*= 等前缀即可忽略大小写。 |
值里带空格怎么办? | 用双引号:title == "The Dark Knight" |
值里有双引号怎么办? | 转义:"A movie with \"quotes\"" |
能过滤嵌套字段吗? | 当前版本只支持一层对象,嵌套可用平铺或后期扩展。 |
查询语法错了会怎样? | 库会抛明确的异常,告诉你哪一列、哪个字符出错。 |
支持 TypeScript 类型提示吗? | 支持,源码已用 .ts 编写并导出全部类型。 |
7. 高级:自定义操作 ROUND、FILTER 扩展
当内置的 SORT
、LIMIT
不够时,你可以注册自己的大写操作。例如把评分四舍五入:
import type { OperationMap } from "filterql"
const customOperations: OperationMap = {
ROUND: (data, args, { resolveField }) => {
const field = resolveField(args[0]) // 把别名转回正式字段
if (!field) throw new Error(`Unknown field '${args[0]}'`)
return data.map(item => ({
...item,
[field]: Math.round(item[field] as number)
}))
}
}
const fql = new FilterQL({ schema, customOperations })
// 使用
const rounded = fql.filter(movies, "* | SORT rating desc | ROUND rating")
console.table(rounded)
输出:
rating: 9 -> 9
rating: 8.8 -> 9
...
你可以写任何纯函数式操作:去重、分组、统计……只要满足签名 (data, args, helpers) => newData
。
8. 语言规范极简导航
如果你打算用 Python、Go、Rust 再实现一份 FilterQL,只需要记住 5 条核心语法规则:
-
查询结构
filter | 操作1 | 操作2 ...
-
表达式优先级(高→低)
()
→!
→&&
→||
-
比较符
== != *= ^= $= ~= >= <=
都可加i
前缀 -
值
-
无空格可裸写: Matrix
-
有空格/括号/特殊字符必须 "双引号包裹"
-
内部双引号用 \"
转义
-
-
操作
-
名字必须全大写 -
参数跟在名字后,空格分隔
-
完整 EBNF 见仓库 Language Specification
一节,不到 40 行,复制即可开工。
9. 小结与下一步
FilterQL 把“写复杂筛选”变成“写一句话”,但:
-
它「不是 SQL 的替代品」,而是 JS / TS 生态里轻如鸿毛的补充。 -
它「不会让你写正则写到头疼」,常用比较符一行搞定。 -
它「可扩展」,自定义操作让业务逻辑脱离查询字符串,保持整洁。
下一步建议:
-
把上面 CLI demo 打包成 npm create movie-cli
模板。 -
在浏览器端用 FilterQL 做表格即时搜索(几百 KB 的库对比 3 KB 的 FilterQL,加载速度肉眼可见)。 -
阅读源码 src/operation-evaluator/operations.ts
,10 分钟就能学会写自定义操作。
祝你玩得开心,下次再见!