hola-server 1.0.11 → 2.0.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 (83) hide show
  1. package/README.md +196 -1
  2. package/core/array.js +79 -142
  3. package/core/bash.js +208 -259
  4. package/core/chart.js +26 -16
  5. package/core/cron.js +14 -3
  6. package/core/date.js +15 -44
  7. package/core/encrypt.js +19 -9
  8. package/core/file.js +42 -29
  9. package/core/lhs.js +32 -6
  10. package/core/meta.js +213 -289
  11. package/core/msg.js +20 -7
  12. package/core/number.js +105 -103
  13. package/core/obj.js +15 -12
  14. package/core/random.js +9 -6
  15. package/core/role.js +69 -77
  16. package/core/thread.js +12 -2
  17. package/core/type.js +300 -261
  18. package/core/url.js +20 -12
  19. package/core/validate.js +29 -26
  20. package/db/db.js +297 -227
  21. package/db/entity.js +631 -963
  22. package/db/gridfs.js +120 -166
  23. package/design/add_default_field_attr.md +56 -0
  24. package/http/context.js +22 -8
  25. package/http/cors.js +25 -8
  26. package/http/error.js +27 -9
  27. package/http/express.js +70 -41
  28. package/http/params.js +70 -42
  29. package/http/router.js +51 -40
  30. package/http/session.js +59 -36
  31. package/index.js +85 -9
  32. package/package.json +2 -2
  33. package/router/clone.js +28 -36
  34. package/router/create.js +21 -26
  35. package/router/delete.js +24 -28
  36. package/router/read.js +137 -123
  37. package/router/update.js +38 -56
  38. package/setting.js +22 -6
  39. package/skills/array.md +155 -0
  40. package/skills/bash.md +91 -0
  41. package/skills/chart.md +54 -0
  42. package/skills/code.md +422 -0
  43. package/skills/context.md +177 -0
  44. package/skills/date.md +58 -0
  45. package/skills/express.md +255 -0
  46. package/skills/file.md +60 -0
  47. package/skills/lhs.md +54 -0
  48. package/skills/meta.md +1023 -0
  49. package/skills/msg.md +30 -0
  50. package/skills/number.md +88 -0
  51. package/skills/obj.md +36 -0
  52. package/skills/params.md +206 -0
  53. package/skills/random.md +22 -0
  54. package/skills/role.md +59 -0
  55. package/skills/session.md +281 -0
  56. package/skills/storage.md +743 -0
  57. package/skills/thread.md +22 -0
  58. package/skills/type.md +547 -0
  59. package/skills/url.md +34 -0
  60. package/skills/validate.md +48 -0
  61. package/test/cleanup/close-db.js +5 -0
  62. package/test/core/array.js +226 -0
  63. package/test/core/chart.js +51 -0
  64. package/test/core/file.js +59 -0
  65. package/test/core/lhs.js +44 -0
  66. package/test/core/number.js +167 -12
  67. package/test/core/obj.js +47 -0
  68. package/test/core/random.js +24 -0
  69. package/test/core/thread.js +20 -0
  70. package/test/core/type.js +216 -0
  71. package/test/core/validate.js +67 -0
  72. package/test/db/db-ops.js +99 -0
  73. package/test/db/pipe_test.txt +0 -0
  74. package/test/db/test_case_design.md +528 -0
  75. package/test/db/test_db_class.js +613 -0
  76. package/test/db/test_entity_class.js +414 -0
  77. package/test/db/test_gridfs_class.js +234 -0
  78. package/test/entity/create.js +1 -1
  79. package/test/entity/delete-mixed.js +156 -0
  80. package/test/entity/ref-filter.js +63 -0
  81. package/tool/gen_i18n.js +55 -21
  82. package/test/crud/router.js +0 -99
  83. package/test/router/user.js +0 -17
package/db/db.js CHANGED
@@ -1,376 +1,446 @@
1
+ /**
2
+ * @fileoverview Database utilities for MongoDB using mongoist.
3
+ * @module db/db
4
+ */
5
+
1
6
  const mongoist = require("mongoist");
2
7
 
3
8
  const { get_settings } = require("../setting");
4
9
  const { get_context_value } = require("../http/context");
5
10
  const { format_date_time } = require("../core/date");
6
11
 
12
+ // Log level constants
7
13
  const LOG_LEVEL_DEBUG = 0;
8
14
  const LOG_LEVEL_INFO = 1;
9
15
  const LOG_LEVEL_WARN = 2;
10
16
  const LOG_LEVEL_ERROR = 3;
11
17
 
18
+ // Log category constants
12
19
  const LOG_DB = "database";
13
20
  const LOG_ENTITY = "entity";
14
21
  const LOG_SYSTEM = "system";
15
22
 
16
- const get_session_userid = () => {
17
- const req = get_context_value("req");
18
- return req && req.session && req.session.user ? req.session.user.id : "";
19
- };
20
-
21
- const log_msg = (category, level, msg, extra) => {
22
- const time = format_date_time(new Date());
23
- const db = get_db();
24
- const { col_log } = get_settings().log;
25
-
26
- const req = get_context_value("req");
27
- const path = req ? req.originalUrl : "";
28
- const user = req && req.session && req.session.user ? req.session.user.id : "";
29
-
30
- db.create(col_log, { time: time, category: category, level: level, msg: msg, user: user, path: path, ...extra }).then(() => { });
31
- };
32
-
33
- const is_log_debug = () => {
34
- const { save_db, log_level } = get_settings().log;
35
- return save_db && log_level <= LOG_LEVEL_DEBUG;
36
- };
37
-
38
- const is_log_info = () => {
39
- const { save_db, log_level } = get_settings().log;
40
- return save_db && log_level <= LOG_LEVEL_INFO;
41
- };
42
-
43
- const is_log_warn = () => {
44
- const { save_db, log_level } = get_settings().log;
45
- return save_db && log_level <= LOG_LEVEL_WARN;
46
- };
23
+ /**
24
+ * Resolve user id from request context if available.
25
+ * @returns {string} user id or empty string when unauthenticated
26
+ */
27
+ const get_session_user_id = () => get_context_value("req")?.session?.user?.id || "";
47
28
 
48
- const is_log_error = () => {
29
+ /**
30
+ * Check if logging is enabled for the given level.
31
+ * @param {number} level - Log level to check
32
+ * @returns {boolean} true if logging is enabled
33
+ */
34
+ const is_log_enabled = (level) => {
49
35
  const { save_db, log_level } = get_settings().log;
50
- return save_db && log_level <= LOG_LEVEL_ERROR;
36
+ return save_db && log_level <= level;
51
37
  };
52
38
 
53
- const log_debug = (category, msg, extra) => {
54
- if (is_log_debug()) {
55
- log_msg(category, LOG_LEVEL_DEBUG, msg, extra);
56
- }
57
- };
39
+ // Log level checkers using the unified helper
40
+ const is_log_debug = () => is_log_enabled(LOG_LEVEL_DEBUG);
41
+ const is_log_info = () => is_log_enabled(LOG_LEVEL_INFO);
42
+ const is_log_warn = () => is_log_enabled(LOG_LEVEL_WARN);
43
+ const is_log_error = () => is_log_enabled(LOG_LEVEL_ERROR);
58
44
 
59
- const log_info = (category, msg, extra) => {
60
- if (is_log_info()) {
61
- log_msg(category, LOG_LEVEL_INFO, msg, extra);
62
- }
45
+ /**
46
+ * Write a log entry to the database.
47
+ * @param {string} category - Log category
48
+ * @param {number} level - Log level
49
+ * @param {string} message - Log message
50
+ * @param {Object} extra - Additional data
51
+ */
52
+ const log_message = (category, level, message, extra = {}) => {
53
+ const req = get_context_value("req");
54
+ const entry = {
55
+ time: format_date_time(new Date()),
56
+ category,
57
+ level,
58
+ msg: message,
59
+ user: req?.session?.user?.id || "",
60
+ path: req?.originalUrl || "",
61
+ ...extra
62
+ };
63
+
64
+ get_db().create(get_settings().log.col_log, entry).catch(() => { });
63
65
  };
64
66
 
65
- const log_warn = (category, msg, extra) => {
66
- if (is_log_warn()) {
67
- log_msg(category, LOG_LEVEL_WARN, msg, extra);
67
+ /**
68
+ * Create a log function for a specific level.
69
+ * @param {number} level - Log level
70
+ * @param {Function} check_fn - Function to check if logging is enabled
71
+ * @returns {Function} Log function
72
+ */
73
+ const create_log_fn = (level, check_fn) => (category, msg, extra) => {
74
+ if (check_fn()) {
75
+ log_message(category, level, msg, extra);
68
76
  }
69
77
  };
70
78
 
71
- const log_error = (category, msg, extra) => {
72
- if (is_log_error()) {
73
- log_msg(category, LOG_LEVEL_ERROR, msg, extra);
74
- }
75
- };
79
+ const log_debug = create_log_fn(LOG_LEVEL_DEBUG, is_log_debug);
80
+ const log_info = create_log_fn(LOG_LEVEL_INFO, is_log_info);
81
+ const log_warn = create_log_fn(LOG_LEVEL_WARN, is_log_warn);
82
+ const log_error = create_log_fn(LOG_LEVEL_ERROR, is_log_error);
76
83
 
77
84
  /**
78
85
  * Construct mongodb ObjectId
79
- * @param {string value of the id} id
80
- * @returns
86
+ * @param {string} id - String value of the id
87
+ * @returns {ObjectId} MongoDB ObjectId
81
88
  */
82
- const oid = (id) => {
83
- return mongoist.ObjectId(id);
84
- };
89
+ const oid = (id) => mongoist.ObjectId(id);
85
90
 
86
91
  /**
87
92
  * Construct objectId query object
88
- * @param {string value of the id} id
89
- * @returns id query if id is valid otherwise return null
93
+ * @param {string} id - String value of the id
94
+ * @returns {Object|null} id query if id is valid, otherwise null
90
95
  */
91
96
  const oid_query = (id) => {
92
97
  try {
93
98
  return { _id: oid(id) };
94
- } catch (e) {
99
+ } catch {
95
100
  return null;
96
101
  }
97
102
  };
98
103
 
99
104
  /**
100
- * Construct in query of ObjectId array
101
- * @param {string value of the id} ids
102
- * @returns id in query if id is valid otherwise return null
105
+ * Construct $in query of ObjectId array
106
+ * @param {string[]} ids - Array of string ids
107
+ * @returns {Object|null} id $in query if valid, otherwise null
103
108
  */
104
109
  const oid_queries = (ids) => {
105
110
  try {
106
- const id_array = ids.map((id) => oid(id));
107
- return { _id: { $in: id_array } };
108
- } catch (e) {
111
+ return { _id: { $in: ids.map(oid) } };
112
+ } catch {
109
113
  return null;
110
114
  }
111
115
  };
112
116
 
113
117
  /**
114
118
  * Execute bulk update using the items
115
- * @param {mongodb collection} col
116
- * @param {the items to execute bulk update} items
117
- * @param {the attributes used as search criteria} attrs
118
- * @returns
119
+ * @param {Object} col - MongoDB collection
120
+ * @param {Object[]} items - Items to execute bulk update
121
+ * @param {string[]} attrs - Attributes used as search criteria
122
+ * @returns {Promise} Bulk operation result
119
123
  */
120
124
  const bulk_update = async (col, items, attrs) => {
121
125
  const bulk = col.initializeOrderedBulkOp();
122
126
  for (const item of items) {
123
- const query = {};
124
- attrs.forEach(function (attr) {
125
- query[attr] = item[attr];
126
- });
127
- delete item["_id"];
128
- bulk.find(query).upsert().update({ $set: item }, true);
127
+ const query = attrs.reduce((acc, attr) => ({ ...acc, [attr]: item[attr] }), {});
128
+ const { _id, ...without_id } = item;
129
+ bulk.find(query).upsert().update({ $set: without_id }, true);
129
130
  }
130
- return await bulk.execute();
131
+ return bulk.execute();
132
+ };
133
+
134
+ /**
135
+ * Log debug message for DB operations.
136
+ * @param {string} operation - Operation name
137
+ * @param {string} code - Collection name
138
+ * @param {Object} params - Operation parameters
139
+ */
140
+ const debug_log = (operation, code, params = {}) => {
141
+ if (!is_log_debug()) return;
142
+
143
+ const parts = Object.entries(params)
144
+ .filter(([, v]) => v !== undefined)
145
+ .map(([k, v]) => `${k}:${JSON.stringify(v)}`);
146
+
147
+ log_debug(LOG_DB, `${operation} for [${code}]${parts.length ? ` with ${parts.join(", ")}` : ""}`);
131
148
  };
132
149
 
133
150
  class DB {
134
151
  constructor(url, options, callback) {
135
152
  if (!url) {
136
- return;
153
+ throw new Error("Mongo url is required to initialize DB");
137
154
  }
138
155
 
139
156
  this.db = mongoist(url, options);
157
+ this.closed = false;
140
158
 
141
- this.db.on("error", function (err) {
142
- if (is_log_error()) {
143
- log_error(LOG_DB, err);
144
- }
145
- });
146
-
147
- this.db.on('connect', function () {
148
- callback && callback();
159
+ this.db.on("error", (err) => log_error(LOG_DB, err));
160
+ this.db.on("close", () => { this.closed = true; });
161
+ this.db.on("connect", () => {
162
+ this.closed = false;
163
+ callback?.();
149
164
  });
150
165
  }
151
166
 
152
167
  /**
153
- * get db collection
154
- * @param {mongodb collection name} code
155
- * @returns
168
+ * Get db collection
169
+ * @param {string} code - Collection name
170
+ * @returns {Object} MongoDB collection
156
171
  */
157
- get_col(code) {
172
+ col(code) {
158
173
  return this.db[code];
159
174
  }
160
175
 
161
176
  /**
162
- * Insert Object to db
163
- * @param {mongodb collection name} code
164
- * @param {inserted object} obj
165
- * @returns
177
+ * Insert object to db
178
+ * @param {string} code - Collection name
179
+ * @param {Object} obj - Object to insert
180
+ * @returns {Promise} Insert result
166
181
  */
167
182
  create(code, obj) {
168
- const col = this.db[code];
169
183
  delete obj["_id"];
170
- return col.insert(obj, { checkKeys: false });
184
+ return this.col(code).insert(obj, { checkKeys: false });
171
185
  }
172
186
 
173
187
  /**
174
- * Update the object, upsert:true, multi:true
175
- * @param {mongodb collection name} code
176
- * @param {*} query
177
- * @param {*} obj
178
- * @returns
188
+ * Update objects with upsert and multi enabled
189
+ * @param {string} code - Collection name
190
+ * @param {Object} query - Query criteria
191
+ * @param {Object} obj - Object to update
192
+ * @returns {Promise} Update result
179
193
  */
180
194
  update(code, query, obj) {
181
- if (is_log_debug()) {
182
- log_debug(LOG_DB, "updating obj:" + JSON.stringify(obj) + ", for [" + code + "] with query:" + JSON.stringify(query));
183
- }
184
-
185
- const col = this.db[code];
195
+ debug_log("updating", code, { query, obj });
186
196
  delete obj["_id"];
187
- return col.update(query, { $set: obj }, { upsert: true, multi: true });
197
+ return this.col(code).update(query, { $set: obj }, { upsert: true, multi: true });
188
198
  }
189
199
 
190
200
  /**
191
- * Remove the records from mongodb
192
- * @param {mongodb collection name} code
193
- * @param {query to execute delete op} query
194
- * @returns
201
+ * Remove records from mongodb
202
+ * @param {string} code - Collection name
203
+ * @param {Object} query - Query to execute delete
204
+ * @returns {Promise} Delete result
195
205
  */
196
206
  delete(code, query) {
197
- if (is_log_debug()) {
198
- log_debug(LOG_DB, "deleting objects for [" + code + "] with query:" + JSON.stringify(query));
199
- }
200
-
201
- const col = this.db[code];
202
- return col.remove(query);
207
+ debug_log("deleting", code, { query });
208
+ return this.col(code).remove(query);
203
209
  }
204
210
 
205
211
  /**
206
- * Search the db using query
207
- * @param {mongodb collection name} code
208
- * @param {search criteria} query
209
- * @param {the attributes to load from db} attr
210
- * @returns
212
+ * Find records by query
213
+ * @param {string} code - Collection name
214
+ * @param {Object} query - Search criteria
215
+ * @param {Object} attr - Attributes to load
216
+ * @returns {Promise} Query result
211
217
  */
212
218
  find(code, query, attr) {
213
- if (is_log_debug()) {
214
- log_debug(LOG_DB, "find objects for [" + code + "] with query:" + JSON.stringify(query) + " and attr:" + JSON.stringify(attr));
215
- }
216
-
217
- const col = this.db[code];
218
- return col.find(query, attr);
219
+ debug_log("find", code, { query, attr });
220
+ return this.col(code).find(query, attr);
219
221
  }
220
222
 
221
223
  /**
222
- * Find one record from db
223
- * @param {mongodb collection name} code
224
- * @param {search criteria} query
225
- * @param {the attributes to load from db} attr
226
- * @returns
224
+ * Find one record
225
+ * @param {string} code - Collection name
226
+ * @param {Object} query - Search criteria
227
+ * @param {Object} attr - Attributes to load
228
+ * @returns {Promise} Single record or null
227
229
  */
228
230
  find_one(code, query, attr) {
229
- if (is_log_debug()) {
230
- log_debug(LOG_DB, "find_one for [" + code + "] with query:" + JSON.stringify(query) + " and attr:" + JSON.stringify(attr));
231
- }
232
-
233
- const col = this.db[code];
234
- return col.findOne(query, attr);
231
+ debug_log("find_one", code, { query, attr });
232
+ return this.col(code).findOne(query, attr);
235
233
  }
236
234
 
237
235
  /**
238
- * Find the records from db using sort to do sorting
239
- * @param {mongodb collection name} code
240
- * @param {search criteria} query
241
- * @param {sort object to sort the result} sort
242
- * @param {the attributes of the object to load from db} attr
243
- * @returns
236
+ * Find records with sorting
237
+ * @param {string} code - Collection name
238
+ * @param {Object} query - Search criteria
239
+ * @param {Object} sort - Sort specification
240
+ * @param {Object} attr - Attributes to load
241
+ * @returns {Promise} Sorted query result
244
242
  */
245
243
  find_sort(code, query, sort, attr) {
246
- if (is_log_debug()) {
247
- log_debug(LOG_DB, "find_sort for [" + code + "] with query:" + JSON.stringify(query) + " and attr:" + JSON.stringify(attr) + " and sort:" + JSON.stringify(sort));
248
- }
249
-
250
- const col = this.db[code];
251
- return col.find(query, attr, { sort: sort });
244
+ debug_log("find_sort", code, { query, attr, sort });
245
+ return this.col(code).find(query, attr, { sort });
252
246
  }
253
247
 
254
248
  /**
255
- * Find the page records
256
- * @param {mongodb collection name} code
257
- * @param {search criteria} query
258
- * @param {sort object to sort the results} sort
259
- * @param {the page index to load} page
260
- * @param {page size } limit
261
- * @param {the attributes of the object to load from db} attr
262
- * @returns
249
+ * Find paginated records
250
+ * @param {string} code - Collection name
251
+ * @param {Object} query - Search criteria
252
+ * @param {Object} sort - Sort specification
253
+ * @param {number} page - Page index (1-based)
254
+ * @param {number} limit - Page size
255
+ * @param {Object} attr - Attributes to load
256
+ * @returns {Promise} Paginated query result
263
257
  */
264
258
  find_page(code, query, sort, page, limit, attr) {
265
- if (is_log_debug()) {
266
- log_debug(LOG_DB, "find_page for [" + code + "] with query:" + JSON.stringify(query) + " and attr:" + JSON.stringify(attr) + " and sort:" + JSON.stringify(sort) + ", page:" + page + ",limit:" + limit);
267
- }
268
-
269
- const skip = (page - 1) * limit > 0 ? (page - 1) * limit : 0;
270
- const col = this.db[code];
271
- return col.find(query, attr, { sort: sort, skip: skip, limit: limit });
259
+ debug_log("find_page", code, { query, attr, sort, page, limit });
260
+ const skip = Math.max((page - 1) * limit, 0);
261
+ return this.col(code).find(query, attr, { sort, skip, limit });
272
262
  }
273
263
 
274
264
  /**
275
- * The count number of the query
276
- * @param {mongodb collection name} code
277
- * @param {search criteria} query
278
- * @returns
265
+ * Count records matching query
266
+ * @param {string} code - Collection name
267
+ * @param {Object} query - Search criteria
268
+ * @returns {Promise<number>} Count result
279
269
  */
280
270
  count(code, query) {
281
- if (is_log_debug()) {
282
- log_debug(LOG_DB, "count for [" + code + "] with query:" + JSON.stringify(query));
283
- }
284
-
285
- const col = this.db[code];
286
- return col.count(query);
271
+ debug_log("count", code, { query });
272
+ return this.col(code).count(query);
287
273
  }
288
274
 
289
275
  /**
290
- * Calculate the sum value based on the field and query criteria
291
- * @param {mongodb collection name} code
292
- * @param {search criteria} query
293
- * @param {field name to calculate sum} field
294
- * @returns
276
+ * Calculate sum of a field
277
+ * @param {string} code - Collection name
278
+ * @param {Object} query - Search criteria
279
+ * @param {string} field - Field name to sum
280
+ * @returns {Promise<number>} Sum result
295
281
  */
296
282
  sum(code, query, field) {
297
- if (is_log_debug()) {
298
- log_debug(LOG_DB, "sum for [" + code + "] with query:" + JSON.stringify(query) + ", and field:" + JSON.stringify(field));
299
- }
300
-
301
- const col = this.db[code];
302
- return col.aggregate([{ $match: query }, { $group: { _id: null, total: { $sum: "$" + field + "" } } }]).then((result) => (result.length > 0 ? result[0].total : 0));
283
+ debug_log("sum", code, { query, field });
284
+ return this.col(code)
285
+ .aggregate([{ $match: query }, { $group: { _id: null, total: { $sum: `$${field}` } } }])
286
+ .then((result) => result[0]?.total ?? 0);
303
287
  }
304
288
 
305
289
  /**
306
- * Pull the object from array
307
- * @param {mongodb collection name} code
308
- * @param {search criteria} query
309
- * @param {object pulled from the array} ele
310
- * @returns
290
+ * Pull element from array field
291
+ * @param {string} code - Collection name
292
+ * @param {Object} query - Search criteria
293
+ * @param {Object} ele - Element to pull
294
+ * @returns {Promise} Update result
311
295
  */
312
296
  pull(code, query, ele) {
313
- if (is_log_debug()) {
314
- log_debug(LOG_DB, "pull ele [" + JSON.stringify(ele) + "] with query:" + JSON.stringify(query) + ",code:" + code);
315
- }
316
-
317
- const col = this.db[code];
318
- return col.update(query, { $pull: ele }, { multi: true });
297
+ debug_log("pull", code, { query, ele });
298
+ return this.col(code).update(query, { $pull: ele }, { multi: true });
319
299
  }
320
300
 
321
301
  /**
322
- * Push the object to array
323
- * @param {mongodb collection name} code
324
- * @param {search criteria} query
325
- * @param {object push to the array} ele
326
- * @returns
302
+ * Push element to array field
303
+ * @param {string} code - Collection name
304
+ * @param {Object} query - Search criteria
305
+ * @param {Object} ele - Element to push
306
+ * @returns {Promise} Update result
327
307
  */
328
308
  push(code, query, ele) {
329
- if (is_log_debug()) {
330
- log_debug(LOG_DB, "push ele [" + JSON.stringify(ele) + "] with query:" + JSON.stringify(query) + ",code:" + code);
331
- }
332
-
333
- const col = this.db[code];
334
- return col.update(query, { $push: ele });
309
+ debug_log("push", code, { query, ele });
310
+ return this.col(code).update(query, { $push: ele });
335
311
  }
336
312
 
337
313
  /**
338
- * add the object to set
339
- * @param {mongodb collection name} code
340
- * @param {search criteria} query
341
- * @param {object added to the set} ele
342
- * @returns
314
+ * Add element to set field
315
+ * @param {string} code - Collection name
316
+ * @param {Object} query - Search criteria
317
+ * @param {Object} ele - Element to add
318
+ * @returns {Promise} Update result
343
319
  */
344
320
  add_to_set(code, query, ele) {
345
- const col = this.db[code];
346
- return col.update(query, { $addToSet: ele }, { upsert: true });
321
+ return this.col(code).update(query, { $addToSet: ele }, { upsert: true });
347
322
  }
348
323
 
349
324
  /**
350
- * Get the mongodb collection
351
- * @param {mongodb collection name} code
352
- * @returns
325
+ * Close the underlying Mongo connection
353
326
  */
354
- col(code) {
355
- return this.db[code];
327
+ async close() {
328
+ if (!this.db || this.closed) return;
329
+ if (typeof this.db.close === "function") {
330
+ await this.db.close();
331
+ }
332
+ this.closed = true;
356
333
  }
357
334
  }
358
335
 
359
336
  let db_instance = null;
360
337
 
361
338
  /**
362
- *
363
- * @returns db instance of mongodb
339
+ * Get or create the database instance
340
+ * @param {Function} callback - Called when connected
341
+ * @returns {DB} Database instance
364
342
  */
365
343
  const get_db = (callback) => {
366
- if (db_instance && db_instance.db) {
344
+ if (db_instance?.db && !db_instance.closed) {
367
345
  return db_instance;
368
- } else {
369
- const mongo = get_settings().mongo;
346
+ }
370
347
 
371
- db_instance = new DB(mongo.url, mongo.options, callback);
372
- return db_instance;
348
+ const { url, options } = get_settings().mongo || {};
349
+ if (!url) {
350
+ throw new Error("Mongo settings are missing url");
373
351
  }
352
+
353
+ db_instance = new DB(url, options, callback);
354
+ return db_instance;
374
355
  };
375
356
 
376
- module.exports = { oid, oid_query, oid_queries, bulk_update, get_db, log_debug, log_info, log_warn, log_error, is_log_debug, is_log_info, is_log_warn, is_log_error, LOG_DB, LOG_SYSTEM, LOG_ENTITY, get_session_userid };
357
+ /**
358
+ * Close the database connection
359
+ */
360
+ const close_db = async () => {
361
+ if (db_instance) {
362
+ await db_instance.close();
363
+ db_instance = null;
364
+ }
365
+ };
366
+
367
+ /**
368
+ * Auto-create indexes based on entity metadata.
369
+ * Safe to call multiple times - MongoDB's createIndex is idempotent.
370
+ * Should be called after all entity metas are registered (after validate_all_metas).
371
+ */
372
+ const ensure_entity_indexes = async () => {
373
+ const { get_all_metas, get_entity_meta } = require('../core/meta');
374
+ const db = get_db();
375
+
376
+ for (const collection of get_all_metas()) {
377
+ const meta = get_entity_meta(collection);
378
+ const col = db.col(collection);
379
+
380
+ try {
381
+ // 1. Primary key compound index (unique)
382
+ if (meta.primary_keys?.length > 0) {
383
+ const key = meta.primary_keys.reduce((k, f) => ({ ...k, [f]: 1 }), {});
384
+ await col.createIndex(key, { unique: true, background: true });
385
+ log_debug(LOG_DB, `index created for [${collection}] primary_keys: ${JSON.stringify(key)}`);
386
+ }
387
+
388
+ // 2. Search fields (individual indexes for query performance)
389
+ for (const field of meta.search_fields || []) {
390
+ if (!meta.primary_keys.includes(field.name)) {
391
+ await col.createIndex({ [field.name]: 1 }, { background: true });
392
+ log_debug(LOG_DB, `index created for [${collection}] search field: ${field.name}`);
393
+ }
394
+ }
395
+
396
+ // 3. Ref label (for reference lookups)
397
+ if (meta.ref_label && !meta.primary_keys.includes(meta.ref_label)) {
398
+ await col.createIndex({ [meta.ref_label]: 1 }, { background: true });
399
+ log_debug(LOG_DB, `index created for [${collection}] ref_label: ${meta.ref_label}`);
400
+ }
401
+
402
+ // 4. Ref fields (foreign key lookups)
403
+ for (const field of meta.ref_fields || []) {
404
+ if (!meta.primary_keys.includes(field.name)) {
405
+ await col.createIndex({ [field.name]: 1 }, { background: true });
406
+ log_debug(LOG_DB, `index created for [${collection}] ref field: ${field.name}`);
407
+ }
408
+ }
409
+ } catch (err) {
410
+ log_error(LOG_DB, `index creation failed for [${collection}]: ${err.message}`);
411
+ }
412
+ }
413
+
414
+ log_info(LOG_DB, `entity indexes ensured for ${get_all_metas().length} collections`);
415
+ };
416
+
417
+ module.exports = {
418
+ // ObjectId utilities
419
+ oid,
420
+ oid_query,
421
+ oid_queries,
422
+ bulk_update,
423
+
424
+ // Database access
425
+ get_db,
426
+ close_db,
427
+ ensure_entity_indexes,
428
+
429
+ // Logging functions
430
+ log_debug,
431
+ log_info,
432
+ log_warn,
433
+ log_error,
434
+ is_log_debug,
435
+ is_log_info,
436
+ is_log_warn,
437
+ is_log_error,
438
+
439
+ // Log categories
440
+ LOG_DB,
441
+ LOG_SYSTEM,
442
+ LOG_ENTITY,
443
+
444
+ // Session
445
+ get_session_user_id
446
+ };