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
@@ -0,0 +1,216 @@
1
+ const { strictEqual, throws } = require('assert');
2
+ const { get_type, register_type } = require('../../core/type');
3
+
4
+ describe('type', function () {
5
+ describe('get_type', function () {
6
+ it('should throw for unregistered type', function () {
7
+ throws(() => get_type('unknown_type'));
8
+ });
9
+
10
+ it('should return registered type', function () {
11
+ const type = get_type('string');
12
+ strictEqual(typeof type.convert, 'function');
13
+ });
14
+ });
15
+
16
+ describe('string type', function () {
17
+ it('should convert and trim string', function () {
18
+ const type = get_type('string');
19
+ strictEqual(type.convert(' hello ').value, 'hello');
20
+ });
21
+
22
+ it('should handle empty values', function () {
23
+ const type = get_type('string');
24
+ strictEqual(type.convert('').value, '');
25
+ strictEqual(type.convert(null).value, '');
26
+ });
27
+ });
28
+
29
+ describe('boolean type', function () {
30
+ it('should convert true values', function () {
31
+ const type = get_type('boolean');
32
+ strictEqual(type.convert(true).value, true);
33
+ strictEqual(type.convert('true').value, true);
34
+ });
35
+
36
+ it('should convert false values', function () {
37
+ const type = get_type('boolean');
38
+ strictEqual(type.convert(false).value, false);
39
+ strictEqual(type.convert('false').value, false);
40
+ });
41
+
42
+ it('should error for invalid boolean', function () {
43
+ const type = get_type('boolean');
44
+ strictEqual(type.convert('yes').err !== undefined, true);
45
+ });
46
+ });
47
+
48
+ describe('int type', function () {
49
+ it('should convert valid integers', function () {
50
+ const type = get_type('int');
51
+ strictEqual(type.convert(42).value, 42);
52
+ strictEqual(type.convert('42').value, 42);
53
+ strictEqual(type.convert(-10).value, -10);
54
+ });
55
+
56
+ it('should error for non-integers', function () {
57
+ const type = get_type('int');
58
+ strictEqual(type.convert(3.14).err !== undefined, true);
59
+ strictEqual(type.convert('abc').err !== undefined, true);
60
+ });
61
+ });
62
+
63
+ describe('uint type', function () {
64
+ it('should convert positive integers', function () {
65
+ const type = get_type('uint');
66
+ strictEqual(type.convert(42).value, 42);
67
+ strictEqual(type.convert(0).value, 0);
68
+ });
69
+
70
+ it('should error for negative integers', function () {
71
+ const type = get_type('uint');
72
+ strictEqual(type.convert(-1).err !== undefined, true);
73
+ });
74
+ });
75
+
76
+ describe('float type', function () {
77
+ it('should convert and round to 2 decimals', function () {
78
+ const type = get_type('float');
79
+ strictEqual(type.convert(3.14159).value, 3.14);
80
+ strictEqual(type.convert('2.999').value, 3);
81
+ });
82
+
83
+ it('should error for non-numbers', function () {
84
+ const type = get_type('float');
85
+ strictEqual(type.convert('abc').err !== undefined, true);
86
+ });
87
+ });
88
+
89
+ describe('ufloat type', function () {
90
+ it('should convert positive floats', function () {
91
+ const type = get_type('ufloat');
92
+ strictEqual(type.convert(3.14).value, 3.14);
93
+ });
94
+
95
+ it('should error for negative floats', function () {
96
+ const type = get_type('ufloat');
97
+ strictEqual(type.convert(-3.14).err !== undefined, true);
98
+ });
99
+ });
100
+
101
+ describe('number type', function () {
102
+ it('should convert any number', function () {
103
+ const type = get_type('number');
104
+ strictEqual(type.convert(42).value, 42);
105
+ strictEqual(type.convert(-3.14).value, -3.14);
106
+ strictEqual(type.convert('100').value, 100);
107
+ });
108
+ });
109
+
110
+ describe('email type', function () {
111
+ it('should accept valid emails', function () {
112
+ const type = get_type('email');
113
+ strictEqual(type.convert('test@example.com').value, 'test@example.com');
114
+ });
115
+
116
+ it('should reject invalid emails', function () {
117
+ const type = get_type('email');
118
+ strictEqual(type.convert('invalid-email').err !== undefined, true);
119
+ });
120
+ });
121
+
122
+ describe('url type', function () {
123
+ it('should accept valid URLs', function () {
124
+ const type = get_type('url');
125
+ strictEqual(type.convert('https://example.com').value, 'https://example.com');
126
+ });
127
+
128
+ it('should reject invalid URLs', function () {
129
+ const type = get_type('url');
130
+ strictEqual(type.convert('not-a-url').err !== undefined, true);
131
+ });
132
+ });
133
+
134
+ describe('array type', function () {
135
+ it('should split comma-separated string', function () {
136
+ const type = get_type('array');
137
+ const result = type.convert('a,b,c').value;
138
+ strictEqual(result.length, 3);
139
+ strictEqual(result.join(''), 'abc');
140
+ });
141
+
142
+ it('should pass through arrays', function () {
143
+ const type = get_type('array');
144
+ const result = type.convert(['a', 'b']).value;
145
+ strictEqual(result.length, 2);
146
+ });
147
+ });
148
+
149
+ describe('json type', function () {
150
+ it('should pass through objects', function () {
151
+ const type = get_type('json');
152
+ const obj = { a: 1 };
153
+ strictEqual(type.convert(obj).value, obj);
154
+ });
155
+
156
+ it('should parse JSON strings', function () {
157
+ const type = get_type('json');
158
+ const result = type.convert('{"a":1}').value;
159
+ strictEqual(result.a, 1);
160
+ });
161
+
162
+ it('should error for invalid JSON', function () {
163
+ const type = get_type('json');
164
+ strictEqual(type.convert('not json').err !== undefined, true);
165
+ });
166
+ });
167
+
168
+ describe('time type', function () {
169
+ it('should accept valid time formats', function () {
170
+ const type = get_type('time');
171
+ strictEqual(type.convert('12:30').value, '12:30');
172
+ strictEqual(type.convert('23:59:59').value, '23:59:59');
173
+ });
174
+
175
+ it('should reject invalid time formats', function () {
176
+ const type = get_type('time');
177
+ strictEqual(type.convert('25:00').err !== undefined, true);
178
+ });
179
+ });
180
+
181
+ describe('ip_address type', function () {
182
+ it('should accept valid IP addresses', function () {
183
+ const type = get_type('ip_address');
184
+ strictEqual(type.convert('192.168.1.1').value, '192.168.1.1');
185
+ });
186
+
187
+ it('should reject invalid IP addresses', function () {
188
+ const type = get_type('ip_address');
189
+ strictEqual(type.convert('256.1.1.1').err !== undefined, true);
190
+ strictEqual(type.convert('not-an-ip').err !== undefined, true);
191
+ });
192
+ });
193
+
194
+ describe('slug type', function () {
195
+ it('should convert to slug format', function () {
196
+ const type = get_type('slug');
197
+ strictEqual(type.convert('Hello World!').value, 'hello-world');
198
+ strictEqual(type.convert(' Multiple Spaces ').value, 'multiple-spaces');
199
+ });
200
+ });
201
+
202
+ describe('age type', function () {
203
+ it('should accept valid ages (0-200)', function () {
204
+ const type = get_type('age');
205
+ strictEqual(type.convert(25).value, 25);
206
+ strictEqual(type.convert(0).value, 0);
207
+ strictEqual(type.convert(200).value, 200);
208
+ });
209
+
210
+ it('should reject invalid ages', function () {
211
+ const type = get_type('age');
212
+ strictEqual(type.convert(-1).err !== undefined, true);
213
+ strictEqual(type.convert(201).err !== undefined, true);
214
+ });
215
+ });
216
+ });
@@ -0,0 +1,67 @@
1
+ const { strictEqual, deepStrictEqual } = require('assert');
2
+ const { is_undefined, has_value, validate_required_fields } = require('../../core/validate');
3
+
4
+ describe('validate', function () {
5
+ describe('is_undefined', function () {
6
+ it('should return true for undefined', function () {
7
+ strictEqual(is_undefined(undefined), true);
8
+ });
9
+
10
+ it('should return false for null', function () {
11
+ strictEqual(is_undefined(null), false);
12
+ });
13
+
14
+ it('should return false for defined values', function () {
15
+ strictEqual(is_undefined(0), false);
16
+ strictEqual(is_undefined(''), false);
17
+ strictEqual(is_undefined(false), false);
18
+ });
19
+ });
20
+
21
+ describe('has_value', function () {
22
+ it('should return false for null', function () {
23
+ strictEqual(has_value(null), false);
24
+ });
25
+
26
+ it('should return false for undefined', function () {
27
+ strictEqual(has_value(undefined), false);
28
+ });
29
+
30
+ it('should return false for NaN', function () {
31
+ strictEqual(has_value(NaN), false);
32
+ });
33
+
34
+ it('should return false for empty string', function () {
35
+ strictEqual(has_value(''), false);
36
+ strictEqual(has_value(' '), false);
37
+ });
38
+
39
+ it('should return true for valid values', function () {
40
+ strictEqual(has_value(0), true);
41
+ strictEqual(has_value(false), true);
42
+ strictEqual(has_value('hello'), true);
43
+ strictEqual(has_value([]), true);
44
+ strictEqual(has_value({}), true);
45
+ });
46
+ });
47
+
48
+ describe('validate_required_fields', function () {
49
+ it('should return empty array when all fields present', function () {
50
+ const obj = { name: 'John', age: 30 };
51
+ const result = validate_required_fields(obj, ['name', 'age']);
52
+ strictEqual(result.length, 0);
53
+ });
54
+
55
+ it('should return missing field names', function () {
56
+ const obj = { name: 'John' };
57
+ const result = validate_required_fields(obj, ['name', 'age', 'email']);
58
+ deepStrictEqual(result, ['age', 'email']);
59
+ });
60
+
61
+ it('should treat empty strings as missing', function () {
62
+ const obj = { name: '', age: 30 };
63
+ const result = validate_required_fields(obj, ['name', 'age']);
64
+ deepStrictEqual(result, ['name']);
65
+ });
66
+ });
67
+ });
@@ -0,0 +1,99 @@
1
+ const { strictEqual, deepStrictEqual } = require("assert");
2
+ const { get_db, oid_query, oid_queries, bulk_update, log_error } = require("../../db/db");
3
+ const { init_settings, get_settings } = require("../../setting");
4
+
5
+ const col = "user_ops";
6
+ const db = get_db();
7
+
8
+ describe("mongodb advanced ops", function () {
9
+ beforeEach(async function () {
10
+ await db.delete(col, {});
11
+ });
12
+
13
+ afterEach(async function () {
14
+ await db.delete(col, {});
15
+ });
16
+
17
+ it("bulk_update should upsert by attrs", async function () {
18
+ const items = [
19
+ { uid: 1, name: "first" },
20
+ { uid: 1, name: "second" },
21
+ { uid: 2, name: "third" }
22
+ ];
23
+
24
+ const collection = db.get_col(col);
25
+ await bulk_update(collection, items, ["uid"]);
26
+
27
+ const docs = await db.find(col, {});
28
+ strictEqual(docs.length, 2);
29
+ const [first] = docs.filter(d => d.uid === 1);
30
+ strictEqual(first.name, "second");
31
+ });
32
+
33
+ it("find_page should paginate deterministically", async function () {
34
+ for (let i = 1; i <= 12; i++) {
35
+ await db.create(col, { name: `user${i}`, age: i });
36
+ }
37
+
38
+ const page = await db.find_page(col, {}, { age: -1 }, 2, 5, { name: 1, age: 1 });
39
+ strictEqual(page.length, 5);
40
+ deepStrictEqual(page.map(p => p.age), [7, 6, 5, 4, 3]);
41
+ strictEqual(page[0].name, "user7");
42
+ });
43
+
44
+ it("find_sort should honor projection", async function () {
45
+ await db.create(col, { name: "a", age: 30 });
46
+ await db.create(col, { name: "b", age: 10 });
47
+ await db.create(col, { name: "c", age: 20 });
48
+
49
+ const sorted = await db.find_sort(col, {}, { age: 1 }, { age: 1 });
50
+ deepStrictEqual(sorted.map(s => s.age), [10, 20, 30]);
51
+ strictEqual(sorted[0].name, undefined);
52
+ });
53
+
54
+ it("push/pull/add_to_set should mutate arrays", async function () {
55
+ await db.create(col, { name: "tags", tags: ["a"] });
56
+
57
+ await db.push(col, { name: "tags" }, { tags: "b" });
58
+ let doc = await db.find_one(col, { name: "tags" });
59
+ deepStrictEqual(doc.tags.sort(), ["a", "b"]);
60
+
61
+ await db.add_to_set(col, { name: "tags" }, { tags: "b" });
62
+ doc = await db.find_one(col, { name: "tags" });
63
+ deepStrictEqual(doc.tags.sort(), ["a", "b"]);
64
+
65
+ await db.pull(col, { name: "tags" }, { tags: "a" });
66
+ doc = await db.find_one(col, { name: "tags" });
67
+ deepStrictEqual(doc.tags, ["b"]);
68
+ });
69
+
70
+ it("sum should aggregate numeric fields", async function () {
71
+ await db.create(col, { name: "p1", points: 5 });
72
+ await db.create(col, { name: "p2", points: 15 });
73
+ await db.create(col, { name: "p3", points: 10 });
74
+
75
+ const total = await db.sum(col, {}, "points");
76
+ strictEqual(total, 30);
77
+ });
78
+
79
+ it("oid_query helpers should return null for invalid ids", function () {
80
+ strictEqual(oid_query("invalid-id"), null);
81
+ strictEqual(oid_queries(["invalid-id"]), null);
82
+ });
83
+
84
+ it("log_error should persist when save_db enabled", async function () {
85
+ const original = get_settings();
86
+ const log_col = "log_test";
87
+ init_settings({ ...original, log: { ...original.log, save_db: true, log_level: 0, col_log: log_col } });
88
+
89
+ await db.delete(log_col, {});
90
+ log_error("database", "failure case", { code: 500 });
91
+ await new Promise((res) => setTimeout(res, 50));
92
+ const logs = await db.find(log_col, { msg: "failure case" });
93
+ strictEqual(logs.length, 1);
94
+ strictEqual(logs[0].code, 500);
95
+
96
+ await db.delete(log_col, {});
97
+ init_settings(original);
98
+ });
99
+ });
File without changes