@joystick.js/db-canary 0.0.0-canary.2251 → 0.0.0-canary.2252
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/client/database.js +1 -1
- package/dist/client/index.js +1 -1
- package/dist/server/cluster/master.js +4 -4
- package/dist/server/cluster/worker.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/lib/auto_index_manager.js +1 -1
- package/dist/server/lib/backup_manager.js +1 -1
- package/dist/server/lib/index_manager.js +1 -1
- package/dist/server/lib/operation_dispatcher.js +1 -1
- package/dist/server/lib/operations/admin.js +1 -1
- package/dist/server/lib/operations/bulk_write.js +1 -1
- package/dist/server/lib/operations/create_index.js +1 -1
- package/dist/server/lib/operations/delete_many.js +1 -1
- package/dist/server/lib/operations/delete_one.js +1 -1
- package/dist/server/lib/operations/find.js +1 -1
- package/dist/server/lib/operations/find_one.js +1 -1
- package/dist/server/lib/operations/insert_one.js +1 -1
- package/dist/server/lib/operations/update_one.js +1 -1
- package/dist/server/lib/send_response.js +1 -1
- package/dist/server/lib/tcp_protocol.js +1 -1
- package/package.json +2 -2
- package/src/client/database.js +92 -119
- package/src/client/index.js +279 -345
- package/src/server/cluster/master.js +265 -156
- package/src/server/cluster/worker.js +26 -18
- package/src/server/index.js +553 -330
- package/src/server/lib/auto_index_manager.js +85 -23
- package/src/server/lib/backup_manager.js +117 -70
- package/src/server/lib/index_manager.js +63 -25
- package/src/server/lib/operation_dispatcher.js +339 -168
- package/src/server/lib/operations/admin.js +343 -205
- package/src/server/lib/operations/bulk_write.js +458 -194
- package/src/server/lib/operations/create_index.js +127 -34
- package/src/server/lib/operations/delete_many.js +204 -67
- package/src/server/lib/operations/delete_one.js +164 -52
- package/src/server/lib/operations/find.js +552 -319
- package/src/server/lib/operations/find_one.js +530 -304
- package/src/server/lib/operations/insert_one.js +147 -52
- package/src/server/lib/operations/update_one.js +334 -93
- package/src/server/lib/send_response.js +37 -17
- package/src/server/lib/tcp_protocol.js +158 -53
- package/test_data_api_key_1758233848259_cglfjzhou/data.mdb +0 -0
- package/test_data_api_key_1758233848259_cglfjzhou/lock.mdb +0 -0
- package/test_data_api_key_1758233848502_urlje2utd/data.mdb +0 -0
- package/test_data_api_key_1758233848502_urlje2utd/lock.mdb +0 -0
- package/test_data_api_key_1758233848738_mtcpfe5ns/data.mdb +0 -0
- package/test_data_api_key_1758233848738_mtcpfe5ns/lock.mdb +0 -0
- package/test_data_api_key_1758233848856_9g97p6gag/data.mdb +0 -0
- package/test_data_api_key_1758233848856_9g97p6gag/lock.mdb +0 -0
- package/test_data_api_key_1758233857008_0tl9zzhj8/data.mdb +0 -0
- package/test_data_api_key_1758233857008_0tl9zzhj8/lock.mdb +0 -0
- package/test_data_api_key_1758233857120_60c2f2uhu/data.mdb +0 -0
- package/test_data_api_key_1758233857120_60c2f2uhu/lock.mdb +0 -0
- package/test_data_api_key_1758233857232_aw7fkqgd9/data.mdb +0 -0
- package/test_data_api_key_1758233857232_aw7fkqgd9/lock.mdb +0 -0
- package/test_data_api_key_1758234881285_4aeflubjb/data.mdb +0 -0
- package/test_data_api_key_1758234881285_4aeflubjb/lock.mdb +0 -0
- package/test_data_api_key_1758234881520_kb0amvtqb/data.mdb +0 -0
- package/test_data_api_key_1758234881520_kb0amvtqb/lock.mdb +0 -0
- package/test_data_api_key_1758234881756_k04gfv2va/data.mdb +0 -0
- package/test_data_api_key_1758234881756_k04gfv2va/lock.mdb +0 -0
- package/test_data_api_key_1758234881876_wn90dpo1z/data.mdb +0 -0
- package/test_data_api_key_1758234881876_wn90dpo1z/lock.mdb +0 -0
- package/test_data_api_key_1758234889461_26xz3dmbr/data.mdb +0 -0
- package/test_data_api_key_1758234889461_26xz3dmbr/lock.mdb +0 -0
- package/test_data_api_key_1758234889572_uziz7e0p5/data.mdb +0 -0
- package/test_data_api_key_1758234889572_uziz7e0p5/lock.mdb +0 -0
- package/test_data_api_key_1758234889684_5f9wmposh/data.mdb +0 -0
- package/test_data_api_key_1758234889684_5f9wmposh/lock.mdb +0 -0
- package/test_data_api_key_1758235657729_prwgm6mxr/data.mdb +0 -0
- package/test_data_api_key_1758235657729_prwgm6mxr/lock.mdb +0 -0
- package/test_data_api_key_1758235657961_rc2da0dc2/data.mdb +0 -0
- package/test_data_api_key_1758235657961_rc2da0dc2/lock.mdb +0 -0
- package/test_data_api_key_1758235658193_oqqxm0sny/data.mdb +0 -0
- package/test_data_api_key_1758235658193_oqqxm0sny/lock.mdb +0 -0
- package/test_data_api_key_1758235658309_vggac1pj6/data.mdb +0 -0
- package/test_data_api_key_1758235658309_vggac1pj6/lock.mdb +0 -0
- package/test_data_api_key_1758235665968_61ko07dd1/data.mdb +0 -0
- package/test_data_api_key_1758235665968_61ko07dd1/lock.mdb +0 -0
- package/test_data_api_key_1758235666082_50lrt6sq8/data.mdb +0 -0
- package/test_data_api_key_1758235666082_50lrt6sq8/lock.mdb +0 -0
- package/test_data_api_key_1758235666194_ykvauwlzh/data.mdb +0 -0
- package/test_data_api_key_1758235666194_ykvauwlzh/lock.mdb +0 -0
- package/test_data_api_key_1758236187207_9c4paeh09/data.mdb +0 -0
- package/test_data_api_key_1758236187207_9c4paeh09/lock.mdb +0 -0
- package/test_data_api_key_1758236187441_4n3o3gkkl/data.mdb +0 -0
- package/test_data_api_key_1758236187441_4n3o3gkkl/lock.mdb +0 -0
- package/test_data_api_key_1758236187672_jt6b21ye0/data.mdb +0 -0
- package/test_data_api_key_1758236187672_jt6b21ye0/lock.mdb +0 -0
- package/test_data_api_key_1758236187788_oo84fz9u6/data.mdb +0 -0
- package/test_data_api_key_1758236187788_oo84fz9u6/lock.mdb +0 -0
- package/test_data_api_key_1758236195507_o9zeznwlm/data.mdb +0 -0
- package/test_data_api_key_1758236195507_o9zeznwlm/lock.mdb +0 -0
- package/test_data_api_key_1758236195619_qsqd60y41/data.mdb +0 -0
- package/test_data_api_key_1758236195619_qsqd60y41/lock.mdb +0 -0
- package/test_data_api_key_1758236195731_im13iq284/data.mdb +0 -0
- package/test_data_api_key_1758236195731_im13iq284/lock.mdb +0 -0
|
@@ -35,6 +35,131 @@ import { performance_monitor } from '../performance_monitor.js';
|
|
|
35
35
|
|
|
36
36
|
const { create_context_logger } = create_logger('admin');
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Safely retrieves database settings with fallback.
|
|
40
|
+
* @returns {Object} Settings object with defaults
|
|
41
|
+
*/
|
|
42
|
+
const get_safe_settings = () => {
|
|
43
|
+
try {
|
|
44
|
+
return get_settings();
|
|
45
|
+
} catch (settings_error) {
|
|
46
|
+
return { port: 1983 };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Safely retrieves database statistics.
|
|
52
|
+
* @param {Object} db - Database instance
|
|
53
|
+
* @returns {Object} Database statistics object
|
|
54
|
+
*/
|
|
55
|
+
const get_safe_database_stats = (db) => {
|
|
56
|
+
try {
|
|
57
|
+
const raw_stats = db.getStats ? db.getStats() : {};
|
|
58
|
+
return {
|
|
59
|
+
pageSize: raw_stats.pageSize || 0,
|
|
60
|
+
treeDepth: raw_stats.treeDepth || 0,
|
|
61
|
+
treeBranchPages: raw_stats.treeBranchPages || 0,
|
|
62
|
+
treeLeafPages: raw_stats.treeLeafPages || 0,
|
|
63
|
+
entryCount: raw_stats.entryCount || 0,
|
|
64
|
+
mapSize: raw_stats.mapSize || 0,
|
|
65
|
+
lastPageNumber: raw_stats.lastPageNumber || 0
|
|
66
|
+
};
|
|
67
|
+
} catch (stats_error) {
|
|
68
|
+
return { error: 'Could not retrieve database stats' };
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Counts collections and documents in database.
|
|
74
|
+
* @param {Object} db - Database instance
|
|
75
|
+
* @param {Object} log - Logger instance
|
|
76
|
+
* @returns {Object} Collections count and total documents
|
|
77
|
+
*/
|
|
78
|
+
const count_collections_and_documents = (db, log) => {
|
|
79
|
+
const collections = {};
|
|
80
|
+
let total_documents = 0;
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
for (const { key } of db.getRange()) {
|
|
84
|
+
if (typeof key === 'string' && key.includes(':') && !key.startsWith('_')) {
|
|
85
|
+
const collection_name = key.split(':')[0];
|
|
86
|
+
collections[collection_name] = (collections[collection_name] || 0) + 1;
|
|
87
|
+
total_documents++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (range_error) {
|
|
91
|
+
log.warn('Could not iterate database range for stats', { error: range_error.message });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { collections, total_documents };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Calculates memory usage in megabytes.
|
|
99
|
+
* @returns {Object} Memory usage statistics
|
|
100
|
+
*/
|
|
101
|
+
const calculate_memory_usage = () => {
|
|
102
|
+
const memory_usage = process.memoryUsage();
|
|
103
|
+
return {
|
|
104
|
+
rss: Math.round(memory_usage.rss / 1024 / 1024),
|
|
105
|
+
heapTotal: Math.round(memory_usage.heapTotal / 1024 / 1024),
|
|
106
|
+
heapUsed: Math.round(memory_usage.heapUsed / 1024 / 1024),
|
|
107
|
+
external: Math.round(memory_usage.external / 1024 / 1024)
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Calculates database size usage percentage.
|
|
113
|
+
* @param {Object} db_stats - Database statistics
|
|
114
|
+
* @returns {number} Usage percentage
|
|
115
|
+
*/
|
|
116
|
+
const calculate_database_usage_percent = (db_stats) => {
|
|
117
|
+
return db_stats.mapSize > 0
|
|
118
|
+
? Math.round((db_stats.lastPageNumber * db_stats.pageSize / db_stats.mapSize) * 100)
|
|
119
|
+
: 0;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Creates server statistics object.
|
|
124
|
+
* @param {Object} memory_usage_mb - Memory usage in MB
|
|
125
|
+
* @returns {Object} Server statistics
|
|
126
|
+
*/
|
|
127
|
+
const create_server_stats = (memory_usage_mb) => {
|
|
128
|
+
return {
|
|
129
|
+
uptime: Math.floor(process.uptime()),
|
|
130
|
+
uptime_formatted: format_uptime(process.uptime()),
|
|
131
|
+
memory_usage: memory_usage_mb,
|
|
132
|
+
memory_usage_raw: process.memoryUsage(),
|
|
133
|
+
node_version: process.version,
|
|
134
|
+
platform: process.platform,
|
|
135
|
+
arch: process.arch,
|
|
136
|
+
pid: process.pid,
|
|
137
|
+
cpu_usage: process.cpuUsage()
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Creates database statistics object.
|
|
143
|
+
* @param {number} total_documents - Total document count
|
|
144
|
+
* @param {Object} collections - Collections object
|
|
145
|
+
* @param {Object} db_stats - Database statistics
|
|
146
|
+
* @param {number} map_size_usage_percent - Usage percentage
|
|
147
|
+
* @returns {Object} Database statistics
|
|
148
|
+
*/
|
|
149
|
+
const create_database_stats = (total_documents, collections, db_stats, map_size_usage_percent) => {
|
|
150
|
+
return {
|
|
151
|
+
total_documents,
|
|
152
|
+
total_collections: Object.keys(collections).length,
|
|
153
|
+
collections,
|
|
154
|
+
stats: db_stats,
|
|
155
|
+
map_size_usage_percent,
|
|
156
|
+
disk_usage: {
|
|
157
|
+
map_size_mb: Math.round((db_stats.mapSize || 0) / 1024 / 1024),
|
|
158
|
+
used_space_mb: Math.round(((db_stats.lastPageNumber || 0) * (db_stats.pageSize || 0)) / 1024 / 1024)
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
|
|
38
163
|
/**
|
|
39
164
|
* Gets comprehensive server and database statistics.
|
|
40
165
|
* @returns {Object} Enhanced statistics including server, database, and performance metrics
|
|
@@ -45,85 +170,15 @@ const get_enhanced_stats = () => {
|
|
|
45
170
|
|
|
46
171
|
try {
|
|
47
172
|
const db = get_database();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} catch (settings_error) {
|
|
54
|
-
settings = { port: 1983 };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Safely get database stats
|
|
58
|
-
let db_stats = {};
|
|
59
|
-
try {
|
|
60
|
-
const raw_stats = db.getStats ? db.getStats() : {};
|
|
61
|
-
db_stats = {
|
|
62
|
-
pageSize: raw_stats.pageSize || 0,
|
|
63
|
-
treeDepth: raw_stats.treeDepth || 0,
|
|
64
|
-
treeBranchPages: raw_stats.treeBranchPages || 0,
|
|
65
|
-
treeLeafPages: raw_stats.treeLeafPages || 0,
|
|
66
|
-
entryCount: raw_stats.entryCount || 0,
|
|
67
|
-
mapSize: raw_stats.mapSize || 0,
|
|
68
|
-
lastPageNumber: raw_stats.lastPageNumber || 0
|
|
69
|
-
};
|
|
70
|
-
} catch (stats_error) {
|
|
71
|
-
db_stats = { error: 'Could not retrieve database stats' };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Count collections and documents safely
|
|
75
|
-
const collections = {};
|
|
76
|
-
let total_documents = 0;
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
for (const { key } of db.getRange()) {
|
|
80
|
-
if (typeof key === 'string' && key.includes(':') && !key.startsWith('_')) {
|
|
81
|
-
const collection_name = key.split(':')[0];
|
|
82
|
-
collections[collection_name] = (collections[collection_name] || 0) + 1;
|
|
83
|
-
total_documents++;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
} catch (range_error) {
|
|
87
|
-
log.warn('Could not iterate database range for stats', { error: range_error.message });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Calculate memory usage percentage
|
|
91
|
-
const memory_usage = process.memoryUsage();
|
|
92
|
-
const memory_usage_mb = {
|
|
93
|
-
rss: Math.round(memory_usage.rss / 1024 / 1024),
|
|
94
|
-
heapTotal: Math.round(memory_usage.heapTotal / 1024 / 1024),
|
|
95
|
-
heapUsed: Math.round(memory_usage.heapUsed / 1024 / 1024),
|
|
96
|
-
external: Math.round(memory_usage.external / 1024 / 1024)
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Calculate database size percentage
|
|
100
|
-
const map_size_usage_percent = db_stats.mapSize > 0
|
|
101
|
-
? Math.round((db_stats.lastPageNumber * db_stats.pageSize / db_stats.mapSize) * 100)
|
|
102
|
-
: 0;
|
|
173
|
+
const settings = get_safe_settings();
|
|
174
|
+
const db_stats = get_safe_database_stats(db);
|
|
175
|
+
const { collections, total_documents } = count_collections_and_documents(db, log);
|
|
176
|
+
const memory_usage_mb = calculate_memory_usage();
|
|
177
|
+
const map_size_usage_percent = calculate_database_usage_percent(db_stats);
|
|
103
178
|
|
|
104
179
|
return {
|
|
105
|
-
server:
|
|
106
|
-
|
|
107
|
-
uptime_formatted: format_uptime(process.uptime()),
|
|
108
|
-
memory_usage: memory_usage_mb,
|
|
109
|
-
memory_usage_raw: memory_usage,
|
|
110
|
-
node_version: process.version,
|
|
111
|
-
platform: process.platform,
|
|
112
|
-
arch: process.arch,
|
|
113
|
-
pid: process.pid,
|
|
114
|
-
cpu_usage: process.cpuUsage()
|
|
115
|
-
},
|
|
116
|
-
database: {
|
|
117
|
-
total_documents,
|
|
118
|
-
total_collections: Object.keys(collections).length,
|
|
119
|
-
collections,
|
|
120
|
-
stats: db_stats,
|
|
121
|
-
map_size_usage_percent,
|
|
122
|
-
disk_usage: {
|
|
123
|
-
map_size_mb: Math.round((db_stats.mapSize || 0) / 1024 / 1024),
|
|
124
|
-
used_space_mb: Math.round(((db_stats.lastPageNumber || 0) * (db_stats.pageSize || 0)) / 1024 / 1024)
|
|
125
|
-
}
|
|
126
|
-
},
|
|
180
|
+
server: create_server_stats(memory_usage_mb),
|
|
181
|
+
database: create_database_stats(total_documents, collections, db_stats, map_size_usage_percent),
|
|
127
182
|
performance: {
|
|
128
183
|
ops_per_second: calculate_ops_per_second(),
|
|
129
184
|
avg_response_time_ms: calculate_avg_response_time()
|
|
@@ -189,100 +244,147 @@ export const track_operation = (duration_ms) => {
|
|
|
189
244
|
};
|
|
190
245
|
|
|
191
246
|
/**
|
|
192
|
-
*
|
|
193
|
-
* @param {string}
|
|
194
|
-
* @returns {Object}
|
|
195
|
-
* @throws {Error} When collection listing fails
|
|
247
|
+
* Creates initial collection metadata object.
|
|
248
|
+
* @param {string} collection_name - Collection name
|
|
249
|
+
* @returns {Object} Collection metadata object
|
|
196
250
|
*/
|
|
197
|
-
const
|
|
198
|
-
|
|
251
|
+
const create_collection_metadata = (collection_name) => {
|
|
252
|
+
return {
|
|
253
|
+
name: collection_name,
|
|
254
|
+
document_count: 0,
|
|
255
|
+
indexes: [],
|
|
256
|
+
estimated_size_bytes: 0
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Scans database range for collections and documents.
|
|
262
|
+
* @param {Object} db - Database instance
|
|
263
|
+
* @param {string} database_name - Database name
|
|
264
|
+
* @param {Object} log - Logger instance
|
|
265
|
+
* @returns {Object} Collections map and total documents count
|
|
266
|
+
*/
|
|
267
|
+
const scan_database_for_collections = (db, database_name, log) => {
|
|
268
|
+
const collections_map = {};
|
|
269
|
+
let total_documents = 0;
|
|
199
270
|
|
|
200
271
|
try {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const key_database = parts[0];
|
|
212
|
-
const collection_name = parts[1];
|
|
213
|
-
|
|
214
|
-
// Only count collections from the specified database
|
|
215
|
-
if (key_database === database_name) {
|
|
216
|
-
if (!collections_map[collection_name]) {
|
|
217
|
-
collections_map[collection_name] = {
|
|
218
|
-
name: collection_name,
|
|
219
|
-
document_count: 0,
|
|
220
|
-
indexes: [],
|
|
221
|
-
estimated_size_bytes: 0
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
collections_map[collection_name].document_count++;
|
|
225
|
-
total_documents++;
|
|
272
|
+
for (const { key } of db.getRange()) {
|
|
273
|
+
if (typeof key === 'string' && key.includes(':') && !key.startsWith('_')) {
|
|
274
|
+
const parts = key.split(':');
|
|
275
|
+
if (parts.length >= 3) {
|
|
276
|
+
const key_database = parts[0];
|
|
277
|
+
const collection_name = parts[1];
|
|
278
|
+
|
|
279
|
+
if (key_database === database_name) {
|
|
280
|
+
if (!collections_map[collection_name]) {
|
|
281
|
+
collections_map[collection_name] = create_collection_metadata(collection_name);
|
|
226
282
|
}
|
|
283
|
+
collections_map[collection_name].document_count++;
|
|
284
|
+
total_documents++;
|
|
227
285
|
}
|
|
228
286
|
}
|
|
229
287
|
}
|
|
230
|
-
}
|
|
231
|
-
|
|
288
|
+
}
|
|
289
|
+
} catch (range_error) {
|
|
290
|
+
log.warn('Could not iterate database range for collections', { error: range_error.message });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { collections_map, total_documents };
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Fallback method to find collections using known patterns.
|
|
298
|
+
* @param {Object} db - Database instance
|
|
299
|
+
* @param {string} database_name - Database name
|
|
300
|
+
* @param {Object} collections_map - Existing collections map
|
|
301
|
+
* @returns {number} Additional documents found
|
|
302
|
+
*/
|
|
303
|
+
const fallback_collection_scan = (db, database_name, collections_map) => {
|
|
304
|
+
const potential_collections = [
|
|
305
|
+
'admin_test', 'test_collection', 'queue_test', 'users', 'products',
|
|
306
|
+
'orders', 'sessions', 'logs', 'analytics', 'settings', 'another_collection',
|
|
307
|
+
'list_test', 'pagination_test', 'get_test', 'query_test', 'admin_insert_test',
|
|
308
|
+
'admin_update_test', 'admin_delete_test'
|
|
309
|
+
];
|
|
310
|
+
|
|
311
|
+
let additional_documents = 0;
|
|
312
|
+
|
|
313
|
+
for (const collection_name of potential_collections) {
|
|
314
|
+
try {
|
|
315
|
+
const prefix = `${database_name}:${collection_name}:`;
|
|
316
|
+
const range = db.getRange({ start: prefix, end: prefix + '\xFF' });
|
|
232
317
|
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
'admin_update_test', 'admin_delete_test'
|
|
239
|
-
];
|
|
318
|
+
let document_count = 0;
|
|
319
|
+
for (const entry of range) {
|
|
320
|
+
document_count++;
|
|
321
|
+
additional_documents++;
|
|
322
|
+
}
|
|
240
323
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
for (const entry of range) {
|
|
249
|
-
document_count++;
|
|
250
|
-
total_documents++;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (document_count > 0) {
|
|
254
|
-
collections_map[collection_name] = {
|
|
255
|
-
name: collection_name,
|
|
256
|
-
document_count,
|
|
257
|
-
indexes: [],
|
|
258
|
-
estimated_size_bytes: document_count * 100 // Rough estimate
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
} catch (collection_error) {
|
|
262
|
-
// Skip collections that can't be accessed
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
324
|
+
if (document_count > 0) {
|
|
325
|
+
collections_map[collection_name] = {
|
|
326
|
+
name: collection_name,
|
|
327
|
+
document_count,
|
|
328
|
+
indexes: [],
|
|
329
|
+
estimated_size_bytes: document_count * 100
|
|
330
|
+
};
|
|
265
331
|
}
|
|
332
|
+
} catch (collection_error) {
|
|
333
|
+
continue;
|
|
266
334
|
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return additional_documents;
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Adds index information to collections.
|
|
342
|
+
* @param {Object} db - Database instance
|
|
343
|
+
* @param {string} database_name - Database name
|
|
344
|
+
* @param {Object} collections_map - Collections map to update
|
|
345
|
+
* @param {Object} log - Logger instance
|
|
346
|
+
*/
|
|
347
|
+
const add_index_information = (db, database_name, collections_map, log) => {
|
|
348
|
+
try {
|
|
349
|
+
const index_prefix = `index:${database_name}:`;
|
|
350
|
+
const index_range = db.getRange({ start: index_prefix, end: index_prefix + '\xFF' });
|
|
267
351
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (!collections_map[collection_name].indexes.includes(remaining_key.split(':')[1])) {
|
|
278
|
-
collections_map[collection_name].indexes.push(remaining_key.split(':')[1]);
|
|
279
|
-
}
|
|
352
|
+
for (const { key, value } of index_range) {
|
|
353
|
+
if (typeof key === 'string' && key.startsWith(index_prefix)) {
|
|
354
|
+
const remaining_key = key.substring(index_prefix.length);
|
|
355
|
+
const collection_name = remaining_key.split(':')[0];
|
|
356
|
+
const index_name = remaining_key.split(':')[1];
|
|
357
|
+
|
|
358
|
+
if (collections_map[collection_name] && index_name) {
|
|
359
|
+
if (!collections_map[collection_name].indexes.includes(index_name)) {
|
|
360
|
+
collections_map[collection_name].indexes.push(index_name);
|
|
280
361
|
}
|
|
281
362
|
}
|
|
282
363
|
}
|
|
283
|
-
} catch (index_range_error) {
|
|
284
|
-
log.warn('Could not iterate index range', { error: index_range_error.message });
|
|
285
364
|
}
|
|
365
|
+
} catch (index_range_error) {
|
|
366
|
+
log.warn('Could not iterate index range', { error: index_range_error.message });
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Lists all collections in the database with metadata.
|
|
372
|
+
* @param {string} [database_name='default'] - Database name to list collections from
|
|
373
|
+
* @returns {Object} Object containing collections array, total counts
|
|
374
|
+
* @throws {Error} When collection listing fails
|
|
375
|
+
*/
|
|
376
|
+
const list_collections = (database_name = 'default') => {
|
|
377
|
+
const log = create_context_logger();
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
const db = get_database();
|
|
381
|
+
let { collections_map, total_documents } = scan_database_for_collections(db, database_name, log);
|
|
382
|
+
|
|
383
|
+
if (Object.keys(collections_map).length === 0) {
|
|
384
|
+
total_documents += fallback_collection_scan(db, database_name, collections_map);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
add_index_information(db, database_name, collections_map, log);
|
|
286
388
|
|
|
287
389
|
const collections_array = Object.values(collections_map);
|
|
288
390
|
|
|
@@ -416,6 +518,80 @@ const get_document = (collection, document_id, database = 'default') => {
|
|
|
416
518
|
}
|
|
417
519
|
};
|
|
418
520
|
|
|
521
|
+
/**
|
|
522
|
+
* Evaluates a single query operator against a document value.
|
|
523
|
+
* @param {string} operator - Query operator (e.g., '$gt', '$regex')
|
|
524
|
+
* @param {*} op_value - Operator value
|
|
525
|
+
* @param {*} doc_value - Document field value
|
|
526
|
+
* @param {Object} filter_value - Full filter value for context
|
|
527
|
+
* @returns {boolean} Whether the operator matches
|
|
528
|
+
*/
|
|
529
|
+
const evaluate_query_operator = (operator, op_value, doc_value, filter_value) => {
|
|
530
|
+
switch (operator) {
|
|
531
|
+
case '$gt': return doc_value > op_value;
|
|
532
|
+
case '$gte': return doc_value >= op_value;
|
|
533
|
+
case '$lt': return doc_value < op_value;
|
|
534
|
+
case '$lte': return doc_value <= op_value;
|
|
535
|
+
case '$ne': return doc_value !== op_value;
|
|
536
|
+
case '$in': return Array.isArray(op_value) && op_value.includes(doc_value);
|
|
537
|
+
case '$regex':
|
|
538
|
+
const regex_options = filter_value.$options || '';
|
|
539
|
+
return new RegExp(op_value, regex_options).test(String(doc_value));
|
|
540
|
+
default: return doc_value === filter_value;
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Checks if a document matches the query filter.
|
|
546
|
+
* @param {Object} document - Document to check
|
|
547
|
+
* @param {Object} filter - Query filter
|
|
548
|
+
* @returns {boolean} Whether document matches filter
|
|
549
|
+
*/
|
|
550
|
+
const document_matches_filter = (document, filter) => {
|
|
551
|
+
return Object.keys(filter).every(field => {
|
|
552
|
+
const filter_value = filter[field];
|
|
553
|
+
const doc_value = document[field];
|
|
554
|
+
|
|
555
|
+
if (typeof filter_value === 'object' && filter_value !== null) {
|
|
556
|
+
return Object.keys(filter_value).every(operator => {
|
|
557
|
+
const op_value = filter_value[operator];
|
|
558
|
+
return evaluate_query_operator(operator, op_value, doc_value, filter_value);
|
|
559
|
+
});
|
|
560
|
+
} else {
|
|
561
|
+
return doc_value === filter_value;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Processes a single document during query iteration.
|
|
568
|
+
* @param {string} key - Document key
|
|
569
|
+
* @param {string} value - Document value
|
|
570
|
+
* @param {string} prefix - Collection prefix
|
|
571
|
+
* @param {Object} filter - Query filter
|
|
572
|
+
* @param {Object} log - Logger instance
|
|
573
|
+
* @returns {Object|null} Processed document or null if invalid
|
|
574
|
+
*/
|
|
575
|
+
const process_query_document = (key, value, prefix, filter, log) => {
|
|
576
|
+
try {
|
|
577
|
+
const document = JSON.parse(value);
|
|
578
|
+
const document_id = key.substring(prefix.length);
|
|
579
|
+
const full_document = { _id: document_id, ...document };
|
|
580
|
+
|
|
581
|
+
if (document_matches_filter(full_document, filter)) {
|
|
582
|
+
return full_document;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return null;
|
|
586
|
+
} catch (parse_error) {
|
|
587
|
+
log.warn('Could not parse document during query', {
|
|
588
|
+
key,
|
|
589
|
+
error: parse_error.message
|
|
590
|
+
});
|
|
591
|
+
return null;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
419
595
|
/**
|
|
420
596
|
* Queries documents in a collection with filtering and pagination.
|
|
421
597
|
* @param {string} collection - Collection name
|
|
@@ -448,58 +624,20 @@ const query_documents = (collection, filter = {}, options = {}) => {
|
|
|
448
624
|
if (typeof key === 'string' && key.startsWith(prefix)) {
|
|
449
625
|
total_examined++;
|
|
450
626
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const filter_value = filter[field];
|
|
459
|
-
const doc_value = full_document[field];
|
|
460
|
-
|
|
461
|
-
if (typeof filter_value === 'object' && filter_value !== null) {
|
|
462
|
-
// Handle operators like { $gt: 5 }, { $regex: "pattern" }
|
|
463
|
-
return Object.keys(filter_value).every(operator => {
|
|
464
|
-
const op_value = filter_value[operator];
|
|
465
|
-
switch (operator) {
|
|
466
|
-
case '$gt': return doc_value > op_value;
|
|
467
|
-
case '$gte': return doc_value >= op_value;
|
|
468
|
-
case '$lt': return doc_value < op_value;
|
|
469
|
-
case '$lte': return doc_value <= op_value;
|
|
470
|
-
case '$ne': return doc_value !== op_value;
|
|
471
|
-
case '$in': return Array.isArray(op_value) && op_value.includes(doc_value);
|
|
472
|
-
case '$regex':
|
|
473
|
-
// Handle $options parameter for regex flags
|
|
474
|
-
const regex_options = filter_value.$options || '';
|
|
475
|
-
return new RegExp(op_value, regex_options).test(String(doc_value));
|
|
476
|
-
default: return doc_value === filter_value;
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
} else {
|
|
480
|
-
return doc_value === filter_value;
|
|
481
|
-
}
|
|
482
|
-
});
|
|
627
|
+
const processed_document = process_query_document(key, value, prefix, filter, log);
|
|
628
|
+
|
|
629
|
+
if (processed_document) {
|
|
630
|
+
if (skipped < skip) {
|
|
631
|
+
skipped++;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
483
634
|
|
|
484
|
-
if (
|
|
485
|
-
|
|
486
|
-
skipped++;
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if (count >= limit) {
|
|
491
|
-
break;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
documents.push(full_document);
|
|
495
|
-
count++;
|
|
635
|
+
if (count >= limit) {
|
|
636
|
+
break;
|
|
496
637
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
key,
|
|
501
|
-
error: parse_error.message
|
|
502
|
-
});
|
|
638
|
+
|
|
639
|
+
documents.push(processed_document);
|
|
640
|
+
count++;
|
|
503
641
|
}
|
|
504
642
|
}
|
|
505
643
|
}
|