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.
- package/README.md +196 -1
- package/core/array.js +79 -142
- package/core/bash.js +208 -259
- package/core/chart.js +26 -16
- package/core/cron.js +14 -3
- package/core/date.js +15 -44
- package/core/encrypt.js +19 -9
- package/core/file.js +42 -29
- package/core/lhs.js +32 -6
- package/core/meta.js +213 -289
- package/core/msg.js +20 -7
- package/core/number.js +105 -103
- package/core/obj.js +15 -12
- package/core/random.js +9 -6
- package/core/role.js +69 -77
- package/core/thread.js +12 -2
- package/core/type.js +300 -261
- package/core/url.js +20 -12
- package/core/validate.js +29 -26
- package/db/db.js +297 -227
- package/db/entity.js +631 -963
- package/db/gridfs.js +120 -166
- package/design/add_default_field_attr.md +56 -0
- package/http/context.js +22 -8
- package/http/cors.js +25 -8
- package/http/error.js +27 -9
- package/http/express.js +70 -41
- package/http/params.js +70 -42
- package/http/router.js +51 -40
- package/http/session.js +59 -36
- package/index.js +85 -9
- package/package.json +2 -2
- package/router/clone.js +28 -36
- package/router/create.js +21 -26
- package/router/delete.js +24 -28
- package/router/read.js +137 -123
- package/router/update.js +38 -56
- package/setting.js +22 -6
- package/skills/array.md +155 -0
- package/skills/bash.md +91 -0
- package/skills/chart.md +54 -0
- package/skills/code.md +422 -0
- package/skills/context.md +177 -0
- package/skills/date.md +58 -0
- package/skills/express.md +255 -0
- package/skills/file.md +60 -0
- package/skills/lhs.md +54 -0
- package/skills/meta.md +1023 -0
- package/skills/msg.md +30 -0
- package/skills/number.md +88 -0
- package/skills/obj.md +36 -0
- package/skills/params.md +206 -0
- package/skills/random.md +22 -0
- package/skills/role.md +59 -0
- package/skills/session.md +281 -0
- package/skills/storage.md +743 -0
- package/skills/thread.md +22 -0
- package/skills/type.md +547 -0
- package/skills/url.md +34 -0
- package/skills/validate.md +48 -0
- package/test/cleanup/close-db.js +5 -0
- package/test/core/array.js +226 -0
- package/test/core/chart.js +51 -0
- package/test/core/file.js +59 -0
- package/test/core/lhs.js +44 -0
- package/test/core/number.js +167 -12
- package/test/core/obj.js +47 -0
- package/test/core/random.js +24 -0
- package/test/core/thread.js +20 -0
- package/test/core/type.js +216 -0
- package/test/core/validate.js +67 -0
- package/test/db/db-ops.js +99 -0
- package/test/db/pipe_test.txt +0 -0
- package/test/db/test_case_design.md +528 -0
- package/test/db/test_db_class.js +613 -0
- package/test/db/test_entity_class.js +414 -0
- package/test/db/test_gridfs_class.js +234 -0
- package/test/entity/create.js +1 -1
- package/test/entity/delete-mixed.js +156 -0
- package/test/entity/ref-filter.js +63 -0
- package/tool/gen_i18n.js +55 -21
- package/test/crud/router.js +0 -99
- package/test/router/user.js +0 -17
package/skills/thread.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Thread Utilities Skill
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `hola-server/core/thread.js` module provides utilities for controlling execution flow (threading/timing).
|
|
6
|
+
|
|
7
|
+
## Importing
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const { snooze } = require("hola-server/core/thread");
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## API Reference
|
|
14
|
+
|
|
15
|
+
### `snooze(ms)`
|
|
16
|
+
Pauses execution for a specified duration (Non-blocking sleep).
|
|
17
|
+
- **param**: `ms` (number) - Milliseconds to wait.
|
|
18
|
+
- **returns**: `Promise<void>`
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
await snooze(1000); // Waits for 1 second
|
|
22
|
+
```
|
package/skills/type.md
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
# Type System in Hola Meta-Programming Framework
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Hola framework uses a robust type system to ensure data validation, conversion, and consistency across the entire stack. This document explains how to use built-in types and how to define customized types for your entities.
|
|
6
|
+
|
|
7
|
+
## Core Principles
|
|
8
|
+
|
|
9
|
+
### 1. Field Attributes Restrictions
|
|
10
|
+
|
|
11
|
+
When defining entity fields, **only the following attributes are allowed**:
|
|
12
|
+
|
|
13
|
+
**Standard Field Attributes (from `FIELD_ATTRS`):**
|
|
14
|
+
- `name` - Field name (required)
|
|
15
|
+
- `type` - Data type (default: "string")
|
|
16
|
+
- `required` - Whether field is required
|
|
17
|
+
- `default` - Default value for the field (validated against field type)
|
|
18
|
+
- `ref` - Reference to another entity collection
|
|
19
|
+
- `link` - Link to another field (auto-populated from ref)
|
|
20
|
+
- `delete` - Deletion behavior for ref fields ("keep" or "cascade")
|
|
21
|
+
- `create` - Show in create form
|
|
22
|
+
- `list` - Show in table list
|
|
23
|
+
- `search` - Show in search form
|
|
24
|
+
- `update` - Allow update
|
|
25
|
+
- `clone` - Include in clone
|
|
26
|
+
- `sys` - System field (server-side only)
|
|
27
|
+
- `secure` - Hidden from client entirely
|
|
28
|
+
- `group` - User group sharing control
|
|
29
|
+
- `view` - Form view identifier
|
|
30
|
+
|
|
31
|
+
**Link Field Attributes (from `LINK_FIELD_ATTRS`):**
|
|
32
|
+
- `name` - Field name
|
|
33
|
+
- `link` - Field to link to
|
|
34
|
+
- `list` - Show in list
|
|
35
|
+
|
|
36
|
+
> **IMPORTANT**: Any attributes outside these lists will cause validation errors. For example, `enum_values`, `max_length`, `accept` are **NOT** valid field attributes in the meta definition.
|
|
37
|
+
|
|
38
|
+
### 2. Type Attribute Rules
|
|
39
|
+
|
|
40
|
+
The `type` attribute should **only** use types defined in the core type system:
|
|
41
|
+
|
|
42
|
+
**Server-Side Types** (from `hola-server/core/type.js`):
|
|
43
|
+
- Basic: `obj`, `string`, `lstr`, `text`, `password`, `file`, `date`, `enum`, `log_category`
|
|
44
|
+
- Boolean: `boolean`
|
|
45
|
+
- Numeric: `number`, `int`, `uint`, `float`, `ufloat`, `decimal`, `percentage`, `currency`
|
|
46
|
+
- Date/Time: `datetime`, `time`
|
|
47
|
+
- Validation: `email`, `url`, `phone`, `uuid`, `color`, `ip_address`
|
|
48
|
+
- Data Structures: `array`, `json`
|
|
49
|
+
- Transformations: `slug`
|
|
50
|
+
- Domain-Specific: `age`, `gender`, `log_level`
|
|
51
|
+
|
|
52
|
+
Any type name not in this list is considered a **customized type** and must be registered before use.
|
|
53
|
+
|
|
54
|
+
### 3. Default Values
|
|
55
|
+
|
|
56
|
+
The `default` attribute allows you to specify default values that will be automatically populated in create forms when the field is empty:
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
fields: [
|
|
60
|
+
{ name: "quantity", type: "int", default: 0 },
|
|
61
|
+
{ name: "active", type: "boolean", default: true },
|
|
62
|
+
{ name: "price", type: "float", default: 9.99 },
|
|
63
|
+
{ name: "category", type: "product_category", default: 0 } // First enum value
|
|
64
|
+
]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Validation Rules:**
|
|
68
|
+
- The default value **must be valid** for the field's type
|
|
69
|
+
- Validation happens during meta definition loading using `type.convert()`
|
|
70
|
+
- If the default value doesn't pass type validation, an error is thrown
|
|
71
|
+
|
|
72
|
+
**Client-Side Behavior:**
|
|
73
|
+
- Default values are applied automatically in `BasicForm.vue` during create operations
|
|
74
|
+
- Defaults are only applied when the field value is `undefined`, `null`, or empty string
|
|
75
|
+
- User can still override default values by entering different values
|
|
76
|
+
|
|
77
|
+
### 4. Enumeration Pattern (g18n-Compatible)
|
|
78
|
+
|
|
79
|
+
The Hola framework follows the **g18n** (global internationalization) pattern for enumerations:
|
|
80
|
+
|
|
81
|
+
- **Store integer values in database** (e.g., 0, 1, 2)
|
|
82
|
+
- **Display translated labels on client side** (e.g., "Male", "Female")
|
|
83
|
+
- **Define enums as customized int enum types**, NOT as string enums
|
|
84
|
+
|
|
85
|
+
**❌ INCORRECT - String Enum:**
|
|
86
|
+
```javascript
|
|
87
|
+
fields: [
|
|
88
|
+
{
|
|
89
|
+
name: "category",
|
|
90
|
+
type: "enum",
|
|
91
|
+
enum_values: ["Electronics", "Clothing", "Food"] // ❌ NO enum_values attribute
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**✅ CORRECT - Int Enum Type:**
|
|
97
|
+
```javascript
|
|
98
|
+
// 1. Register customized type using built-in helper
|
|
99
|
+
const { register_type, int_enum_type } = require("hola-server/core/type");
|
|
100
|
+
|
|
101
|
+
register_type(int_enum_type("product_category", [0, 1, 2]));
|
|
102
|
+
// 0=Electronics, 1=Clothing, 2=Food
|
|
103
|
+
|
|
104
|
+
// 2. Use in field definition
|
|
105
|
+
fields: [
|
|
106
|
+
{
|
|
107
|
+
name: "category",
|
|
108
|
+
type: "product_category" // ✅ Custom type
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Client-Side Labels** (in `hola-web/src/core/type.js`):
|
|
114
|
+
```javascript
|
|
115
|
+
register_type({
|
|
116
|
+
name: "product_category",
|
|
117
|
+
input_type: "autocomplete",
|
|
118
|
+
items: (vue) => [
|
|
119
|
+
{ value: 0, text: vue.$t("product_category.electronics") },
|
|
120
|
+
{ value: 1, text: vue.$t("product_category.clothing") },
|
|
121
|
+
{ value: 2, text: vue.$t("product_category.food") }
|
|
122
|
+
],
|
|
123
|
+
format: (value, vue) => {
|
|
124
|
+
const labels = ["electronics", "clothing", "food"];
|
|
125
|
+
return labels[value] ? vue.$t(`product_category.${labels[value]}`) : "";
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Localized Labels** (in `hola-web/src/locales/en.json`):
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"product_category": {
|
|
134
|
+
"electronics": "Electronics",
|
|
135
|
+
"clothing": "Clothing",
|
|
136
|
+
"food": "Food"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Built-in Types Reference
|
|
142
|
+
|
|
143
|
+
### Basic Types
|
|
144
|
+
|
|
145
|
+
| Type | Server Conversion | Client Input | Use Case |
|
|
146
|
+
|------|------------------|--------------|----------|
|
|
147
|
+
| `string` | Trim whitespace, default "" | `v-text-field` | Short text (≤255 chars) |
|
|
148
|
+
| `lstr` | Passthrough string | `v-textarea` | Long string |
|
|
149
|
+
| `text` | Passthrough string | Rich editor | Long formatted text |
|
|
150
|
+
| `password` | Encrypt with hash | Password input | Secure credentials |
|
|
151
|
+
| `file` | Passthrough | File upload | File attachments |
|
|
152
|
+
| `date` | Passthrough string | Date picker | Date only |
|
|
153
|
+
| `enum` | Passthrough string | Autocomplete | String options |
|
|
154
|
+
|
|
155
|
+
### Numeric Types
|
|
156
|
+
|
|
157
|
+
| Type | Server Conversion | Validation | Client Input |
|
|
158
|
+
|------|------------------|------------|--------------|
|
|
159
|
+
| `number` | Parse to number | Any number | Number input |
|
|
160
|
+
| `int` | Parse to integer | Integer only | Number input |
|
|
161
|
+
| `uint` | Parse to unsigned int | Integer ≥ 0 | Number input |
|
|
162
|
+
| `float` | Parse to 2 decimals | Float with 2 decimals | Number input |
|
|
163
|
+
| `ufloat` | Parse to unsigned 2 decimals | Float ≥ 0 with 2 decimals | Number input |
|
|
164
|
+
| `decimal` | Parse to decimal | Any float | Number input |
|
|
165
|
+
| `percentage` | Parse to 2 decimals | Float | Number input with % |
|
|
166
|
+
| `currency` | Parse to number | Number | Number input with $ |
|
|
167
|
+
|
|
168
|
+
### Validation Types
|
|
169
|
+
|
|
170
|
+
| Type | Pattern/Rule | Example |
|
|
171
|
+
|------|-------------|---------|
|
|
172
|
+
| `email` | Email regex pattern | `user@example.com` |
|
|
173
|
+
| `url` | Valid URL structure | `https://example.com` |
|
|
174
|
+
| `phone` | International format | `+1234567890` |
|
|
175
|
+
| `uuid` | UUID v1-v5 | `550e8400-e29b-41d4-a716-446655440000` |
|
|
176
|
+
| `color` | Hex color | `#FF5733` or `#F57` |
|
|
177
|
+
| `ip_address` | IPv4 format | `192.168.1.1` |
|
|
178
|
+
|
|
179
|
+
### Domain-Specific Types
|
|
180
|
+
|
|
181
|
+
| Type | Valid Values | Description |
|
|
182
|
+
|------|-------------|-------------|
|
|
183
|
+
| `age` | 0-200 (int) | Person age |
|
|
184
|
+
| `gender` | 0=Male, 1=Female | Gender enum |
|
|
185
|
+
| `log_level` | 0=Debug, 1=Info, 2=Warn, 3=Error | Log severity |
|
|
186
|
+
|
|
187
|
+
## Creating Customized Types
|
|
188
|
+
|
|
189
|
+
### Step 1: Server-Side Type Registration
|
|
190
|
+
|
|
191
|
+
Create a type definition in your entity file or a shared types file:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
const { register_type, ok, err, is_int, int_enum_type, int_range_type, regex_type } = require("hola-server/core/type");
|
|
195
|
+
|
|
196
|
+
// Example 1: Int Enum Type
|
|
197
|
+
// Use the built-in helper function
|
|
198
|
+
register_type(int_enum_type("order_status", [0, 1, 2, 3]));
|
|
199
|
+
// 0=Pending, 1=Processing, 2=Shipped, 3=Delivered
|
|
200
|
+
|
|
201
|
+
// Example 2: Int Range Type
|
|
202
|
+
// Use the built-in helper function
|
|
203
|
+
register_type(int_range_type("priority", 1, 5));
|
|
204
|
+
|
|
205
|
+
// Example 3: Min/Max Value Type (like age type)
|
|
206
|
+
// Server-side: Use int_range_type helper for integer ranges
|
|
207
|
+
register_type(int_range_type("employee_age", 18, 65));
|
|
208
|
+
// This creates validation: 18 <= value <= 65
|
|
209
|
+
|
|
210
|
+
// Example 4: Regex Validation Type
|
|
211
|
+
// Use the built-in helper function
|
|
212
|
+
register_type(regex_type("sku_code", /^[A-Z]{3}-\d{6}$/));
|
|
213
|
+
|
|
214
|
+
// Example 5: Custom Business Logic Type
|
|
215
|
+
// Use ok() and err() helpers for return values
|
|
216
|
+
register_type({
|
|
217
|
+
name: "discount_rate",
|
|
218
|
+
convert: (value) => {
|
|
219
|
+
const num = parseFloat(value);
|
|
220
|
+
if (isNaN(num)) return err("discount_rate", value);
|
|
221
|
+
if (num < 0 || num > 100) return err("discount_rate", value);
|
|
222
|
+
return ok(parseFloat(num.toFixed(2)));
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Example 6: Custom Int Enum with Business Logic
|
|
227
|
+
// Use is_int() helper for validation
|
|
228
|
+
register_type({
|
|
229
|
+
name: "approval_status",
|
|
230
|
+
convert: (value) => {
|
|
231
|
+
if (!is_int(value)) return err("approval_status", value);
|
|
232
|
+
const int_value = parseInt(value);
|
|
233
|
+
const valid = [0, 1, 2]; // 0=Pending, 1=Approved, 2=Rejected
|
|
234
|
+
return valid.includes(int_value) ? ok(int_value) : err("approval_status", value);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Available Helper Functions:**
|
|
240
|
+
|
|
241
|
+
| Helper | Purpose | Example |
|
|
242
|
+
|--------|---------|---------|
|
|
243
|
+
| `ok(value)` | Return success result | `ok(42)` → `{value: 42}` |
|
|
244
|
+
| `err(type, value)` | Return error result | `err("int", "abc")` → `{err: "invalid int:abc"}` |
|
|
245
|
+
| `is_int(value)` | Check if value is integer | `is_int(42)` → `true` |
|
|
246
|
+
| `int_enum_type(name, values)` | Create int enum type | `int_enum_type("status", [0,1,2])` |
|
|
247
|
+
| `int_range_type(name, min, max)` | Create int range type | `int_range_type("age", 0, 200)` |
|
|
248
|
+
| `regex_type(name, pattern)` | Create regex validation type | `regex_type("email", /.../)` |
|
|
249
|
+
| `string_type(name)` | Create passthrough string type | `string_type("code")` |
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
### Step 2: Client-Side Type Registration
|
|
253
|
+
|
|
254
|
+
Register the corresponding client-side type in your Vue app:
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
// hola-web/src/core/type.js or custom types file
|
|
258
|
+
import { register_type } from "@/core/type";
|
|
259
|
+
|
|
260
|
+
// Example 1: Int Enum with i18n
|
|
261
|
+
register_type({
|
|
262
|
+
name: "order_status",
|
|
263
|
+
input_type: "autocomplete",
|
|
264
|
+
items: (vue) => [
|
|
265
|
+
{ value: 0, text: vue.$t("order_status.pending") },
|
|
266
|
+
{ value: 1, text: vue.$t("order_status.processing") },
|
|
267
|
+
{ value: 2, text: vue.$t("order_status.shipped") },
|
|
268
|
+
{ value: 3, text: vue.$t("order_status.delivered") }
|
|
269
|
+
],
|
|
270
|
+
format: (value, vue) => {
|
|
271
|
+
const statuses = ["pending", "processing", "shipped", "delivered"];
|
|
272
|
+
return statuses[value] ? vue.$t(`order_status.${statuses[value]}`) : "";
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Example 2: Range Input
|
|
277
|
+
register_type({
|
|
278
|
+
name: "priority",
|
|
279
|
+
input_type: "slider",
|
|
280
|
+
min: 1,
|
|
281
|
+
max: 5,
|
|
282
|
+
step: 1,
|
|
283
|
+
rule: (vue, field_name) => {
|
|
284
|
+
const err = vue.$t("type.priority", { field: field_name });
|
|
285
|
+
return (value) => {
|
|
286
|
+
const num = parseInt(value);
|
|
287
|
+
return (num >= 1 && num <= 5) || err;
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
format: (value) => `Priority ${value}`
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Example 3: Min/Max Value with Suffix (like age type)
|
|
294
|
+
// Client-side: Number input with validation and localized suffix
|
|
295
|
+
register_type({
|
|
296
|
+
name: "employee_age",
|
|
297
|
+
input_type: "number",
|
|
298
|
+
search_input_type: "text",
|
|
299
|
+
suffix: (vue) => vue.$t("type.age_unit"), // e.g., "years old"
|
|
300
|
+
rule: (vue, field_name) => {
|
|
301
|
+
const err = vue.$t("type.employee_age", { field: field_name });
|
|
302
|
+
return (value) => {
|
|
303
|
+
if (!value) return true;
|
|
304
|
+
const num = parseInt(value);
|
|
305
|
+
return (num >= 18 && num <= 65) || err;
|
|
306
|
+
};
|
|
307
|
+
},
|
|
308
|
+
format: (value, vue) => value ? `${value} ${vue.$t("type.age_unit")}` : ""
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Example 4: Custom Validation
|
|
312
|
+
register_type({
|
|
313
|
+
name: "sku_code",
|
|
314
|
+
input_type: "text",
|
|
315
|
+
rule: (vue, field_name) => {
|
|
316
|
+
const err = vue.$t("type.sku_code", { field: field_name });
|
|
317
|
+
const pattern = /^[A-Z]{3}-\d{6}$/;
|
|
318
|
+
return (value) => !value || pattern.test(value) || err;
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// Example 4: Formatted Number
|
|
323
|
+
register_type({
|
|
324
|
+
name: "discount_rate",
|
|
325
|
+
input_type: "number",
|
|
326
|
+
suffix: "%",
|
|
327
|
+
rule: (vue, field_name) => {
|
|
328
|
+
const err = vue.$t("type.discount_rate", { field: field_name });
|
|
329
|
+
return (value) => {
|
|
330
|
+
if (!value) return true;
|
|
331
|
+
const num = parseFloat(value);
|
|
332
|
+
return (!isNaN(num) && num >= 0 && num <= 100) || err;
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
format: (value) => value ? `${value.toFixed(2)}%` : ""
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Step 3: Add i18n Translations
|
|
340
|
+
|
|
341
|
+
Add translations for your custom types:
|
|
342
|
+
|
|
343
|
+
```json
|
|
344
|
+
// hola-web/src/locales/en.json
|
|
345
|
+
{
|
|
346
|
+
"order_status": {
|
|
347
|
+
"pending": "Pending",
|
|
348
|
+
"processing": "Processing",
|
|
349
|
+
"shipped": "Shipped",
|
|
350
|
+
"delivered": "Delivered"
|
|
351
|
+
},
|
|
352
|
+
"type": {
|
|
353
|
+
"priority": "Priority must be between 1 and 5",
|
|
354
|
+
"employee_age": "Age must be between 18 and 65",
|
|
355
|
+
"age_unit": "years old",
|
|
356
|
+
"sku_code": "SKU code must be in format XXX-123456",
|
|
357
|
+
"discount_rate": "Discount rate must be between 0 and 100"
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Step 4: Use in Entity Definition
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
const { init_router } = require("hola-server");
|
|
366
|
+
|
|
367
|
+
module.exports = init_router({
|
|
368
|
+
collection: "product",
|
|
369
|
+
creatable: true,
|
|
370
|
+
readable: true,
|
|
371
|
+
updatable: true,
|
|
372
|
+
deleteable: true,
|
|
373
|
+
|
|
374
|
+
primary_keys: ["sku"],
|
|
375
|
+
ref_label: "name",
|
|
376
|
+
|
|
377
|
+
fields: [
|
|
378
|
+
{ name: "sku", type: "sku_code", required: true },
|
|
379
|
+
{ name: "name", type: "string", required: true },
|
|
380
|
+
{ name: "status", type: "order_status", required: true },
|
|
381
|
+
{ name: "priority", type: "priority" },
|
|
382
|
+
{ name: "age", type: "employee_age" },
|
|
383
|
+
{ name: "discount", type: "discount_rate" }
|
|
384
|
+
]
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Complete Example: Product Category Type
|
|
389
|
+
|
|
390
|
+
Here's a complete example showing all steps:
|
|
391
|
+
|
|
392
|
+
**1. Server Type (`hola-server/router/product.js`):**
|
|
393
|
+
```javascript
|
|
394
|
+
const { init_router } = require("hola-server");
|
|
395
|
+
const { register_type, int_enum_type } = require("hola-server/core/type");
|
|
396
|
+
|
|
397
|
+
// Define custom type using built-in helper
|
|
398
|
+
register_type(int_enum_type("product_category", [0, 1, 2]));
|
|
399
|
+
// 0=Electronics, 1=Clothing, 2=Food
|
|
400
|
+
|
|
401
|
+
// Use in entity
|
|
402
|
+
module.exports = init_router({
|
|
403
|
+
collection: "product",
|
|
404
|
+
creatable: true,
|
|
405
|
+
readable: true,
|
|
406
|
+
updatable: true,
|
|
407
|
+
deleteable: true,
|
|
408
|
+
|
|
409
|
+
primary_keys: ["name"],
|
|
410
|
+
ref_label: "name",
|
|
411
|
+
|
|
412
|
+
fields: [
|
|
413
|
+
{ name: "name", type: "string", required: true },
|
|
414
|
+
{ name: "price", type: "decimal", required: true },
|
|
415
|
+
{ name: "category", type: "product_category", required: true }
|
|
416
|
+
]
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
**2. Client Type (`hola-web/src/types/product.js`):**
|
|
421
|
+
```javascript
|
|
422
|
+
import { register_type } from "@/core/type";
|
|
423
|
+
|
|
424
|
+
register_type({
|
|
425
|
+
name: "product_category",
|
|
426
|
+
input_type: "autocomplete",
|
|
427
|
+
items: (vue) => [
|
|
428
|
+
{ value: 0, text: vue.$t("product_category.electronics") },
|
|
429
|
+
{ value: 1, text: vue.$t("product_category.clothing") },
|
|
430
|
+
{ value: 2, text: vue.$t("product_category.food") }
|
|
431
|
+
],
|
|
432
|
+
format: (value, vue) => {
|
|
433
|
+
const categories = ["electronics", "clothing", "food"];
|
|
434
|
+
return categories[value] ? vue.$t(`product_category.${categories[value]}`) : "";
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**3. Translations (`hola-web/src/locales/en.json`):**
|
|
440
|
+
```json
|
|
441
|
+
{
|
|
442
|
+
"product_category": {
|
|
443
|
+
"electronics": "Electronics",
|
|
444
|
+
"clothing": "Clothing",
|
|
445
|
+
"food": "Food"
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Best Practices
|
|
451
|
+
|
|
452
|
+
### 1. Use Int Enums for All Enumerations
|
|
453
|
+
- Store integers in the database for efficiency and language-independence
|
|
454
|
+
- Use i18n labels on the client side for display
|
|
455
|
+
- Never use string enums with hardcoded values
|
|
456
|
+
|
|
457
|
+
### 2. Keep Types DRY
|
|
458
|
+
- Use the built-in helper functions exported from `hola-server/core/type` (`int_enum_type`, `int_range_type`, `regex_type`, etc.)
|
|
459
|
+
- Share type definitions across multiple entities if appropriate
|
|
460
|
+
- Only define custom helpers when you need specialized business logic not covered by built-ins
|
|
461
|
+
|
|
462
|
+
### 3. Validation Consistency
|
|
463
|
+
- Ensure server-side and client-side validation rules match
|
|
464
|
+
- Server validation is authoritative; client validation improves UX
|
|
465
|
+
|
|
466
|
+
### 4. Type Naming Conventions
|
|
467
|
+
- Use descriptive names: `order_status`, `product_category`, `priority_level`
|
|
468
|
+
- Avoid generic names: `status`, `type`, `category` (too vague)
|
|
469
|
+
- Use snake_case for consistency with other Hola conventions
|
|
470
|
+
|
|
471
|
+
### 5. Error Messages
|
|
472
|
+
- Provide clear, actionable error messages
|
|
473
|
+
- Use i18n for all user-facing messages
|
|
474
|
+
- Include field context in validation errors
|
|
475
|
+
|
|
476
|
+
### 6. Register Before Use
|
|
477
|
+
- Always register custom types before defining entities that use them
|
|
478
|
+
- Register in entity file or in a shared types initialization module
|
|
479
|
+
- Verify type exists using `get_type(name)` if needed
|
|
480
|
+
|
|
481
|
+
## Common Mistakes to Avoid
|
|
482
|
+
|
|
483
|
+
### ❌ Don't: Add Custom Attributes to Fields
|
|
484
|
+
```javascript
|
|
485
|
+
// ❌ WRONG
|
|
486
|
+
fields: [
|
|
487
|
+
{
|
|
488
|
+
name: "category",
|
|
489
|
+
type: "enum",
|
|
490
|
+
enum_values: ["A", "B"], // ❌ Not a valid field attribute
|
|
491
|
+
max_length: 100 // ❌ Not a valid field attribute
|
|
492
|
+
}
|
|
493
|
+
]
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### ✅ Do: Use Customized Types
|
|
497
|
+
```javascript
|
|
498
|
+
// ✅ CORRECT
|
|
499
|
+
register_type({
|
|
500
|
+
name: "my_category",
|
|
501
|
+
convert: (value) => {
|
|
502
|
+
const valid = [0, 1];
|
|
503
|
+
const int_val = parseInt(value);
|
|
504
|
+
return valid.includes(int_val) ? { value: int_val } : { err: "invalid" };
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
fields: [
|
|
509
|
+
{ name: "category", type: "my_category", required: true, default: 0 } // ✅ Valid with default
|
|
510
|
+
]
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### ❌ Don't: Use String Enums
|
|
514
|
+
```javascript
|
|
515
|
+
// ❌ WRONG - String values
|
|
516
|
+
register_type({
|
|
517
|
+
name: "status",
|
|
518
|
+
convert: (value) => {
|
|
519
|
+
const valid = ["active", "inactive"];
|
|
520
|
+
return valid.includes(value) ? { value } : { err: "invalid" };
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### ✅ Do: Use Int Enums
|
|
526
|
+
```javascript
|
|
527
|
+
// ✅ CORRECT - Int values
|
|
528
|
+
register_type({
|
|
529
|
+
name: "status",
|
|
530
|
+
convert: (value) => {
|
|
531
|
+
const int_val = parseInt(value);
|
|
532
|
+
const valid = [0, 1]; // 0=inactive, 1=active
|
|
533
|
+
return valid.includes(int_val) ? { value: int_val } : { err: "invalid" };
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Summary
|
|
539
|
+
|
|
540
|
+
The Hola type system provides:
|
|
541
|
+
- **Strict field attribute validation** - only predefined attributes allowed
|
|
542
|
+
- **Comprehensive built-in types** - covering common data validation needs
|
|
543
|
+
- **Customized type support** - extend with your own business logic
|
|
544
|
+
- **g18n-compatible enums** - int values in DB, i18n labels in UI
|
|
545
|
+
- **Server-client consistency** - matching validation on both sides
|
|
546
|
+
|
|
547
|
+
Follow these guidelines to create robust, maintainable, and internationalization-ready entities in your Hola applications.
|
package/skills/url.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# URL & HTTP Utilities Skill
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `hola-server/core/url.js` module provides a factory for creating pre-configured Axios request functions using system settings (like proxy configuration).
|
|
6
|
+
|
|
7
|
+
## Importing
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const { url } = require("hola-server/core/url");
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## API Reference
|
|
14
|
+
|
|
15
|
+
### `url(target_url, method)`
|
|
16
|
+
Creates a function that performs an HTTP request to the specified target.
|
|
17
|
+
- **param**: `target_url` (string) - The URL endpoint.
|
|
18
|
+
- **param**: `method` (string) - HTTP method ('GET', 'POST', etc.).
|
|
19
|
+
- **returns**: `Function` - `(config) => Promise`
|
|
20
|
+
|
|
21
|
+
#### Usage Example
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// 1. Create a reusable request function
|
|
25
|
+
const getGithubProfile = url("https://api.github.com/users/octocat", "GET");
|
|
26
|
+
|
|
27
|
+
// 2. Execute it (optionally passing axios config overrides/headers)
|
|
28
|
+
try {
|
|
29
|
+
const response = await getGithubProfile({ headers: { 'User-Agent': 'node' } });
|
|
30
|
+
console.log(response.data);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error(err);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Validation Utilities Skill
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `hola-server/core/validate.js` module provides basic validation helpers to check for undefined, empty, or missing values.
|
|
6
|
+
|
|
7
|
+
## Importing
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const {
|
|
11
|
+
is_undefined, has_value, validate_required_fields
|
|
12
|
+
} = require("hola-server/core/validate");
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## API Reference
|
|
16
|
+
|
|
17
|
+
### `is_undefined(value)`
|
|
18
|
+
Checks if a value is strictly `undefined`.
|
|
19
|
+
- **param**: `value` (*)
|
|
20
|
+
- **returns**: `boolean`
|
|
21
|
+
|
|
22
|
+
### `has_value(value)`
|
|
23
|
+
Checks if a value is "meaningful". Returns `false` for:
|
|
24
|
+
- `null`
|
|
25
|
+
- `undefined`
|
|
26
|
+
- `NaN`
|
|
27
|
+
- Empty strings `""` or whitespace-only strings ` " " `
|
|
28
|
+
- **returns**: `boolean`
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
has_value(0); // true
|
|
32
|
+
has_value(false); // true
|
|
33
|
+
has_value(""); // false
|
|
34
|
+
has_value(" "); // false
|
|
35
|
+
has_value(null); // false
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### `validate_required_fields(obj, field_names)`
|
|
39
|
+
Checks an object for missing required properties (using `has_value`).
|
|
40
|
+
- **param**: `obj` (Object) - Object to validate.
|
|
41
|
+
- **param**: `field_names` (string[]) - List of keys that must be present.
|
|
42
|
+
- **returns**: `string[]` - Array of field names that failed validation.
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
const data = { name: "Alice", age: null };
|
|
46
|
+
const missing = validate_required_fields(data, ["name", "age", "email"]);
|
|
47
|
+
// missing is ["age", "email"]
|
|
48
|
+
```
|