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/skills/code.md ADDED
@@ -0,0 +1,422 @@
1
+ # HTTP Response Codes Skill
2
+
3
+ ## Overview
4
+
5
+ The `hola-server/http/code.js` module defines standard response codes used throughout the Hola framework for consistent error handling and status reporting.
6
+
7
+ **Important:** Hola framework returns all responses as JSON objects with `code` and optional `err` fields. Do not map these codes to HTTP status codes - always return `200 OK` with the code in the JSON body.
8
+
9
+ ## Importing
10
+
11
+ ```javascript
12
+ const {
13
+ SUCCESS, ERROR,
14
+ NO_SESSION, NO_RIGHTS, NO_PARAMS, NOT_FOUND,
15
+ INVALID_PARAMS, REF_NOT_FOUND, REF_NOT_UNIQUE, HAS_REF,
16
+ DUPLICATE_KEY, NO_RESOURCE,
17
+ IMPORT_EMPTY_KEY, IMPORT_WRONG_FIELDS, IMPORT_DUPLICATE_KEY, IMPORT_NO_FOUND_REF
18
+ } = require("hola-server/http/code");
19
+ ```
20
+
21
+ ## Response Codes Reference
22
+
23
+ ### Success & General Errors
24
+
25
+ | Code | Value | Description | Usage |
26
+ |------|-------|-------------|-------|
27
+ | `SUCCESS` | 1 | Operation succeeded | All successful operations |
28
+ | `ERROR` | 0 | General error | Unexpected failures, database errors |
29
+
30
+ ### Authentication & Authorization (200-299)
31
+
32
+ | Code | Value | Description | Usage |
33
+ |------|-------|-------------|-------|
34
+ | `NO_SESSION` | 200 | No valid session | User not logged in |
35
+ | `NO_RIGHTS` | 201 | Insufficient permissions | User lacks required role/permissions |
36
+
37
+ ### Validation & Parameters (202-207)
38
+
39
+ | Code | Value | Description | Usage |
40
+ |------|-------|-------------|-------|
41
+ | `NO_PARAMS` | 202 | Missing required parameters | Required fields not provided |
42
+ | `NOT_FOUND` | 203 | Entity not found | Query returned no results |
43
+ | `INVALID_PARAMS` | 204 | Invalid parameter values | Type conversion failed, validation error |
44
+ | `REF_NOT_FOUND` | 205 | Referenced entity not found | Foreign key constraint violation |
45
+ | `REF_NOT_UNIQUE` | 206 | Ambiguous reference | Multiple entities match ref_label |
46
+ | `HAS_REF` | 207 | Entity has references | Cannot delete due to foreign key constraints |
47
+
48
+ ### Data Integrity (300-399)
49
+
50
+ | Code | Value | Description | Usage |
51
+ |------|-------|-------------|-------|
52
+ | `DUPLICATE_KEY` | 300 | Primary key already exists | Insert/update violates uniqueness |
53
+
54
+ ### Resources (400-499)
55
+
56
+ | Code | Value | Description | Usage |
57
+ |------|-------|-------------|-------|
58
+ | `NO_RESOURCE` | 404 | Resource not found | Static file or route not found |
59
+
60
+ ### Import Operations (100-199)
61
+
62
+ | Code | Value | Description | Usage |
63
+ |------|-------|-------------|-------|
64
+ | `IMPORT_EMPTY_KEY` | 100 | Empty primary key in import | CSV row missing key field |
65
+ | `IMPORT_WRONG_FIELDS` | 101 | Invalid fields in import | CSV columns don't match entity |
66
+ | `IMPORT_DUPLICATE_KEY` | 102 | Duplicate key in import | Multiple rows with same key |
67
+ | `IMPORT_NO_FOUND_REF` | 103 | Reference not found in import | Foreign key lookup failed |
68
+
69
+ ## Usage Examples
70
+
71
+ ### Basic Response Pattern
72
+
73
+ ```javascript
74
+ const { SUCCESS, NO_PARAMS } = require("hola-server/http/code");
75
+
76
+ router.post("/create", async (req, res) => {
77
+ const { name, email } = req.body;
78
+
79
+ if (!name || !email) {
80
+ return res.json({ code: NO_PARAMS, err: ["name", "email"] });
81
+ }
82
+
83
+ const result = await entity.create_entity(req.body, "*");
84
+ return res.json(result); // Returns { code: SUCCESS } or { code: ERROR_CODE, err: [...] }
85
+ });
86
+ ```
87
+
88
+ ### Standard CRUD Operations
89
+
90
+ ```javascript
91
+ const { SUCCESS } = require("hola-server/http/code");
92
+
93
+ // Create
94
+ router.post("/", async (req, res) => {
95
+ const result = await entity.create_entity(req.body, "*");
96
+ return res.json(result);
97
+ });
98
+
99
+ // Read
100
+ router.get("/:id", async (req, res) => {
101
+ const result = await entity.read_entity(req.params.id, "name,email,age", "*");
102
+ return res.json(result);
103
+ });
104
+
105
+ // Update
106
+ router.put("/:id", async (req, res) => {
107
+ const result = await entity.update_entity(req.params.id, req.body, "*");
108
+ return res.json(result);
109
+ });
110
+
111
+ // Delete
112
+ router.delete("/", async (req, res) => {
113
+ const result = await entity.delete_entity(req.body.ids);
114
+ return res.json(result);
115
+ });
116
+
117
+ // List
118
+ router.get("/", async (req, res) => {
119
+ const result = await entity.list_entity(
120
+ { attr_names: "name,email", page: 1, limit: 20, sort_by: "created_at", desc: "true" },
121
+ {},
122
+ {},
123
+ "*"
124
+ );
125
+ return res.json(result);
126
+ });
127
+ ```
128
+
129
+ ## Client-Side Helpers
130
+
131
+ The `hola-web/src/core/axios.js` module provides convenience functions for interacting with the Hola API from Vue.js applications.
132
+
133
+ ### Importing
134
+
135
+ ```javascript
136
+ import {
137
+ init_axios,
138
+ is_success_response,
139
+ save_entity,
140
+ read_entity,
141
+ list_entity,
142
+ delete_entity,
143
+ get_entity_meta,
144
+ get_ref_labels
145
+ } from "@/core/axios";
146
+ ```
147
+
148
+ ### Initialization
149
+
150
+ ```javascript
151
+ // In main.js or app setup
152
+ import { init_axios } from "@/core/axios";
153
+
154
+ init_axios(
155
+ { baseURL: "http://localhost:3000/api" },
156
+ {
157
+ handle_response: (code, data) => {
158
+ // Custom response handling
159
+ if (code === 200) { // NO_SESSION
160
+ router.push("/login");
161
+ }
162
+ }
163
+ }
164
+ );
165
+ ```
166
+
167
+ ### Response Code Checking
168
+
169
+ ```javascript
170
+ import { is_success_response, is_duplicated, is_been_referred } from "@/core/axios";
171
+
172
+ const result = await save_entity("product", form_data, false);
173
+
174
+ if (is_success_response(result.code)) {
175
+ this.$message.success(this.$t("msg.saved_successfully"));
176
+ } else if (is_duplicated(result.code)) {
177
+ this.$message.error(this.$t("msg.already_exists"));
178
+ } else if (is_been_referred(result.code)) {
179
+ this.$message.error(this.$t("msg.cannot_delete_referenced", { refs: result.err.join(", ") }));
180
+ }
181
+ ```
182
+
183
+ **Available Checkers:**
184
+ - `is_success_response(code)` - Code is SUCCESS (1)
185
+ - `is_error_response(code)` - Code is ERROR (0)
186
+ - `is_duplicated(code)` - Code is DUPLICATE_KEY
187
+ - `is_been_referred(code)` - Code is HAS_REF
188
+ - `has_invalid_params(code)` - Code is INVALID_PARAMS
189
+ - `is_no_session(code)` - Code is NO_SESSION
190
+
191
+ ### Entity Operations
192
+
193
+ #### Create/Update Entity
194
+
195
+ ```javascript
196
+ import { save_entity, is_success_response } from "@/core/axios";
197
+
198
+ // Create (edit_mode = false)
199
+ const result = await save_entity("product", {
200
+ name: "iPhone 15",
201
+ price: 999,
202
+ category: "Electronics"
203
+ }, false);
204
+
205
+ // Update (edit_mode = true)
206
+ const result = await save_entity("product", {
207
+ _id: "507f...",
208
+ price: 899
209
+ }, true);
210
+
211
+ // Clone (edit_mode = true, clone = true)
212
+ const result = await save_entity("product", {
213
+ _id: "507f...",
214
+ name: "iPhone 15 Pro"
215
+ }, true, true);
216
+
217
+ if (is_success_response(result.code)) {
218
+ this.$message.success(this.$t("msg.saved_successfully"));
219
+ }
220
+ ```
221
+
222
+ #### Read Entity
223
+
224
+ ```javascript
225
+ import { read_entity, read_property } from "@/core/axios";
226
+
227
+ // Read with references expanded
228
+ const product = await read_entity("product", "507f...", "name,price,category");
229
+ // product.category will be "Electronics" (ref_label)
230
+
231
+ // Read without reference expansion (faster)
232
+ const product = await read_property("product", "507f...", "name,price,category");
233
+ // product.category will be ObjectId
234
+ ```
235
+
236
+ #### List Entities
237
+
238
+ ```javascript
239
+ import { list_entity } from "@/core/axios";
240
+
241
+ const result = await list_entity(
242
+ "product",
243
+ { category: "Electronics", min_price: 500 }, // Search params
244
+ {
245
+ attr_names: "name,price,category",
246
+ page: 1,
247
+ limit: 20,
248
+ sort_by: "price",
249
+ desc: "false"
250
+ }
251
+ );
252
+
253
+ if (is_success_response(result.code)) {
254
+ this.products = result.data;
255
+ this.total = result.total;
256
+ }
257
+ ```
258
+
259
+ #### Query Entities
260
+
261
+ ```javascript
262
+ import { query_entity } from "@/core/axios";
263
+
264
+ // Get all active products
265
+ const result = await query_entity(
266
+ "product",
267
+ ["name", "price"],
268
+ { active: true }
269
+ );
270
+
271
+ if (is_success_response(result.code)) {
272
+ this.products = result.data;
273
+ }
274
+ ```
275
+
276
+ #### Delete Entities
277
+
278
+ ```javascript
279
+ import { delete_entity, is_success_response, is_been_referred } from "@/core/axios";
280
+
281
+ const result = await delete_entity("product", ["507f...", "608a..."]);
282
+
283
+ if (is_success_response(result.code)) {
284
+ this.$message.success("Deleted");
285
+ } else if (is_been_referred(result.code)) {
286
+ this.$message.error("Cannot delete: referenced by " + result.err.join(", "));
287
+ }
288
+ ```
289
+
290
+ ### Metadata Operations
291
+
292
+ #### Get Entity Meta
293
+
294
+ ```javascript
295
+ import { get_entity_meta } from "@/core/axios";
296
+
297
+ const meta = await get_entity_meta("product");
298
+ // Returns meta definition or null
299
+ // Result is cached automatically
300
+ ```
301
+
302
+ #### Get Reference Labels
303
+
304
+ ```javascript
305
+ import { get_ref_labels } from "@/core/axios";
306
+
307
+ // Get all categories for dropdown
308
+ const categories = await get_ref_labels("category", "product");
309
+ // Returns array of { _id, ref_label } objects
310
+ ```
311
+
312
+ ### File Operations
313
+
314
+ #### Upload File
315
+
316
+ ```javascript
317
+ import { axios_upload } from "@/core/axios";
318
+
319
+ const file = this.$refs.fileInput.files[0];
320
+ const result = await axios_upload("/api/upload", file);
321
+ ```
322
+
323
+ #### Download File
324
+
325
+ ```javascript
326
+ import { axios_download } from "@/core/axios";
327
+
328
+ axios_download("/api/export", "export.csv", { format: "csv" });
329
+ // Triggers browser download
330
+ ```
331
+
332
+ ### Complete Vue Component Example
333
+
334
+ ```vue
335
+ <template>
336
+ <div>
337
+ <el-form :model="form">
338
+ <el-form-item label="Name">
339
+ <el-input v-model="form.name"></el-input>
340
+ </el-form-item>
341
+ <el-form-item label="Price">
342
+ <el-input-number v-model="form.price"></el-input-number>
343
+ </el-form-item>
344
+ <el-button @click="save">Save</el-button>
345
+ </el-form>
346
+
347
+ <el-table :data="list">
348
+ <el-table-column prop="name" label="Name"></el-table-column>
349
+ <el-table-column prop="price" label="Price"></el-table-column>
350
+ <el-table-column>
351
+ <template #default="{ row }">
352
+ <el-button @click="deleteItem(row._id)">Delete</el-button>
353
+ </template>
354
+ </el-table-column>
355
+ </el-table>
356
+ </div>
357
+ </template>
358
+
359
+ <script>
360
+ import {
361
+ save_entity,
362
+ list_entity,
363
+ delete_entity,
364
+ is_success_response
365
+ } from "@/core/axios";
366
+
367
+ export default {
368
+ data() {
369
+ return {
370
+ form: { name: "", price: 0 },
371
+ list: [],
372
+ total: 0
373
+ };
374
+ },
375
+
376
+ mounted() {
377
+ this.loadList();
378
+ },
379
+
380
+ methods: {
381
+ async save() {
382
+ const result = await save_entity("product", this.form, false);
383
+
384
+ if (is_success_response(result.code)) {
385
+ this.$message.success(this.$t("msg.saved_successfully"));
386
+ this.form = { name: "", price: 0 };
387
+ this.loadList();
388
+ } else {
389
+ this.$message.error(this.$t("msg.save_failed", { err: JSON.stringify(result.err) }));
390
+ }
391
+ },
392
+
393
+ async loadList() {
394
+ const result = await list_entity("product", {}, {
395
+ attr_names: "name,price",
396
+ page: 1,
397
+ limit: 20,
398
+ sort_by: "created_at",
399
+ desc: "true"
400
+ });
401
+
402
+ if (is_success_response(result.code)) {
403
+ this.list = result.data;
404
+ this.total = result.total;
405
+ }
406
+ },
407
+
408
+ async deleteItem(id) {
409
+ const result = await delete_entity("product", [id]);
410
+
411
+ if (is_success_response(result.code)) {
412
+ this.$message.success(this.$t("msg.deleted_successfully"));
413
+ this.loadList();
414
+ } else {
415
+ this.$message.error(this.$t("msg.delete_failed", { err: JSON.stringify(result.err) }));
416
+ }
417
+ }
418
+ }
419
+ };
420
+ </script>
421
+ ```
422
+
@@ -0,0 +1,177 @@
1
+ # HTTP Context Utilities Skill
2
+
3
+ ## Overview
4
+
5
+ The `hola-server/http/context.js` module provides request-scoped context storage using Node.js `AsyncLocalStorage`. This allows you to store and retrieve values that are tied to the current HTTP request without explicitly passing them through function parameters.
6
+
7
+ ## Importing
8
+
9
+ ```javascript
10
+ const { set_context_value, get_context_value } = require("hola-server/http/context");
11
+ ```
12
+
13
+ ## API Reference
14
+
15
+ ### `set_context_value(key, obj)`
16
+ Stores a value in the current request context.
17
+
18
+ **Parameters:**
19
+ - `key` (string): Context key
20
+ - `obj` (*): Value to store
21
+
22
+ ```javascript
23
+ set_context_value("user_id", "507f1f77bcf86cd799439011");
24
+ set_context_value("request_start", Date.now());
25
+ ```
26
+
27
+ ### `get_context_value(key)`
28
+ Retrieves a value from the current request context.
29
+
30
+ **Parameters:**
31
+ - `key` (string): Context key
32
+
33
+ **Returns:** `*` - Stored value or `null` if not found
34
+
35
+ ```javascript
36
+ const user_id = get_context_value("user_id");
37
+ const start_time = get_context_value("request_start");
38
+ ```
39
+
40
+ ## How It Works
41
+
42
+ The Hola framework automatically initializes AsyncLocalStorage context for each request in the authentication middleware (`http/express.js`). The request object is automatically stored with key `"req"`.
43
+
44
+ ```javascript
45
+ // Happens automatically in express.js
46
+ asyncLocalStorage.run({}, () => {
47
+ set_context_value("req", req);
48
+ next();
49
+ });
50
+ ```
51
+
52
+ ## Usage Patterns
53
+
54
+ ### Pattern 1: Accessing Request from Deep Functions
55
+
56
+ Instead of passing `req` through multiple function levels:
57
+
58
+ ```javascript
59
+ // Without context (verbose):
60
+ async function process_order(req, order_data) {
61
+ const user_id = get_session_user_id(req);
62
+ await create_audit_log(req, "order_created", order_data);
63
+ }
64
+
65
+ async function create_audit_log(req, action, data) {
66
+ const user_id = get_session_user_id(req);
67
+ // ...
68
+ }
69
+
70
+ // With context (clean):
71
+ const { get_context_value } = require("hola-server/http/context");
72
+
73
+ async function process_order(order_data) {
74
+ const req = get_context_value("req");
75
+ const user_id = get_session_user_id(req);
76
+ await create_audit_log("order_created", order_data);
77
+ }
78
+
79
+ async function create_audit_log(action, data) {
80
+ const req = get_context_value("req");
81
+ const user_id = get_session_user_id(req);
82
+ // ...
83
+ }
84
+ ```
85
+
86
+ ### Pattern 2: Request Timing
87
+
88
+ ```javascript
89
+ // Middleware to track request duration
90
+ app.use((req, res, next) => {
91
+ set_context_value("request_start", Date.now());
92
+ next();
93
+ });
94
+
95
+ // Later in response
96
+ router.get("/api/data", async (req, res) => {
97
+ const data = await fetch_data();
98
+
99
+ const start = get_context_value("request_start");
100
+ const duration = Date.now() - start;
101
+
102
+ console.log(`Request took ${duration}ms`);
103
+ return res.json(data);
104
+ });
105
+ ```
106
+
107
+ ### Pattern 3: Storing Computed Values
108
+
109
+ ```javascript
110
+ // Middleware that resolves user once
111
+ app.use(async (req, res, next) => {
112
+ const user_id = get_session_user_id(req);
113
+ if (user_id) {
114
+ const user = await user_entity.find_one({ _id: user_id }, {});
115
+ set_context_value("current_user", user);
116
+ }
117
+ next();
118
+ });
119
+
120
+ // Access anywhere without re-fetching
121
+ router.post("/api/order", async (req, res) => {
122
+ const user = get_context_value("current_user");
123
+ const order = await create_order(req.body, user);
124
+ return res.json(order);
125
+ });
126
+ ```
127
+
128
+ ### Pattern 4: Custom Logging Context
129
+
130
+ ```javascript
131
+ // Set trace ID for request
132
+ app.use((req, res, next) => {
133
+ const trace_id = generate_trace_id();
134
+ set_context_value("trace_id", trace_id);
135
+ next();
136
+ });
137
+
138
+ // Use in logging
139
+ function log_info(message, data) {
140
+ const trace_id = get_context_value("trace_id");
141
+ console.log(`[${trace_id}] ${message}`, data);
142
+ }
143
+
144
+ // Anywhere in the request lifecycle
145
+ router.get("/api/process", async (req, res) => {
146
+ log_info("Processing started"); // Automatically includes trace_id
147
+ // ...
148
+ });
149
+ ```
150
+
151
+ ## Best Practices
152
+
153
+ 1. **Use for cross-cutting concerns**: Context is ideal for request ID, user info, timing, etc.
154
+
155
+ 2. **Don't overuse**: For values only needed in 1-2 places, pass them directly.
156
+
157
+ 3. **Clear naming**: Use descriptive keys like `"current_user"` not `"u"`.
158
+
159
+ 4. **Document context keys**: If your app uses many context values, document them.
160
+
161
+ ```javascript
162
+ // Good: centralized context keys
163
+ const CONTEXT_KEYS = {
164
+ REQUEST: "req",
165
+ USER: "current_user",
166
+ TRACE_ID: "trace_id",
167
+ REQUEST_START: "request_start"
168
+ };
169
+
170
+ set_context_value(CONTEXT_KEYS.TRACE_ID, trace_id);
171
+ const user = get_context_value(CONTEXT_KEYS.USER);
172
+ ```
173
+
174
+ ## Limitations
175
+
176
+ - **Only works in request context**: Cannot use in background jobs, timers, or event handlers unless they originated from a request.
177
+ - **Async boundaries**: Works correctly with `async/await` and Promises, but not with callbacks or event emitters.
package/skills/date.md ADDED
@@ -0,0 +1,58 @@
1
+ # Date Utilities Skill
2
+
3
+ ## Overview
4
+
5
+ The `hola-server/core/date.js` module provides date formatting and parsing utilities, wrapping the `dateformat` library for consistent application-wide formats.
6
+
7
+ ## Importing
8
+
9
+ ```javascript
10
+ const {
11
+ simple_date, format_date, format_time,
12
+ format_date_time, parse_date
13
+ } = require("hola-server/core/date");
14
+ ```
15
+
16
+ ## API Reference
17
+
18
+ ### Formatting Functions
19
+
20
+ #### `simple_date(date)`
21
+ Formats date as `mm/dd`.
22
+ - **param**: `date` (Date)
23
+ - **returns**: `string`
24
+
25
+ #### `format_date(date)`
26
+ Formats date as `yyyymmdd`.
27
+ - **param**: `date` (Date)
28
+ - **returns**: `string`
29
+
30
+ #### `format_time(date)`
31
+ Formats time as `HH:MM`.
32
+ - **param**: `date` (Date)
33
+ - **returns**: `string`
34
+
35
+ #### `format_date_time(date)`
36
+ Formats as `yyyymmdd HH:MM:ss`.
37
+ - **param**: `date` (Date)
38
+ - **returns**: `string`
39
+
40
+ ```javascript
41
+ const now = new Date("2023-12-25T14:30:00");
42
+ format_date(now); // "20231225"
43
+ format_time(now); // "14:30"
44
+ format_date_time(now); // "20231225 14:30:00"
45
+ simple_date(now); // "12/25"
46
+ ```
47
+
48
+ ### Parsing Functions
49
+
50
+ #### `parse_date(date_str)`
51
+ Parses a string in `yyyymmdd` format into a Date object (time set to 00:00:00).
52
+ - **param**: `date_str` (string) - E.g., "20231225".
53
+ - **returns**: `Date`
54
+
55
+ ```javascript
56
+ const d = parse_date("20230101");
57
+ // d is Date object for Jan 1, 2023 00:00:00 local time
58
+ ```