monsqlize 1.0.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.
- package/CHANGELOG.md +2474 -0
- package/LICENSE +21 -0
- package/README.md +1368 -0
- package/index.d.ts +1052 -0
- package/lib/cache.js +491 -0
- package/lib/common/cursor.js +58 -0
- package/lib/common/docs-urls.js +72 -0
- package/lib/common/index-options.js +222 -0
- package/lib/common/log.js +60 -0
- package/lib/common/namespace.js +21 -0
- package/lib/common/normalize.js +33 -0
- package/lib/common/page-result.js +42 -0
- package/lib/common/runner.js +56 -0
- package/lib/common/server-features.js +231 -0
- package/lib/common/shape-builders.js +26 -0
- package/lib/common/validation.js +49 -0
- package/lib/connect.js +76 -0
- package/lib/constants.js +54 -0
- package/lib/count-queue.js +187 -0
- package/lib/distributed-cache-invalidator.js +259 -0
- package/lib/errors.js +157 -0
- package/lib/index.js +352 -0
- package/lib/logger.js +224 -0
- package/lib/model/examples/test.js +114 -0
- package/lib/mongodb/common/accessor-helpers.js +44 -0
- package/lib/mongodb/common/agg-pipeline.js +32 -0
- package/lib/mongodb/common/iid.js +27 -0
- package/lib/mongodb/common/lexicographic-expr.js +52 -0
- package/lib/mongodb/common/shape.js +31 -0
- package/lib/mongodb/common/sort.js +38 -0
- package/lib/mongodb/common/transaction-aware.js +24 -0
- package/lib/mongodb/connect.js +178 -0
- package/lib/mongodb/index.js +458 -0
- package/lib/mongodb/management/admin-ops.js +199 -0
- package/lib/mongodb/management/bookmark-ops.js +166 -0
- package/lib/mongodb/management/cache-ops.js +49 -0
- package/lib/mongodb/management/collection-ops.js +386 -0
- package/lib/mongodb/management/database-ops.js +201 -0
- package/lib/mongodb/management/index-ops.js +474 -0
- package/lib/mongodb/management/index.js +16 -0
- package/lib/mongodb/management/namespace.js +30 -0
- package/lib/mongodb/management/validation-ops.js +267 -0
- package/lib/mongodb/queries/aggregate.js +133 -0
- package/lib/mongodb/queries/chain.js +623 -0
- package/lib/mongodb/queries/count.js +88 -0
- package/lib/mongodb/queries/distinct.js +68 -0
- package/lib/mongodb/queries/find-and-count.js +183 -0
- package/lib/mongodb/queries/find-by-ids.js +235 -0
- package/lib/mongodb/queries/find-one-by-id.js +170 -0
- package/lib/mongodb/queries/find-one.js +61 -0
- package/lib/mongodb/queries/find-page.js +565 -0
- package/lib/mongodb/queries/find.js +161 -0
- package/lib/mongodb/queries/index.js +49 -0
- package/lib/mongodb/writes/delete-many.js +181 -0
- package/lib/mongodb/writes/delete-one.js +173 -0
- package/lib/mongodb/writes/find-one-and-delete.js +193 -0
- package/lib/mongodb/writes/find-one-and-replace.js +222 -0
- package/lib/mongodb/writes/find-one-and-update.js +223 -0
- package/lib/mongodb/writes/increment-one.js +243 -0
- package/lib/mongodb/writes/index.js +41 -0
- package/lib/mongodb/writes/insert-batch.js +498 -0
- package/lib/mongodb/writes/insert-many.js +218 -0
- package/lib/mongodb/writes/insert-one.js +171 -0
- package/lib/mongodb/writes/replace-one.js +199 -0
- package/lib/mongodb/writes/result-handler.js +236 -0
- package/lib/mongodb/writes/update-many.js +205 -0
- package/lib/mongodb/writes/update-one.js +207 -0
- package/lib/mongodb/writes/upsert-one.js +190 -0
- package/lib/multi-level-cache.js +189 -0
- package/lib/operators.js +330 -0
- package/lib/redis-cache-adapter.js +237 -0
- package/lib/transaction/CacheLockManager.js +161 -0
- package/lib/transaction/DistributedCacheLockManager.js +239 -0
- package/lib/transaction/Transaction.js +314 -0
- package/lib/transaction/TransactionManager.js +266 -0
- package/lib/transaction/index.js +10 -0
- package/package.json +111 -0
package/lib/cache.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 缓存工厂类,提供默认缓存实现
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 简单的内存缓存实现
|
|
6
|
+
class Cache {
|
|
7
|
+
constructor(options = {}) {
|
|
8
|
+
this.cache = new Map(); // Map<key, { value, size, expireAt|null }>
|
|
9
|
+
this.timers = new Map(); // 保留字段(兼容),但采用惰性过期不再使用定时器
|
|
10
|
+
this.options = {
|
|
11
|
+
maxSize: options.maxSize || 100000, // 最大缓存条目数
|
|
12
|
+
maxMemory: options.maxMemory || 0, // 最大内存使用量(字节),0表示无限制
|
|
13
|
+
enableStats: options.enableStats !== false, // 启用统计信息
|
|
14
|
+
...options
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// 统计信息
|
|
18
|
+
this.stats = {
|
|
19
|
+
hits: 0,
|
|
20
|
+
misses: 0,
|
|
21
|
+
sets: 0,
|
|
22
|
+
deletes: 0,
|
|
23
|
+
evictions: 0,
|
|
24
|
+
memoryUsage: 0
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// 缓存锁管理器(由 TransactionManager 设置)
|
|
28
|
+
this.lockManager = null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 设置缓存锁管理器
|
|
33
|
+
* @param {CacheLockManager} lockManager
|
|
34
|
+
*/
|
|
35
|
+
setLockManager(lockManager) {
|
|
36
|
+
this.lockManager = lockManager;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 获取缓存锁管理器
|
|
41
|
+
* @returns {CacheLockManager|null}
|
|
42
|
+
*/
|
|
43
|
+
getLockManager() {
|
|
44
|
+
return this.lockManager;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async set(key, value, ttl = 0) {
|
|
48
|
+
// 检查缓存锁:如果该键被锁定,拒绝写入
|
|
49
|
+
if (this.lockManager && this.lockManager.isLocked(key)) {
|
|
50
|
+
// 事务期间该缓存键被锁定,跳过写入
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 估算内存使用量
|
|
55
|
+
const memorySize = this._estimateSize(key, value);
|
|
56
|
+
|
|
57
|
+
// 如果存在旧值,准确回退内存
|
|
58
|
+
const existedEntry = this.cache.get(key);
|
|
59
|
+
if (existedEntry) {
|
|
60
|
+
this.stats.memoryUsage -= existedEntry.size;
|
|
61
|
+
// 移除后续会重建以刷新 LRU 顺序
|
|
62
|
+
this.cache.delete(key);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const expireAt = ttl > 0 ? Date.now() + ttl : null;
|
|
66
|
+
const entry = { value, size: memorySize, expireAt };
|
|
67
|
+
// 插入到 Map 尾部(最新),形成 LRU 结构
|
|
68
|
+
this.cache.set(key, entry);
|
|
69
|
+
this.stats.sets++;
|
|
70
|
+
this.stats.memoryUsage += memorySize;
|
|
71
|
+
|
|
72
|
+
// 采用惰性过期,无需定时器(避免阻止事件循环退出)
|
|
73
|
+
|
|
74
|
+
// 强制执行大小与内存限制(可能触发 LRU 淘汰)
|
|
75
|
+
this._enforceLimits();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async get(key) {
|
|
79
|
+
const entry = this.cache.get(key);
|
|
80
|
+
if (!entry) {
|
|
81
|
+
this.stats.misses++;
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
// 惰性过期判断
|
|
85
|
+
if (entry.expireAt && entry.expireAt <= Date.now()) {
|
|
86
|
+
// 过期:删除并计为未命中
|
|
87
|
+
this._deleteInternal(key);
|
|
88
|
+
this.stats.misses++;
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
// 命中:LRU 刷新(移动到尾部)
|
|
92
|
+
this.cache.delete(key);
|
|
93
|
+
this.cache.set(key, entry);
|
|
94
|
+
this.stats.hits++;
|
|
95
|
+
return entry.value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async del(key) {
|
|
99
|
+
return this._deleteInternal(key);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async exists(key) {
|
|
103
|
+
const entry = this.cache.get(key);
|
|
104
|
+
if (!entry) return false;
|
|
105
|
+
if (entry.expireAt && entry.expireAt <= Date.now()) {
|
|
106
|
+
this._deleteInternal(key);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 批量操作
|
|
113
|
+
async getMany(keys) {
|
|
114
|
+
const results = {};
|
|
115
|
+
for (const key of keys) {
|
|
116
|
+
results[key] = await this.get(key);
|
|
117
|
+
}
|
|
118
|
+
return results;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async setMany(keyValuePairs, ttl = 0) {
|
|
122
|
+
// 批量设置后再统一执行限制检查,减少多次淘汰
|
|
123
|
+
for (const [key, value] of Object.entries(keyValuePairs)) {
|
|
124
|
+
await this.set(key, value, ttl);
|
|
125
|
+
}
|
|
126
|
+
// set() 已经会调用 _enforceLimits,这里无需重复
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async delMany(keys) {
|
|
131
|
+
let deleted = 0;
|
|
132
|
+
for (const key of keys) {
|
|
133
|
+
if (await this.del(key)) {
|
|
134
|
+
deleted++;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return deleted;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 模式匹配删除
|
|
141
|
+
async delPattern(pattern) {
|
|
142
|
+
const regex = this._patternToRegex(pattern);
|
|
143
|
+
const keysToDelete = [];
|
|
144
|
+
|
|
145
|
+
for (const key of this.cache.keys()) {
|
|
146
|
+
if (regex.test(key)) {
|
|
147
|
+
keysToDelete.push(key);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
return await this.delMany(keysToDelete);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 清空所有缓存
|
|
156
|
+
clear() {
|
|
157
|
+
// 不再创建定时器,这里保持向后兼容的清理
|
|
158
|
+
for (const timer of this.timers.values()) {
|
|
159
|
+
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
160
|
+
clearTimeout(timer);
|
|
161
|
+
}
|
|
162
|
+
this.timers.clear();
|
|
163
|
+
this.cache.clear();
|
|
164
|
+
this.stats.memoryUsage = 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 获取所有键
|
|
168
|
+
keys(pattern = '*') {
|
|
169
|
+
const keys = Array.from(this.cache.keys());
|
|
170
|
+
if (pattern === '*') {
|
|
171
|
+
return keys;
|
|
172
|
+
}
|
|
173
|
+
const regex = this._patternToRegex(pattern);
|
|
174
|
+
return keys.filter(key => regex.test(key));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 获取缓存统计信息
|
|
178
|
+
getStats() {
|
|
179
|
+
return {
|
|
180
|
+
...this.stats,
|
|
181
|
+
size: this.cache.size,
|
|
182
|
+
hitRate: this.stats.hits / (this.stats.hits + this.stats.misses) || 0,
|
|
183
|
+
memoryUsageMB: this.stats.memoryUsage / (1024 * 1024)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 重置统计信息
|
|
188
|
+
resetStats() {
|
|
189
|
+
this.stats = {
|
|
190
|
+
hits: 0,
|
|
191
|
+
misses: 0,
|
|
192
|
+
sets: 0,
|
|
193
|
+
deletes: 0,
|
|
194
|
+
evictions: 0,
|
|
195
|
+
memoryUsage: this.stats.memoryUsage // 保持当前内存使用量
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 内部删除方法
|
|
200
|
+
_deleteInternal(key) {
|
|
201
|
+
const entry = this.cache.get(key);
|
|
202
|
+
if (!entry) return false;
|
|
203
|
+
this.cache.delete(key);
|
|
204
|
+
this.stats.deletes++;
|
|
205
|
+
// 准确扣减内存
|
|
206
|
+
this.stats.memoryUsage -= entry.size;
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 执行大小/内存限制(LRU 淘汰)
|
|
211
|
+
_enforceLimits() {
|
|
212
|
+
// 先按条目数限制
|
|
213
|
+
while (this.cache.size > this.options.maxSize) {
|
|
214
|
+
const oldestKey = this.cache.keys().next().value;
|
|
215
|
+
if (oldestKey === undefined) break;
|
|
216
|
+
this._deleteInternal(oldestKey);
|
|
217
|
+
this.stats.evictions++;
|
|
218
|
+
}
|
|
219
|
+
// 再按内存限制(0 表示无限制)
|
|
220
|
+
if (this.options.maxMemory > 0) {
|
|
221
|
+
while (this.stats.memoryUsage > this.options.maxMemory) {
|
|
222
|
+
const oldestKey = this.cache.keys().next().value;
|
|
223
|
+
if (oldestKey === undefined) break;
|
|
224
|
+
this._deleteInternal(oldestKey);
|
|
225
|
+
this.stats.evictions++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 估算内存使用量(简化版本)
|
|
231
|
+
_estimateSize(key, value) {
|
|
232
|
+
const keySize = typeof key === 'string' ? key.length * 2 : 8;
|
|
233
|
+
let valueSize = 8; // 默认值
|
|
234
|
+
|
|
235
|
+
if (typeof value === 'string') {
|
|
236
|
+
valueSize = value.length * 2;
|
|
237
|
+
} else if (typeof value === 'object' && value !== null) {
|
|
238
|
+
try {
|
|
239
|
+
valueSize = JSON.stringify(value).length * 2;
|
|
240
|
+
} catch (e) {
|
|
241
|
+
valueSize = 100; // 估算值
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return keySize + valueSize;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 将通配符模式转换为安全正则(仅 * -> .*, 其他元字符转义)
|
|
249
|
+
_patternToRegex(pattern = '*') {
|
|
250
|
+
if (pattern === '*') return /.*/;
|
|
251
|
+
const escaped = String(pattern).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
252
|
+
const wildcarded = escaped.replace(/\\\*/g, '.*');
|
|
253
|
+
return new RegExp(`^${wildcarded}$`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 并发去重映射(缓存键 -> 正在进行的 Promise)
|
|
258
|
+
const __inflight = new Map();
|
|
259
|
+
|
|
260
|
+
module.exports = class CacheFactory {
|
|
261
|
+
/**
|
|
262
|
+
* 创建默认缓存实例
|
|
263
|
+
* @param {Object} options - 缓存配置选项
|
|
264
|
+
* @returns {Cache} 默认内存缓存实例
|
|
265
|
+
*/
|
|
266
|
+
static createDefault(options = {}) {
|
|
267
|
+
return new Cache(options);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 验证缓存实例是否有效
|
|
272
|
+
* @param {Object} cache - 缓存实例
|
|
273
|
+
* @returns {boolean} 是否有效
|
|
274
|
+
*/
|
|
275
|
+
static isValidCache(cache) {
|
|
276
|
+
if (!cache || typeof cache !== 'object') {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
// 统一缓存接口要求的方法集合(确保行为一致)
|
|
280
|
+
const requiredMethods = [
|
|
281
|
+
'get', 'set', 'del', 'exists',
|
|
282
|
+
'getMany', 'setMany', 'delMany',
|
|
283
|
+
'delPattern', 'clear', 'keys'
|
|
284
|
+
];
|
|
285
|
+
return requiredMethods.every(method => typeof cache[method] === 'function');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 获取缓存实例,如果没有提供或无效则使用默认缓存
|
|
290
|
+
* @param {Object} cache - 用户提供的缓存实例
|
|
291
|
+
* @param {Object} options - 缓存配置选项
|
|
292
|
+
* @returns {Object} 有效的缓存实例
|
|
293
|
+
*/
|
|
294
|
+
static getOrCreateCache(cache, options = {}) {
|
|
295
|
+
// 已是标准缓存实例(内存缓存接口)
|
|
296
|
+
if (this.isValidCache(cache)) {
|
|
297
|
+
return cache;
|
|
298
|
+
}
|
|
299
|
+
// 若传入的是配置对象,支持 multiLevel
|
|
300
|
+
if (cache && typeof cache === 'object' && !Array.isArray(cache)) {
|
|
301
|
+
try {
|
|
302
|
+
if (cache.multiLevel) {
|
|
303
|
+
const MultiLevelCache = require('./multi-level-cache');
|
|
304
|
+
const local = this.createDefault({ ...options, ...(cache.local || {}) });
|
|
305
|
+
const remote = cache.remote && this.isValidCache(cache.remote)
|
|
306
|
+
? cache.remote
|
|
307
|
+
: (cache.remote && typeof cache.remote === 'object' && !Array.isArray(cache.remote)
|
|
308
|
+
? this.createDefault(cache.remote) // 简化:默认用内存实现代替远端;用户可注入真正远端实现
|
|
309
|
+
: undefined);
|
|
310
|
+
return new MultiLevelCache({
|
|
311
|
+
local,
|
|
312
|
+
remote,
|
|
313
|
+
policy: cache.policy || {},
|
|
314
|
+
remoteTimeoutMs: cache.remote?.timeoutMs || 50,
|
|
315
|
+
publish: cache.publish,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
} catch (_) {
|
|
319
|
+
// 忽略 multi-level 构建失败,回退为默认内存缓存
|
|
320
|
+
}
|
|
321
|
+
return this.createDefault({ ...options, ...cache });
|
|
322
|
+
}
|
|
323
|
+
// 未提供或不识别 -> 使用默认内存缓存
|
|
324
|
+
return this.createDefault(options);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 稳定序列化:确保相同结构对象序列化一致(用于缓存键)
|
|
329
|
+
* - 对象键按字母排序
|
|
330
|
+
* - 数组保序
|
|
331
|
+
* - RegExp 转为 /pattern/flags 字符串
|
|
332
|
+
*/
|
|
333
|
+
static stableStringify(value) {
|
|
334
|
+
// 惰性加载 bson 以避免在非 Mongo 场景下的硬依赖
|
|
335
|
+
let BSON;
|
|
336
|
+
try { BSON = require('bson'); } catch (_) { BSON = null; }
|
|
337
|
+
const isNaNNumber = (x) => typeof x === 'number' && Number.isNaN(x);
|
|
338
|
+
const seen = new WeakSet();
|
|
339
|
+
function stringify(v) {
|
|
340
|
+
// 基本不可序列化类型处理
|
|
341
|
+
if (typeof v === 'function' || typeof v === 'symbol') {
|
|
342
|
+
return JSON.stringify('[UNSUPPORTED]');
|
|
343
|
+
}
|
|
344
|
+
if (isNaNNumber(v)) {
|
|
345
|
+
return JSON.stringify('NaN');
|
|
346
|
+
}
|
|
347
|
+
if (v instanceof RegExp) {
|
|
348
|
+
return JSON.stringify(v.toString()); // "/pattern/flags"
|
|
349
|
+
}
|
|
350
|
+
if (v instanceof Date) {
|
|
351
|
+
return JSON.stringify(v.toISOString());
|
|
352
|
+
}
|
|
353
|
+
// BSON 常见类型支持
|
|
354
|
+
if (BSON && v && typeof v === 'object' && v._bsontype) {
|
|
355
|
+
try {
|
|
356
|
+
switch (v._bsontype) {
|
|
357
|
+
case 'ObjectId':
|
|
358
|
+
return JSON.stringify(`ObjectId(${v.toHexString()})`);
|
|
359
|
+
case 'Decimal128':
|
|
360
|
+
return JSON.stringify(`Decimal128(${v.toString()})`);
|
|
361
|
+
case 'Long':
|
|
362
|
+
return JSON.stringify(`Long(${v.toString()})`);
|
|
363
|
+
case 'UUID':
|
|
364
|
+
return JSON.stringify(`UUID(${v.toString()})`);
|
|
365
|
+
case 'Binary':
|
|
366
|
+
return JSON.stringify(`Binary(${v.sub_type},${Buffer.from(v.buffer).toString('hex')})`);
|
|
367
|
+
default:
|
|
368
|
+
// 其他 BSON 类型退化为其 toString 表达
|
|
369
|
+
return JSON.stringify(`${v._bsontype}(${String(v)})`);
|
|
370
|
+
}
|
|
371
|
+
} catch (_) {
|
|
372
|
+
// 兜底
|
|
373
|
+
return JSON.stringify(`[BSON:${v._bsontype}]`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (v === null || typeof v !== 'object') {
|
|
377
|
+
return JSON.stringify(v);
|
|
378
|
+
}
|
|
379
|
+
if (Array.isArray(v)) {
|
|
380
|
+
return '[' + v.map(x => stringify(x)).join(',') + ']';
|
|
381
|
+
}
|
|
382
|
+
if (seen.has(v)) {
|
|
383
|
+
return JSON.stringify('[CIRCULAR]');
|
|
384
|
+
}
|
|
385
|
+
seen.add(v);
|
|
386
|
+
const keys = Object.keys(v).sort();
|
|
387
|
+
const out = '{' + keys.map(k => JSON.stringify(k) + ':' + stringify(v[k])).join(',') + '}';
|
|
388
|
+
seen.delete(v);
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
return stringify(value);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 构造按 {iid, type, db, collection} 命名空间的通配前缀,便于批量失效
|
|
396
|
+
* 仅用于内置 keys()/delPattern() 的简单匹配
|
|
397
|
+
*/
|
|
398
|
+
static buildNamespacePattern({ iid, type, db, collection }) {
|
|
399
|
+
const nsObj = { p: 'monSQLize', v: 1, iid, type, db, collection };
|
|
400
|
+
// 匹配键字符串中稳定的 "ns":{...} 片段,前后加通配 *
|
|
401
|
+
const nsStr = '"ns":' + this.stableStringify(nsObj);
|
|
402
|
+
return `*${nsStr}*`;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* 构造按 {iid,type,db,collection} 且指定 op 的模式,便于只失效某操作
|
|
407
|
+
*/
|
|
408
|
+
static buildNamespaceOpPattern({ iid, type, db, collection }, op) {
|
|
409
|
+
const nsObj = { p: 'monSQLize', v: 1, iid, type, db, collection };
|
|
410
|
+
const nsStr = '"ns":' + this.stableStringify(nsObj);
|
|
411
|
+
const opStr = op ? `,"op":${JSON.stringify(op)}` : '';
|
|
412
|
+
// 注意 buildCacheKey 的键顺序为 ns -> op -> base,因此这里匹配 ns 后立刻跟 op
|
|
413
|
+
return `*${nsStr}${opStr}*`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* 构建缓存键对象的统一外壳(便于命名空间与版本升级)
|
|
418
|
+
* 传入 base(核心区分字段)与可选 extra。
|
|
419
|
+
*/
|
|
420
|
+
static buildCacheKey({ iid, type, db, collection, op, base = {} }) {
|
|
421
|
+
return {
|
|
422
|
+
ns: { p: 'monSQLize', v: 1, iid, type, db, collection },
|
|
423
|
+
op,
|
|
424
|
+
...base,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* 读穿缓存:
|
|
430
|
+
* - ttl<=0 或未提供 cache 时直接执行 fetcher
|
|
431
|
+
* - 优先查缓存;允许缓存 null(仅将 undefined 视为未命中)
|
|
432
|
+
* - 并发去重:相同 key 的并发共享同一 Promise
|
|
433
|
+
* @param {Object} cache - 缓存实例
|
|
434
|
+
* @param {number} ttlMs - 过期毫秒
|
|
435
|
+
* @param {Object} keyObj - 将用于生成缓存键的对象
|
|
436
|
+
* @param {Function} fetcher - 未命中时的拉取函数,返回 Promise
|
|
437
|
+
*/
|
|
438
|
+
static async readThrough(cache, ttlMs, keyObj, fetcher) {
|
|
439
|
+
const ttl = Number(ttlMs || 0);
|
|
440
|
+
if (!cache || ttl <= 0) {
|
|
441
|
+
return await fetcher();
|
|
442
|
+
}
|
|
443
|
+
const key = this.stableStringify(keyObj);
|
|
444
|
+
|
|
445
|
+
const cached = await cache.get(key);
|
|
446
|
+
if (cached !== undefined) return cached;
|
|
447
|
+
|
|
448
|
+
if (__inflight.has(key)) {
|
|
449
|
+
try { return await __inflight.get(key); } catch (_) { /* 上次失败时继续 */ }
|
|
450
|
+
}
|
|
451
|
+
const p = (async () => {
|
|
452
|
+
const fresh = await fetcher();
|
|
453
|
+
try { await cache.set(key, fresh, ttl); } catch (_) { /* 忽略缓存写失败 */ }
|
|
454
|
+
return fresh;
|
|
455
|
+
})();
|
|
456
|
+
__inflight.set(key, p);
|
|
457
|
+
try {
|
|
458
|
+
return await p;
|
|
459
|
+
} finally {
|
|
460
|
+
__inflight.delete(key);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* 生成绑定上下文的读缓存助手
|
|
466
|
+
* @param {Object} cache - 缓存实例
|
|
467
|
+
* @param {{iid?:string, type:string, db:string, collection:string}} ctx - 键上下文
|
|
468
|
+
* @returns {(op:string, base:Object, fetcher:Function)=>Promise<any>}
|
|
469
|
+
*/
|
|
470
|
+
static createCachedReader(cache, ctx) {
|
|
471
|
+
return (op, base = {}, fetcher) => {
|
|
472
|
+
// 检查是否在事务中
|
|
473
|
+
const inTransaction = base.session && base.session.__monSQLizeTransaction;
|
|
474
|
+
|
|
475
|
+
// 事务内默认不缓存(除非显式指定 cache)
|
|
476
|
+
let ttl = 0;
|
|
477
|
+
if (inTransaction) {
|
|
478
|
+
// 事务中:只有显式设置 cache 才启用缓存
|
|
479
|
+
ttl = (base.cache !== undefined && base.cache !== null) ? Number(base.cache) : 0;
|
|
480
|
+
} else {
|
|
481
|
+
// 非事务:正常处理 cache 参数
|
|
482
|
+
ttl = base.cache ? Number(base.cache) : 0;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 使用浅拷贝构建用于键的对象,避免修改调用方入参
|
|
486
|
+
const { cache: _cacheTTL, maxTimeMS: _maxTimeMS, session: _session, ...keyBase } = base || {};
|
|
487
|
+
const key = this.buildCacheKey({ ...ctx, op, base: keyBase });
|
|
488
|
+
return this.readThrough(cache, ttl, key, fetcher);
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用游标工具(跨数据库)
|
|
3
|
+
* - Base64URL(JSON) 进行编码/解码,最小结构:{ v, s, a, d? }
|
|
4
|
+
* - 仅负责“字符串化与结构校验”;锚点提取与比较逻辑由适配器实现。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** @param {string} s @returns {string} Base64URL 字符串 */
|
|
8
|
+
function b64urlEncodeStr(s) {
|
|
9
|
+
return Buffer.from(s).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** @param {string} s @returns {string} 原始字符串 */
|
|
13
|
+
function b64urlDecodeStr(s) {
|
|
14
|
+
const pad = (x) => x + '==='.slice((x.length + 3) % 4);
|
|
15
|
+
const b64 = pad(String(s || '')).replace(/-/g, '+').replace(/_/g, '/');
|
|
16
|
+
return Buffer.from(b64, 'base64').toString();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 编码游标
|
|
21
|
+
* @param {{v?:number,s:object,a:object,d?:'after'|'before'}} payload
|
|
22
|
+
* @returns {string} Base64URL 游标
|
|
23
|
+
*/
|
|
24
|
+
function encodeCursor({ v = 1, s, a, d }) {
|
|
25
|
+
// 仅做最小校验
|
|
26
|
+
if (!s || !a) throw new Error('encodeCursor requires sort (s) and anchor (a)');
|
|
27
|
+
const payload = JSON.stringify({ v, s, a, d });
|
|
28
|
+
return b64urlEncodeStr(payload);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function makeInvalidCursorError(cause) {
|
|
32
|
+
const err = new Error('游标无效');
|
|
33
|
+
err.code = 'INVALID_CURSOR';
|
|
34
|
+
if (cause) err.cause = cause;
|
|
35
|
+
return err;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 解码游标(含最小结构校验)
|
|
41
|
+
* @param {string} str
|
|
42
|
+
* @throws INVALID_CURSOR
|
|
43
|
+
* @returns {{v:number,s:object,a:object,d?:string}}
|
|
44
|
+
*/
|
|
45
|
+
function decodeCursor(str) {
|
|
46
|
+
try {
|
|
47
|
+
const obj = JSON.parse(b64urlDecodeStr(str));
|
|
48
|
+
if (!obj || obj.v !== 1 || !obj.s || !obj.a) throw new Error('bad-structure');
|
|
49
|
+
return obj;
|
|
50
|
+
} catch (e) {
|
|
51
|
+
throw makeInvalidCursorError(e);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
encodeCursor,
|
|
57
|
+
decodeCursor,
|
|
58
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文档 URL 配置
|
|
3
|
+
* 统一管理所有文档链接
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// GitHub 仓库基础 URL
|
|
7
|
+
const REPO_BASE_URL = 'https://github.com/vextjs/monSQLize';
|
|
8
|
+
|
|
9
|
+
// 文档基础 URL
|
|
10
|
+
const DOCS_BASE_URL = `${REPO_BASE_URL}/blob/main/docs`;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 文档 URL 映射
|
|
14
|
+
*/
|
|
15
|
+
const DOCS_URLS = {
|
|
16
|
+
// 链式调用 API 文档
|
|
17
|
+
chainingAPI: `${DOCS_BASE_URL}/chaining-api.md`,
|
|
18
|
+
chainingMethods: `${DOCS_BASE_URL}/chaining-methods.md`,
|
|
19
|
+
|
|
20
|
+
// 具体方法文档锚点
|
|
21
|
+
'chaining.limit': `${DOCS_BASE_URL}/chaining-api.md#limit`,
|
|
22
|
+
'chaining.skip': `${DOCS_BASE_URL}/chaining-api.md#skip`,
|
|
23
|
+
'chaining.sort': `${DOCS_BASE_URL}/chaining-api.md#sort`,
|
|
24
|
+
'chaining.project': `${DOCS_BASE_URL}/chaining-api.md#project`,
|
|
25
|
+
'chaining.hint': `${DOCS_BASE_URL}/chaining-api.md#hint`,
|
|
26
|
+
'chaining.collation': `${DOCS_BASE_URL}/chaining-api.md#collation`,
|
|
27
|
+
'chaining.comment': `${DOCS_BASE_URL}/chaining-api.md#comment`,
|
|
28
|
+
'chaining.maxTimeMS': `${DOCS_BASE_URL}/chaining-api.md#maxtimems`,
|
|
29
|
+
'chaining.batchSize': `${DOCS_BASE_URL}/chaining-api.md#batchsize`,
|
|
30
|
+
'chaining.explain': `${DOCS_BASE_URL}/chaining-api.md#explain`,
|
|
31
|
+
'chaining.stream': `${DOCS_BASE_URL}/chaining-api.md#stream`,
|
|
32
|
+
'chaining.toArray': `${DOCS_BASE_URL}/chaining-api.md#toarray`,
|
|
33
|
+
'chaining.allowDiskUse': `${DOCS_BASE_URL}/chaining-api.md#allowdiskuse`,
|
|
34
|
+
|
|
35
|
+
// find 方法文档
|
|
36
|
+
find: `${DOCS_BASE_URL}/find.md`,
|
|
37
|
+
|
|
38
|
+
// aggregate 方法文档
|
|
39
|
+
aggregate: `${REPO_BASE_URL}/blob/main/README.md#aggregate`,
|
|
40
|
+
|
|
41
|
+
// 错误处理
|
|
42
|
+
errors: `${REPO_BASE_URL}/blob/main/README.md#error-handling`,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 获取文档 URL
|
|
47
|
+
* @param {string} key - 文档键
|
|
48
|
+
* @returns {string} 文档 URL
|
|
49
|
+
*/
|
|
50
|
+
function getDocUrl(key) {
|
|
51
|
+
return DOCS_URLS[key] || DOCS_URLS.chainingAPI;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 生成带文档链接的错误消息
|
|
56
|
+
* @param {string} message - 错误消息
|
|
57
|
+
* @param {string} docKey - 文档键
|
|
58
|
+
* @returns {string} 完整的错误消息
|
|
59
|
+
*/
|
|
60
|
+
function createErrorMessage(message, docKey) {
|
|
61
|
+
const docUrl = getDocUrl(docKey);
|
|
62
|
+
return `${message}\nSee: ${docUrl}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
REPO_BASE_URL,
|
|
67
|
+
DOCS_BASE_URL,
|
|
68
|
+
DOCS_URLS,
|
|
69
|
+
getDocUrl,
|
|
70
|
+
createErrorMessage
|
|
71
|
+
};
|
|
72
|
+
|