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.
Files changed (242) hide show
  1. package/README.md +318 -132
  2. package/dist/config/index.d.ts +46 -0
  3. package/dist/config/index.d.ts.map +1 -0
  4. package/dist/config/index.js +55 -0
  5. package/dist/config/index.js.map +1 -0
  6. package/dist/core/array.d.ts +27 -0
  7. package/dist/core/array.d.ts.map +1 -0
  8. package/dist/core/array.js +66 -0
  9. package/dist/core/array.js.map +1 -0
  10. package/dist/core/bash.d.ts +51 -0
  11. package/dist/core/bash.d.ts.map +1 -0
  12. package/dist/core/bash.js +161 -0
  13. package/dist/core/bash.js.map +1 -0
  14. package/dist/core/chart.d.ts +11 -0
  15. package/dist/core/chart.d.ts.map +1 -0
  16. package/dist/core/chart.js +35 -0
  17. package/dist/core/chart.js.map +1 -0
  18. package/dist/core/date.d.ts +11 -0
  19. package/dist/core/date.d.ts.map +1 -0
  20. package/dist/core/date.js +18 -0
  21. package/dist/core/date.js.map +1 -0
  22. package/dist/core/encrypt.d.ts +18 -0
  23. package/dist/core/encrypt.d.ts.map +1 -0
  24. package/dist/core/encrypt.js +50 -0
  25. package/dist/core/encrypt.js.map +1 -0
  26. package/dist/core/file.d.ts +22 -0
  27. package/dist/core/file.d.ts.map +1 -0
  28. package/dist/core/file.js +21 -0
  29. package/dist/core/file.js.map +1 -0
  30. package/dist/core/lhs.d.ts +17 -0
  31. package/dist/core/lhs.d.ts.map +1 -0
  32. package/dist/core/lhs.js +30 -0
  33. package/dist/core/lhs.js.map +1 -0
  34. package/dist/core/meta.d.ts +200 -0
  35. package/dist/core/meta.d.ts.map +1 -0
  36. package/dist/core/meta.js +336 -0
  37. package/dist/core/meta.js.map +1 -0
  38. package/dist/core/number.d.ts +37 -0
  39. package/dist/core/number.d.ts.map +1 -0
  40. package/dist/core/number.js +99 -0
  41. package/dist/core/number.js.map +1 -0
  42. package/dist/core/obj.d.ts +9 -0
  43. package/dist/core/obj.d.ts.map +1 -0
  44. package/dist/core/obj.js +15 -0
  45. package/dist/core/obj.js.map +1 -0
  46. package/dist/core/random.d.ts +7 -0
  47. package/dist/core/random.d.ts.map +1 -0
  48. package/dist/core/random.js +7 -0
  49. package/dist/core/random.js.map +1 -0
  50. package/dist/core/role.d.ts +42 -0
  51. package/dist/core/role.d.ts.map +1 -0
  52. package/dist/core/role.js +81 -0
  53. package/dist/core/role.js.map +1 -0
  54. package/dist/core/thread.d.ts +7 -0
  55. package/dist/core/thread.d.ts.map +1 -0
  56. package/dist/core/thread.js +7 -0
  57. package/dist/core/thread.js.map +1 -0
  58. package/dist/core/type.d.ts +46 -0
  59. package/dist/core/type.d.ts.map +1 -0
  60. package/dist/core/type.js +281 -0
  61. package/dist/core/type.js.map +1 -0
  62. package/dist/core/url.d.ts +20 -0
  63. package/dist/core/url.d.ts.map +1 -0
  64. package/dist/core/url.js +24 -0
  65. package/dist/core/url.js.map +1 -0
  66. package/dist/core/validate.d.ts +11 -0
  67. package/dist/core/validate.d.ts.map +1 -0
  68. package/dist/core/validate.js +19 -0
  69. package/dist/core/validate.js.map +1 -0
  70. package/dist/db/db.d.ts +72 -0
  71. package/dist/db/db.d.ts.map +1 -0
  72. package/dist/db/db.js +225 -0
  73. package/dist/db/db.js.map +1 -0
  74. package/dist/db/entity.d.ts +77 -0
  75. package/dist/db/entity.d.ts.map +1 -0
  76. package/dist/db/entity.js +671 -0
  77. package/dist/db/entity.js.map +1 -0
  78. package/dist/db/gridfs.d.ts +29 -0
  79. package/dist/db/gridfs.d.ts.map +1 -0
  80. package/dist/db/gridfs.js +125 -0
  81. package/dist/db/gridfs.js.map +1 -0
  82. package/dist/db/index.d.ts +8 -0
  83. package/dist/db/index.d.ts.map +1 -0
  84. package/dist/db/index.js +8 -0
  85. package/dist/db/index.js.map +1 -0
  86. package/dist/errors/auth.d.ts +15 -0
  87. package/dist/errors/auth.d.ts.map +1 -0
  88. package/dist/errors/auth.js +21 -0
  89. package/dist/errors/auth.js.map +1 -0
  90. package/dist/errors/http.d.ts +15 -0
  91. package/dist/errors/http.d.ts.map +1 -0
  92. package/dist/errors/http.js +21 -0
  93. package/dist/errors/http.js.map +1 -0
  94. package/dist/errors/index.d.ts +18 -0
  95. package/dist/errors/index.d.ts.map +1 -0
  96. package/dist/errors/index.js +18 -0
  97. package/dist/errors/index.js.map +1 -0
  98. package/dist/errors/validation.d.ts +11 -0
  99. package/dist/errors/validation.d.ts.map +1 -0
  100. package/dist/errors/validation.js +15 -0
  101. package/dist/errors/validation.js.map +1 -0
  102. package/dist/http/code.d.ts +21 -0
  103. package/dist/http/code.d.ts.map +1 -0
  104. package/dist/http/code.js +27 -0
  105. package/dist/http/code.js.map +1 -0
  106. package/dist/index.d.ts +57 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +61 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/meta/index.d.ts +9 -0
  111. package/dist/meta/index.d.ts.map +1 -0
  112. package/dist/meta/index.js +11 -0
  113. package/dist/meta/index.js.map +1 -0
  114. package/dist/meta/router.d.ts +26 -0
  115. package/dist/meta/router.d.ts.map +1 -0
  116. package/dist/meta/router.js +258 -0
  117. package/dist/meta/router.js.map +1 -0
  118. package/dist/meta/schema.d.ts +41 -0
  119. package/dist/meta/schema.d.ts.map +1 -0
  120. package/dist/meta/schema.js +69 -0
  121. package/dist/meta/schema.js.map +1 -0
  122. package/dist/plugins/auth.d.ts +248 -0
  123. package/dist/plugins/auth.d.ts.map +1 -0
  124. package/dist/plugins/auth.js +121 -0
  125. package/dist/plugins/auth.js.map +1 -0
  126. package/dist/plugins/body.d.ts +47 -0
  127. package/dist/plugins/body.d.ts.map +1 -0
  128. package/dist/plugins/body.js +36 -0
  129. package/dist/plugins/body.js.map +1 -0
  130. package/dist/plugins/cors.d.ts +62 -0
  131. package/dist/plugins/cors.d.ts.map +1 -0
  132. package/dist/plugins/cors.js +17 -0
  133. package/dist/plugins/cors.js.map +1 -0
  134. package/dist/plugins/error.d.ts +51 -0
  135. package/dist/plugins/error.d.ts.map +1 -0
  136. package/dist/plugins/error.js +51 -0
  137. package/dist/plugins/error.js.map +1 -0
  138. package/dist/plugins/index.d.ts +9 -0
  139. package/dist/plugins/index.d.ts.map +1 -0
  140. package/dist/plugins/index.js +9 -0
  141. package/dist/plugins/index.js.map +1 -0
  142. package/dist/setting.d.ts +66 -0
  143. package/dist/setting.d.ts.map +1 -0
  144. package/dist/setting.js +27 -0
  145. package/dist/setting.js.map +1 -0
  146. package/dist/tool/gen_i18n.d.ts +10 -0
  147. package/dist/tool/gen_i18n.d.ts.map +1 -0
  148. package/{tool → dist/tool}/gen_i18n.js +9 -22
  149. package/dist/tool/gen_i18n.js.map +1 -0
  150. package/dist/tool/vector_store.d.ts +72 -0
  151. package/dist/tool/vector_store.d.ts.map +1 -0
  152. package/dist/tool/vector_store.js +203 -0
  153. package/dist/tool/vector_store.js.map +1 -0
  154. package/package.json +37 -22
  155. package/core/array.js +0 -124
  156. package/core/bash.js +0 -294
  157. package/core/chart.js +0 -46
  158. package/core/cron.js +0 -21
  159. package/core/date.js +0 -26
  160. package/core/encrypt.js +0 -26
  161. package/core/file.js +0 -51
  162. package/core/lhs.js +0 -53
  163. package/core/meta.js +0 -283
  164. package/core/msg.js +0 -24
  165. package/core/number.js +0 -181
  166. package/core/obj.js +0 -25
  167. package/core/random.js +0 -12
  168. package/core/role.js +0 -108
  169. package/core/thread.js +0 -13
  170. package/core/type.js +0 -368
  171. package/core/url.js +0 -30
  172. package/core/validate.js +0 -35
  173. package/db/db.js +0 -446
  174. package/db/entity.js +0 -920
  175. package/db/gridfs.js +0 -175
  176. package/design/add_default_field_attr.md +0 -56
  177. package/http/code.js +0 -18
  178. package/http/context.js +0 -31
  179. package/http/cors.js +0 -32
  180. package/http/error.js +0 -39
  181. package/http/express.js +0 -104
  182. package/http/params.js +0 -85
  183. package/http/router.js +0 -83
  184. package/http/session.js +0 -73
  185. package/index.js +0 -112
  186. package/router/clone.js +0 -65
  187. package/router/create.js +0 -54
  188. package/router/delete.js +0 -49
  189. package/router/read.js +0 -191
  190. package/router/update.js +0 -89
  191. package/setting.js +0 -67
  192. package/skills/array.md +0 -155
  193. package/skills/bash.md +0 -91
  194. package/skills/chart.md +0 -54
  195. package/skills/code.md +0 -422
  196. package/skills/context.md +0 -177
  197. package/skills/date.md +0 -58
  198. package/skills/express.md +0 -255
  199. package/skills/file.md +0 -60
  200. package/skills/lhs.md +0 -54
  201. package/skills/meta.md +0 -1023
  202. package/skills/msg.md +0 -30
  203. package/skills/number.md +0 -88
  204. package/skills/obj.md +0 -36
  205. package/skills/params.md +0 -206
  206. package/skills/random.md +0 -22
  207. package/skills/role.md +0 -59
  208. package/skills/session.md +0 -281
  209. package/skills/storage.md +0 -743
  210. package/skills/thread.md +0 -22
  211. package/skills/type.md +0 -547
  212. package/skills/url.md +0 -34
  213. package/skills/validate.md +0 -48
  214. package/test/cleanup/close-db.js +0 -5
  215. package/test/core/array.js +0 -226
  216. package/test/core/chart.js +0 -51
  217. package/test/core/date.js +0 -37
  218. package/test/core/encrypt.js +0 -14
  219. package/test/core/file.js +0 -59
  220. package/test/core/lhs.js +0 -44
  221. package/test/core/meta.js +0 -594
  222. package/test/core/number.js +0 -172
  223. package/test/core/obj.js +0 -47
  224. package/test/core/random.js +0 -24
  225. package/test/core/thread.js +0 -20
  226. package/test/core/type.js +0 -216
  227. package/test/core/validate.js +0 -67
  228. package/test/db/db-ops.js +0 -99
  229. package/test/db/db.js +0 -72
  230. package/test/db/pipe_test.txt +0 -0
  231. package/test/db/test_case_design.md +0 -528
  232. package/test/db/test_db_class.js +0 -613
  233. package/test/db/test_entity_class.js +0 -414
  234. package/test/db/test_gridfs_class.js +0 -234
  235. package/test/entity/create.js +0 -442
  236. package/test/entity/delete-mixed.js +0 -156
  237. package/test/entity/delete.js +0 -480
  238. package/test/entity/read.js +0 -285
  239. package/test/entity/ref-filter.js +0 -63
  240. package/test/entity/update.js +0 -252
  241. package/test/router/role.js +0 -15
  242. 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.