hola-server 1.0.10 → 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/README.md CHANGED
@@ -1 +1,196 @@
1
- # This is a meta programming framework used to build nodejs restful api
1
+ # Hola Server
2
+
3
+ A meta-programming framework for building Node.js RESTful APIs with MongoDB. Hola Server provides a declarative, metadata-driven approach to building CRUD APIs with built-in authentication, role-based access control, and file handling.
4
+
5
+ ## Features
6
+
7
+ - **Meta-Driven CRUD**: Define entity schemas with metadata and get full CRUD APIs automatically
8
+ - **Role-Based Access Control**: Fine-grained permissions per entity, operation, and view
9
+ - **Type System**: Extensible type conversion and validation system
10
+ - **File Handling**: Built-in GridFS integration for file uploads
11
+ - **Session Management**: Express session with MongoDB store
12
+ - **Entity Relationships**: Support for references with cascade/keep delete behavior
13
+ - **Query Building**: Advanced search, filtering, and pagination
14
+ - **Async Context**: Request-scoped context using AsyncLocalStorage
15
+ - **Comprehensive Testing**: Full test suite with 111+ passing tests
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ npm install
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ### 1. Configure Settings
26
+
27
+ ```javascript
28
+ const { init_settings } = require("hola-server");
29
+
30
+ init_settings({
31
+ mongo: {
32
+ url: "mongodb://localhost/myapp",
33
+ pool: 10,
34
+ },
35
+ server: {
36
+ service_port: 8088,
37
+ client_web_url: ["http://localhost:3000"],
38
+ session: {
39
+ secret: "your-secret-key",
40
+ cookie_max_age: 86400000, // 1 day
41
+ },
42
+ },
43
+ roles: [{ name: "admin", root: true }, { name: "user" }],
44
+ });
45
+ ```
46
+
47
+ ### 2. Define Entity Metadata
48
+
49
+ ```javascript
50
+ const { EntityMeta } = require("hola-server");
51
+
52
+ const user_meta = {
53
+ collection: "user",
54
+ mode: "crud",
55
+ fields: [
56
+ { name: "name", type: "string", required: true },
57
+ { name: "email", type: "string", required: true, primary: true },
58
+ { name: "age", type: "uint" },
59
+ { name: "role", type: "obj", ref: "role" },
60
+ ],
61
+ roles: ["admin:crud:*", "user:r:*"],
62
+ };
63
+ ```
64
+
65
+ ### 3. Create Router
66
+
67
+ ```javascript
68
+ const { init_router } = require("hola-server");
69
+
70
+ const router = init_router(user_meta);
71
+ module.exports = router;
72
+ ```
73
+
74
+ ### 4. Start Server
75
+
76
+ ```javascript
77
+ const { init_express_server } = require("hola-server");
78
+
79
+ init_express_server(__dirname, "service_port", async () => {
80
+ console.log("Server started on port 8088");
81
+ });
82
+ ```
83
+
84
+ ## Entity Metadata
85
+
86
+ ### Field Types
87
+
88
+ - `string` - String with trim
89
+ - `int` - Integer
90
+ - `uint` - Unsigned integer
91
+ - `float` - Float with 2 decimal places
92
+ - `ufloat` - Unsigned float
93
+ - `number` - Any number
94
+ - `boolean` - Boolean
95
+ - `password` - Encrypted password
96
+ - `array` - Array type
97
+ - `obj` - Object/Reference type
98
+ - `file` - File upload
99
+
100
+ ### Field Options
101
+
102
+ - `required` - Field is required
103
+ - `primary` - Primary key (unique)
104
+ - `searchable` - Searchable in queries
105
+ - `invisible` - Hidden from responses
106
+ - `sys` - System field
107
+ - `ref` - Reference to another entity
108
+ - `delete` - Cascade behavior: `cascade` or `keep`
109
+
110
+ ### CRUD Modes
111
+
112
+ - `c` - Create
113
+ - `r` - Read
114
+ - `u` - Update
115
+ - `d` - Delete
116
+ - `o` - Clone
117
+ - `crud` - All operations
118
+
119
+ ### Role Configuration
120
+
121
+ Format: `role_name:mode:view`
122
+
123
+ Example:
124
+
125
+ ```javascript
126
+ roles: [
127
+ "admin:crud:*", // Admin can do all operations on all views
128
+ "user:r:public", // User can only read public view
129
+ "editor:cru:edit", // Editor can create/read/update edit view
130
+ ];
131
+ ```
132
+
133
+ ## API Endpoints
134
+
135
+ For an entity with `mode: 'crud'`:
136
+
137
+ - `POST /{entity}/create` - Create entity
138
+ - `POST /{entity}/query` - Query entities
139
+ - `POST /{entity}/count` - Count entities
140
+ - `POST /{entity}/update` - Update entity
141
+ - `POST /{entity}/delete` - Delete entity
142
+ - `POST /{entity}/clone` - Clone entity (if cloneable)
143
+
144
+ ## Core Utilities
145
+
146
+ ### Database
147
+
148
+ ```javascript
149
+ const { get_db } = require("hola-server");
150
+
151
+ const db = await get_db();
152
+ const users = await db.collection("user").find({});
153
+ ```
154
+
155
+ ### Entity Operations
156
+
157
+ ```javascript
158
+ const { Entity } = require("hola-server");
159
+
160
+ const entity = new Entity(meta);
161
+ await entity.create_entity(data);
162
+ await entity.find_entity(query);
163
+ await entity.update_entity(id, updates);
164
+ await entity.delete_entity(id);
165
+ ```
166
+
167
+ ### Logging
168
+
169
+ ```javascript
170
+ const { log_info, log_error } = require("hola-server");
171
+
172
+ log_info("user", "User logged in", { user_id });
173
+ log_error("auth", "Login failed", { email });
174
+ ```
175
+
176
+ ## Testing
177
+
178
+ ```bash
179
+ npm test
180
+ ```
181
+
182
+ ## Project Structure
183
+
184
+ ```
185
+ hola-server/
186
+ ├── core/ # Core utilities (array, date, validate, etc.)
187
+ ├── db/ # Database layer (connection, entity, gridfs)
188
+ ├── http/ # HTTP layer (express, router, middleware)
189
+ ├── router/ # CRUD route handlers
190
+ ├── test/ # Test suite
191
+ └── tool/ # Tools (i18n generation)
192
+ ```
193
+
194
+ ## License
195
+
196
+ ISC
package/core/array.js CHANGED
@@ -1,187 +1,124 @@
1
+ /**
2
+ * @fileoverview Array manipulation utility functions.
3
+ * @module core/array
4
+ */
5
+
1
6
  const { round_to_fixed2 } = require('./number');
2
7
 
3
8
  /**
4
- * Shuffle the array randomly
5
- *
6
- * @param {array to be shuffle} a
9
+ * Shuffle array elements randomly in place.
10
+ * @param {Array} arr - Array to shuffle.
7
11
  */
8
- const shuffle = (a) => {
9
- let j, x, i;
10
- for (i = a.length; i; i--) {
11
- j = Math.floor(Math.random() * i);
12
- x = a[i - 1];
13
- a[i - 1] = a[j];
14
- a[j] = x;
12
+ const shuffle = (arr) => {
13
+ for (let i = arr.length; i; i--) {
14
+ const j = Math.floor(Math.random() * i);
15
+ [arr[i - 1], arr[j]] = [arr[j], arr[i - 1]];
15
16
  }
16
- }
17
+ };
17
18
 
18
19
  /**
19
- * Remove the object from array based on the attribute value of the object
20
- * @param {array of object} array
21
- * @param {the attribute name of the object} field
22
- * @param {the attribute value} value
20
+ * Remove elements from array by matching field value.
21
+ * @param {Object[]} array - Array of objects to modify.
22
+ * @param {string} field - Field name to match.
23
+ * @param {*} value - Value to match for removal.
23
24
  */
24
25
  const remove_element = (array, field, value) => {
25
26
  for (let i = array.length - 1; i >= 0; --i) {
26
- if (array[i][field] == value) {
27
- array.splice(i, 1);
28
- }
29
- }
30
- }
31
-
32
- /**
33
- * Pop n elements from the array
34
- * @param {array of elements} array
35
- * @param {n number of object to pop} n
36
- * @returns
37
- */
38
- const pop_n = (array, n) => {
39
- const subarray = [];
40
- while (subarray.length < n) {
41
- const ele = array.pop();
42
- if (ele) {
43
- subarray.push(ele);
44
- } else {
45
- break;
46
- }
47
- }
48
- if (subarray.length > 0) {
49
- return subarray;
50
- } else {
51
- return undefined;
27
+ if (array[i][field] == value) array.splice(i, 1);
52
28
  }
53
- }
29
+ };
54
30
 
55
31
  /**
56
- * Shift n elements from the array
57
- * @param {array of elements} array
58
- * @param {n number of object to pop} n
59
- * @returns
32
+ * Extract n elements from array using specified method.
33
+ * @param {Array} array - Source array.
34
+ * @param {number} n - Number of elements to extract.
35
+ * @param {string} method - 'pop' or 'shift'.
36
+ * @returns {Array|undefined} Array of extracted elements or undefined if empty.
60
37
  */
61
- const shift_n = (array, n) => {
62
- const subarray = [];
63
- while (subarray.length < n) {
64
- const ele = array.shift();
65
- if (ele) {
66
- subarray.push(ele);
67
- } else {
68
- break;
69
- }
70
- }
71
- if (subarray.length > 0) {
72
- return subarray;
73
- } else {
74
- return undefined;
38
+ const extract_n = (array, n, method) => {
39
+ const result = [];
40
+ while (result.length < n) {
41
+ const ele = array[method]();
42
+ if (ele) result.push(ele);
43
+ else break;
75
44
  }
76
- }
77
-
78
- /**
79
- * Calculate the sum value of the array of the number
80
- * @param {array of number} arr
81
- * @returns
82
- */
83
- const sum = function (arr) {
84
- return round_to_fixed2(arr.reduce((pre, cur) => pre + cur));
45
+ return result.length > 0 ? result : undefined;
85
46
  };
86
47
 
48
+ const pop_n = (array, n) => extract_n(array, n, 'pop');
49
+ const shift_n = (array, n) => extract_n(array, n, 'shift');
50
+
87
51
  /**
88
- * Calculate the average value of the array of number
89
- * @param {array of number} arr
90
- * @returns
52
+ * Calculate sum of number array.
53
+ * @param {number[]} arr - Array of numbers.
54
+ * @returns {number} Sum rounded to 2 decimal places.
91
55
  */
92
- const avg = function (arr) {
93
- if (arr.length === 0) {
94
- return 0;
95
- } else {
96
- return round_to_fixed2(sum(arr) / arr.length);
97
- }
98
- };
56
+ const sum = (arr) => round_to_fixed2(arr.reduce((pre, cur) => pre + cur, 0));
99
57
 
100
58
  /**
101
- *
102
- * @param {array of object} arr
103
- * @param {key attr of element as key} key_attr
104
- * @param {value attr of element as value} value_attr
105
- * @returns
59
+ * Calculate average of number array.
60
+ * @param {number[]} arr - Array of numbers.
61
+ * @returns {number} Average rounded to 2 decimal places.
106
62
  */
107
- const map_array_to_obj = function (arr, key_attr, value_attr) {
108
- const obj = {};
109
- arr.forEach(element => {
110
- obj[element[key_attr]] = element[value_attr];
111
- });
112
- return obj;
113
- }
63
+ const avg = (arr) => arr.length === 0 ? 0 : round_to_fixed2(sum(arr) / arr.length);
114
64
 
115
65
  /**
116
- * Sort array of element based on the attr value in desc order
117
- * @param {array to be sort} arr
118
- * @param {attribute used to do sorting} attr
119
- * @returns
66
+ * Convert array of objects to key-value object.
67
+ * @param {Object[]} arr - Array of objects.
68
+ * @param {string} key_attr - Attribute to use as key.
69
+ * @param {string} value_attr - Attribute to use as value.
70
+ * @returns {Object} Mapped object.
120
71
  */
121
- const sort_desc = function (arr, attr) {
122
- arr.sort((a, b) => {
123
- return b[attr] - a[attr];
124
- });
125
- return arr;
72
+ const map_array_to_obj = (arr, key_attr, value_attr) => {
73
+ return arr.reduce((obj, el) => ({ ...obj, [el[key_attr]]: el[value_attr] }), {});
126
74
  };
127
75
 
128
76
  /**
129
- * Sort array of element based on the attr value in asc order
130
- * @param {array to be sort} arr
131
- * @param {attribute used to do sorting} attr
132
- * @returns
77
+ * Sort array of objects by attribute.
78
+ * @param {Object[]} arr - Array to sort.
79
+ * @param {string} attr - Attribute name to sort by.
80
+ * @param {boolean} [desc=false] - Sort descending if true.
81
+ * @returns {Object[]} Sorted array.
133
82
  */
134
- const sort_asc = function (arr, attr) {
135
- arr.sort((a, b) => {
136
- return a[attr] - b[attr];
137
- });
83
+ const sort_by_attr = (arr, attr, desc = false) => {
84
+ arr.sort((a, b) => desc ? b[attr] - a[attr] : a[attr] - b[attr]);
138
85
  return arr;
139
86
  };
140
87
 
88
+ const sort_desc = (arr, attr) => sort_by_attr(arr, attr, true);
89
+ const sort_asc = (arr, attr) => sort_by_attr(arr, attr, false);
90
+
141
91
  /**
142
- * Use the keys sequence to sort the array
143
- * @param {array of object} arr
144
- * @param {the attr used to do sorting } attr
145
- * @param {the key values} keys
146
- * @returns
92
+ * Sort array by predefined key sequence.
93
+ * @param {Object[]} arr - Array to sort.
94
+ * @param {string} attr - Attribute name to sort by.
95
+ * @param {Array} keys - Ordered array of key values.
96
+ * @returns {Object[]} Sorted array.
147
97
  */
148
- const sort_by_key_seq = function (arr, attr, keys) {
149
- arr.sort((a, b) => {
150
- return keys.indexOf(a[attr]) - keys.indexOf(b[attr]);
151
- });
98
+ const sort_by_key_seq = (arr, attr, keys) => {
99
+ arr.sort((a, b) => keys.indexOf(a[attr]) - keys.indexOf(b[attr]));
152
100
  return arr;
153
101
  };
154
102
 
155
103
  /**
156
- * Create combination of two object, the size will be arr1.length * arr2.length
157
- * @param {array of object} arr1
158
- * @param {array of object} arr2
159
- * @returns
104
+ * Create cartesian product of two object arrays.
105
+ * @param {Object[]} arr1 - First array.
106
+ * @param {Object[]} arr2 - Second array.
107
+ * @returns {Object[]} Combined array with length arr1.length * arr2.length.
160
108
  */
161
- const combine = (arr1, arr2) => {
162
- const result = [];
163
- for (let i = 0; i < arr1.length; i++) {
164
- const obj1 = arr1[i];
165
- for (let j = 0; j < arr2.length; j++) {
166
- const obj2 = arr2[j];
167
- result.push({ ...obj1, ...obj2 });
168
- }
169
- }
170
- return result;
171
- }
109
+ const combine = (arr1, arr2) => arr1.flatMap(obj1 => arr2.map(obj2 => ({ ...obj1, ...obj2 })));
172
110
 
173
111
  /**
174
- * Remove duplicate element and keep it unique
175
- * @param {array of elements} arr
176
- * @returns
112
+ * Remove duplicate elements from array.
113
+ * @param {Array} array - Array to deduplicate.
114
+ * @returns {Array} Array with unique elements.
177
115
  */
178
116
  const unique = (array) => {
179
- return array.filter((value, index) => {
180
- const _value = JSON.stringify(value);
181
- return index === array.findIndex(obj => {
182
- return JSON.stringify(obj) === _value;
183
- });
117
+ const seen = new Map();
118
+ return array.filter(value => {
119
+ const key = JSON.stringify(value);
120
+ return seen.has(key) ? false : (seen.set(key, true), true);
184
121
  });
185
- }
122
+ };
186
123
 
187
- module.exports = { shuffle, remove_element, pop_n, shift_n, sum, avg, combine, sort_desc, sort_asc, sort_by_key_seq, unique, map_array_to_obj }
124
+ module.exports = { shuffle, remove_element, pop_n, shift_n, sum, avg, combine, sort_desc, sort_asc, sort_by_key_seq, unique, map_array_to_obj };