@yydb/light-db 0.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/dist/api/LightDB.d.ts +194 -0
- package/dist/api/LightDB.d.ts.map +1 -0
- package/dist/api/LightDB.js +299 -0
- package/dist/api/LightDB.js.map +1 -0
- package/dist/api/Transaction.d.ts +100 -0
- package/dist/api/Transaction.d.ts.map +1 -0
- package/dist/api/Transaction.js +263 -0
- package/dist/api/Transaction.js.map +1 -0
- package/dist/api/index.d.ts +24 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +54 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/types.d.ts +154 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +7 -0
- package/dist/api/types.js.map +1 -0
- package/dist/collection/Collection.d.ts +235 -0
- package/dist/collection/Collection.d.ts.map +1 -0
- package/dist/collection/Collection.js +671 -0
- package/dist/collection/Collection.js.map +1 -0
- package/dist/collection/QueryBuilder.d.ts +225 -0
- package/dist/collection/QueryBuilder.d.ts.map +1 -0
- package/dist/collection/QueryBuilder.js +475 -0
- package/dist/collection/QueryBuilder.js.map +1 -0
- package/dist/collection/QueryParser.d.ts +117 -0
- package/dist/collection/QueryParser.d.ts.map +1 -0
- package/dist/collection/QueryParser.js +438 -0
- package/dist/collection/QueryParser.js.map +1 -0
- package/dist/collection/index.d.ts +42 -0
- package/dist/collection/index.d.ts.map +1 -0
- package/dist/collection/index.js +41 -0
- package/dist/collection/index.js.map +1 -0
- package/dist/collection/types.d.ts +365 -0
- package/dist/collection/types.d.ts.map +1 -0
- package/dist/collection/types.js +7 -0
- package/dist/collection/types.js.map +1 -0
- package/dist/constants/index.d.ts +51 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +68 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +132 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +220 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +729 -0
- package/dist/index.js.map +1 -0
- package/dist/indexes/BPlusTree.d.ts +181 -0
- package/dist/indexes/BPlusTree.d.ts.map +1 -0
- package/dist/indexes/BPlusTree.js +528 -0
- package/dist/indexes/BPlusTree.js.map +1 -0
- package/dist/indexes/IndexManager.d.ts +236 -0
- package/dist/indexes/IndexManager.d.ts.map +1 -0
- package/dist/indexes/IndexManager.js +502 -0
- package/dist/indexes/IndexManager.js.map +1 -0
- package/dist/indexes/PrimaryIndex.d.ts +240 -0
- package/dist/indexes/PrimaryIndex.d.ts.map +1 -0
- package/dist/indexes/PrimaryIndex.js +354 -0
- package/dist/indexes/PrimaryIndex.js.map +1 -0
- package/dist/indexes/SecondaryIndex.d.ts +204 -0
- package/dist/indexes/SecondaryIndex.d.ts.map +1 -0
- package/dist/indexes/SecondaryIndex.js +480 -0
- package/dist/indexes/SecondaryIndex.js.map +1 -0
- package/dist/indexes/errors.d.ts +66 -0
- package/dist/indexes/errors.d.ts.map +1 -0
- package/dist/indexes/errors.js +117 -0
- package/dist/indexes/errors.js.map +1 -0
- package/dist/indexes/index.d.ts +52 -0
- package/dist/indexes/index.d.ts.map +1 -0
- package/dist/indexes/index.js +131 -0
- package/dist/indexes/index.js.map +1 -0
- package/dist/indexes/types.d.ts +246 -0
- package/dist/indexes/types.d.ts.map +1 -0
- package/dist/indexes/types.js +30 -0
- package/dist/indexes/types.js.map +1 -0
- package/dist/performance/BatchWALWriter.d.ts +185 -0
- package/dist/performance/BatchWALWriter.d.ts.map +1 -0
- package/dist/performance/BatchWALWriter.js +490 -0
- package/dist/performance/BatchWALWriter.js.map +1 -0
- package/dist/performance/ColumnarStorage.d.ts +138 -0
- package/dist/performance/ColumnarStorage.d.ts.map +1 -0
- package/dist/performance/ColumnarStorage.js +875 -0
- package/dist/performance/ColumnarStorage.js.map +1 -0
- package/dist/performance/LazyQuery.d.ts +307 -0
- package/dist/performance/LazyQuery.d.ts.map +1 -0
- package/dist/performance/LazyQuery.js +588 -0
- package/dist/performance/LazyQuery.js.map +1 -0
- package/dist/performance/OptimizedBuffer.d.ts +375 -0
- package/dist/performance/OptimizedBuffer.d.ts.map +1 -0
- package/dist/performance/OptimizedBuffer.js +730 -0
- package/dist/performance/OptimizedBuffer.js.map +1 -0
- package/dist/performance/RWLock.d.ts +260 -0
- package/dist/performance/RWLock.d.ts.map +1 -0
- package/dist/performance/RWLock.js +570 -0
- package/dist/performance/RWLock.js.map +1 -0
- package/dist/performance/index.d.ts +41 -0
- package/dist/performance/index.d.ts.map +1 -0
- package/dist/performance/index.js +162 -0
- package/dist/performance/index.js.map +1 -0
- package/dist/performance/types.d.ts +247 -0
- package/dist/performance/types.d.ts.map +1 -0
- package/dist/performance/types.js +26 -0
- package/dist/performance/types.js.map +1 -0
- package/dist/storage/Checkpoint.d.ts +128 -0
- package/dist/storage/Checkpoint.d.ts.map +1 -0
- package/dist/storage/Checkpoint.js +251 -0
- package/dist/storage/Checkpoint.js.map +1 -0
- package/dist/storage/LightFile.d.ts +227 -0
- package/dist/storage/LightFile.d.ts.map +1 -0
- package/dist/storage/LightFile.js +526 -0
- package/dist/storage/LightFile.js.map +1 -0
- package/dist/storage/Recovery.d.ts +172 -0
- package/dist/storage/Recovery.d.ts.map +1 -0
- package/dist/storage/Recovery.js +399 -0
- package/dist/storage/Recovery.js.map +1 -0
- package/dist/storage/SHMFile.d.ts +243 -0
- package/dist/storage/SHMFile.d.ts.map +1 -0
- package/dist/storage/SHMFile.js +436 -0
- package/dist/storage/SHMFile.js.map +1 -0
- package/dist/storage/StorageEngine.d.ts +146 -0
- package/dist/storage/StorageEngine.d.ts.map +1 -0
- package/dist/storage/StorageEngine.js +353 -0
- package/dist/storage/StorageEngine.js.map +1 -0
- package/dist/storage/WALFile.d.ts +248 -0
- package/dist/storage/WALFile.d.ts.map +1 -0
- package/dist/storage/WALFile.js +597 -0
- package/dist/storage/WALFile.js.map +1 -0
- package/dist/storage/index.d.ts +10 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +153 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/types/index.d.ts +220 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/binary.d.ts +271 -0
- package/dist/utils/binary.d.ts.map +1 -0
- package/dist/utils/binary.js +454 -0
- package/dist/utils/binary.js.map +1 -0
- package/dist/utils/file.d.ts +166 -0
- package/dist/utils/file.d.ts.map +1 -0
- package/dist/utils/file.js +346 -0
- package/dist/utils/file.js.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +39 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +37 -0
- package/readme.md +124 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LightDB 集合类
|
|
4
|
+
* 提供类型安全的 CRUD 操作和链式查询 API
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Collection = exports.CollectionError = void 0;
|
|
8
|
+
const indexes_1 = require("../indexes");
|
|
9
|
+
const QueryBuilder_1 = require("./QueryBuilder");
|
|
10
|
+
const QueryParser_1 = require("./QueryParser");
|
|
11
|
+
/**
|
|
12
|
+
* 集合错误类
|
|
13
|
+
*/
|
|
14
|
+
class CollectionError extends Error {
|
|
15
|
+
constructor(message, code) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "CollectionError";
|
|
18
|
+
this.code = code;
|
|
19
|
+
Object.setPrototypeOf(this, CollectionError.prototype);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.CollectionError = CollectionError;
|
|
23
|
+
/**
|
|
24
|
+
* LightDB 集合
|
|
25
|
+
* 类似数据库表的概念,提供类型安全的 CRUD 操作
|
|
26
|
+
*/
|
|
27
|
+
class Collection {
|
|
28
|
+
/**
|
|
29
|
+
* 创建集合实例
|
|
30
|
+
* @param options 集合配置选项
|
|
31
|
+
*/
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.name = options.name ?? "default";
|
|
34
|
+
this.primaryKeyField = options.primaryKey;
|
|
35
|
+
this.eventListeners = new Map();
|
|
36
|
+
const indexConfig = {
|
|
37
|
+
name: this.name,
|
|
38
|
+
primaryKey: this.primaryKeyField,
|
|
39
|
+
primaryKeyExtractor: (doc) => this.extractPrimaryKey(doc),
|
|
40
|
+
secondaryIndexes: (options.indexes ?? []).map((idx) => ({
|
|
41
|
+
name: idx.name,
|
|
42
|
+
fieldName: idx.fieldName,
|
|
43
|
+
unique: idx.unique ?? false,
|
|
44
|
+
sparse: idx.sparse ?? false,
|
|
45
|
+
valueExtractor: (doc) => doc[idx.fieldName],
|
|
46
|
+
})),
|
|
47
|
+
};
|
|
48
|
+
this.indexManager = new indexes_1.IndexManager(indexConfig);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 获取集合名称
|
|
52
|
+
*/
|
|
53
|
+
get Name() {
|
|
54
|
+
return this.name;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 获取主键字段名
|
|
58
|
+
*/
|
|
59
|
+
get PrimaryKeyField() {
|
|
60
|
+
return this.primaryKeyField;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 获取文档数量
|
|
64
|
+
*/
|
|
65
|
+
get Size() {
|
|
66
|
+
return this.indexManager.Size;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 插入单条文档
|
|
70
|
+
* @param doc 要插入的文档
|
|
71
|
+
* @param options 插入选项
|
|
72
|
+
*/
|
|
73
|
+
insert(doc, options = {}) {
|
|
74
|
+
const primaryKey = this.extractPrimaryKey(doc);
|
|
75
|
+
if (this.indexManager.hasByPrimaryKey(primaryKey)) {
|
|
76
|
+
if (options.throwOnDuplicate) {
|
|
77
|
+
throw new CollectionError(`Document with primary key "${primaryKey}" already exists`, "DUPLICATE_KEY");
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
insertedCount: 0,
|
|
82
|
+
insertedDocs: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
this.indexManager.insert(doc, true);
|
|
86
|
+
this.emitEvent("insert", { doc });
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
insertedCount: 1,
|
|
90
|
+
insertedDocs: [doc],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 批量插入文档
|
|
95
|
+
* @param docs 要插入的文档数组
|
|
96
|
+
* @param options 插入选项
|
|
97
|
+
*/
|
|
98
|
+
insertMany(docs, options = {}) {
|
|
99
|
+
const insertedDocs = [];
|
|
100
|
+
const errors = [];
|
|
101
|
+
let success = 0;
|
|
102
|
+
let failed = 0;
|
|
103
|
+
for (let i = 0; i < docs.length; i++) {
|
|
104
|
+
const doc = docs[i];
|
|
105
|
+
const primaryKey = this.extractPrimaryKey(doc);
|
|
106
|
+
if (this.indexManager.hasByPrimaryKey(primaryKey)) {
|
|
107
|
+
if (options.throwOnDuplicate) {
|
|
108
|
+
failed++;
|
|
109
|
+
errors.push({
|
|
110
|
+
index: i,
|
|
111
|
+
error: `Document with primary key "${primaryKey}" already exists`,
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
failed++;
|
|
116
|
+
errors.push({
|
|
117
|
+
index: i,
|
|
118
|
+
error: `Duplicate key: ${primaryKey}`,
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
this.indexManager.insert(doc, true);
|
|
124
|
+
insertedDocs.push(doc);
|
|
125
|
+
success++;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
failed++;
|
|
128
|
+
errors.push({
|
|
129
|
+
index: i,
|
|
130
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (insertedDocs.length > 0) {
|
|
135
|
+
this.emitEvent("insert", { docs: insertedDocs });
|
|
136
|
+
}
|
|
137
|
+
const result = {
|
|
138
|
+
success: failed === 0,
|
|
139
|
+
insertedCount: success,
|
|
140
|
+
failedCount: failed,
|
|
141
|
+
insertedDocs,
|
|
142
|
+
};
|
|
143
|
+
if (errors.length > 0) {
|
|
144
|
+
result.errors = errors;
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* 查找文档
|
|
150
|
+
* @param options 查询选项
|
|
151
|
+
*/
|
|
152
|
+
find(options = {}) {
|
|
153
|
+
const builder = this.query();
|
|
154
|
+
if (options.where) {
|
|
155
|
+
builder.where(options.where);
|
|
156
|
+
}
|
|
157
|
+
if (options.sort) {
|
|
158
|
+
for (const [field, direction] of Object.entries(options.sort)) {
|
|
159
|
+
builder.sortBy(field, direction);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (options.offset !== undefined && options.offset > 0) {
|
|
163
|
+
builder.skip(options.offset);
|
|
164
|
+
}
|
|
165
|
+
if (options.limit !== undefined && options.limit > 0) {
|
|
166
|
+
builder.limit(options.limit);
|
|
167
|
+
}
|
|
168
|
+
if (options.fields && options.fields.length > 0) {
|
|
169
|
+
builder.select(...options.fields);
|
|
170
|
+
}
|
|
171
|
+
return builder.toArray();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* 查找单条文档
|
|
175
|
+
* @param filter 查询条件
|
|
176
|
+
*/
|
|
177
|
+
findOne(filter) {
|
|
178
|
+
return this.query().where(filter).first();
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 通过主键查找文档
|
|
182
|
+
* @param primaryKey 主键值
|
|
183
|
+
*/
|
|
184
|
+
findByPrimaryKey(primaryKey) {
|
|
185
|
+
return this.indexManager.findByPrimaryKey(primaryKey);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* 通过二级索引查找文档
|
|
189
|
+
* @param indexName 索引名称
|
|
190
|
+
* @param value 索引值
|
|
191
|
+
*/
|
|
192
|
+
findBySecondaryIndex(indexName, value) {
|
|
193
|
+
return this.indexManager.findBySecondaryIndex(indexName, value);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 更新文档
|
|
197
|
+
* @param filter 查询条件
|
|
198
|
+
* @param update 更新操作
|
|
199
|
+
* @param options 更新选项
|
|
200
|
+
*/
|
|
201
|
+
update(filter, update, options = {}) {
|
|
202
|
+
const docs = this.find({ where: filter }).docs;
|
|
203
|
+
if (docs.length === 0) {
|
|
204
|
+
if (options.upsert) {
|
|
205
|
+
return this.upsert(filter, update);
|
|
206
|
+
}
|
|
207
|
+
if (options.throwOnMissing) {
|
|
208
|
+
throw new CollectionError("No document matches the filter", "DOCUMENT_NOT_FOUND");
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
success: false,
|
|
212
|
+
matchedCount: 0,
|
|
213
|
+
modifiedCount: 0,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const toUpdate = options.multi ? docs : docs.slice(0, 1);
|
|
217
|
+
let modifiedCount = 0;
|
|
218
|
+
for (const doc of toUpdate) {
|
|
219
|
+
const updatedDoc = this.applyUpdate(doc, update);
|
|
220
|
+
const oldDoc = { ...doc };
|
|
221
|
+
if (this.indexManager.update(oldDoc, updatedDoc)) {
|
|
222
|
+
modifiedCount++;
|
|
223
|
+
this.emitEvent("update", { doc: updatedDoc, oldDoc });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
success: true,
|
|
228
|
+
matchedCount: docs.length,
|
|
229
|
+
modifiedCount,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* 更新单条文档
|
|
234
|
+
* @param filter 查询条件
|
|
235
|
+
* @param update 更新操作
|
|
236
|
+
* @param options 更新选项
|
|
237
|
+
*/
|
|
238
|
+
updateOne(filter, update, options = {}) {
|
|
239
|
+
return this.update(filter, update, { ...options, multi: false });
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* 更新多条文档
|
|
243
|
+
* @param filter 查询条件
|
|
244
|
+
* @param update 更新操作
|
|
245
|
+
* @param options 更新选项
|
|
246
|
+
*/
|
|
247
|
+
updateMany(filter, update, options = {}) {
|
|
248
|
+
return this.update(filter, update, { ...options, multi: true });
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Upsert 操作
|
|
252
|
+
*/
|
|
253
|
+
upsert(filter, update) {
|
|
254
|
+
const newDoc = this.createDocFromUpdate(filter, update);
|
|
255
|
+
const primaryKey = this.extractPrimaryKey(newDoc);
|
|
256
|
+
if (this.indexManager.hasByPrimaryKey(primaryKey)) {
|
|
257
|
+
const existingDoc = this.indexManager.findByPrimaryKey(primaryKey);
|
|
258
|
+
const updatedDoc = this.applyUpdate(existingDoc, update);
|
|
259
|
+
this.indexManager.update(existingDoc, updatedDoc);
|
|
260
|
+
this.emitEvent("update", { doc: updatedDoc, oldDoc: existingDoc });
|
|
261
|
+
return {
|
|
262
|
+
success: true,
|
|
263
|
+
matchedCount: 1,
|
|
264
|
+
modifiedCount: 1,
|
|
265
|
+
upserted: false,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
this.indexManager.insert(newDoc, true);
|
|
269
|
+
this.emitEvent("insert", { doc: newDoc });
|
|
270
|
+
return {
|
|
271
|
+
success: true,
|
|
272
|
+
matchedCount: 0,
|
|
273
|
+
modifiedCount: 0,
|
|
274
|
+
upserted: true,
|
|
275
|
+
upsertedId: primaryKey,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* 删除文档
|
|
280
|
+
* @param filter 查询条件
|
|
281
|
+
* @param options 删除选项
|
|
282
|
+
*/
|
|
283
|
+
delete(filter, options = {}) {
|
|
284
|
+
const docs = this.find({ where: filter }).docs;
|
|
285
|
+
if (docs.length === 0) {
|
|
286
|
+
if (options.throwOnMissing) {
|
|
287
|
+
throw new CollectionError("No document matches the filter", "DOCUMENT_NOT_FOUND");
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
success: false,
|
|
291
|
+
deletedCount: 0,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const toDelete = options.multi ? docs : docs.slice(0, 1);
|
|
295
|
+
let deletedCount = 0;
|
|
296
|
+
for (const doc of toDelete) {
|
|
297
|
+
if (this.indexManager.delete(doc)) {
|
|
298
|
+
deletedCount++;
|
|
299
|
+
this.emitEvent("delete", { doc });
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
success: true,
|
|
304
|
+
deletedCount,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* 删除单条文档
|
|
309
|
+
* @param filter 查询条件
|
|
310
|
+
* @param options 删除选项
|
|
311
|
+
*/
|
|
312
|
+
deleteOne(filter, options = {}) {
|
|
313
|
+
return this.delete(filter, { ...options, multi: false });
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* 删除多条文档
|
|
317
|
+
* @param filter 查询条件
|
|
318
|
+
* @param options 删除选项
|
|
319
|
+
*/
|
|
320
|
+
deleteMany(filter, options = {}) {
|
|
321
|
+
return this.delete(filter, { ...options, multi: true });
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* 通过主键删除文档
|
|
325
|
+
* @param primaryKey 主键值
|
|
326
|
+
*/
|
|
327
|
+
deleteByPrimaryKey(primaryKey) {
|
|
328
|
+
const doc = this.indexManager.findByPrimaryKey(primaryKey);
|
|
329
|
+
if (!doc) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
const result = this.indexManager.delete(doc);
|
|
333
|
+
if (result) {
|
|
334
|
+
this.emitEvent("delete", { doc });
|
|
335
|
+
}
|
|
336
|
+
return result !== undefined;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* 统计文档数量
|
|
340
|
+
* @param filter 查询条件
|
|
341
|
+
*/
|
|
342
|
+
count(filter = {}) {
|
|
343
|
+
if (Object.keys(filter).length === 0) {
|
|
344
|
+
return this.indexManager.Size;
|
|
345
|
+
}
|
|
346
|
+
return this.query().where(filter).count();
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* 检查文档是否存在
|
|
350
|
+
* @param filter 查询条件
|
|
351
|
+
*/
|
|
352
|
+
exists(filter) {
|
|
353
|
+
return this.query().where(filter).hasMatch();
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* 创建查询构建器
|
|
357
|
+
*/
|
|
358
|
+
query() {
|
|
359
|
+
return new QueryBuilder_1.QueryBuilder(this);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 清空集合
|
|
363
|
+
*/
|
|
364
|
+
clear() {
|
|
365
|
+
this.indexManager.clear();
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* 获取集合统计信息
|
|
369
|
+
*/
|
|
370
|
+
getStats() {
|
|
371
|
+
const indexStats = this.indexManager.getStats();
|
|
372
|
+
return {
|
|
373
|
+
name: this.name,
|
|
374
|
+
documentCount: this.indexManager.Size,
|
|
375
|
+
indexCount: indexStats.length,
|
|
376
|
+
indexes: indexStats.map((stat) => ({
|
|
377
|
+
name: stat.name,
|
|
378
|
+
type: stat.type === "primary" ? "primary" : "secondary",
|
|
379
|
+
fieldName: stat.fieldName,
|
|
380
|
+
unique: stat.unique,
|
|
381
|
+
keyCount: stat.keyCount,
|
|
382
|
+
})),
|
|
383
|
+
memoryUsage: this.indexManager.getMemoryUsage(),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* 创建二级索引
|
|
388
|
+
* @param config 索引配置
|
|
389
|
+
*/
|
|
390
|
+
createIndex(config) {
|
|
391
|
+
this.indexManager.createSecondaryIndex({
|
|
392
|
+
name: config.name,
|
|
393
|
+
fieldName: config.fieldName,
|
|
394
|
+
unique: config.unique ?? false,
|
|
395
|
+
sparse: config.sparse ?? false,
|
|
396
|
+
valueExtractor: (doc) => doc[config.fieldName],
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* 删除二级索引
|
|
401
|
+
* @param name 索引名称
|
|
402
|
+
*/
|
|
403
|
+
dropIndex(name) {
|
|
404
|
+
return this.indexManager.dropSecondaryIndex(name);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* 获取所有索引名称
|
|
408
|
+
*/
|
|
409
|
+
getIndexNames() {
|
|
410
|
+
return [this.primaryKeyField, ...this.indexManager.getSecondaryIndexNames()];
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* 添加事件监听器
|
|
414
|
+
* @param event 事件类型
|
|
415
|
+
* @param listener 监听器函数
|
|
416
|
+
*/
|
|
417
|
+
on(event, listener) {
|
|
418
|
+
if (!this.eventListeners.has(event)) {
|
|
419
|
+
this.eventListeners.set(event, new Set());
|
|
420
|
+
}
|
|
421
|
+
this.eventListeners.get(event).add(listener);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* 移除事件监听器
|
|
425
|
+
* @param event 事件类型
|
|
426
|
+
* @param listener 监听器函数
|
|
427
|
+
*/
|
|
428
|
+
off(event, listener) {
|
|
429
|
+
const listeners = this.eventListeners.get(event);
|
|
430
|
+
if (listeners) {
|
|
431
|
+
listeners.delete(listener);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* 触发事件
|
|
436
|
+
*/
|
|
437
|
+
emitEvent(event, data) {
|
|
438
|
+
const listeners = this.eventListeners.get(event);
|
|
439
|
+
if (listeners) {
|
|
440
|
+
for (const listener of listeners) {
|
|
441
|
+
try {
|
|
442
|
+
listener(event, data);
|
|
443
|
+
} catch {
|
|
444
|
+
// 忽略监听器错误
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* 提取主键值
|
|
451
|
+
*/
|
|
452
|
+
extractPrimaryKey(doc) {
|
|
453
|
+
const value = doc[this.primaryKeyField];
|
|
454
|
+
if (value === undefined || value === null) {
|
|
455
|
+
throw new CollectionError(`Primary key field "${this.primaryKeyField}" is required`, "MISSING_PRIMARY_KEY");
|
|
456
|
+
}
|
|
457
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
458
|
+
throw new CollectionError(`Primary key must be string or number, got ${typeof value}`, "INVALID_PRIMARY_KEY_TYPE");
|
|
459
|
+
}
|
|
460
|
+
return value;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* 应用更新操作
|
|
464
|
+
*/
|
|
465
|
+
applyUpdate(doc, update) {
|
|
466
|
+
const updated = { ...doc };
|
|
467
|
+
if (this.isUpdateOperators(update)) {
|
|
468
|
+
return this.applyUpdateOperators(updated, update);
|
|
469
|
+
}
|
|
470
|
+
return { ...updated, ...update };
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* 检查是否是更新操作符
|
|
474
|
+
*/
|
|
475
|
+
isUpdateOperators(update) {
|
|
476
|
+
if (typeof update !== "object" || update === null) {
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
const keys = Object.keys(update);
|
|
480
|
+
return keys.some((key) => key.startsWith("$"));
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* 应用更新操作符
|
|
484
|
+
*/
|
|
485
|
+
applyUpdateOperators(doc, operators) {
|
|
486
|
+
const result = { ...doc };
|
|
487
|
+
if (operators.$set) {
|
|
488
|
+
Object.assign(result, operators.$set);
|
|
489
|
+
}
|
|
490
|
+
if (operators.$unset) {
|
|
491
|
+
for (const field of Object.keys(operators.$unset)) {
|
|
492
|
+
delete result[field];
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (operators.$inc) {
|
|
496
|
+
for (const [field, amount] of Object.entries(operators.$inc)) {
|
|
497
|
+
const current = result[field];
|
|
498
|
+
if (typeof current === "number") {
|
|
499
|
+
result[field] = current + amount;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (operators.$mul) {
|
|
504
|
+
for (const [field, multiplier] of Object.entries(operators.$mul)) {
|
|
505
|
+
const current = result[field];
|
|
506
|
+
if (typeof current === "number") {
|
|
507
|
+
result[field] = current * multiplier;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (operators.$rename) {
|
|
512
|
+
for (const [oldName, newName] of Object.entries(operators.$rename)) {
|
|
513
|
+
if (oldName in result) {
|
|
514
|
+
result[newName] = result[oldName];
|
|
515
|
+
delete result[oldName];
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
if (operators.$currentDate) {
|
|
520
|
+
const now = new Date();
|
|
521
|
+
for (const field of Object.keys(operators.$currentDate)) {
|
|
522
|
+
const config = operators.$currentDate[field];
|
|
523
|
+
if (config === true) {
|
|
524
|
+
result[field] = now;
|
|
525
|
+
} else if (typeof config === "object") {
|
|
526
|
+
result[field] = config.$type === "timestamp" ? now.getTime() : now;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (operators.$push) {
|
|
531
|
+
for (const [field, value] of Object.entries(operators.$push)) {
|
|
532
|
+
const current = result[field];
|
|
533
|
+
if (Array.isArray(current)) {
|
|
534
|
+
if (typeof value === "object" && value !== null && "$each" in value) {
|
|
535
|
+
const eachValue = value;
|
|
536
|
+
if (eachValue.$each) {
|
|
537
|
+
if (eachValue.$position !== undefined) {
|
|
538
|
+
current.splice(eachValue.$position, 0, ...eachValue.$each);
|
|
539
|
+
} else {
|
|
540
|
+
current.push(...eachValue.$each);
|
|
541
|
+
}
|
|
542
|
+
if (eachValue.$slice !== undefined) {
|
|
543
|
+
if (eachValue.$slice < 0) {
|
|
544
|
+
current.splice(0, current.length + eachValue.$slice);
|
|
545
|
+
} else if (eachValue.$slice > 0) {
|
|
546
|
+
current.splice(eachValue.$slice);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
} else {
|
|
551
|
+
current.push(value);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (operators.$pull) {
|
|
557
|
+
for (const [field, condition] of Object.entries(operators.$pull)) {
|
|
558
|
+
const current = result[field];
|
|
559
|
+
if (Array.isArray(current)) {
|
|
560
|
+
if (typeof condition === "object" && condition !== null) {
|
|
561
|
+
const filter = condition;
|
|
562
|
+
const indicesToRemove = [];
|
|
563
|
+
for (let i = 0; i < current.length; i++) {
|
|
564
|
+
if (QueryParser_1.QueryParser.matches(current[i], filter)) {
|
|
565
|
+
indicesToRemove.push(i);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
for (let i = indicesToRemove.length - 1; i >= 0; i--) {
|
|
569
|
+
current.splice(indicesToRemove[i], 1);
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
const index = current.indexOf(condition);
|
|
573
|
+
if (index > -1) {
|
|
574
|
+
current.splice(index, 1);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (operators.$addToSet) {
|
|
581
|
+
for (const [field, value] of Object.entries(operators.$addToSet)) {
|
|
582
|
+
const current = result[field];
|
|
583
|
+
if (Array.isArray(current)) {
|
|
584
|
+
if (typeof value === "object" && value !== null && "$each" in value) {
|
|
585
|
+
const eachValue = value;
|
|
586
|
+
if (eachValue.$each) {
|
|
587
|
+
for (const item of eachValue.$each) {
|
|
588
|
+
if (!current.includes(item)) {
|
|
589
|
+
current.push(item);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
} else {
|
|
594
|
+
if (!current.includes(value)) {
|
|
595
|
+
current.push(value);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (operators.$pop) {
|
|
602
|
+
for (const [field, direction] of Object.entries(operators.$pop)) {
|
|
603
|
+
const current = result[field];
|
|
604
|
+
if (Array.isArray(current) && current.length > 0) {
|
|
605
|
+
if (direction === 1) {
|
|
606
|
+
current.pop();
|
|
607
|
+
} else {
|
|
608
|
+
current.shift();
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* 从更新操作创建文档
|
|
617
|
+
*/
|
|
618
|
+
createDocFromUpdate(filter, update) {
|
|
619
|
+
const doc = {};
|
|
620
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
621
|
+
if (!key.startsWith("$") && typeof value !== "object") {
|
|
622
|
+
doc[key] = value;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (this.isUpdateOperators(update)) {
|
|
626
|
+
if (update.$set) {
|
|
627
|
+
Object.assign(doc, update.$set);
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
Object.assign(doc, update);
|
|
631
|
+
}
|
|
632
|
+
return doc;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* 获取所有文档
|
|
636
|
+
*/
|
|
637
|
+
getAll() {
|
|
638
|
+
return this.indexManager.getAll();
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* 获取主键字段名(实现 QueryExecutor 接口)
|
|
642
|
+
*/
|
|
643
|
+
getPrimaryKeyField() {
|
|
644
|
+
return this.primaryKeyField;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* 获取二级索引字段列表(实现 QueryExecutor 接口)
|
|
648
|
+
*/
|
|
649
|
+
getSecondaryIndexFields() {
|
|
650
|
+
return this.indexManager.getSecondaryIndexNames();
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* 验证集合一致性
|
|
654
|
+
*/
|
|
655
|
+
validate() {
|
|
656
|
+
const result = this.indexManager.validate();
|
|
657
|
+
return {
|
|
658
|
+
valid: result.valid,
|
|
659
|
+
errors: result.errors.flatMap((e) => e.errors),
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* 重建所有索引
|
|
664
|
+
*/
|
|
665
|
+
rebuildIndexes() {
|
|
666
|
+
const docs = this.getAll();
|
|
667
|
+
this.indexManager.rebuild(docs);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
exports.Collection = Collection;
|
|
671
|
+
//# sourceMappingURL=Collection.js.map
|