@yanit/jsondb 0.1.1

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.
Files changed (118) hide show
  1. package/README.md +903 -0
  2. package/dist/bin/cli-export.d.ts +7 -0
  3. package/dist/bin/cli-export.d.ts.map +1 -0
  4. package/dist/bin/cli-export.js +318 -0
  5. package/dist/bin/cli-export.js.map +1 -0
  6. package/dist/bin/cli-import.d.ts +7 -0
  7. package/dist/bin/cli-import.d.ts.map +1 -0
  8. package/dist/bin/cli-import.js +298 -0
  9. package/dist/bin/cli-import.js.map +1 -0
  10. package/dist/bin/server.d.ts +7 -0
  11. package/dist/bin/server.d.ts.map +1 -0
  12. package/dist/bin/server.js +92 -0
  13. package/dist/bin/server.js.map +1 -0
  14. package/dist/examples/sql-example.d.ts +7 -0
  15. package/dist/examples/sql-example.d.ts.map +1 -0
  16. package/dist/examples/sql-example.js +131 -0
  17. package/dist/examples/sql-example.js.map +1 -0
  18. package/dist/src/BulkOp.d.ts +74 -0
  19. package/dist/src/BulkOp.d.ts.map +1 -0
  20. package/dist/src/BulkOp.js +143 -0
  21. package/dist/src/BulkOp.js.map +1 -0
  22. package/dist/src/Collection.d.ts +232 -0
  23. package/dist/src/Collection.d.ts.map +1 -0
  24. package/dist/src/Collection.js +705 -0
  25. package/dist/src/Collection.js.map +1 -0
  26. package/dist/src/Cursor.d.ts +94 -0
  27. package/dist/src/Cursor.d.ts.map +1 -0
  28. package/dist/src/Cursor.js +259 -0
  29. package/dist/src/Cursor.js.map +1 -0
  30. package/dist/src/Database.d.ts +98 -0
  31. package/dist/src/Database.d.ts.map +1 -0
  32. package/dist/src/Database.js +198 -0
  33. package/dist/src/Database.js.map +1 -0
  34. package/dist/src/Operators.d.ts +73 -0
  35. package/dist/src/Operators.d.ts.map +1 -0
  36. package/dist/src/Operators.js +339 -0
  37. package/dist/src/Operators.js.map +1 -0
  38. package/dist/src/QueryCache.d.ts +87 -0
  39. package/dist/src/QueryCache.d.ts.map +1 -0
  40. package/dist/src/QueryCache.js +155 -0
  41. package/dist/src/QueryCache.js.map +1 -0
  42. package/dist/src/SQLExecutor.d.ts +60 -0
  43. package/dist/src/SQLExecutor.d.ts.map +1 -0
  44. package/dist/src/SQLExecutor.js +317 -0
  45. package/dist/src/SQLExecutor.js.map +1 -0
  46. package/dist/src/SQLParser.d.ts +181 -0
  47. package/dist/src/SQLParser.d.ts.map +1 -0
  48. package/dist/src/SQLParser.js +640 -0
  49. package/dist/src/SQLParser.js.map +1 -0
  50. package/dist/src/Schema.d.ts +92 -0
  51. package/dist/src/Schema.d.ts.map +1 -0
  52. package/dist/src/Schema.js +253 -0
  53. package/dist/src/Schema.js.map +1 -0
  54. package/dist/src/Transaction.d.ts +118 -0
  55. package/dist/src/Transaction.d.ts.map +1 -0
  56. package/dist/src/Transaction.js +233 -0
  57. package/dist/src/Transaction.js.map +1 -0
  58. package/dist/src/Utils.d.ts +68 -0
  59. package/dist/src/Utils.d.ts.map +1 -0
  60. package/dist/src/Utils.js +187 -0
  61. package/dist/src/Utils.js.map +1 -0
  62. package/dist/src/errors.d.ts +58 -0
  63. package/dist/src/errors.d.ts.map +1 -0
  64. package/dist/src/errors.js +85 -0
  65. package/dist/src/errors.js.map +1 -0
  66. package/dist/src/index.d.ts +39 -0
  67. package/dist/src/index.d.ts.map +1 -0
  68. package/dist/src/index.js +44 -0
  69. package/dist/src/index.js.map +1 -0
  70. package/dist/test/basic.test.d.ts +5 -0
  71. package/dist/test/basic.test.d.ts.map +1 -0
  72. package/dist/test/basic.test.js +283 -0
  73. package/dist/test/basic.test.js.map +1 -0
  74. package/dist/test/index.test.d.ts +5 -0
  75. package/dist/test/index.test.d.ts.map +1 -0
  76. package/dist/test/index.test.js +126 -0
  77. package/dist/test/index.test.js.map +1 -0
  78. package/dist/test/jsonb.test.d.ts +5 -0
  79. package/dist/test/jsonb.test.d.ts.map +1 -0
  80. package/dist/test/jsonb.test.js +165 -0
  81. package/dist/test/jsonb.test.js.map +1 -0
  82. package/dist/test/optimization.test.d.ts +6 -0
  83. package/dist/test/optimization.test.d.ts.map +1 -0
  84. package/dist/test/optimization.test.js +196 -0
  85. package/dist/test/optimization.test.js.map +1 -0
  86. package/dist/test/schema.test.d.ts +5 -0
  87. package/dist/test/schema.test.d.ts.map +1 -0
  88. package/dist/test/schema.test.js +197 -0
  89. package/dist/test/schema.test.js.map +1 -0
  90. package/dist/test/sql.test.d.ts +7 -0
  91. package/dist/test/sql.test.d.ts.map +1 -0
  92. package/dist/test/sql.test.js +21 -0
  93. package/dist/test/sql.test.js.map +1 -0
  94. package/package.json +73 -0
  95. package/src/BulkOp.js +181 -0
  96. package/src/BulkOp.ts +191 -0
  97. package/src/Collection.js +843 -0
  98. package/src/Collection.ts +896 -0
  99. package/src/Cursor.js +315 -0
  100. package/src/Cursor.ts +319 -0
  101. package/src/Database.js +244 -0
  102. package/src/Database.ts +268 -0
  103. package/src/Operators.js +382 -0
  104. package/src/Operators.ts +375 -0
  105. package/src/QueryCache.js +190 -0
  106. package/src/QueryCache.ts +208 -0
  107. package/src/SQLExecutor.ts +391 -0
  108. package/src/SQLParser.ts +814 -0
  109. package/src/Schema.js +292 -0
  110. package/src/Schema.ts +317 -0
  111. package/src/Transaction.js +291 -0
  112. package/src/Transaction.ts +313 -0
  113. package/src/Utils.js +205 -0
  114. package/src/Utils.ts +205 -0
  115. package/src/errors.js +93 -0
  116. package/src/errors.ts +93 -0
  117. package/src/index.js +90 -0
  118. package/src/index.ts +106 -0
@@ -0,0 +1,375 @@
1
+ /**
2
+ * 查询和操作符实现
3
+ */
4
+
5
+ import { getNestedValue, isType, setNestedValue, deleteNestedValue } from './Utils.js';
6
+
7
+ /**
8
+ * 比较操作符
9
+ */
10
+ export const comparisonOperators = {
11
+ $eq: (value: unknown, queryValue: unknown): boolean => value === queryValue,
12
+
13
+ $ne: (value: unknown, queryValue: unknown): boolean => value !== queryValue,
14
+
15
+ $gt: (value: unknown, queryValue: unknown): boolean => value > queryValue,
16
+
17
+ $gte: (value: unknown, queryValue: unknown): boolean => value >= queryValue,
18
+
19
+ $lt: (value: unknown, queryValue: unknown): boolean => value < queryValue,
20
+
21
+ $lte: (value: unknown, queryValue: unknown): boolean => value <= queryValue,
22
+
23
+ $in: (value: unknown, queryValue: unknown): boolean => {
24
+ if (!Array.isArray(queryValue)) {
25
+ throw new Error('$in 操作符需要数组');
26
+ }
27
+ return queryValue.includes(value);
28
+ },
29
+
30
+ $nin: (value: unknown, queryValue: unknown): boolean => {
31
+ if (!Array.isArray(queryValue)) {
32
+ throw new Error('$nin 操作符需要数组');
33
+ }
34
+ return !queryValue.includes(value);
35
+ }
36
+ };
37
+
38
+ /**
39
+ * 逻辑操作符
40
+ */
41
+ export const logicalOperators = {
42
+ $and: (doc: Record<string, unknown>, conditions: Record<string, unknown>[]): boolean => {
43
+ if (!Array.isArray(conditions)) {
44
+ throw new Error('$and 操作符需要数组');
45
+ }
46
+ return conditions.every(condition => matchQuery(doc, condition));
47
+ },
48
+
49
+ $or: (doc: Record<string, unknown>, conditions: Record<string, unknown>[]): boolean => {
50
+ if (!Array.isArray(conditions)) {
51
+ throw new Error('$or 操作符需要数组');
52
+ }
53
+ return conditions.some(condition => matchQuery(doc, condition));
54
+ },
55
+
56
+ $nor: (doc: Record<string, unknown>, conditions: Record<string, unknown>[]): boolean => {
57
+ if (!Array.isArray(conditions)) {
58
+ throw new Error('$nor 操作符需要数组');
59
+ }
60
+ return !conditions.some(condition => matchQuery(doc, condition));
61
+ },
62
+
63
+ $not: (doc: Record<string, unknown>, condition: Record<string, unknown>, field?: string): boolean => {
64
+ return !matchField(doc, field!, condition);
65
+ }
66
+ };
67
+
68
+ /**
69
+ * 元素操作符
70
+ */
71
+ export const elementOperators = {
72
+ $exists: (value: unknown, queryValue: boolean): boolean => {
73
+ return queryValue ? value !== undefined : value === undefined;
74
+ },
75
+
76
+ $type: (value: unknown, queryValue: string): boolean => {
77
+ return isType(value, queryValue);
78
+ }
79
+ };
80
+
81
+ /**
82
+ * 数组操作符
83
+ */
84
+ export const arrayOperators = {
85
+ $all: (value: unknown, queryValue: unknown[]): boolean => {
86
+ if (!Array.isArray(value)) {
87
+ return false;
88
+ }
89
+ if (!Array.isArray(queryValue)) {
90
+ throw new Error('$all 操作符需要数组');
91
+ }
92
+ return queryValue.every(item => value.includes(item));
93
+ },
94
+
95
+ $elemMatch: (value: unknown, queryValue: Record<string, unknown> | unknown): boolean => {
96
+ if (!Array.isArray(value)) {
97
+ return false;
98
+ }
99
+ // 处理数组元素匹配
100
+ return value.some(item => {
101
+ // 如果 queryValue 是对象,需要匹配所有条件
102
+ if (typeof queryValue === 'object' && queryValue !== null) {
103
+ for (const [key, condition] of Object.entries(queryValue)) {
104
+ const itemValue = (item as Record<string, unknown>)[key] !== undefined
105
+ ? (item as Record<string, unknown>)[key]
106
+ : (typeof key === 'string' && (item as Record<string, unknown>)[key] !== undefined
107
+ ? (item as Record<string, unknown>)[key]
108
+ : undefined);
109
+ if (typeof condition === 'object' && condition !== null) {
110
+ // 处理操作符条件
111
+ for (const [op, opValue] of Object.entries(condition)) {
112
+ if (op === '$gte' && !(itemValue >= opValue)) return false;
113
+ if (op === '$lte' && !(itemValue <= opValue)) return false;
114
+ if (op === '$gt' && !(itemValue > opValue)) return false;
115
+ if (op === '$lt' && !(itemValue < opValue)) return false;
116
+ if (op === '$eq' && !(itemValue === opValue)) return false;
117
+ if (op === '$ne' && !(itemValue !== opValue)) return false;
118
+ }
119
+ } else if (itemValue !== condition) {
120
+ return false;
121
+ }
122
+ }
123
+ return true;
124
+ }
125
+ return item === queryValue;
126
+ });
127
+ },
128
+
129
+ $size: (value: unknown, queryValue: number): boolean => {
130
+ if (!Array.isArray(value)) {
131
+ return false;
132
+ }
133
+ return value.length === queryValue;
134
+ }
135
+ };
136
+
137
+ /**
138
+ * 正则表达式操作符
139
+ */
140
+ export function matchRegex(value: unknown, options: { $regex: string | RegExp; $options?: string }): boolean {
141
+ if (typeof value !== 'string') {
142
+ return false;
143
+ }
144
+
145
+ const { $regex, $options = '' } = options;
146
+
147
+ try {
148
+ const regex = typeof $regex === 'string' ? new RegExp($regex, $options) : $regex;
149
+ return regex.test(value);
150
+ } catch (e) {
151
+ throw new Error(`无效的正则表达式:${$regex}`);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * 匹配字段值
157
+ */
158
+ function matchField(doc: Record<string, unknown>, field: string, condition: unknown): boolean {
159
+ const value = getNestedValue(doc, field);
160
+
161
+ // 如果条件是普通值,直接比较
162
+ if (!condition || typeof condition !== 'object') {
163
+ return value === condition;
164
+ }
165
+
166
+ // 处理操作符
167
+ let hasOperatorMatch = false;
168
+
169
+ for (const [operator, queryValue] of Object.entries(condition as Record<string, unknown>)) {
170
+ if (operator.startsWith('$')) {
171
+ // $options 是 $regex 的修饰符,跳过
172
+ if (operator === '$options') {
173
+ continue;
174
+ }
175
+
176
+ hasOperatorMatch = true;
177
+ let result: boolean;
178
+
179
+ // 比较操作符
180
+ if (comparisonOperators[operator]) {
181
+ result = comparisonOperators[operator](value, queryValue);
182
+ }
183
+ // 元素操作符
184
+ else if (elementOperators[operator]) {
185
+ result = elementOperators[operator](value, queryValue);
186
+ }
187
+ // 数组操作符
188
+ else if (arrayOperators[operator]) {
189
+ result = arrayOperators[operator](value, queryValue);
190
+ }
191
+ // 正则操作符
192
+ else if (operator === '$regex') {
193
+ result = matchRegex(value, condition as { $regex: string | RegExp; $options?: string });
194
+ }
195
+ // 未知操作符
196
+ else {
197
+ throw new Error(`未知操作符:${operator}`);
198
+ }
199
+
200
+ if (!result) {
201
+ return false;
202
+ }
203
+ }
204
+ }
205
+
206
+ // 如果没有操作符匹配,直接比较值
207
+ if (!hasOperatorMatch) {
208
+ return value === condition;
209
+ }
210
+
211
+ return true;
212
+ }
213
+
214
+ /**
215
+ * 匹配查询条件
216
+ */
217
+ export function matchQuery(doc: Record<string, unknown>, query: Record<string, unknown>): boolean {
218
+ if (!query || typeof query !== 'object') {
219
+ return true;
220
+ }
221
+
222
+ for (const [key, condition] of Object.entries(query)) {
223
+ // 逻辑操作符
224
+ if (key.startsWith('$')) {
225
+ if (logicalOperators[key]) {
226
+ const result = logicalOperators[key](doc, condition as Record<string, unknown>[]);
227
+ if (!result) {
228
+ return false;
229
+ }
230
+ } else {
231
+ throw new Error(`未知逻辑操作符:${key}`);
232
+ }
233
+ } else {
234
+ // 字段匹配
235
+ const result = matchField(doc, key, condition);
236
+ if (!result) {
237
+ return false;
238
+ }
239
+ }
240
+ }
241
+
242
+ return true;
243
+ }
244
+
245
+ /**
246
+ * 更新操作符实现
247
+ */
248
+ export const updateOperators = {
249
+ $set: (doc: Record<string, unknown>, updates: Record<string, unknown>): void => {
250
+ for (const [path, value] of Object.entries(updates)) {
251
+ setNestedValue(doc, path, value);
252
+ }
253
+ },
254
+
255
+ $unset: (doc: Record<string, unknown>, paths: Record<string, unknown>): void => {
256
+ for (const path of Object.keys(paths)) {
257
+ deleteNestedValue(doc, path);
258
+ }
259
+ },
260
+
261
+ $inc: (doc: Record<string, unknown>, updates: Record<string, number>): void => {
262
+ for (const [path, value] of Object.entries(updates)) {
263
+ const currentValue = getNestedValue(doc, path) as number || 0;
264
+ setNestedValue(doc, path, currentValue + value);
265
+ }
266
+ },
267
+
268
+ $mul: (doc: Record<string, unknown>, updates: Record<string, number>): void => {
269
+ for (const [path, value] of Object.entries(updates)) {
270
+ const currentValue = getNestedValue(doc, path) as number || 1;
271
+ setNestedValue(doc, path, currentValue * value);
272
+ }
273
+ },
274
+
275
+ $rename: (doc: Record<string, unknown>, renames: Record<string, string>): void => {
276
+ for (const [oldPath, newPath] of Object.entries(renames)) {
277
+ const value = getNestedValue(doc, oldPath);
278
+ if (value !== undefined) {
279
+ setNestedValue(doc, newPath, value);
280
+ deleteNestedValue(doc, oldPath);
281
+ }
282
+ }
283
+ },
284
+
285
+ $setOnInsert: (doc: Record<string, unknown>, updates: Record<string, unknown>, isInsert: boolean): void => {
286
+ if (isInsert) {
287
+ updateOperators.$set(doc, updates);
288
+ }
289
+ },
290
+
291
+ $min: (doc: Record<string, unknown>, updates: Record<string, number>): void => {
292
+ for (const [path, value] of Object.entries(updates)) {
293
+ const currentValue = getNestedValue(doc, path) as number;
294
+ if (currentValue === undefined || value < currentValue) {
295
+ setNestedValue(doc, path, value);
296
+ }
297
+ }
298
+ },
299
+
300
+ $max: (doc: Record<string, unknown>, updates: Record<string, number>): void => {
301
+ for (const [path, value] of Object.entries(updates)) {
302
+ const currentValue = getNestedValue(doc, path) as number;
303
+ if (currentValue === undefined || value > currentValue) {
304
+ setNestedValue(doc, path, value);
305
+ }
306
+ }
307
+ },
308
+
309
+ $push: (doc: Record<string, unknown>, updates: Record<string, unknown>): void => {
310
+ for (const [path, value] of Object.entries(updates)) {
311
+ const currentValue = getNestedValue(doc, path) as unknown[];
312
+ const array = Array.isArray(currentValue) ? currentValue : [];
313
+ setNestedValue(doc, path, [...array, value]);
314
+ }
315
+ },
316
+
317
+ $pop: (doc: Record<string, unknown>, updates: Record<string, number>): void => {
318
+ for (const [path, direction] of Object.entries(updates)) {
319
+ const currentValue = getNestedValue(doc, path) as unknown[];
320
+ if (Array.isArray(currentValue) && currentValue.length > 0) {
321
+ if (direction === 1) {
322
+ setNestedValue(doc, path, currentValue.slice(0, -1));
323
+ } else if (direction === -1) {
324
+ setNestedValue(doc, path, currentValue.slice(1));
325
+ }
326
+ }
327
+ }
328
+ },
329
+
330
+ $pull: (doc: Record<string, unknown>, updates: Record<string, unknown>): void => {
331
+ for (const [path, condition] of Object.entries(updates)) {
332
+ const currentValue = getNestedValue(doc, path) as unknown[];
333
+ if (Array.isArray(currentValue)) {
334
+ const filtered = currentValue.filter(item => {
335
+ if (typeof condition === 'object') {
336
+ return !matchQuery(item as Record<string, unknown>, condition as Record<string, unknown>);
337
+ }
338
+ return item !== condition;
339
+ });
340
+ setNestedValue(doc, path, filtered);
341
+ }
342
+ }
343
+ },
344
+
345
+ $addToSet: (doc: Record<string, unknown>, updates: Record<string, unknown>): void => {
346
+ for (const [path, value] of Object.entries(updates)) {
347
+ const currentValue = getNestedValue(doc, path) as unknown[];
348
+ const array = Array.isArray(currentValue) ? currentValue : [];
349
+ if (!array.some(item => JSON.stringify(item) === JSON.stringify(value))) {
350
+ setNestedValue(doc, path, [...array, value]);
351
+ }
352
+ }
353
+ }
354
+ };
355
+
356
+ /**
357
+ * 应用更新到文档
358
+ */
359
+ export function applyUpdate(doc: Record<string, unknown>, update: Record<string, unknown>, isInsert = false): Record<string, unknown> {
360
+ const result = JSON.parse(JSON.stringify(doc)) as Record<string, unknown>;
361
+
362
+ for (const [operator, updates] of Object.entries(update)) {
363
+ if (!operator.startsWith('$')) {
364
+ throw new Error('更新操作必须使用操作符');
365
+ }
366
+
367
+ if (updateOperators[operator]) {
368
+ updateOperators[operator](result, updates as Record<string, unknown>, isInsert);
369
+ } else {
370
+ throw new Error(`未知更新操作符:${operator}`);
371
+ }
372
+ }
373
+
374
+ return result;
375
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * 查询结果缓存模块
3
+ * 缓存相同查询的结果,提升重复查询性能
4
+ */
5
+
6
+ import { createHash } from 'crypto';
7
+
8
+ /**
9
+ * 查询缓存类
10
+ */
11
+ export class QueryCache {
12
+ constructor(options = {}) {
13
+ this.maxSize = options.maxSize || 1000; // 最大缓存条目数
14
+ this.ttl = options.ttl || 60000; // 默认 1 分钟过期
15
+ this.cache = new Map();
16
+ this.hits = 0;
17
+ this.misses = 0;
18
+ }
19
+
20
+ /**
21
+ * 生成查询缓存键
22
+ * @param {string} collection - 集合名
23
+ * @param {Object} query - 查询条件
24
+ * @param {Object} options - 查询选项
25
+ * @returns {string} 缓存键
26
+ */
27
+ generateKey(collection, query, options = {}) {
28
+ const keyData = JSON.stringify({
29
+ collection,
30
+ query,
31
+ options: {
32
+ sort: options.sort,
33
+ limit: options.limit,
34
+ skip: options.skip,
35
+ projection: options.projection
36
+ }
37
+ });
38
+
39
+ return createHash('sha256').update(keyData).digest('hex');
40
+ }
41
+
42
+ /**
43
+ * 获取缓存
44
+ * @param {string} key - 缓存键
45
+ * @returns {any|null} 缓存的数据或 null
46
+ */
47
+ get(key) {
48
+ const entry = this.cache.get(key);
49
+
50
+ if (!entry) {
51
+ this.misses++;
52
+ return null;
53
+ }
54
+
55
+ // 检查是否过期
56
+ if (Date.now() > entry.expiresAt) {
57
+ this.cache.delete(key);
58
+ this.misses++;
59
+ return null;
60
+ }
61
+
62
+ // 更新访问时间(LRU)
63
+ entry.lastAccessed = Date.now();
64
+ this.hits++;
65
+
66
+ return entry.data;
67
+ }
68
+
69
+ /**
70
+ * 设置缓存
71
+ * @param {string} key - 缓存键
72
+ * @param {any} data - 数据
73
+ * @param {number} ttl - 过期时间(毫秒)
74
+ */
75
+ set(key, data, ttl) {
76
+ // 如果缓存已满,删除最久未使用的条目
77
+ if (this.cache.size >= this.maxSize) {
78
+ this._evictOldest();
79
+ }
80
+
81
+ this.cache.set(key, {
82
+ data,
83
+ createdAt: Date.now(),
84
+ expiresAt: Date.now() + (ttl || this.ttl),
85
+ lastAccessed: Date.now()
86
+ });
87
+ }
88
+
89
+ /**
90
+ * 删除缓存
91
+ * @param {string} key - 缓存键
92
+ */
93
+ delete(key) {
94
+ this.cache.delete(key);
95
+ }
96
+
97
+ /**
98
+ * 清空缓存
99
+ */
100
+ clear() {
101
+ this.cache.clear();
102
+ this.hits = 0;
103
+ this.misses = 0;
104
+ }
105
+
106
+ /**
107
+ * 删除与集合相关的所有缓存
108
+ * @param {string} collection - 集合名
109
+ */
110
+ invalidateCollection(collection) {
111
+ for (const [key, entry] of this.cache.entries()) {
112
+ if (key.includes(collection)) {
113
+ this.cache.delete(key);
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * 删除最久未使用的条目(LRU)
120
+ * @private
121
+ */
122
+ _evictOldest() {
123
+ let oldestKey = null;
124
+ let oldestTime = Infinity;
125
+
126
+ for (const [key, entry] of this.cache.entries()) {
127
+ if (entry.lastAccessed < oldestTime) {
128
+ oldestTime = entry.lastAccessed;
129
+ oldestKey = key;
130
+ }
131
+ }
132
+
133
+ if (oldestKey) {
134
+ this.cache.delete(oldestKey);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * 获取缓存统计
140
+ * @returns {Object} 统计信息
141
+ */
142
+ getStats() {
143
+ const total = this.hits + this.misses;
144
+ return {
145
+ size: this.cache.size,
146
+ maxSize: this.maxSize,
147
+ hits: this.hits,
148
+ misses: this.misses,
149
+ hitRate: total > 0 ? ((this.hits / total) * 100).toFixed(2) + '%' : '0%',
150
+ ttl: this.ttl
151
+ };
152
+ }
153
+ }
154
+
155
+ /**
156
+ * 全局查询缓存实例
157
+ */
158
+ export const globalQueryCache = new QueryCache({
159
+ maxSize: 500,
160
+ ttl: 30000 // 30 秒
161
+ });
162
+
163
+ /**
164
+ * 缓存查询装饰器
165
+ * @param {Function} queryFn - 查询函数
166
+ * @param {Object} options - 选项
167
+ * @returns {Function} 装饰后的查询函数
168
+ */
169
+ export function cacheQuery(queryFn, options = {}) {
170
+ const cache = options.cache || globalQueryCache;
171
+ const ttl = options.ttl;
172
+
173
+ return async function(collection, query, queryOptions) {
174
+ const key = cache.generateKey(collection.name, query, queryOptions);
175
+
176
+ // 尝试从缓存获取
177
+ const cached = cache.get(key);
178
+ if (cached !== null) {
179
+ return cached;
180
+ }
181
+
182
+ // 执行查询
183
+ const result = await queryFn.call(this, collection, query, queryOptions);
184
+
185
+ // 存入缓存
186
+ cache.set(key, result, ttl);
187
+
188
+ return result;
189
+ };
190
+ }