Skip to content

Commit 54cc9cb

Browse files
committed
update
1 parent a3437af commit 54cc9cb

13 files changed

Lines changed: 1695 additions & 428 deletions

File tree

.github/workflows/node.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Node CI
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
9+
jobs:
10+
build:
11+
runs-on: ${{ matrix.os }}
12+
13+
strategy:
14+
matrix:
15+
os: [ubuntu-latest, windows-latest, macos-latest]
16+
node-version: [18.x, 20.x, 22.x]
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
- name: Use Node.js ${{ matrix.node-version }}
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: ${{ matrix.node-version }}
24+
cache: 'npm'
25+
- run: npm ci
26+
- run: npm test

README.md

Lines changed: 307 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,308 @@
11
# monSQLize
2-
monSQLize is a universal query adapter that converts various query languages (e.g., SQL for MySQL, PostgreSQL) into MongoDB syntax. It dynamically translates familiar query styles into MongoDB-compatible operations, reducing complexity, saving effort, and improving efficiency, so developers can focus on application logic.
2+
3+
一个面向多数据库的统一(Mongo 风格)读 API。目前支持 MongoDB。目标是在不同后端之间平滑切换,同时保持熟悉的查询形态与选项。
4+
5+
## 目录
6+
- [状态](#status)
7+
- [安装](#install)
8+
- [快速开始](#quick-start)
9+
- [缓存与失效](#cache)
10+
- [缓存配置](#缓存配置)
11+
- [缓存行为与细节](#缓存行为与细节)
12+
- [统计与可观测性](#统计与可观测性)
13+
- [缓存操作方法](#缓存操作方法)
14+
- [invalidate(op) 用法](#invalidate)
15+
- [进阶:手动失效](#cache-advanced)
16+
- [跨库访问注意事项](#cross-db)
17+
- [说明](#notes)
18+
19+
<a id='status'></a>
20+
## 状态(总览)
21+
22+
> 说明:本表统一四列(分类 | 能力 | 状态 | 备注),状态使用标记:✅ 已实现、❌ 未实现、🗺️ 计划中。
23+
24+
| 分类 | 能力 | 状态 | 备注 |
25+
|---|---|---|---|
26+
| 数据库类型 | MongoDB | ✅ 已实现 | 当前唯一已实现适配器 |
27+
| 数据库类型 | PostgreSQL | 🗺️ 计划中 | 未实现 |
28+
| 数据库类型 | MySQL | 🗺️ 计划中 | 未实现 |
29+
| 数据库类型 | SQLite | 🗺️ 计划中 | 未实现 |
30+
| 数据库类型 | 查询运算符映射(operators) | 🗺️ 计划中 | 预研草案,尚未实现跨库翻译 |
31+
| 数据模型/Schema | Schema 能力 | ❌ 未实现 | 由上层应用自行约束 |
32+
| 读 API(Read) | findOne | ✅ 已实现 | 支持 projection、sort、cache、maxTimeMS |
33+
| 读 API(Read) | find | ✅ 已实现 | 支持 limit/skip 普通分页;未传 limit 使用全局 findLimit(默认 10);limit=0 表示不限制 |
34+
| 读 API(Read) | 深分页(游标/主键) | ❌ 未实现 | 计划中 |
35+
| 读 API(Read) | 链表/聚合驱动分页 | ❌ 未实现 | 计划中 |
36+
| 读 API(Read) | count | ✅ 已实现 | 统计匹配文档数 |
37+
| 读 API(Read) | stream(find 流式返回) | ❌ 未实现 | 计划中 |
38+
| 读 API(Read) | 聚合(aggregate/或 find 支持聚合) | ❌ 未实现 | 后续可能透传或翻译 |
39+
| 缓存与失效 | 内置内存缓存 | ✅ 已实现 | 读穿、TTL(毫秒)、LRU、惰性过期、并发去重 |
40+
| 缓存与失效 | 稳定序列化键 | ✅ 已实现 | 支持常见 BSON;keys()/delPattern();统计(enableStats 可选) |
41+
| 缓存与失效 | 命名空间与精准失效 | ✅ 已实现 | collection.invalidate(op?);getNamespace() |
42+
| 缓存与失效 | 多层缓存(本地+远端) | 🗺️ 计划中 | 未实现 |
43+
| 跨库访问 | 跨库读与失效 | ✅ 已实现 | db('<目标库>').collection('<集合>') 支持 find/findOne/count/invalidate |
44+
| 超时与慢日志 | 全局默认值 | ✅ 已实现 | maxTimeMS、findLimit 构造时设定,单次可覆盖 |
45+
| 超时与慢日志 | 慢查询日志 | ✅ 已实现 | slowQueryMs(默认 500ms);日志包含安全字段与查询形状(无敏感值) |
46+
| 类型与接口 | TypeScript 类型声明 | ✅ 已实现 | index.d.ts;含 CacheLike、Find/Count、getNamespace、getDefaults |
47+
| 类型与接口 | getDefaults() | ✅ 已实现 | 返回当前实例默认配置视图 |
48+
| 类型与接口 | 模块格式 | 🗺️ 计划中 | 目前 CJS;ESM 条件导出未实现 |
49+
| 连接与运维 | connect/close | ✅ 已实现 | 连接与关闭 |
50+
| 连接与运维 | 健康检查/事件钩子 | 🗺️ 计划中 | 未实现 |
51+
| 写相关辅助 | createCollection/createView/dropCollection | ✅ 已实现 | Mongo 适配器功能 |
52+
| 写相关辅助 | 写后读缓存一致性 | 手动 | 不自动失效,建议写后调用 collection.invalidate(op?) |
53+
| 其他 | 安全默认 | ✅ 已实现 | find 未指定 limit 使用全局 findLimit;limit=0 表示不限制 |
54+
| 其他 | 命名空间 instanceId | ✅ 已实现 | 可显式指定或自动生成;scope 支持 database/connection |
55+
56+
<a id='install'></a>
57+
## 安装
58+
```
59+
npm i monsqlize
60+
```
61+
62+
<a id='quick-start'></a>
63+
## 快速开始(含默认配置与自动 instanceId)
64+
```js
65+
const MonSQLize = require('monsqlize');
66+
(async () => {
67+
const { db, collection } = await (new MonSQLize({
68+
type: 'mongodb',
69+
databaseName: 'example',
70+
config: { uri: 'mongodb://localhost:27017' },
71+
maxTimeMS: 3000, //全局默认配置(本实例的默认 maxTimeMS)
72+
findLimit:10, // 分成查询每页数量,默认:10
73+
}).connect());
74+
75+
// 单次查询可覆盖 maxTimeMS;cache 为毫秒
76+
const one = await collection('test').findOne({ query: {}, cache: 5000, maxTimeMS: 1500 });
77+
console.log(one);
78+
79+
// find 的安全默认:未传 limit 时使用全局 findLimit(默认 10);传 0 表示不限制
80+
const list = await collection('test').find({ query: {} }); // 等效 limit=10
81+
const all = await collection('test').find({ query: {}, limit: 0 }); // 不限制
82+
83+
// 写后失效(可选 op:'find' | 'findOne' | 'count')
84+
await collection('test').invalidate(); // 失效该集合的全部读缓存
85+
await collection('test').invalidate('find'); // 仅失效 find 的缓存
86+
87+
// —— 跨库访问(Cross-DB)——
88+
// 1) 访问其他数据库下的集合
89+
const docOther = await db('analytics').collection('events').findOne({
90+
query: { type: 'click' },
91+
cache: 3000, // 可选缓存(毫秒)
92+
maxTimeMS: 1500 // 单次查询的超时覆盖
93+
});
94+
console.log('analytics.events ->', docOther);
95+
96+
// 2) 在同一调用中进行多次跨库查询(顺序执行)
97+
const [u1, u2] = [
98+
await db('users_db').collection('users').findOne({ query: { name: 'Alice' }, cache: 2000 }),
99+
await db('users_db').collection('users').findOne({ query: { name: 'Bob' } })
100+
];
101+
console.log(u1, u2);
102+
})();
103+
```
104+
105+
<a id='cache'></a>
106+
## 缓存与失效
107+
108+
- 默认提供内存缓存(LRU + 惰性过期),也可传入自定义缓存实现(需实现标准接口:get/set/del/delPattern/keys 等)。
109+
- 读穿(read-through)策略:当 options.cache>0 时开启缓存;0 或未传则直连数据库。
110+
- TTL 单位为毫秒;允许缓存 null(仅将 undefined 视为未命中)。
111+
- 键采用稳定序列化,确保同一查询结构产生相同键(含常见 BSON 类型)。
112+
113+
### 缓存配置
114+
- 方式一:传入“配置对象”,自动创建内置内存缓存
115+
```js
116+
const { db, collection } = await new MonSQLize({
117+
type: 'mongodb',
118+
databaseName: 'example',
119+
config: { uri: 'mongodb://localhost:27017' },
120+
// 缓存配置(创建默认内存缓存实例)
121+
cache: {
122+
maxSize: 100000, // 最大键数量(默认 100000)
123+
maxMemory: 0, // 最大内存占用(字节);0 表示不限制
124+
enableStats: true, // 是否启用命中率等统计(默认 true)
125+
},
126+
// 全局查询默认值
127+
maxTimeMS: 3000,
128+
findLimit: 10,
129+
}).connect();
130+
```
131+
132+
- 方式二:注入自定义缓存实例(需实现 CacheLike 接口)
133+
```ts
134+
// TypeScript 接口(简化),见 index.d.ts 的 CacheLike
135+
interface CacheLike {
136+
get(key: string): Promise<any>;
137+
set(key: string, val: any, ttl?: number): Promise<void>;
138+
del(key: string): Promise<boolean>;
139+
exists(key: string): Promise<boolean>;
140+
getMany(keys: string[]): Promise<Record<string, any>>;
141+
setMany(obj: Record<string, any>, ttl?: number): Promise<boolean>;
142+
delMany(keys: string[]): Promise<number>;
143+
delPattern(pattern: string): Promise<number>;
144+
clear(): void;
145+
keys(pattern?: string): string[];
146+
getStats?(): any;
147+
}
148+
```
149+
```js
150+
// 注入自定义实现(例如封装 Redis/Memcached/本地 LRU 等),只要方法签名一致即可
151+
// 假设已获得自定义缓存实例
152+
const customCache = getCustomCache();
153+
const msq = new MonSQLize({
154+
type: 'mongodb',
155+
databaseName: 'example',
156+
config: { uri: 'mongodb://localhost:27017' },
157+
cache: customCache,
158+
});
159+
await msq.connect();
160+
```
161+
162+
- 每次查询是否使用缓存由“查询级 options.cache”决定:
163+
- >0:使用读穿缓存,单位毫秒(TTL)
164+
- 0 或未传:绕过缓存,直连数据库
165+
166+
### 缓存行为与细节
167+
- 读穿(read-through):首次未命中会执行实际查询,结果写入缓存;命中直接返回缓存。
168+
- 并发去重:相同键的并发请求共享同一 Promise,避免对后端的 thundering herd(参见 lib/cache.js 中 __inflight 逻辑)。
169+
- LRU 淘汰:超出 maxSize 或 maxMemory 时,从最久未使用的键开始淘汰。
170+
- TTL 精度:以毫秒为单位;采用“惰性过期”,即在读取/扫描时才判断过期、并清理键。
171+
- 值语义:允许缓存 null;仅将 undefined 视为未命中(便于明确区分“确无数据”和“未缓存”)。
172+
- 键生成:采用稳定序列化(stableStringify),对象键排序、数组保序;内置支持常见 BSON 类型(ObjectId、Decimal128、Long、UUID、Binary)。
173+
- 内存估算:内置缓存对 value 进行粗略 size 估算(JSON.stringify 长度等),仅用于淘汰策略,并非精确内存计量。
174+
175+
### 统计与可观测性
176+
- 获取底层缓存实例,并查看统计/键:
177+
```js
178+
const msq = new MonSQLize({ /* ... */ });
179+
await msq.connect();
180+
const cache = msq.getCache();
181+
182+
// 命中率与基本统计(需 enableStats=true)
183+
const stats = cache.getStats && cache.getStats();
184+
console.log('cache stats:', stats);
185+
186+
// 列出当前所有键(或按简单通配过滤)
187+
console.log(cache.keys()); // 全部键
188+
console.log(cache.keys('*users*')); // 仅包含 users 的键
189+
```
190+
- 手动清理:
191+
```js
192+
cache.clear(); // 清空全部缓存(谨慎使用)
193+
await cache.delPattern('*x*'); // 通配删除,复杂场景建议使用更强的外部缓存
194+
```
195+
196+
<a id='缓存操作方法'></a>
197+
### 缓存操作方法
198+
- 以下方法由内置内存缓存与自定义 CacheLike 实现共同支持。先通过实例方法 `getCache()` 获取缓存对象:
199+
```js
200+
const msq = new MonSQLize({ /* ... */ });
201+
await msq.connect();
202+
const cache = msq.getCache();
203+
```
204+
205+
- 方法速览:
206+
- `get(key: string): Promise<any>` 获取键值(未命中返回 undefined;允许缓存 null)。
207+
- `set(key: string, val: any, ttl?: number): Promise<void>` 设置键值与可选 TTL(毫秒)。
208+
- `del(key: string): Promise<boolean>` 删除单个键。
209+
- `exists(key: string): Promise<boolean>` 判断键是否存在且未过期。
210+
- `getMany(keys: string[]): Promise<Record<string, any>>` 批量获取。
211+
- `setMany(obj: Record<string, any>, ttl?: number): Promise<boolean>` 批量设置。
212+
- `delMany(keys: string[]): Promise<number>` 批量删除,返回删除数量。
213+
- `delPattern(pattern: string): Promise<number>` 按通配模式删除(内存实现为 O(N) 扫描)。
214+
- `clear(): void` 清空所有键(谨慎使用)。
215+
- `keys(pattern?: string): string[]` 列出键(可选通配模式)。
216+
- `getStats?(): any` 返回命中率、淘汰数、内存估算等统计(若启用)。
217+
218+
- 使用示例:
219+
```js
220+
// 单键 set/get
221+
await cache.set('foo', { a: 1 }, 5000); // TTL 5s
222+
const foo = await cache.get('foo'); // => { a: 1 }
223+
224+
// 判断存在/删除
225+
const ok = await cache.exists('foo'); // true/false
226+
await cache.del('foo');
227+
228+
// 批量操作
229+
await cache.setMany({
230+
'k:1': { id: 1 },
231+
'k:2': { id: 2 },
232+
}, 3000);
233+
const many = await cache.getMany(['k:1', 'k:2']);
234+
const removed = await cache.delMany(['k:1', 'k:2']);
235+
236+
// 模式删除与列举
237+
await cache.delPattern('*users*');
238+
const userKeys = cache.keys('*users*');
239+
240+
// 统计(需 enableStats)
241+
const stats = cache.getStats && cache.getStats();
242+
console.log(stats);
243+
```
244+
245+
<a id='invalidate'></a>
246+
### invalidate(op) 用法
247+
- 作用:失效目标集合在当前命名空间(iid/type/db/collection)下的读缓存。
248+
- op 可选:'find' | 'findOne' | 'count'。不传 op 表示失效该集合的全部读缓存。
249+
- 示例:
250+
```js
251+
await collection('users').invalidate(); // 删除 users 集合所有读缓存
252+
await collection('users').invalidate('find'); // 仅删除 find 相关缓存
253+
```
254+
- 典型时机:写操作(insert/update/delete/bulk)后调用,以保证读缓存与数据库一致。
255+
256+
<a id='cache-advanced'></a>
257+
### 进阶:手动失效
258+
- 建议优先使用集合访问器的 invalidate(op?) 进行失效。
259+
- 如需更粗粒度的手动操作,可通过实例方法 getCache() 获取底层缓存实例:
260+
```js
261+
const msq = new MonSQLize({ /* ... */ });
262+
await msq.connect();
263+
const cache = msq.getCache();
264+
265+
// 清空全部缓存(谨慎使用)
266+
cache.clear();
267+
268+
// 按简单模式删除包含某集合名的键(实现为通配匹配)
269+
await cache.delPattern('*users*');
270+
```
271+
- 注意:底层内存缓存的 delPattern 为 O(N) 扫描,适合中小规模场景;重型场景可替换为外部缓存实现。
272+
273+
<a id='cross-db'></a>
274+
## 跨库访问注意事项
275+
- 快速入门的跨库示例与注意点已在「快速开始」中给出,推荐优先阅读该小节。
276+
- 速查清单:
277+
- 用法:`db('<目标库名>').collection('<集合名>')`,返回集合读访问器,支持 find/findOne/count/invalidate。
278+
- 失效:跨库失效仍可用集合访问器的 `invalidate(op?)`;或使用 getCache().delPattern 进行批量模式失效。
279+
- 性能:跨库访问与同库一致;建议为高频跨库查询设置合适的 `cache` TTL 与 `maxTimeMS`
280+
- 权限:确保连接账号具备目标库的读权限;否则驱动层会抛出权限错误。
281+
282+
<a id='notes'></a>
283+
## 说明
284+
- 全局 maxTimeMS 为默认值,单次 options.maxTimeMS 优先。
285+
- 全局 findLimit 配置:构造时传入 findLimit(默认 10)。
286+
- 缓存键稳定序列化已支持常见 BSON 类型(ObjectId、Decimal128、Long、UUID、Binary)。
287+
288+
欢迎 PR。
289+
290+
291+
### 辅助方法与慢查询日志
292+
- 获取默认配置(全局 maxTimeMS、findLimit、namespace、slowQueryMs):
293+
```js
294+
const msq = new MonSQLize({
295+
type: 'mongodb',
296+
databaseName: 'example',
297+
config: { uri: 'mongodb://localhost:27017' },
298+
slowQueryMs: 800
299+
});
300+
await msq.connect();
301+
console.log(msq.getDefaults());
302+
```
303+
- 获取集合访问器的命名空间(便于调试与手动失效脚本):
304+
```js
305+
const ns = db('example').collection('users').getNamespace();
306+
// => { iid, type: 'mongodb', db: 'example', collection: 'users' }
307+
```
308+
- 慢查询日志:findOne/find/find/count 会在一次调用耗时超过 slowQueryMs(默认为 500ms)时输出 warn 日志。

example/mongo.js

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,36 @@
11
const MonSQLize = require('../lib/index');
22
(async ()=>{
3-
const db = await new MonSQLize({
4-
type: 'mongodb', // 连接类型
5-
databaseName :'test', // 数据库名称
3+
4+
const { db, collection } = await (new MonSQLize({
5+
type: 'mongodb', // 连接类型
6+
databaseName :'example', // 数据库名称
7+
maxTimeMS: 3000, // 全局默认:查询超时 3000ms,可被单次 options.maxTimeMS 覆盖
8+
findLimit: 10, // 全局默认:find 未传 limit 时的默认值;传 0 表示不限制
9+
slowQueryMs: 5, // 演示用:阈值设为 5ms,便于观察慢查询日志
10+
// namespace: { instanceId: 'local-example' }, // 可选:显式设置实例命名空间,未设置则会基于 uri+db 自动生成
611
config: {
7-
uri: 'mongodb://localhost:27017'
8-
}
9-
}).connect();
12+
uri: 'mongodb://localhost:27017' // 链接 uri
13+
},
14+
// 可选:自定义 logger,观察 warn 输出(默认已输出到控制台)
15+
// logger: { debug: console.debug, info: console.log, warn: console.warn, error: console.error }
16+
}).connect());
17+
18+
// 创建一个集合示例
19+
// console.log(await db('test').createCollection('test'));
20+
21+
// 查询一条记录示例(如果存在),并使用 5 秒缓存
22+
console.log(await collection('test').findOne({ query: {}, cache: 5000 }))
23+
24+
// 或者
25+
console.log(await db('example').collection('test').findOne({ query: {}, cache: 5000 }))
1026

11-
console.log(await db('demo').create('test'))
27+
// 演示慢日志:执行一次 find,包含 projection/sort/limit 等,可在控制台查看 [WARN] ⏱️ Slow query
28+
console.log(await collection('test').find({
29+
query: { a: { $gte: 1 }, tags: ['x', 'y'] },
30+
projection: { a: 1, b: 1 },
31+
sort: { createdAt: -1 },
32+
limit: 50,
33+
cache: 1000,
34+
maxTimeMS: 1500,
35+
}));
1236
})();

0 commit comments

Comments
 (0)