monsqlize 1.3.0 → 2.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 +56 -60
- package/LICENSE +201 -21
- package/README.md +537 -1828
- package/changelogs/README.md +160 -0
- package/changelogs/v2.0.0.md +222 -0
- package/dist/cjs/index.cjs +10600 -0
- package/dist/cjs/mongodb/common/transaction-aware.cjs +10 -0
- package/dist/cjs/transaction/CacheLockManager.cjs +100 -0
- package/dist/cjs/transaction/Transaction.cjs +158 -0
- package/dist/cjs/transaction/TransactionManager.cjs +298 -0
- package/dist/esm/index.mjs +10650 -0
- package/dist/types/base.d.ts +81 -0
- package/dist/types/collection.d.ts +1031 -0
- package/dist/types/expression.d.ts +115 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/lock.d.ts +74 -0
- package/dist/types/model.d.ts +526 -0
- package/dist/types/mongodb.d.ts +49 -0
- package/dist/types/monsqlize.d.ts +491 -0
- package/dist/types/pool.d.ts +84 -0
- package/dist/types/runtime.d.ts +362 -0
- package/dist/types/saga.d.ts +143 -0
- package/dist/types/slow-query-log.d.ts +126 -0
- package/dist/types/sync.d.ts +103 -0
- package/dist/types/transaction.d.ts +132 -0
- package/package.json +67 -69
- package/index.d.ts +0 -206
- package/index.mjs +0 -52
- package/lib/cache-invalidation.js +0 -279
- package/lib/cache.js +0 -530
- package/lib/common/cursor.js +0 -59
- package/lib/common/docs-urls.js +0 -73
- package/lib/common/index-options.js +0 -223
- package/lib/common/log.js +0 -61
- package/lib/common/namespace.js +0 -22
- package/lib/common/normalize.js +0 -34
- package/lib/common/page-result.js +0 -43
- package/lib/common/runner.js +0 -57
- package/lib/common/server-features.js +0 -232
- package/lib/common/shape-builders.js +0 -27
- package/lib/common/validation.js +0 -113
- package/lib/connect.js +0 -99
- package/lib/constants.js +0 -55
- package/lib/count-queue.js +0 -188
- package/lib/distributed-cache-invalidator.js +0 -260
- package/lib/errors.js +0 -168
- package/lib/expression/cache/ExpressionCache.js +0 -114
- package/lib/expression/compiler/ExpressionCompiler.js +0 -1090
- package/lib/expression/compiler/ExpressionCompilerExtensions.js +0 -531
- package/lib/expression/detector.js +0 -84
- package/lib/expression/factory.js +0 -29
- package/lib/expression/index.js +0 -19
- package/lib/function-cache.js +0 -533
- package/lib/index.js +0 -1251
- package/lib/infrastructure/ConnectionPoolManager.js +0 -464
- package/lib/infrastructure/HealthChecker.js +0 -281
- package/lib/infrastructure/PoolConfig.js +0 -199
- package/lib/infrastructure/PoolSelector.js +0 -225
- package/lib/infrastructure/PoolStats.js +0 -244
- package/lib/infrastructure/ssh-tunnel-ssh2.js +0 -212
- package/lib/infrastructure/ssh-tunnel.js +0 -41
- package/lib/infrastructure/uri-parser.js +0 -36
- package/lib/lock/Lock.js +0 -67
- package/lib/lock/errors.js +0 -28
- package/lib/lock/index.js +0 -13
- package/lib/logger.js +0 -225
- package/lib/model/examples/test.js +0 -311
- package/lib/model/features/defaults.js +0 -161
- package/lib/model/features/populate.js +0 -568
- package/lib/model/features/relations.js +0 -120
- package/lib/model/features/soft-delete.js +0 -349
- package/lib/model/features/version.js +0 -157
- package/lib/model/features/virtuals.js +0 -219
- package/lib/model/index.js +0 -1265
- package/lib/mongodb/common/accessor-helpers.js +0 -59
- package/lib/mongodb/common/agg-pipeline.js +0 -36
- package/lib/mongodb/common/aggregation-validator.js +0 -127
- package/lib/mongodb/common/iid.js +0 -28
- package/lib/mongodb/common/lexicographic-expr.js +0 -53
- package/lib/mongodb/common/shape.js +0 -32
- package/lib/mongodb/common/sort.js +0 -39
- package/lib/mongodb/common/transaction-aware.js +0 -25
- package/lib/mongodb/connect.js +0 -234
- package/lib/mongodb/index.js +0 -639
- package/lib/mongodb/management/admin-ops.js +0 -200
- package/lib/mongodb/management/bookmark-ops.js +0 -167
- package/lib/mongodb/management/cache-ops.js +0 -50
- package/lib/mongodb/management/collection-ops.js +0 -387
- package/lib/mongodb/management/database-ops.js +0 -202
- package/lib/mongodb/management/index-ops.js +0 -475
- package/lib/mongodb/management/index.js +0 -17
- package/lib/mongodb/management/namespace.js +0 -31
- package/lib/mongodb/management/validation-ops.js +0 -268
- package/lib/mongodb/queries/aggregate.js +0 -172
- package/lib/mongodb/queries/chain.js +0 -631
- package/lib/mongodb/queries/count.js +0 -99
- package/lib/mongodb/queries/distinct.js +0 -78
- package/lib/mongodb/queries/find-and-count.js +0 -193
- package/lib/mongodb/queries/find-by-ids.js +0 -236
- package/lib/mongodb/queries/find-one-by-id.js +0 -171
- package/lib/mongodb/queries/find-one.js +0 -71
- package/lib/mongodb/queries/find-page.js +0 -618
- package/lib/mongodb/queries/find.js +0 -171
- package/lib/mongodb/queries/index.js +0 -51
- package/lib/mongodb/queries/watch.js +0 -538
- package/lib/mongodb/writes/common/batch-retry.js +0 -65
- package/lib/mongodb/writes/delete-batch.js +0 -323
- package/lib/mongodb/writes/delete-many.js +0 -181
- package/lib/mongodb/writes/delete-one.js +0 -173
- package/lib/mongodb/writes/find-one-and-delete.js +0 -203
- package/lib/mongodb/writes/find-one-and-replace.js +0 -239
- package/lib/mongodb/writes/find-one-and-update.js +0 -240
- package/lib/mongodb/writes/increment-one.js +0 -259
- package/lib/mongodb/writes/index.js +0 -46
- package/lib/mongodb/writes/insert-batch.js +0 -508
- package/lib/mongodb/writes/insert-many.js +0 -223
- package/lib/mongodb/writes/insert-one.js +0 -169
- package/lib/mongodb/writes/replace-one.js +0 -226
- package/lib/mongodb/writes/result-handler.js +0 -237
- package/lib/mongodb/writes/update-batch.js +0 -416
- package/lib/mongodb/writes/update-many.js +0 -275
- package/lib/mongodb/writes/update-one.js +0 -273
- package/lib/mongodb/writes/upsert-one.js +0 -203
- package/lib/multi-level-cache.js +0 -244
- package/lib/operators.js +0 -330
- package/lib/redis-cache-adapter.js +0 -267
- package/lib/saga/SagaContext.js +0 -67
- package/lib/saga/SagaDefinition.js +0 -32
- package/lib/saga/SagaExecutor.js +0 -201
- package/lib/saga/SagaOrchestrator.js +0 -186
- package/lib/saga/index.js +0 -11
- package/lib/slow-query-log/base-storage.js +0 -70
- package/lib/slow-query-log/batch-queue.js +0 -97
- package/lib/slow-query-log/config-manager.js +0 -196
- package/lib/slow-query-log/index.js +0 -238
- package/lib/slow-query-log/mongodb-storage.js +0 -324
- package/lib/slow-query-log/query-hash.js +0 -39
- package/lib/sync/ChangeStreamSyncManager.js +0 -405
- package/lib/sync/ResumeTokenStore.js +0 -192
- package/lib/sync/SyncConfig.js +0 -127
- package/lib/sync/SyncTarget.js +0 -240
- package/lib/sync/index.js +0 -20
- package/lib/transaction/CacheLockManager.js +0 -162
- package/lib/transaction/DistributedCacheLockManager.js +0 -475
- package/lib/transaction/Transaction.js +0 -315
- package/lib/transaction/TransactionManager.js +0 -267
- package/lib/transaction/index.js +0 -11
- package/lib/utils/objectid-converter.js +0 -632
- package/types/README.md +0 -122
- package/types/base.ts +0 -94
- package/types/batch.ts +0 -187
- package/types/cache.ts +0 -71
- package/types/chain.ts +0 -254
- package/types/collection.ts +0 -357
- package/types/expression.ts +0 -109
- package/types/function-cache.d.ts +0 -135
- package/types/lock.ts +0 -95
- package/types/model/definition.ts +0 -152
- package/types/model/index.ts +0 -10
- package/types/model/instance.ts +0 -121
- package/types/model/relations.ts +0 -121
- package/types/model/virtuals.ts +0 -32
- package/types/monsqlize.ts +0 -245
- package/types/options.ts +0 -192
- package/types/pagination.ts +0 -154
- package/types/pool.ts +0 -125
- package/types/query.ts +0 -71
- package/types/saga.ts +0 -125
- package/types/stream.ts +0 -64
- package/types/sync.ts +0 -79
- package/types/transaction.ts +0 -79
- package/types/write.ts +0 -77
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* upsertOne 写操作模块
|
|
3
|
-
* @description 便利方法:存在则更新,不存在则插入
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
-
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
8
|
-
const { convertObjectIdStrings } = require('../../utils/objectid-converter');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 创建 upsertOne 操作
|
|
12
|
-
* @param {Object} context - 上下文对象
|
|
13
|
-
* @returns {Function} upsertOne 方法
|
|
14
|
-
*/
|
|
15
|
-
function createUpsertOneOps(context) {
|
|
16
|
-
const {
|
|
17
|
-
collection,
|
|
18
|
-
defaults,
|
|
19
|
-
instanceId,
|
|
20
|
-
effectiveDbName,
|
|
21
|
-
logger,
|
|
22
|
-
emit,
|
|
23
|
-
mongoSlowLogShaper,
|
|
24
|
-
cache,
|
|
25
|
-
type
|
|
26
|
-
} = context;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* upsert 单个文档(存在则更新,不存在则插入)
|
|
30
|
-
* @param {Object} filter - 查询条件
|
|
31
|
-
* @param {Object} update - 更新内容(直接设置字段,自动包装为 $set)
|
|
32
|
-
* @param {Object} [options={}] - 操作选项
|
|
33
|
-
* @param {number} [options.maxTimeMS] - 操作超时(毫秒)
|
|
34
|
-
* @param {string} [options.comment] - 查询注释
|
|
35
|
-
* @returns {Promise<Object>} 操作结果
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* // 基础用法
|
|
39
|
-
* const result = await collection('users').upsertOne(
|
|
40
|
-
* { userId: 'user123' },
|
|
41
|
-
* { name: 'Alice', email: 'alice@example.com' }
|
|
42
|
-
* );
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* // 带选项
|
|
46
|
-
* const result = await collection('config').upsertOne(
|
|
47
|
-
* { key: 'theme' },
|
|
48
|
-
* { value: 'dark', updatedAt: new Date() },
|
|
49
|
-
* { maxTimeMS: 5000, comment: 'sync-config' }
|
|
50
|
-
* );
|
|
51
|
-
*/
|
|
52
|
-
const upsertOne = async function upsertOne(filter, update, options = {}) {
|
|
53
|
-
const startTime = Date.now();
|
|
54
|
-
|
|
55
|
-
// 1. 参数验证
|
|
56
|
-
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
|
|
57
|
-
throw createError(
|
|
58
|
-
ErrorCodes.INVALID_ARGUMENT,
|
|
59
|
-
'filter 必须是非空对象',
|
|
60
|
-
[{ field: 'filter', type: 'type', message: 'filter 必须是对象', received: typeof filter }]
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!update || typeof update !== 'object' || Array.isArray(update)) {
|
|
65
|
-
throw createError(
|
|
66
|
-
ErrorCodes.INVALID_ARGUMENT,
|
|
67
|
-
'update 必须是非空对象',
|
|
68
|
-
[{ field: 'update', type: 'type', message: 'update 必须是对象', received: typeof update }]
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
73
|
-
const convertedFilter = convertObjectIdStrings(filter, 'filter', 0, new WeakSet(), {
|
|
74
|
-
logger: context.logger,
|
|
75
|
-
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
76
|
-
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
77
|
-
maxDepth: context.autoConvertConfig?.maxDepth
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const convertedUpdate = convertObjectIdStrings(update, 'document', 0, new WeakSet(), {
|
|
81
|
-
logger: context.logger,
|
|
82
|
-
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
83
|
-
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
84
|
-
maxDepth: context.autoConvertConfig?.maxDepth
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// 2. 检查 update 是否包含更新操作符
|
|
88
|
-
const hasOperator = Object.keys(convertedUpdate).some(key => key.startsWith('$'));
|
|
89
|
-
|
|
90
|
-
// 如果没有操作符,自动包装为 $set
|
|
91
|
-
const updateDoc = hasOperator ? convertedUpdate : { $set: convertedUpdate };
|
|
92
|
-
|
|
93
|
-
// 3. 构建选项
|
|
94
|
-
const maxTimeMS = options.maxTimeMS !== undefined ? options.maxTimeMS : defaults.maxTimeMS;
|
|
95
|
-
const comment = options.comment;
|
|
96
|
-
|
|
97
|
-
const driverOpts = { upsert: true, maxTimeMS };
|
|
98
|
-
if (comment) driverOpts.comment = comment;
|
|
99
|
-
|
|
100
|
-
// 4. 执行 updateOne 操作
|
|
101
|
-
let result;
|
|
102
|
-
try {
|
|
103
|
-
result = await collection.updateOne(convertedFilter, updateDoc, driverOpts);
|
|
104
|
-
} catch (error) {
|
|
105
|
-
// 统一错误处理
|
|
106
|
-
if (error.code === 11000) {
|
|
107
|
-
throw createError(
|
|
108
|
-
ErrorCodes.DUPLICATE_KEY,
|
|
109
|
-
'批量插入失败:违反唯一性约束',
|
|
110
|
-
[{ field: '_id', type: 'unique', message: error.message }],
|
|
111
|
-
error
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
throw error;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 5. 自动失效缓存
|
|
118
|
-
const upsertedCount = result.matchedCount === 0 ? 1 : 0;
|
|
119
|
-
|
|
120
|
-
if (cache && (result.modifiedCount > 0 || upsertedCount > 0)) {
|
|
121
|
-
try {
|
|
122
|
-
// 🆕 v1.1.6: 解析精准失效配置
|
|
123
|
-
const instanceAuto = context.cacheAutoInvalidate || false;
|
|
124
|
-
const queryAuto = options.autoInvalidate;
|
|
125
|
-
const shouldAuto = queryAuto !== undefined ? queryAuto : instanceAuto;
|
|
126
|
-
|
|
127
|
-
if (shouldAuto) {
|
|
128
|
-
// 精准失效:upsertOne 使用 filter 来匹配缓存
|
|
129
|
-
const CacheInvalidationEngine = require('../../cache-invalidation');
|
|
130
|
-
|
|
131
|
-
const deleted = await CacheInvalidationEngine.invalidatePrecise(cache, {
|
|
132
|
-
instanceId,
|
|
133
|
-
type,
|
|
134
|
-
db: effectiveDbName,
|
|
135
|
-
collection: collection.collectionName,
|
|
136
|
-
document: convertedFilter, // 使用 filter 来匹配
|
|
137
|
-
operation: 'upsertOne'
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
if (deleted > 0) {
|
|
141
|
-
logger?.debug?.(`[upsertOne] 精准失效缓存: ${collection.collectionName}, 删除 ${deleted} 个缓存键`);
|
|
142
|
-
}
|
|
143
|
-
} else {
|
|
144
|
-
logger?.debug?.(`[upsertOne] 跳过自动失效(autoInvalidate=false)`);
|
|
145
|
-
}
|
|
146
|
-
} catch (cacheError) {
|
|
147
|
-
logger?.warn?.('[upsertOne] 缓存失效失败', { error: cacheError.message });
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 6. 慢查询日志
|
|
152
|
-
const duration = Date.now() - startTime;
|
|
153
|
-
const slowQueryMs = defaults?.slowQueryMs || 1000;
|
|
154
|
-
|
|
155
|
-
if (duration >= slowQueryMs) {
|
|
156
|
-
try {
|
|
157
|
-
const meta = {
|
|
158
|
-
operation: 'upsertOne',
|
|
159
|
-
durationMs: duration,
|
|
160
|
-
iid: instanceId,
|
|
161
|
-
type,
|
|
162
|
-
db: effectiveDbName,
|
|
163
|
-
collection: collection.collectionName,
|
|
164
|
-
matchedCount: result.matchedCount,
|
|
165
|
-
modifiedCount: result.modifiedCount,
|
|
166
|
-
upsertedId: result.upsertedId,
|
|
167
|
-
filter: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(filter) : filter,
|
|
168
|
-
update: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(updateDoc) : updateDoc,
|
|
169
|
-
comment
|
|
170
|
-
};
|
|
171
|
-
logger?.warn?.('🐌 Slow query: upsertOne', meta);
|
|
172
|
-
emit?.('slow-query', meta);
|
|
173
|
-
} catch (_) {
|
|
174
|
-
// 忽略日志错误
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 7. 日志记录
|
|
179
|
-
logger?.debug?.('[upsertOne] 操作完成', {
|
|
180
|
-
ns: `${effectiveDbName}.${collection.collectionName}`,
|
|
181
|
-
duration,
|
|
182
|
-
matchedCount: result.matchedCount,
|
|
183
|
-
modifiedCount: result.modifiedCount,
|
|
184
|
-
upsertedId: result.upsertedId === null ? undefined : result.upsertedId,
|
|
185
|
-
upsertedCount
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// 8. 返回结果
|
|
189
|
-
return {
|
|
190
|
-
acknowledged: result.acknowledged,
|
|
191
|
-
matchedCount: result.matchedCount,
|
|
192
|
-
modifiedCount: result.modifiedCount,
|
|
193
|
-
upsertedId: result.upsertedId === null ? undefined : result.upsertedId,
|
|
194
|
-
upsertedCount
|
|
195
|
-
};
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
return { upsertOne };
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
module.exports = { createUpsertOneOps };
|
|
202
|
-
|
|
203
|
-
|
package/lib/multi-level-cache.js
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MultiLevelCache: 组合本地与远端缓存,实现 CacheLike 接口
|
|
3
|
-
* 设计要点:
|
|
4
|
-
* - 读:本地优先;本地未命中查远端,命中则异步回填本地;远端失败需优雅降级
|
|
5
|
-
* - 写:默认本地+远端双写;可配置本地优先、远端异步
|
|
6
|
-
* - 删:本地删除;远端删除尽力而为;delPattern 默认仅本地执行并返回删除数(重型场景交由远端广播/索引)
|
|
7
|
-
* - keys/clear:仅作用于本地层(避免误伤远端)
|
|
8
|
-
* - TTL 单位毫秒,与上层保持一致
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
class MultiLevelCache {
|
|
12
|
-
/**
|
|
13
|
-
* @param {Object} options
|
|
14
|
-
* @param {Object} options.local - 本地 CacheLike
|
|
15
|
-
* @param {Object} options.remote - 远端 CacheLike(可选)
|
|
16
|
-
* @param {Object} [options.policy]
|
|
17
|
-
* @param {'both'|'local-first-async-remote'} [options.policy.writePolicy='both']
|
|
18
|
-
* @param {boolean} [options.policy.backfillLocalOnRemoteHit=true]
|
|
19
|
-
* @param {number} [options.policy.backfillLocalTTL=0] - 回填 L1 时使用的兜底 TTL(毫秒);
|
|
20
|
-
* 当 remote 支持 getWithTTL 时优先使用 L2 剩余 TTL,否则降级到此值;
|
|
21
|
-
* 0 = 不设 TTL(永不过期,向后兼容)。建议生产环境配置合理值(如 60000)
|
|
22
|
-
* @param {number} [options.remoteTimeoutMs=50] - 远端单次操作超时
|
|
23
|
-
* @param {(msg:object)=>void} [options.publish] - 可选:失效广播发布器
|
|
24
|
-
* @since 1.1.9 backfillLocalTTL 支持
|
|
25
|
-
*/
|
|
26
|
-
constructor(options = {}) {
|
|
27
|
-
const { local, remote, policy = {}, remoteTimeoutMs = 50, publish } = options;
|
|
28
|
-
if (!local || typeof local.get !== 'function') {
|
|
29
|
-
throw new Error('MultiLevelCache requires a valid local cache');
|
|
30
|
-
}
|
|
31
|
-
this.local = local;
|
|
32
|
-
this.remote = remote;
|
|
33
|
-
this.policy = {
|
|
34
|
-
writePolicy: policy.writePolicy || 'both',
|
|
35
|
-
backfillLocalOnRemoteHit: policy.backfillLocalOnRemoteHit !== false,
|
|
36
|
-
// 🔧 v1.1.9 修复:新增回填 TTL 兜底配置,0 = 不设 TTL(向后兼容)
|
|
37
|
-
backfillLocalTTL: typeof policy.backfillLocalTTL === 'number' ? policy.backfillLocalTTL : 0,
|
|
38
|
-
};
|
|
39
|
-
this.remoteTimeoutMs = Number(remoteTimeoutMs) || 50;
|
|
40
|
-
this.publish = typeof publish === 'function' ? publish : null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 动态设置 publish 回调(用于分布式缓存失效)
|
|
45
|
-
* @param {Function} publishFn - 发布函数,接收 { type, pattern, ts } 对象
|
|
46
|
-
*/
|
|
47
|
-
setPublish(publishFn) {
|
|
48
|
-
if (typeof publishFn === 'function') {
|
|
49
|
-
this.publish = publishFn;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* 设置锁管理器(透传到本地缓存)
|
|
55
|
-
* @param {Object} lockManager
|
|
56
|
-
*/
|
|
57
|
-
setLockManager(lockManager) {
|
|
58
|
-
if (this.local && typeof this.local.setLockManager === 'function') {
|
|
59
|
-
this.local.setLockManager(lockManager);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 工具:为远端操作加超时与降级
|
|
64
|
-
async _withTimeout(promise) {
|
|
65
|
-
if (!this.remote) return Promise.reject(new Error('NO_REMOTE'));
|
|
66
|
-
let timer;
|
|
67
|
-
const t = new Promise((_, reject) => {
|
|
68
|
-
timer = setTimeout(() => reject(new Error('REMOTE_TIMEOUT')), this.remoteTimeoutMs);
|
|
69
|
-
if (timer && typeof timer.unref === 'function') try { timer.unref(); } catch(_) {}
|
|
70
|
-
});
|
|
71
|
-
try {
|
|
72
|
-
const val = await Promise.race([promise, t]);
|
|
73
|
-
return val;
|
|
74
|
-
} finally {
|
|
75
|
-
clearTimeout(timer);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async get(key) {
|
|
80
|
-
const v = await this.local.get(key);
|
|
81
|
-
if (v !== undefined) return v;
|
|
82
|
-
try {
|
|
83
|
-
let r;
|
|
84
|
-
// 🔧 v1.1.9 修复:backfillTTL 优先取 L2 剩余 TTL(方案A),降级到 backfillLocalTTL(方案B)
|
|
85
|
-
let backfillTTL = this.policy.backfillLocalTTL;
|
|
86
|
-
|
|
87
|
-
if (this.remote && typeof this.remote.getWithTTL === 'function') {
|
|
88
|
-
// 方案A:remote 支持 getWithTTL,单次 RTT 同时获取值与剩余 TTL
|
|
89
|
-
const meta = await this._withTimeout(this.remote.getWithTTL(key));
|
|
90
|
-
if (meta !== undefined) {
|
|
91
|
-
r = meta.value;
|
|
92
|
-
if (meta.remainingTTL > 0) backfillTTL = meta.remainingTTL;
|
|
93
|
-
}
|
|
94
|
-
} else {
|
|
95
|
-
// 方案B:remote 不支持 getWithTTL,使用普通 get + backfillLocalTTL 兜底
|
|
96
|
-
r = await this._withTimeout(this.remote.get(key));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (r !== undefined && this.policy.backfillLocalOnRemoteHit) {
|
|
100
|
-
// 🔧 v1.1.9 补充:backfillTTL=0 时跳过 null 回填,防止无配置用户触发永久驻留 Bug
|
|
101
|
-
// - backfillTTL>0:回填所有值(含 null),TTL 保护下能正常过期
|
|
102
|
-
// - backfillTTL=0 + null:跳过——无 TTL 保护的 null 永久驻留 = Bug 本身
|
|
103
|
-
// - backfillTTL=0 + 非 null:回填(保留原有永久缓存行为,向后兼容)
|
|
104
|
-
if (backfillTTL > 0 || r !== null) {
|
|
105
|
-
try { await this.local.set(key, r, backfillTTL); } catch(_) {}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return r;
|
|
109
|
-
} catch(_) {
|
|
110
|
-
return undefined; // 远端异常降级
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async set(key, val, ttl = 0) {
|
|
115
|
-
await this.local.set(key, val, ttl);
|
|
116
|
-
if (!this.remote) return;
|
|
117
|
-
if (this.policy.writePolicy === 'both') {
|
|
118
|
-
try { await this._withTimeout(this.remote.set(key, val, ttl)); } catch(_) {}
|
|
119
|
-
} else {
|
|
120
|
-
// local-first-async-remote
|
|
121
|
-
this._withTimeout(this.remote.set(key, val, ttl)).catch(() => {});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async del(key) {
|
|
126
|
-
let a = false, b = false;
|
|
127
|
-
try { a = await this.local.del(key); } catch(_) {}
|
|
128
|
-
if (this.remote) {
|
|
129
|
-
try { b = await this._withTimeout(this.remote.del(key)).then(Boolean).catch(() => false); } catch(_) {}
|
|
130
|
-
}
|
|
131
|
-
return !!(a || b);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
async exists(key) {
|
|
135
|
-
const a = await this.local.exists(key);
|
|
136
|
-
if (a) return true;
|
|
137
|
-
if (!this.remote) return false;
|
|
138
|
-
try { return !!(await this._withTimeout(this.remote.exists(key))); } catch(_) { return false; }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
async getMany(keys) {
|
|
142
|
-
const out = {};
|
|
143
|
-
// 先批量从本地取
|
|
144
|
-
const localRes = await this.local.getMany(keys);
|
|
145
|
-
const misses = [];
|
|
146
|
-
for (const k of keys) {
|
|
147
|
-
const v = localRes[k];
|
|
148
|
-
if (v !== undefined) out[k] = v; else misses.push(k);
|
|
149
|
-
}
|
|
150
|
-
if (!misses.length || !this.remote) return out;
|
|
151
|
-
try {
|
|
152
|
-
const remoteRes = await this._withTimeout(this.remote.getMany(misses));
|
|
153
|
-
if (remoteRes && typeof remoteRes === 'object') {
|
|
154
|
-
// 🔧 v1.1.9 修复:传入 backfillLocalTTL,防止无 TTL 永久回填(含 null 值)
|
|
155
|
-
// 🔧 v1.1.9 补充:backfillLocalTTL=0 时过滤 null,防止无配置用户触发永久驻留 Bug
|
|
156
|
-
if (this.policy.backfillLocalOnRemoteHit) {
|
|
157
|
-
const backfillData = this.policy.backfillLocalTTL > 0
|
|
158
|
-
? remoteRes
|
|
159
|
-
: Object.fromEntries(Object.entries(remoteRes).filter(([, v]) => v !== null));
|
|
160
|
-
if (Object.keys(backfillData).length > 0) {
|
|
161
|
-
this.local.setMany(backfillData, this.policy.backfillLocalTTL).catch(() => {});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
for (const k of misses) { if (remoteRes[k] !== undefined) out[k] = remoteRes[k]; }
|
|
165
|
-
}
|
|
166
|
-
} catch(_) { /* 降级 */ }
|
|
167
|
-
return out;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async setMany(obj, ttl = 0) {
|
|
171
|
-
await this.local.setMany(obj, ttl);
|
|
172
|
-
if (!this.remote) return true;
|
|
173
|
-
if (this.policy.writePolicy === 'both') {
|
|
174
|
-
try { await this._withTimeout(this.remote.setMany(obj, ttl)); } catch(_) {}
|
|
175
|
-
} else {
|
|
176
|
-
this._withTimeout(this.remote.setMany(obj, ttl)).catch(() => {});
|
|
177
|
-
}
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async delMany(keys) {
|
|
182
|
-
let n = 0;
|
|
183
|
-
try { n += await this.local.delMany(keys); } catch(_) {}
|
|
184
|
-
if (this.remote) {
|
|
185
|
-
try { await this._withTimeout(this.remote.delMany(keys)); } catch(_) {}
|
|
186
|
-
}
|
|
187
|
-
return n;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async delPattern(pattern) {
|
|
191
|
-
let deleted = 0;
|
|
192
|
-
|
|
193
|
-
// 删除本地缓存
|
|
194
|
-
try {
|
|
195
|
-
deleted = await this.local.delPattern(pattern);
|
|
196
|
-
} catch(err) {
|
|
197
|
-
// 忽略本地删除错误
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 删除远端缓存(如果存在)
|
|
201
|
-
if (this.remote && typeof this.remote.delPattern === 'function') {
|
|
202
|
-
try {
|
|
203
|
-
await this._withTimeout(this.remote.delPattern(pattern));
|
|
204
|
-
} catch(err) {
|
|
205
|
-
// 远端删除失败,降级处理
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// 向集群广播(可选)
|
|
210
|
-
try {
|
|
211
|
-
if (this.publish) {
|
|
212
|
-
this.publish({ type: 'invalidate', pattern, ts: Date.now() });
|
|
213
|
-
}
|
|
214
|
-
} catch(err) {
|
|
215
|
-
// 忽略广播错误
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return deleted;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
clear() {
|
|
222
|
-
// 仅清理本地
|
|
223
|
-
try { this.local.clear(); } catch(_) {}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
keys(pattern = '*') {
|
|
227
|
-
try { return this.local.keys(pattern); } catch(_) { return []; }
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
getStats() {
|
|
231
|
-
const local = this.local.getStats ? this.local.getStats() : null;
|
|
232
|
-
const remote = this.remote && this.remote.getStats ? this.remote.getStats() : null;
|
|
233
|
-
const hits = (local?.hits || 0) + (remote?.hits || 0);
|
|
234
|
-
const misses = (local?.misses || 0) + (remote?.misses || 0);
|
|
235
|
-
return {
|
|
236
|
-
local,
|
|
237
|
-
remote,
|
|
238
|
-
hitRateApprox: hits / (hits + misses) || 0,
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
module.exports = MultiLevelCache;
|
|
244
|
-
|