hola-server 2.0.1 → 3.0.0
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 +318 -132
- package/dist/config/index.d.ts +46 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +55 -0
- package/dist/config/index.js.map +1 -0
- package/dist/core/array.d.ts +27 -0
- package/dist/core/array.d.ts.map +1 -0
- package/dist/core/array.js +66 -0
- package/dist/core/array.js.map +1 -0
- package/dist/core/bash.d.ts +51 -0
- package/dist/core/bash.d.ts.map +1 -0
- package/dist/core/bash.js +161 -0
- package/dist/core/bash.js.map +1 -0
- package/dist/core/chart.d.ts +11 -0
- package/dist/core/chart.d.ts.map +1 -0
- package/dist/core/chart.js +35 -0
- package/dist/core/chart.js.map +1 -0
- package/dist/core/date.d.ts +11 -0
- package/dist/core/date.d.ts.map +1 -0
- package/dist/core/date.js +18 -0
- package/dist/core/date.js.map +1 -0
- package/dist/core/encrypt.d.ts +18 -0
- package/dist/core/encrypt.d.ts.map +1 -0
- package/dist/core/encrypt.js +50 -0
- package/dist/core/encrypt.js.map +1 -0
- package/dist/core/file.d.ts +22 -0
- package/dist/core/file.d.ts.map +1 -0
- package/dist/core/file.js +21 -0
- package/dist/core/file.js.map +1 -0
- package/dist/core/lhs.d.ts +17 -0
- package/dist/core/lhs.d.ts.map +1 -0
- package/dist/core/lhs.js +30 -0
- package/dist/core/lhs.js.map +1 -0
- package/dist/core/meta.d.ts +200 -0
- package/dist/core/meta.d.ts.map +1 -0
- package/dist/core/meta.js +336 -0
- package/dist/core/meta.js.map +1 -0
- package/dist/core/number.d.ts +37 -0
- package/dist/core/number.d.ts.map +1 -0
- package/dist/core/number.js +99 -0
- package/dist/core/number.js.map +1 -0
- package/dist/core/obj.d.ts +9 -0
- package/dist/core/obj.d.ts.map +1 -0
- package/dist/core/obj.js +15 -0
- package/dist/core/obj.js.map +1 -0
- package/dist/core/random.d.ts +7 -0
- package/dist/core/random.d.ts.map +1 -0
- package/dist/core/random.js +7 -0
- package/dist/core/random.js.map +1 -0
- package/dist/core/role.d.ts +42 -0
- package/dist/core/role.d.ts.map +1 -0
- package/dist/core/role.js +81 -0
- package/dist/core/role.js.map +1 -0
- package/dist/core/thread.d.ts +7 -0
- package/dist/core/thread.d.ts.map +1 -0
- package/dist/core/thread.js +7 -0
- package/dist/core/thread.js.map +1 -0
- package/dist/core/type.d.ts +46 -0
- package/dist/core/type.d.ts.map +1 -0
- package/dist/core/type.js +281 -0
- package/dist/core/type.js.map +1 -0
- package/dist/core/url.d.ts +20 -0
- package/dist/core/url.d.ts.map +1 -0
- package/dist/core/url.js +24 -0
- package/dist/core/url.js.map +1 -0
- package/dist/core/validate.d.ts +11 -0
- package/dist/core/validate.d.ts.map +1 -0
- package/dist/core/validate.js +19 -0
- package/dist/core/validate.js.map +1 -0
- package/dist/db/db.d.ts +72 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +225 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/entity.d.ts +77 -0
- package/dist/db/entity.d.ts.map +1 -0
- package/dist/db/entity.js +671 -0
- package/dist/db/entity.js.map +1 -0
- package/dist/db/gridfs.d.ts +29 -0
- package/dist/db/gridfs.d.ts.map +1 -0
- package/dist/db/gridfs.js +125 -0
- package/dist/db/gridfs.js.map +1 -0
- package/dist/db/index.d.ts +8 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +8 -0
- package/dist/db/index.js.map +1 -0
- package/dist/errors/auth.d.ts +15 -0
- package/dist/errors/auth.d.ts.map +1 -0
- package/dist/errors/auth.js +21 -0
- package/dist/errors/auth.js.map +1 -0
- package/dist/errors/http.d.ts +15 -0
- package/dist/errors/http.d.ts.map +1 -0
- package/dist/errors/http.js +21 -0
- package/dist/errors/http.js.map +1 -0
- package/dist/errors/index.d.ts +18 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +18 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/validation.d.ts +11 -0
- package/dist/errors/validation.d.ts.map +1 -0
- package/dist/errors/validation.js +15 -0
- package/dist/errors/validation.js.map +1 -0
- package/dist/http/code.d.ts +21 -0
- package/dist/http/code.d.ts.map +1 -0
- package/dist/http/code.js +27 -0
- package/dist/http/code.js.map +1 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/meta/index.d.ts +9 -0
- package/dist/meta/index.d.ts.map +1 -0
- package/dist/meta/index.js +11 -0
- package/dist/meta/index.js.map +1 -0
- package/dist/meta/router.d.ts +26 -0
- package/dist/meta/router.d.ts.map +1 -0
- package/dist/meta/router.js +258 -0
- package/dist/meta/router.js.map +1 -0
- package/dist/meta/schema.d.ts +41 -0
- package/dist/meta/schema.d.ts.map +1 -0
- package/dist/meta/schema.js +69 -0
- package/dist/meta/schema.js.map +1 -0
- package/dist/plugins/auth.d.ts +248 -0
- package/dist/plugins/auth.d.ts.map +1 -0
- package/dist/plugins/auth.js +121 -0
- package/dist/plugins/auth.js.map +1 -0
- package/dist/plugins/body.d.ts +47 -0
- package/dist/plugins/body.d.ts.map +1 -0
- package/dist/plugins/body.js +36 -0
- package/dist/plugins/body.js.map +1 -0
- package/dist/plugins/cors.d.ts +62 -0
- package/dist/plugins/cors.d.ts.map +1 -0
- package/dist/plugins/cors.js +17 -0
- package/dist/plugins/cors.js.map +1 -0
- package/dist/plugins/error.d.ts +51 -0
- package/dist/plugins/error.d.ts.map +1 -0
- package/dist/plugins/error.js +51 -0
- package/dist/plugins/error.js.map +1 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +9 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/setting.d.ts +66 -0
- package/dist/setting.d.ts.map +1 -0
- package/dist/setting.js +27 -0
- package/dist/setting.js.map +1 -0
- package/dist/tool/gen_i18n.d.ts +10 -0
- package/dist/tool/gen_i18n.d.ts.map +1 -0
- package/{tool → dist/tool}/gen_i18n.js +9 -22
- package/dist/tool/gen_i18n.js.map +1 -0
- package/dist/tool/vector_store.d.ts +72 -0
- package/dist/tool/vector_store.d.ts.map +1 -0
- package/dist/tool/vector_store.js +203 -0
- package/dist/tool/vector_store.js.map +1 -0
- package/package.json +37 -22
- package/core/array.js +0 -124
- package/core/bash.js +0 -294
- package/core/chart.js +0 -46
- package/core/cron.js +0 -21
- package/core/date.js +0 -26
- package/core/encrypt.js +0 -26
- package/core/file.js +0 -51
- package/core/lhs.js +0 -53
- package/core/meta.js +0 -283
- package/core/msg.js +0 -24
- package/core/number.js +0 -181
- package/core/obj.js +0 -25
- package/core/random.js +0 -12
- package/core/role.js +0 -108
- package/core/thread.js +0 -13
- package/core/type.js +0 -368
- package/core/url.js +0 -30
- package/core/validate.js +0 -35
- package/db/db.js +0 -446
- package/db/entity.js +0 -920
- package/db/gridfs.js +0 -175
- package/design/add_default_field_attr.md +0 -56
- package/http/code.js +0 -18
- package/http/context.js +0 -31
- package/http/cors.js +0 -32
- package/http/error.js +0 -39
- package/http/express.js +0 -104
- package/http/params.js +0 -85
- package/http/router.js +0 -83
- package/http/session.js +0 -73
- package/index.js +0 -112
- package/router/clone.js +0 -65
- package/router/create.js +0 -54
- package/router/delete.js +0 -49
- package/router/read.js +0 -191
- package/router/update.js +0 -89
- package/setting.js +0 -67
- package/skills/array.md +0 -155
- package/skills/bash.md +0 -91
- package/skills/chart.md +0 -54
- package/skills/code.md +0 -422
- package/skills/context.md +0 -177
- package/skills/date.md +0 -58
- package/skills/express.md +0 -255
- package/skills/file.md +0 -60
- package/skills/lhs.md +0 -54
- package/skills/meta.md +0 -1023
- package/skills/msg.md +0 -30
- package/skills/number.md +0 -88
- package/skills/obj.md +0 -36
- package/skills/params.md +0 -206
- package/skills/random.md +0 -22
- package/skills/role.md +0 -59
- package/skills/session.md +0 -281
- package/skills/storage.md +0 -743
- package/skills/thread.md +0 -22
- package/skills/type.md +0 -547
- package/skills/url.md +0 -34
- package/skills/validate.md +0 -48
- package/test/cleanup/close-db.js +0 -5
- package/test/core/array.js +0 -226
- package/test/core/chart.js +0 -51
- package/test/core/date.js +0 -37
- package/test/core/encrypt.js +0 -14
- package/test/core/file.js +0 -59
- package/test/core/lhs.js +0 -44
- package/test/core/meta.js +0 -594
- package/test/core/number.js +0 -172
- package/test/core/obj.js +0 -47
- package/test/core/random.js +0 -24
- package/test/core/thread.js +0 -20
- package/test/core/type.js +0 -216
- package/test/core/validate.js +0 -67
- package/test/db/db-ops.js +0 -99
- package/test/db/db.js +0 -72
- package/test/db/pipe_test.txt +0 -0
- package/test/db/test_case_design.md +0 -528
- package/test/db/test_db_class.js +0 -613
- package/test/db/test_entity_class.js +0 -414
- package/test/db/test_gridfs_class.js +0 -234
- package/test/entity/create.js +0 -442
- package/test/entity/delete-mixed.js +0 -156
- package/test/entity/delete.js +0 -480
- package/test/entity/read.js +0 -285
- package/test/entity/ref-filter.js +0 -63
- package/test/entity/update.js +0 -252
- package/test/router/role.js +0 -15
- package/tool/test.json +0 -25
package/skills/meta.md
DELETED
|
@@ -1,1023 +0,0 @@
|
|
|
1
|
-
# Meta Class in Hola Meta-Programming Framework
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
The Hola meta-programming model provides a declarative way to define entity schemas with built-in validation, reference management, field organization, and lifecycle hooks.
|
|
6
|
-
|
|
7
|
-
> **IMPORTANT**: In practice, you **never directly instantiate `EntityMeta`**. Instead, you always use `init_router()` in your router definition, which internally creates the `EntityMeta` instance for you.
|
|
8
|
-
|
|
9
|
-
**Developer-Facing API:**
|
|
10
|
-
```javascript
|
|
11
|
-
// ✅ CORRECT - Use init_router in router files
|
|
12
|
-
const { init_router } = require("hola-server");
|
|
13
|
-
|
|
14
|
-
module.exports = init_router({
|
|
15
|
-
collection: "user",
|
|
16
|
-
primary_keys: ["email"],
|
|
17
|
-
fields: [...],
|
|
18
|
-
// ... meta attributes
|
|
19
|
-
});
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**Internal Implementation (for reference only):**
|
|
23
|
-
```javascript
|
|
24
|
-
// ❌ DON'T DO THIS - EntityMeta is internal
|
|
25
|
-
const { EntityMeta } = require("hola-server/core/meta");
|
|
26
|
-
const meta = new EntityMeta({...}); // Don't use directly
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
This document explains the meta attributes and field definitions you provide to `init_router()`.
|
|
30
|
-
|
|
31
|
-
## Core Principles
|
|
32
|
-
|
|
33
|
-
### 1. Meta Attributes
|
|
34
|
-
|
|
35
|
-
When defining entity metadata, **only the following attributes are allowed**:
|
|
36
|
-
|
|
37
|
-
**Entity-Level Attributes (from `META_ATTRS`):**
|
|
38
|
-
- `collection` - Collection name in MongoDB (required)
|
|
39
|
-
- `primary_keys` - Array of field names that form the primary key (required)
|
|
40
|
-
- `fields` - Array of field definitions (required)
|
|
41
|
-
- `roles` - Array of role access control definitions
|
|
42
|
-
- `ref_label` - Field name to use as display label when referenced
|
|
43
|
-
- `ref_filter` - Filter object for reference queries
|
|
44
|
-
- `route` - Custom route path
|
|
45
|
-
- `user_field` - Field name containing user ID for ownership
|
|
46
|
-
|
|
47
|
-
**Operation Flags:**
|
|
48
|
-
- `creatable` - Allow create operations (default: false)
|
|
49
|
-
- `readable` - Allow read operations (default: false)
|
|
50
|
-
- `updatable` - Allow update operations (default: false)
|
|
51
|
-
- `deleteable` - Allow delete operations (default: false)
|
|
52
|
-
- `cloneable` - Allow clone operations (default: false)
|
|
53
|
-
- `importable` - Allow import operations (default: false)
|
|
54
|
-
- `exportable` - Allow export operations (default: false)
|
|
55
|
-
|
|
56
|
-
**Lifecycle Callbacks:**
|
|
57
|
-
- `after_read` - Called after reading entity
|
|
58
|
-
- `list_query` - Modify list query
|
|
59
|
-
- `before_create` - Called before creating entity
|
|
60
|
-
- `before_clone` - Called before cloning entity
|
|
61
|
-
- `before_update` - Called before updating entity
|
|
62
|
-
- `before_delete` - Called before deleting entity
|
|
63
|
-
- `after_create` - Called after creating entity
|
|
64
|
-
- `after_clone` - Called after cloning entity
|
|
65
|
-
- `after_update` - Called after updating entity
|
|
66
|
-
- `after_delete` - Called after deleting entity
|
|
67
|
-
- `create` - Custom create handler
|
|
68
|
-
- `clone` - Custom clone handler
|
|
69
|
-
- `update` - Custom update handler
|
|
70
|
-
- `batch_update` - Custom batch update handler
|
|
71
|
-
- `after_batch_update` - Called after batch update
|
|
72
|
-
- `delete` - Custom delete handler
|
|
73
|
-
|
|
74
|
-
> **IMPORTANT**: Any attributes outside this list will cause validation errors during meta initialization.
|
|
75
|
-
|
|
76
|
-
### 2. Field Attributes
|
|
77
|
-
|
|
78
|
-
Each field definition supports the following attributes:
|
|
79
|
-
|
|
80
|
-
**Standard Field Attributes (from `FIELD_ATTRS`):**
|
|
81
|
-
- `name` - Field name (required)
|
|
82
|
-
- `type` - Data type (default: "string")
|
|
83
|
-
- `required` - Whether field is required (default: false)
|
|
84
|
-
- `default` - Default value for the field (validated against field type)
|
|
85
|
-
- `ref` - Reference to another entity (collection name)
|
|
86
|
-
- **One-to-One Reference**: When field type is `"string"` (or omitted, defaulting to `"string"`), stores a single ObjectId
|
|
87
|
-
- **One-to-Many Reference**: When field type is `"array"`, stores an array of ObjectIds
|
|
88
|
-
- `link` - Link to another field in this entity (must be a field of type 'ref')
|
|
89
|
-
- `delete` - Deletion behavior for ref fields ("keep" or "cascade")
|
|
90
|
-
- `create` - Show in create form (default: true)
|
|
91
|
-
- `list` - Show in table list (default: true)
|
|
92
|
-
- `search` - Show in search form (default: true)
|
|
93
|
-
- `update` - Allow update (default: true)
|
|
94
|
-
- `clone` - Include in clone (default: true)
|
|
95
|
-
- `sys` - System field (server-side only)
|
|
96
|
-
- `secure` - Hidden from client entirely (e.g., password hash)
|
|
97
|
-
- `group` - User group sharing control (field name containing group ID)
|
|
98
|
-
- `view` - Form view identifier ("*" for all views, or specific view name)
|
|
99
|
-
|
|
100
|
-
**Link Field Attributes (from `LINK_FIELD_ATTRS`):**
|
|
101
|
-
- `name` - Field name
|
|
102
|
-
- `link` - Field to link to
|
|
103
|
-
- `list` - Show in list
|
|
104
|
-
|
|
105
|
-
> **IMPORTANT**: Link fields only support these three attributes. Any other attributes will cause validation errors.
|
|
106
|
-
|
|
107
|
-
### 3. Deletion Modes
|
|
108
|
-
|
|
109
|
-
For reference fields, you can specify deletion behavior:
|
|
110
|
-
|
|
111
|
-
- `"keep"` - Keep this record when referenced entity is deleted
|
|
112
|
-
- `"cascade"` - Delete this record when referenced entity is deleted
|
|
113
|
-
- `undefined` - No action (default)
|
|
114
|
-
|
|
115
|
-
```javascript
|
|
116
|
-
const { DELETE_MODE } = require("hola-server/core/meta");
|
|
117
|
-
|
|
118
|
-
// DELETE_MODE.all = ["keep", "cascade"]
|
|
119
|
-
// DELETE_MODE.keep = "keep"
|
|
120
|
-
// DELETE_MODE.cascade = "cascade"
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### 4. Role-Based Access Control
|
|
124
|
-
|
|
125
|
-
Roles are defined in the format: `"role_name:mode"` or `"role_name:mode:view"`
|
|
126
|
-
|
|
127
|
-
- `role_name` - Must be defined in settings
|
|
128
|
-
- `mode` - Access permissions using mode characters:
|
|
129
|
-
- `c` - Create access
|
|
130
|
-
- `r` - Read access (single item)
|
|
131
|
-
- `s` - Read access (search/list)
|
|
132
|
-
- `u` - Update access
|
|
133
|
-
- `d` - Delete access (single item)
|
|
134
|
-
- `b` - Batch delete access
|
|
135
|
-
- `o` - Clone access
|
|
136
|
-
- `i` - Import access
|
|
137
|
-
- `e` - Export access
|
|
138
|
-
- `*` - All modes
|
|
139
|
-
|
|
140
|
-
**Examples:**
|
|
141
|
-
```javascript
|
|
142
|
-
roles: [
|
|
143
|
-
"admin:*", // Admin has all permissions
|
|
144
|
-
"editor:crsu", // Editor can create, read, search, update
|
|
145
|
-
"viewer:rs", // Viewer can only read and search
|
|
146
|
-
"moderator:rsdb" // Moderator can read, search, delete
|
|
147
|
-
]
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## EntityMeta Class (Internal Reference)
|
|
151
|
-
|
|
152
|
-
> **Note**: This section describes the internal `EntityMeta` class for reference purposes. **You should use `init_router()` instead** (see examples below).
|
|
153
|
-
|
|
154
|
-
The `EntityMeta` class is created internally by `init_router()`. Understanding its structure helps you know what attributes you can pass to `init_router()`.
|
|
155
|
-
|
|
156
|
-
**What you actually write (in router files):**
|
|
157
|
-
|
|
158
|
-
```javascript
|
|
159
|
-
const { init_router } = require("hola-server");
|
|
160
|
-
|
|
161
|
-
module.exports = init_router({
|
|
162
|
-
collection: "user",
|
|
163
|
-
primary_keys: ["email"],
|
|
164
|
-
readable: true,
|
|
165
|
-
creatable: true,
|
|
166
|
-
updatable: true,
|
|
167
|
-
deleteable: true,
|
|
168
|
-
|
|
169
|
-
fields: [
|
|
170
|
-
{ name: "email", type: "email", required: true },
|
|
171
|
-
{ name: "name", type: "string", required: true },
|
|
172
|
-
{ name: "age", type: "int" }
|
|
173
|
-
]
|
|
174
|
-
});
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### Properties
|
|
178
|
-
|
|
179
|
-
After construction, the `EntityMeta` instance provides these properties:
|
|
180
|
-
|
|
181
|
-
**Basic Info:**
|
|
182
|
-
- `collection` - Collection name
|
|
183
|
-
- `primary_keys` - Array of primary key field names
|
|
184
|
-
- `roles` - Role definitions
|
|
185
|
-
- `user_field` - User ownership field
|
|
186
|
-
- `ref_label` - Reference label field
|
|
187
|
-
- `ref_filter` - Reference filter object
|
|
188
|
-
|
|
189
|
-
**Operation Flags:**
|
|
190
|
-
- `creatable`, `readable`, `updatable`, `deleteable`, `cloneable`, `importable`, `exportable` - Boolean flags
|
|
191
|
-
- `editable` - True if creatable or updatable
|
|
192
|
-
- `mode` - String combining allowed operations (e.g., "crsu")
|
|
193
|
-
|
|
194
|
-
**Field Organization:**
|
|
195
|
-
- `fields` - All field definitions (array)
|
|
196
|
-
- `fields_map` - Fields indexed by name (object)
|
|
197
|
-
- `field_names` - Array of all field names
|
|
198
|
-
- `client_fields` - Fields visible to client (excludes sys fields)
|
|
199
|
-
- `property_fields` - Fields for display (excludes sys and secure)
|
|
200
|
-
- `create_fields` - Fields shown in create form
|
|
201
|
-
- `update_fields` - Fields editable in update form
|
|
202
|
-
- `search_fields` - Fields shown in search form
|
|
203
|
-
- `clone_fields` - Fields included in clone
|
|
204
|
-
- `list_fields` - Fields shown in list view
|
|
205
|
-
- `primary_key_fields` - Primary key field definitions
|
|
206
|
-
- `required_field_names` - Names of required fields
|
|
207
|
-
- `file_fields` - Fields of type 'file'
|
|
208
|
-
- `upload_fields` - File upload field specs
|
|
209
|
-
|
|
210
|
-
**Reference Management:**
|
|
211
|
-
- `ref_fields` - Fields that reference other entities
|
|
212
|
-
- `link_fields` - Fields that link to ref fields
|
|
213
|
-
- `ref_by_metas` - Array of metas that reference this entity
|
|
214
|
-
|
|
215
|
-
### Methods
|
|
216
|
-
|
|
217
|
-
#### validate_meta_info()
|
|
218
|
-
|
|
219
|
-
Validates the entire meta definition. Called automatically after all metas are registered.
|
|
220
|
-
|
|
221
|
-
```javascript
|
|
222
|
-
meta.validate_meta_info();
|
|
223
|
-
// Throws Error if validation fails
|
|
224
|
-
// Returns true if valid
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
**Validation checks:**
|
|
228
|
-
- Collection name is defined
|
|
229
|
-
- No unsupported attributes
|
|
230
|
-
- Primary keys exist in fields
|
|
231
|
-
- Roles are properly formatted
|
|
232
|
-
- ref_label and user_field exist in fields
|
|
233
|
-
- ref_filter is an object
|
|
234
|
-
- All field definitions are valid
|
|
235
|
-
- No duplicate field names
|
|
236
|
-
- Link fields reference valid ref fields
|
|
237
|
-
|
|
238
|
-
## Helper Functions
|
|
239
|
-
|
|
240
|
-
### get_entity_meta(collection)
|
|
241
|
-
|
|
242
|
-
Get entity meta by collection name.
|
|
243
|
-
|
|
244
|
-
```javascript
|
|
245
|
-
const { get_entity_meta } = require("hola-server/core/meta");
|
|
246
|
-
|
|
247
|
-
const user_meta = get_entity_meta("user");
|
|
248
|
-
console.log(user_meta.collection); // "user"
|
|
249
|
-
console.log(user_meta.field_names); // ["email", "name", "age"]
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### get_all_metas()
|
|
253
|
-
|
|
254
|
-
Get all registered meta collection names.
|
|
255
|
-
|
|
256
|
-
```javascript
|
|
257
|
-
const { get_all_metas } = require("hola-server/core/meta");
|
|
258
|
-
|
|
259
|
-
const collections = get_all_metas();
|
|
260
|
-
// ["user", "product", "order", ...]
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
### validate_all_metas()
|
|
264
|
-
|
|
265
|
-
Validate all registered metas. Called after all entity definitions are loaded.
|
|
266
|
-
|
|
267
|
-
```javascript
|
|
268
|
-
const { validate_all_metas } = require("hola-server/core/meta");
|
|
269
|
-
|
|
270
|
-
validate_all_metas();
|
|
271
|
-
// Throws Error if any meta is invalid
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
## Common Use Cases
|
|
275
|
-
|
|
276
|
-
### Use Case 1: Simple Entity Definition
|
|
277
|
-
|
|
278
|
-
```javascript
|
|
279
|
-
const { init_router } = require("hola-server");
|
|
280
|
-
|
|
281
|
-
module.exports = init_router({
|
|
282
|
-
collection: "user",
|
|
283
|
-
primary_keys: ["email"],
|
|
284
|
-
ref_label: "name",
|
|
285
|
-
|
|
286
|
-
readable: true,
|
|
287
|
-
creatable: true,
|
|
288
|
-
updatable: true,
|
|
289
|
-
deleteable: true,
|
|
290
|
-
|
|
291
|
-
roles: [
|
|
292
|
-
"admin:*",
|
|
293
|
-
"user:rs"
|
|
294
|
-
],
|
|
295
|
-
|
|
296
|
-
fields: [
|
|
297
|
-
{
|
|
298
|
-
name: "email",
|
|
299
|
-
type: "email",
|
|
300
|
-
required: true
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
name: "name",
|
|
304
|
-
type: "string",
|
|
305
|
-
required: true
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
name: "age",
|
|
309
|
-
type: "int",
|
|
310
|
-
required: false
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
name: "created_at",
|
|
314
|
-
type: "datetime",
|
|
315
|
-
sys: true,
|
|
316
|
-
create: false,
|
|
317
|
-
update: false
|
|
318
|
-
}
|
|
319
|
-
]
|
|
320
|
-
});
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### Use Case 2: Entity with References
|
|
324
|
-
|
|
325
|
-
```javascript
|
|
326
|
-
const { init_router } = require("hola-server");
|
|
327
|
-
|
|
328
|
-
module.exports = init_router({
|
|
329
|
-
collection: "task",
|
|
330
|
-
primary_keys: ["title"],
|
|
331
|
-
ref_label: "title",
|
|
332
|
-
|
|
333
|
-
readable: true,
|
|
334
|
-
creatable: true,
|
|
335
|
-
updatable: true,
|
|
336
|
-
deleteable: true,
|
|
337
|
-
|
|
338
|
-
fields: [
|
|
339
|
-
{
|
|
340
|
-
name: "title",
|
|
341
|
-
type: "string",
|
|
342
|
-
required: true
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
name: "description",
|
|
346
|
-
type: "text"
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
name: "assigned_to",
|
|
350
|
-
ref: "user", // References user collection
|
|
351
|
-
delete: "cascade" // Delete task if user is deleted
|
|
352
|
-
},
|
|
353
|
-
{
|
|
354
|
-
name: "assigned_name",
|
|
355
|
-
link: "assigned_to", // Auto-populated from user.name
|
|
356
|
-
list: true
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
name: "status",
|
|
360
|
-
type: "task_status",
|
|
361
|
-
required: true,
|
|
362
|
-
default: 0
|
|
363
|
-
}
|
|
364
|
-
]
|
|
365
|
-
});
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
### Use Case 3: Reference Relationships (One-to-One vs One-to-Many)
|
|
369
|
-
|
|
370
|
-
The `ref` attribute supports both one-to-one and one-to-many relationships depending on the field type.
|
|
371
|
-
|
|
372
|
-
#### One-to-One Reference (Single ObjectId)
|
|
373
|
-
|
|
374
|
-
When the field type is `"string"` (or omitted, defaulting to `"string"`), and has a `ref` attribute, it stores a single ObjectId reference:
|
|
375
|
-
|
|
376
|
-
```javascript
|
|
377
|
-
const { init_router } = require("hola-server");
|
|
378
|
-
|
|
379
|
-
module.exports = init_router({
|
|
380
|
-
collection: "task",
|
|
381
|
-
primary_keys: ["title"],
|
|
382
|
-
|
|
383
|
-
fields: [
|
|
384
|
-
{
|
|
385
|
-
name: "title",
|
|
386
|
-
type: "string",
|
|
387
|
-
required: true
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
name: "owner", // No explicit type, defaults to "string"
|
|
391
|
-
ref: "user", // One-to-one: single user reference
|
|
392
|
-
required: true,
|
|
393
|
-
delete: "cascade"
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
name: "docker",
|
|
397
|
-
type: "string", // Explicit string type
|
|
398
|
-
ref: "docker", // One-to-one: single docker reference
|
|
399
|
-
required: true
|
|
400
|
-
}
|
|
401
|
-
]
|
|
402
|
-
});
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
#### One-to-Many Reference (Array of ObjectIds)
|
|
406
|
-
|
|
407
|
-
When the field type is `"array"` with `ref` attribute, it stores an array of ObjectIds:
|
|
408
|
-
|
|
409
|
-
```javascript
|
|
410
|
-
const { init_router } = require("hola-server");
|
|
411
|
-
|
|
412
|
-
module.exports = init_router({
|
|
413
|
-
collection: "exec",
|
|
414
|
-
primary_keys: ["owner", "name", "type"],
|
|
415
|
-
|
|
416
|
-
fields: [
|
|
417
|
-
{
|
|
418
|
-
name: "owner", // No explicit type, defaults to "string"
|
|
419
|
-
ref: "user", // One-to-one: single user
|
|
420
|
-
required: true,
|
|
421
|
-
delete: "cascade"
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
name: "hosts",
|
|
425
|
-
type: "array",
|
|
426
|
-
ref: "host", // One-to-many: array of hosts
|
|
427
|
-
required: true,
|
|
428
|
-
search: false
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
name: "name",
|
|
432
|
-
type: "string",
|
|
433
|
-
required: true
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
name: "type",
|
|
437
|
-
type: "int",
|
|
438
|
-
required: true
|
|
439
|
-
}
|
|
440
|
-
]
|
|
441
|
-
});
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
**Key Differences:**
|
|
445
|
-
|
|
446
|
-
| Aspect | One-to-One | One-to-Many |
|
|
447
|
-
|--------|-----------|-------------|
|
|
448
|
-
| **Field Type** | `"string"` (default if omitted) | `"array"` |
|
|
449
|
-
| **Stores** | Single ObjectId | Array of ObjectIds |
|
|
450
|
-
| **Example** | `{ owner: ObjectId("...") }` | `{ hosts: [ObjectId("..."), ObjectId("...")] }` |
|
|
451
|
-
| **Use Case** | Single parent, single author | Multiple assignees, multiple tags |
|
|
452
|
-
|
|
453
|
-
### Use Case 4: Link Fields
|
|
454
|
-
|
|
455
|
-
Link fields automatically populate their values from referenced entities.
|
|
456
|
-
|
|
457
|
-
```javascript
|
|
458
|
-
const { init_router } = require("hola-server");
|
|
459
|
-
|
|
460
|
-
module.exports = init_router({
|
|
461
|
-
collection: "order",
|
|
462
|
-
primary_keys: ["order_id"],
|
|
463
|
-
ref_label: "order_id",
|
|
464
|
-
|
|
465
|
-
readable: true,
|
|
466
|
-
creatable: true,
|
|
467
|
-
|
|
468
|
-
fields: [
|
|
469
|
-
{
|
|
470
|
-
name: "order_id",
|
|
471
|
-
type: "string",
|
|
472
|
-
required: true
|
|
473
|
-
},
|
|
474
|
-
{
|
|
475
|
-
name: "customer",
|
|
476
|
-
ref: "user",
|
|
477
|
-
delete: "keep" // Keep order if user is deleted
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
name: "customer_email",
|
|
481
|
-
link: "customer", // Auto-populated from customer.email
|
|
482
|
-
list: true // Only name, link, list allowed
|
|
483
|
-
}
|
|
484
|
-
]
|
|
485
|
-
});
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### Use Case 5: Multiple Form Views
|
|
489
|
-
|
|
490
|
-
Organize complex entities into different form views.
|
|
491
|
-
|
|
492
|
-
```javascript
|
|
493
|
-
const { init_router } = require("hola-server");
|
|
494
|
-
|
|
495
|
-
module.exports = init_router({
|
|
496
|
-
collection: "product",
|
|
497
|
-
primary_keys: ["sku"],
|
|
498
|
-
ref_label: "name",
|
|
499
|
-
|
|
500
|
-
creatable: true,
|
|
501
|
-
readable: true,
|
|
502
|
-
updatable: true,
|
|
503
|
-
|
|
504
|
-
fields: [
|
|
505
|
-
// Basic view
|
|
506
|
-
{
|
|
507
|
-
name: "sku",
|
|
508
|
-
type: "string",
|
|
509
|
-
required: true,
|
|
510
|
-
view: "basic"
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
name: "name",
|
|
514
|
-
type: "string",
|
|
515
|
-
required: true,
|
|
516
|
-
view: "basic"
|
|
517
|
-
},
|
|
518
|
-
|
|
519
|
-
// Pricing view
|
|
520
|
-
{
|
|
521
|
-
name: "price",
|
|
522
|
-
type: "decimal",
|
|
523
|
-
view: "pricing"
|
|
524
|
-
},
|
|
525
|
-
{
|
|
526
|
-
name: "cost",
|
|
527
|
-
type: "decimal",
|
|
528
|
-
view: "pricing"
|
|
529
|
-
},
|
|
530
|
-
|
|
531
|
-
// Inventory view
|
|
532
|
-
{
|
|
533
|
-
name: "stock",
|
|
534
|
-
type: "int",
|
|
535
|
-
view: "inventory"
|
|
536
|
-
},
|
|
537
|
-
|
|
538
|
-
// All views
|
|
539
|
-
{
|
|
540
|
-
name: "category",
|
|
541
|
-
type: "product_category",
|
|
542
|
-
view: "*"
|
|
543
|
-
}
|
|
544
|
-
]
|
|
545
|
-
});
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
### Use Case 6: Lifecycle Callbacks
|
|
549
|
-
|
|
550
|
-
Add custom logic at various points in the entity lifecycle.
|
|
551
|
-
|
|
552
|
-
```javascript
|
|
553
|
-
const { init_router } = require("hola-server");
|
|
554
|
-
|
|
555
|
-
module.exports = init_router({
|
|
556
|
-
collection: "audit_log",
|
|
557
|
-
primary_keys: ["_id"],
|
|
558
|
-
|
|
559
|
-
creatable: true,
|
|
560
|
-
readable: true,
|
|
561
|
-
|
|
562
|
-
fields: [
|
|
563
|
-
{ name: "action", type: "string", required: true },
|
|
564
|
-
{ name: "user_id", ref: "user" },
|
|
565
|
-
{ name: "timestamp", type: "datetime", sys: true }
|
|
566
|
-
],
|
|
567
|
-
|
|
568
|
-
before_create: async function(param_obj, ctx) {
|
|
569
|
-
// Set timestamp before creating
|
|
570
|
-
param_obj.timestamp = new Date();
|
|
571
|
-
return param_obj;
|
|
572
|
-
},
|
|
573
|
-
|
|
574
|
-
after_read: async function(item, ctx) {
|
|
575
|
-
// Mask sensitive data after reading
|
|
576
|
-
if (item.sensitive_data) {
|
|
577
|
-
item.sensitive_data = "***";
|
|
578
|
-
}
|
|
579
|
-
return item;
|
|
580
|
-
},
|
|
581
|
-
|
|
582
|
-
list_query: async function(query, ctx) {
|
|
583
|
-
// Filter list by user
|
|
584
|
-
if (ctx.user && !ctx.user.is_admin) {
|
|
585
|
-
query.user_id = ctx.user._id;
|
|
586
|
-
}
|
|
587
|
-
return query;
|
|
588
|
-
},
|
|
589
|
-
|
|
590
|
-
before_delete: async function(id_array, ctx) {
|
|
591
|
-
// Prevent deletion of critical records
|
|
592
|
-
const critical = await this.find({ _id: { $in: id_array }, critical: true });
|
|
593
|
-
if (critical.length > 0) {
|
|
594
|
-
throw new Error("Cannot delete critical records");
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
### Use Case 7: Secure and System Fields
|
|
601
|
-
|
|
602
|
-
Control field visibility to client and in different operations.
|
|
603
|
-
|
|
604
|
-
```javascript
|
|
605
|
-
const { init_router } = require("hola-server");
|
|
606
|
-
|
|
607
|
-
module.exports = init_router({
|
|
608
|
-
collection: "user",
|
|
609
|
-
primary_keys: ["email"],
|
|
610
|
-
ref_label: "name",
|
|
611
|
-
|
|
612
|
-
creatable: true,
|
|
613
|
-
readable: true,
|
|
614
|
-
updatable: true,
|
|
615
|
-
|
|
616
|
-
fields: [
|
|
617
|
-
{
|
|
618
|
-
name: "email",
|
|
619
|
-
type: "email",
|
|
620
|
-
required: true
|
|
621
|
-
},
|
|
622
|
-
{
|
|
623
|
-
name: "name",
|
|
624
|
-
type: "string",
|
|
625
|
-
required: true
|
|
626
|
-
},
|
|
627
|
-
{
|
|
628
|
-
name: "password_hash",
|
|
629
|
-
type: "string",
|
|
630
|
-
secure: true, // Never sent to client
|
|
631
|
-
create: false,
|
|
632
|
-
update: false,
|
|
633
|
-
list: false
|
|
634
|
-
},
|
|
635
|
-
{
|
|
636
|
-
name: "created_at",
|
|
637
|
-
type: "datetime",
|
|
638
|
-
sys: true, // Server-only, not sent unless requested
|
|
639
|
-
create: false,
|
|
640
|
-
update: false
|
|
641
|
-
},
|
|
642
|
-
{
|
|
643
|
-
name: "last_login",
|
|
644
|
-
type: "datetime",
|
|
645
|
-
sys: true,
|
|
646
|
-
create: false,
|
|
647
|
-
update: false
|
|
648
|
-
}
|
|
649
|
-
]
|
|
650
|
-
});
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
### Use Case 8: Deletion Behaviors
|
|
654
|
-
|
|
655
|
-
Control what happens to records when referenced entities are deleted.
|
|
656
|
-
|
|
657
|
-
```javascript
|
|
658
|
-
// User entity - user.js
|
|
659
|
-
const { init_router } = require("hola-server");
|
|
660
|
-
|
|
661
|
-
module.exports = init_router({
|
|
662
|
-
collection: "user",
|
|
663
|
-
primary_keys: ["email"],
|
|
664
|
-
ref_label: "name",
|
|
665
|
-
deleteable: true,
|
|
666
|
-
// ... other config
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
// Task entity with cascade delete - task.js
|
|
670
|
-
module.exports = init_router({
|
|
671
|
-
collection: "task",
|
|
672
|
-
primary_keys: ["title"],
|
|
673
|
-
deleteable: true,
|
|
674
|
-
|
|
675
|
-
fields: [
|
|
676
|
-
{ name: "title", type: "string", required: true },
|
|
677
|
-
{
|
|
678
|
-
name: "assigned_to",
|
|
679
|
-
ref: "user",
|
|
680
|
-
delete: "cascade" // Tasks deleted when user is deleted
|
|
681
|
-
}
|
|
682
|
-
]
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
// Comment entity with keep behavior - comment.js
|
|
686
|
-
module.exports = init_router({
|
|
687
|
-
collection: "comment",
|
|
688
|
-
primary_keys: ["_id"],
|
|
689
|
-
deleteable: true,
|
|
690
|
-
|
|
691
|
-
fields: [
|
|
692
|
-
{ name: "text", type: "text", required: true },
|
|
693
|
-
{
|
|
694
|
-
name: "author",
|
|
695
|
-
ref: "user",
|
|
696
|
-
delete: "keep" // Comments kept when user is deleted
|
|
697
|
-
}
|
|
698
|
-
]
|
|
699
|
-
});
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
## Field Subsets
|
|
703
|
-
|
|
704
|
-
The `EntityMeta` class automatically organizes fields into useful subsets:
|
|
705
|
-
|
|
706
|
-
```javascript
|
|
707
|
-
const meta = get_entity_meta("product");
|
|
708
|
-
|
|
709
|
-
// All fields
|
|
710
|
-
meta.fields // All field definitions
|
|
711
|
-
meta.field_names // ["sku", "name", "price", ...]
|
|
712
|
-
|
|
713
|
-
// Visibility filtering
|
|
714
|
-
meta.client_fields // Excludes sys:true fields
|
|
715
|
-
meta.property_fields // Excludes sys:true and secure:true fields
|
|
716
|
-
|
|
717
|
-
// Operation filtering
|
|
718
|
-
meta.create_fields // Where create !== false
|
|
719
|
-
meta.update_fields // Where create !== false and update !== false
|
|
720
|
-
meta.search_fields // Where search !== false
|
|
721
|
-
meta.clone_fields // Where clone !== false
|
|
722
|
-
meta.list_fields // Where list !== false (excludes sys and secure)
|
|
723
|
-
|
|
724
|
-
// Special fields
|
|
725
|
-
meta.primary_key_fields // Primary key field definitions
|
|
726
|
-
meta.required_field_names // Names of required fields
|
|
727
|
-
meta.file_fields // Fields with type:'file'
|
|
728
|
-
meta.ref_fields // Fields with ref attribute
|
|
729
|
-
meta.link_fields // Fields with link attribute
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
## Reference Tracking
|
|
733
|
-
|
|
734
|
-
The meta system automatically tracks which entities reference each entity:
|
|
735
|
-
|
|
736
|
-
```javascript
|
|
737
|
-
const user_meta = get_entity_meta("user");
|
|
738
|
-
|
|
739
|
-
// After all metas are loaded and validated:
|
|
740
|
-
console.log(user_meta.ref_by_metas);
|
|
741
|
-
// [
|
|
742
|
-
// EntityMeta { collection: "task", ... },
|
|
743
|
-
// EntityMeta { collection: "comment", ... },
|
|
744
|
-
// EntityMeta { collection: "order", ... }
|
|
745
|
-
// ]
|
|
746
|
-
|
|
747
|
-
// This is used to enforce referential integrity
|
|
748
|
-
// and handle cascade deletions
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
## Best Practices
|
|
752
|
-
|
|
753
|
-
### 1. Always Define Primary Keys
|
|
754
|
-
|
|
755
|
-
Primary keys are required and used for:
|
|
756
|
-
- Uniqueness validation
|
|
757
|
-
- Update operations
|
|
758
|
-
- Clone operations
|
|
759
|
-
- Reference integrity
|
|
760
|
-
|
|
761
|
-
```javascript
|
|
762
|
-
// ✅ Good
|
|
763
|
-
{
|
|
764
|
-
collection: "product",
|
|
765
|
-
primary_keys: ["sku"],
|
|
766
|
-
// ...
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// ✅ Also good - composite key
|
|
770
|
-
{
|
|
771
|
-
collection: "order_item",
|
|
772
|
-
primary_keys: ["order_id", "product_id"],
|
|
773
|
-
// ...
|
|
774
|
-
}
|
|
775
|
-
```
|
|
776
|
-
|
|
777
|
-
### 2. Set ref_label for Referenced Entities
|
|
778
|
-
|
|
779
|
-
If an entity will be referenced by others, define `ref_label`:
|
|
780
|
-
|
|
781
|
-
```javascript
|
|
782
|
-
// ✅ Good - can be referenced
|
|
783
|
-
{
|
|
784
|
-
collection: "user",
|
|
785
|
-
ref_label: "name",
|
|
786
|
-
primary_keys: ["email"],
|
|
787
|
-
// ...
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
// ❌ Bad - cannot be referenced
|
|
791
|
-
{
|
|
792
|
-
collection: "user",
|
|
793
|
-
primary_keys: ["email"],
|
|
794
|
-
// Missing ref_label
|
|
795
|
-
}
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
### 3. Use Link Fields for Denormalization
|
|
799
|
-
|
|
800
|
-
Instead of joining in queries, use link fields for common display data:
|
|
801
|
-
|
|
802
|
-
```javascript
|
|
803
|
-
{
|
|
804
|
-
collection: "task",
|
|
805
|
-
fields: [
|
|
806
|
-
{
|
|
807
|
-
name: "assigned_to", // No type, defaults to "string"
|
|
808
|
-
ref: "user"
|
|
809
|
-
},
|
|
810
|
-
{
|
|
811
|
-
name: "assigned_name", // Auto-populated
|
|
812
|
-
link: "assigned_to",
|
|
813
|
-
list: true // Show in list view
|
|
814
|
-
}
|
|
815
|
-
]
|
|
816
|
-
}
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
### 4. Choose Delete Behavior Carefully
|
|
820
|
-
|
|
821
|
-
Consider the business logic when setting delete behavior:
|
|
822
|
-
|
|
823
|
-
```javascript
|
|
824
|
-
// Cascade: Dependent data
|
|
825
|
-
{ name: "user_id", ref: "user", delete: "cascade" } // No type, defaults to "string"
|
|
826
|
-
|
|
827
|
-
// Keep: Historical data
|
|
828
|
-
{ name: "created_by", ref: "user", delete: "keep" }
|
|
829
|
-
|
|
830
|
-
// Default (no action): Optional references
|
|
831
|
-
{ name: "related_item", ref: "item" }
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
### 5. Use Callbacks for Business Logic
|
|
835
|
-
|
|
836
|
-
Don't put business logic in route handlers - use lifecycle callbacks:
|
|
837
|
-
|
|
838
|
-
```javascript
|
|
839
|
-
{
|
|
840
|
-
collection: "order",
|
|
841
|
-
|
|
842
|
-
before_create: async function(param_obj, ctx) {
|
|
843
|
-
// Auto-generate order number
|
|
844
|
-
param_obj.order_number = await generate_order_number();
|
|
845
|
-
return param_obj;
|
|
846
|
-
},
|
|
847
|
-
|
|
848
|
-
after_create: async function(item, ctx) {
|
|
849
|
-
// Send notification
|
|
850
|
-
await send_order_confirmation(item);
|
|
851
|
-
},
|
|
852
|
-
|
|
853
|
-
before_delete: async function(id_array, ctx) {
|
|
854
|
-
// Check if can delete
|
|
855
|
-
const orders = await this.find({ _id: { $in: id_array } });
|
|
856
|
-
if (orders.some(o => o.status === "shipped")) {
|
|
857
|
-
throw new Error("Cannot delete shipped orders");
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
## Common Mistakes to Avoid
|
|
864
|
-
|
|
865
|
-
### ❌ Don't: Add Unsupported Meta Attributes
|
|
866
|
-
|
|
867
|
-
```javascript
|
|
868
|
-
// ❌ WRONG
|
|
869
|
-
module.exports = init_router({
|
|
870
|
-
collection: "product",
|
|
871
|
-
primary_keys: ["sku"],
|
|
872
|
-
custom_attribute: "value", // ❌ Not in META_ATTRS
|
|
873
|
-
// ...
|
|
874
|
-
});
|
|
875
|
-
```
|
|
876
|
-
|
|
877
|
-
### ✅ Do: Use Only Supported Attributes
|
|
878
|
-
|
|
879
|
-
```javascript
|
|
880
|
-
// ✅ CORRECT
|
|
881
|
-
module.exports = init_router({
|
|
882
|
-
collection: "product",
|
|
883
|
-
primary_keys: ["sku"],
|
|
884
|
-
ref_label: "name",
|
|
885
|
-
creatable: true,
|
|
886
|
-
readable: true,
|
|
887
|
-
// ... only META_ATTRS
|
|
888
|
-
});
|
|
889
|
-
```
|
|
890
|
-
|
|
891
|
-
### ❌ Don't: Add Extra Attributes to Link Fields
|
|
892
|
-
|
|
893
|
-
```javascript
|
|
894
|
-
// ❌ WRONG
|
|
895
|
-
fields: [
|
|
896
|
-
{
|
|
897
|
-
name: "user_email",
|
|
898
|
-
link: "user_id",
|
|
899
|
-
type: "email", // ❌ Not allowed
|
|
900
|
-
required: true // ❌ Not allowed
|
|
901
|
-
}
|
|
902
|
-
]
|
|
903
|
-
```
|
|
904
|
-
|
|
905
|
-
### ✅ Do: Use Only name, link, list
|
|
906
|
-
|
|
907
|
-
```javascript
|
|
908
|
-
// ✅ CORRECT
|
|
909
|
-
fields: [
|
|
910
|
-
{
|
|
911
|
-
name: "user_email",
|
|
912
|
-
link: "user_id",
|
|
913
|
-
list: true // ✅ Only name, link, list
|
|
914
|
-
}
|
|
915
|
-
]
|
|
916
|
-
```
|
|
917
|
-
|
|
918
|
-
### ❌ Don't: Forget to Set Operation Flags
|
|
919
|
-
|
|
920
|
-
```javascript
|
|
921
|
-
// ❌ WRONG - All operations disabled by default
|
|
922
|
-
module.exports = init_router({
|
|
923
|
-
collection: "user",
|
|
924
|
-
primary_keys: ["email"],
|
|
925
|
-
fields: [...]
|
|
926
|
-
// Missing: creatable, readable, updatable, deleteable
|
|
927
|
-
});
|
|
928
|
-
```
|
|
929
|
-
|
|
930
|
-
### ✅ Do: Explicitly Enable Operations
|
|
931
|
-
|
|
932
|
-
```javascript
|
|
933
|
-
// ✅ CORRECT
|
|
934
|
-
module.exports = init_router({
|
|
935
|
-
collection: "user",
|
|
936
|
-
primary_keys: ["email"],
|
|
937
|
-
creatable: true,
|
|
938
|
-
readable: true,
|
|
939
|
-
updatable: true,
|
|
940
|
-
deleteable: true,
|
|
941
|
-
fields: [...]
|
|
942
|
-
});
|
|
943
|
-
```
|
|
944
|
-
|
|
945
|
-
### ❌ Don't: Reference Non-existent Collections
|
|
946
|
-
|
|
947
|
-
```javascript
|
|
948
|
-
// ❌ WRONG
|
|
949
|
-
fields: [
|
|
950
|
-
{
|
|
951
|
-
name: "category_id",
|
|
952
|
-
ref: "category" // ❌ Category meta not registered (but type:"ref" is also wrong)
|
|
953
|
-
}
|
|
954
|
-
]
|
|
955
|
-
```
|
|
956
|
-
|
|
957
|
-
### ✅ Do: Ensure Referenced Metas Exist
|
|
958
|
-
|
|
959
|
-
```javascript
|
|
960
|
-
// ✅ CORRECT - Register category meta first (category.js)
|
|
961
|
-
module.exports = init_router({
|
|
962
|
-
collection: "category",
|
|
963
|
-
ref_label: "name", // ✅ ref_label required for referenced entities
|
|
964
|
-
primary_keys: ["name"],
|
|
965
|
-
fields: [...]
|
|
966
|
-
});
|
|
967
|
-
|
|
968
|
-
// Then reference it in product router (product.js)
|
|
969
|
-
module.exports = init_router({
|
|
970
|
-
collection: "product",
|
|
971
|
-
primary_keys: ["sku"],
|
|
972
|
-
fields: [
|
|
973
|
-
{
|
|
974
|
-
name: "category_id",
|
|
975
|
-
ref: "category" // ✅ Now valid
|
|
976
|
-
}
|
|
977
|
-
]
|
|
978
|
-
});
|
|
979
|
-
```
|
|
980
|
-
|
|
981
|
-
## Router Definition Pattern
|
|
982
|
-
|
|
983
|
-
This is the standard way to define entity routers in Hola:
|
|
984
|
-
|
|
985
|
-
```javascript
|
|
986
|
-
const { init_router } = require("hola-server");
|
|
987
|
-
|
|
988
|
-
// init_router creates an EntityMeta internally
|
|
989
|
-
module.exports = init_router({
|
|
990
|
-
collection: "product",
|
|
991
|
-
primary_keys: ["sku"],
|
|
992
|
-
ref_label: "name",
|
|
993
|
-
|
|
994
|
-
creatable: true,
|
|
995
|
-
readable: true,
|
|
996
|
-
updatable: true,
|
|
997
|
-
deleteable: true,
|
|
998
|
-
|
|
999
|
-
fields: [
|
|
1000
|
-
{ name: "sku", type: "string", required: true },
|
|
1001
|
-
{ name: "name", type: "string", required: true },
|
|
1002
|
-
{ name: "price", type: "decimal" }
|
|
1003
|
-
],
|
|
1004
|
-
|
|
1005
|
-
before_create: async function(param_obj, ctx) {
|
|
1006
|
-
// Custom logic
|
|
1007
|
-
return param_obj;
|
|
1008
|
-
}
|
|
1009
|
-
});
|
|
1010
|
-
```
|
|
1011
|
-
|
|
1012
|
-
## Summary
|
|
1013
|
-
|
|
1014
|
-
The EntityMeta class provides:
|
|
1015
|
-
- **Declarative schema definition** - Define entity structure with simple objects
|
|
1016
|
-
- **Automatic validation** - Validates meta and field definitions
|
|
1017
|
-
- **Reference management** - Tracks relationships and enforces integrity
|
|
1018
|
-
- **Field organization** - Automatically groups fields by operation and visibility
|
|
1019
|
-
- **Lifecycle hooks** - Add custom logic at key points
|
|
1020
|
-
- **Role-based access** - Control who can perform which operations
|
|
1021
|
-
- **View filtering** - Organize fields into multiple form views
|
|
1022
|
-
|
|
1023
|
-
Follow these guidelines to create robust, maintainable entity definitions in your Hola applications.
|