hola-server 1.0.10 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +196 -1
  2. package/core/array.js +79 -142
  3. package/core/bash.js +208 -259
  4. package/core/chart.js +26 -16
  5. package/core/cron.js +14 -3
  6. package/core/date.js +15 -44
  7. package/core/encrypt.js +19 -9
  8. package/core/file.js +42 -29
  9. package/core/lhs.js +32 -6
  10. package/core/meta.js +213 -289
  11. package/core/msg.js +20 -7
  12. package/core/number.js +105 -103
  13. package/core/obj.js +15 -12
  14. package/core/random.js +9 -6
  15. package/core/role.js +69 -77
  16. package/core/thread.js +12 -2
  17. package/core/type.js +300 -261
  18. package/core/url.js +20 -12
  19. package/core/validate.js +29 -26
  20. package/db/db.js +297 -227
  21. package/db/entity.js +631 -963
  22. package/db/gridfs.js +120 -166
  23. package/design/add_default_field_attr.md +56 -0
  24. package/http/context.js +22 -8
  25. package/http/cors.js +25 -8
  26. package/http/error.js +27 -9
  27. package/http/express.js +70 -41
  28. package/http/params.js +70 -42
  29. package/http/router.js +51 -40
  30. package/http/session.js +59 -36
  31. package/index.js +85 -9
  32. package/package.json +2 -2
  33. package/router/clone.js +28 -36
  34. package/router/create.js +21 -26
  35. package/router/delete.js +24 -28
  36. package/router/read.js +137 -123
  37. package/router/update.js +38 -56
  38. package/setting.js +22 -6
  39. package/skills/array.md +155 -0
  40. package/skills/bash.md +91 -0
  41. package/skills/chart.md +54 -0
  42. package/skills/code.md +422 -0
  43. package/skills/context.md +177 -0
  44. package/skills/date.md +58 -0
  45. package/skills/express.md +255 -0
  46. package/skills/file.md +60 -0
  47. package/skills/lhs.md +54 -0
  48. package/skills/meta.md +1023 -0
  49. package/skills/msg.md +30 -0
  50. package/skills/number.md +88 -0
  51. package/skills/obj.md +36 -0
  52. package/skills/params.md +206 -0
  53. package/skills/random.md +22 -0
  54. package/skills/role.md +59 -0
  55. package/skills/session.md +281 -0
  56. package/skills/storage.md +743 -0
  57. package/skills/thread.md +22 -0
  58. package/skills/type.md +547 -0
  59. package/skills/url.md +34 -0
  60. package/skills/validate.md +48 -0
  61. package/test/cleanup/close-db.js +5 -0
  62. package/test/core/array.js +226 -0
  63. package/test/core/chart.js +51 -0
  64. package/test/core/file.js +59 -0
  65. package/test/core/lhs.js +44 -0
  66. package/test/core/number.js +167 -12
  67. package/test/core/obj.js +47 -0
  68. package/test/core/random.js +24 -0
  69. package/test/core/thread.js +20 -0
  70. package/test/core/type.js +216 -0
  71. package/test/core/validate.js +67 -0
  72. package/test/db/db-ops.js +99 -0
  73. package/test/db/pipe_test.txt +0 -0
  74. package/test/db/test_case_design.md +528 -0
  75. package/test/db/test_db_class.js +613 -0
  76. package/test/db/test_entity_class.js +414 -0
  77. package/test/db/test_gridfs_class.js +234 -0
  78. package/test/entity/create.js +1 -1
  79. package/test/entity/delete-mixed.js +156 -0
  80. package/test/entity/ref-filter.js +63 -0
  81. package/tool/gen_i18n.js +55 -21
  82. package/test/crud/router.js +0 -99
  83. package/test/router/user.js +0 -17
@@ -0,0 +1,743 @@
1
+ # Storage System Skill
2
+
3
+ ## Overview
4
+
5
+ The Hola framework provides a comprehensive dual storage system built on MongoDB:
6
+
7
+ 1. **Entity System** (`hola-server/db/entity.js`): High-level, metadata-driven document storage with automatic validation, type conversion, reference resolution, and lifecycle hooks.
8
+ 2. **GridFS System** (`hola-server/db/gridfs.js`): Large file storage for handling binary content like images, documents, and media files.
9
+
10
+ Both systems integrate seamlessly to provide complete data persistence for Hola applications.
11
+
12
+ ---
13
+
14
+ ## 1. Entity System
15
+
16
+ ### 1.1 Overview
17
+
18
+ The `Entity` class provides a meta-driven abstraction layer over MongoDB collections. It automatically handles:
19
+
20
+ - **Type Conversion**: Converts field values based on meta type definitions
21
+ - **Validation**: Required fields, type validation, reference validation
22
+ - **Reference Resolution**: Converts between user-friendly labels and database IDs
23
+ - **Link Population**: Auto-fetches related data from referenced entities
24
+ - **Lifecycle Hooks**: Custom logic at key points (before/after create, update, delete)
25
+ - **Cascade Operations**: Automatic cleanup of related records
26
+
27
+ ### 1.2 Import
28
+
29
+ ```javascript
30
+ const { Entity } = require("hola-server/db/entity");
31
+ ```
32
+
33
+ ### 1.3 Constructor
34
+
35
+ > **Note:** Throughout this documentation, we use hypothetical entities like "product", "user", and "category" for illustrative purposes. Replace these with your actual entity collection names defined in your meta definitions.
36
+
37
+ ```javascript
38
+ // Method 1: Create from meta object
39
+ const meta = get_entity_meta("your_entity_name");
40
+ const entity = new Entity(meta);
41
+
42
+ // Method 2: Create from collection name (recommended)
43
+ const entity = new Entity("your_entity_name");
44
+
45
+ // Example with hypothetical "product" entity
46
+ const productEntity = new Entity("product");
47
+ ```
48
+
49
+ ---
50
+
51
+ ## 2. Entity CRUD Operations
52
+
53
+ ### 2.1 Create Entity
54
+
55
+ **Method:** `create_entity(param_obj, view)`
56
+
57
+ Creates a new document with full validation and hooks.
58
+
59
+ **Parameters:**
60
+ - `param_obj` (Object): Entity data from client
61
+ - `view` (string): View filter (typically `"*"` for all fields)
62
+
63
+ **Returns:** `{code, err?}`
64
+
65
+ **Process Flow:**
66
+ 1. Filter fields by view
67
+ 2. Convert types using `convert_type()`
68
+ 3. Run `before_create` hook
69
+ 4. Validate required fields
70
+ 5. Check for duplicate primary keys
71
+ 6. Validate reference fields
72
+ 7. Run `create` hook or insert document
73
+ 8. Run `after_create` hook
74
+
75
+ **Example:**
76
+
77
+ ```javascript
78
+ const productEntity = new Entity("product");
79
+
80
+ const result = await productEntity.create_entity({
81
+ name: "iPhone 15",
82
+ price: 999.99,
83
+ category: "Electronics", // Will be resolved to category ObjectId
84
+ stock: 50
85
+ }, "*");
86
+
87
+ if (result.code === SUCCESS) {
88
+ console.log("Product created successfully");
89
+ } else {
90
+ console.error("Creation failed:", result.err);
91
+ }
92
+ ```
93
+
94
+ **Common Error Codes:**
95
+ - `NO_PARAMS`: Missing required fields
96
+ - `INVALID_PARAMS`: Type conversion failed
97
+ - `DUPLICATE_KEY`: Primary key already exists
98
+ - `REF_NOT_FOUND`: Referenced entity doesn't exist
99
+ - `REF_NOT_UNIQUE`: Multiple entities match reference label
100
+
101
+ ---
102
+
103
+ ### 2.2 Read Entity
104
+
105
+ **Method:** `read_entity(_id, attr_names, view)`
106
+
107
+ Reads a single document with automatic reference and link resolution.
108
+
109
+ **Parameters:**
110
+ - `_id` (string): Entity ObjectId
111
+ - `attr_names` (string): Comma-separated field names to fetch
112
+ - `view` (string): View filter
113
+
114
+ **Returns:** `{code, data?, err?}`
115
+
116
+ **Process Flow:**
117
+ 1. Validate _id parameter
118
+ 2. Filter property fields by view
119
+ 3. Extract requested attributes, ref fields, and link fields
120
+ 4. Find document
121
+ 5. Run `after_read` hook
122
+ 6. Populate link fields from referenced entities
123
+ 7. Convert ref ObjectIds to ref_labels
124
+
125
+ **Example:**
126
+
127
+ ```javascript
128
+ const result = await productEntity.read_entity(
129
+ "507f1f77bcf86cd799439011",
130
+ "name,price,category,category_code",
131
+ "*"
132
+ );
133
+
134
+ if (result.code === SUCCESS) {
135
+ console.log(result.data);
136
+ // {
137
+ // _id: "507f1f77bcf86cd799439011",
138
+ // name: "iPhone 15",
139
+ // price: 999.99,
140
+ // category: "Electronics", // Converted from ObjectId to label
141
+ // category_code: "ELEC-001" // Link field from category entity
142
+ // }
143
+ }
144
+ ```
145
+
146
+ **Read Property vs Read Entity:**
147
+
148
+ Use `read_property()` when you don't need reference conversion (faster):
149
+
150
+ ```javascript
151
+ const result = await productEntity.read_property(
152
+ "507f1f77bcf86cd799439011",
153
+ "name,price",
154
+ "*"
155
+ );
156
+ // Returns raw data without ref/link processing
157
+ ```
158
+
159
+ ---
160
+
161
+ ### 2.3 Update Entity
162
+
163
+ **Method:** `update_entity(_id, param_obj, view)`
164
+
165
+ Updates an existing document.
166
+
167
+ **Parameters:**
168
+ - `_id` (string|null): Entity ObjectId, or null to use primary keys from param_obj
169
+ - `param_obj` (Object): Update data
170
+ - `view` (string): View filter
171
+
172
+ **Returns:** `{code, err?}`
173
+
174
+ **Process Flow:**
175
+ 1. Filter update fields by view
176
+ 2. Convert types using `convert_update_type()` (preserves empty values)
177
+ 3. Run `before_update` hook
178
+ 4. Build query (by _id or primary keys)
179
+ 5. Verify entity exists (count must be 1)
180
+ 6. Validate reference fields
181
+ 7. Run `update` hook or perform update
182
+ 8. Run `after_update` hook
183
+
184
+ **Example:**
185
+
186
+ ```javascript
187
+ // Update by ID
188
+ const result = await productEntity.update_entity(
189
+ "507f1f77bcf86cd799439011",
190
+ { price: 899.99, stock: 45 },
191
+ "*"
192
+ );
193
+
194
+ // Update by primary key (if _id is null)
195
+ const result2 = await productEntity.update_entity(
196
+ null,
197
+ { sku: "IPHONE-15", price: 899.99 }, // sku is primary key
198
+ "*"
199
+ );
200
+ ```
201
+
202
+ **Update vs Batch Update:**
203
+
204
+ For updating multiple entities at once, use `batch_update_entity()`:
205
+
206
+ ```javascript
207
+ const ids = ["507f...", "608a...", "709b..."];
208
+ await productEntity.batch_update_entity(ids, { discount: 0.1 }, "*");
209
+ ```
210
+
211
+ ---
212
+
213
+ ### 2.4 Delete Entity
214
+
215
+ **Method:** `delete_entity(id_array)`
216
+
217
+ Deletes one or more documents with reference checking and cascade delete.
218
+
219
+ **Parameters:**
220
+ - `id_array` (string[]): Array of entity ObjectIds
221
+
222
+ **Returns:** `{code, err?}`
223
+
224
+ **Process Flow:**
225
+ 1. Validate IDs
226
+ 2. Run `before_delete` hook
227
+ 3. Check for referring entities (unless delete mode allows it)
228
+ 4. Run `delete` hook or perform deletion
229
+ 5. Process cascade deletes for related entities
230
+ 6. Run `after_delete` hook
231
+
232
+ **Example:**
233
+
234
+ ```javascript
235
+ const result = await productEntity.delete_entity([
236
+ "507f1f77bcf86cd799439011"
237
+ ]);
238
+
239
+ if (result.code === HAS_REF) {
240
+ console.error("Cannot delete: referenced by", result.err);
241
+ // e.g., ["product<-order:ORD-001", "product<-cart:CART-123"]
242
+ }
243
+ ```
244
+
245
+ **Cascade Delete Behavior:**
246
+
247
+ Configured in field definition:
248
+
249
+ ```javascript
250
+ fields: [
251
+ {
252
+ name: "category",
253
+ ref: "category",
254
+ delete: "keep" // Keep products when category is deleted
255
+ },
256
+ {
257
+ name: "created_by",
258
+ ref: "user",
259
+ delete: "cascade" // Delete products when user is deleted
260
+ }
261
+ ]
262
+ ```
263
+
264
+ ---
265
+
266
+ ### 2.5 List Entity
267
+
268
+ **Method:** `list_entity(query_params, query, param_obj, view)`
269
+
270
+ Lists entities with pagination, sorting, search, and filtering.
271
+
272
+ **Parameters:**
273
+ - `query_params` (Object): Pagination and sorting config
274
+ - `attr_names` (string): Comma-separated fields to fetch
275
+ - `page` (number): Page number (1-indexed)
276
+ - `limit` (number): Results per page
277
+ - `sort_by` (string): Comma-separated sort fields
278
+ - `desc` (string): Comma-separated boolean flags ("true"/"false")
279
+ - `query` (Object): Additional MongoDB query filter
280
+ - `param_obj` (Object): Search parameters (field values to match)
281
+ - `view` (string): View filter
282
+
283
+ **Returns:** `{code, total, data, err?}`
284
+
285
+ **Example:**
286
+
287
+ ```javascript
288
+ const result = await productEntity.list_entity(
289
+ {
290
+ attr_names: "name,price,category",
291
+ page: 1,
292
+ limit: 20,
293
+ sort_by: "price,created_at",
294
+ desc: "false,true" // price ascending, created_at descending
295
+ },
296
+ { active: true }, // Additional filter
297
+ { category: "Electronics", price: ">=500" }, // Search params
298
+ "*"
299
+ );
300
+
301
+ if (result.code === SUCCESS) {
302
+ console.log(`Total: ${result.total}`);
303
+ console.log(`Page data:`, result.data);
304
+ }
305
+ ```
306
+
307
+ **Search Query Syntax:**
308
+
309
+ The `param_obj` supports advanced search operators:
310
+
311
+ ```javascript
312
+ {
313
+ price: ">=100", // Greater than or equal
314
+ price: "<1000", // Less than
315
+ stock: ">0", // Greater than
316
+ name: "phone", // Regex search (case-insensitive)
317
+ category: "A,B,C", // Multiple values (OR)
318
+ tags: "sale,new" // Array contains all (for array fields)
319
+ }
320
+ ```
321
+
322
+ ---
323
+
324
+ ### 2.6 Clone Entity
325
+
326
+ **Method:** `clone_entity(_id, param_obj, view)`
327
+
328
+ Creates a copy of an existing entity with modifications.
329
+
330
+ **Parameters:**
331
+ - `_id` (string): Source entity ObjectId
332
+ - `param_obj` (Object): New entity data (overrides)
333
+ - `view` (string): View filter
334
+
335
+ **Returns:** `{code, err?}`
336
+
337
+ **Example:**
338
+
339
+ ```javascript
340
+ const result = await productEntity.clone_entity(
341
+ "507f1f77bcf86cd799439011",
342
+ { name: "iPhone 15 Pro", price: 1199.99 },
343
+ "*"
344
+ );
345
+ // Creates a new product based on the original, with new name and price
346
+ ```
347
+
348
+ ---
349
+
350
+ ## 3. Direct Database Operations
351
+
352
+ For low-level operations without validation and hooks:
353
+
354
+ ### 3.1 Basic CRUD
355
+
356
+ ```javascript
357
+ // Create (insert)
358
+ const doc = await entity.create({ name: "Test", value: 42 });
359
+
360
+ // Update
361
+ const result = await entity.update(
362
+ { name: "Test" }, // query
363
+ { $set: { value: 100 } } // update
364
+ );
365
+
366
+ // Delete
367
+ const result = await entity.delete({ name: "Test" });
368
+
369
+ // Find multiple
370
+ const docs = await entity.find(
371
+ { active: true }, // query
372
+ { name: 1, value: 1 } // projection
373
+ );
374
+
375
+ // Find one
376
+ const doc = await entity.find_one({ _id: oid }, { name: 1 });
377
+
378
+ // Count
379
+ const count = await entity.count({ active: true });
380
+ ```
381
+
382
+ ### 3.2 Sorting and Pagination
383
+
384
+ ```javascript
385
+ // Find with sort
386
+ const docs = await entity.find_sort(
387
+ { active: true },
388
+ { created_at: -1 }, // sort descending
389
+ { name: 1, created_at: 1 }
390
+ );
391
+
392
+ // Paginated find
393
+ const docs = await entity.find_page(
394
+ { active: true },
395
+ { price: 1 }, // sort
396
+ 2, // page (1-indexed)
397
+ 20, // limit
398
+ { name: 1, price: 1 }
399
+ );
400
+ ```
401
+
402
+ ### 3.3 Aggregation
403
+
404
+ ```javascript
405
+ // Sum field values
406
+ const total = await entity.sum({ category: "Electronics" }, "price");
407
+ ```
408
+
409
+ ### 3.4 Array Operations
410
+
411
+ ```javascript
412
+ // Remove from array field
413
+ await entity.pull({ _id: oid }, { tags: "deprecated" });
414
+
415
+ // Add to array field
416
+ await entity.push({ _id: oid }, { tags: "featured" });
417
+
418
+ // Add unique to array field
419
+ await entity.add_to_set({ _id: oid }, { tags: "sale" });
420
+ ```
421
+
422
+ ### 3.5 Bulk Operations
423
+
424
+ ```javascript
425
+ const items = [
426
+ { sku: "A001", price: 99 },
427
+ { sku: "A002", price: 149 }
428
+ ];
429
+
430
+ await entity.bulk_update(items, ["sku"]);
431
+ // Updates documents matching sku, creates if not exists
432
+ ```
433
+
434
+ ---
435
+
436
+ ## 4. Reference Field Operations
437
+
438
+ ### 4.1 Find by Reference
439
+
440
+ ```javascript
441
+ // Find by ObjectId or ref_label
442
+ const products = await entity.find_by_ref_value(
443
+ "Electronics", // Can be ObjectId or ref_label value
444
+ { name: 1, price: 1 }, // projection
445
+ "order" // referring entity name (for ref_filter)
446
+ );
447
+ ```
448
+
449
+ ### 4.2 Validate References
450
+
451
+ ```javascript
452
+ const param_obj = {
453
+ name: "iPhone",
454
+ category: "Electronics" // Will be converted to ObjectId
455
+ };
456
+
457
+ const result = await entity.validate_ref(param_obj);
458
+ if (result.code === SUCCESS) {
459
+ // param_obj.category now contains ObjectId
460
+ console.log(param_obj.category); // "507f1f77bcf86cd799439011"
461
+ }
462
+ ```
463
+
464
+ ### 4.3 Convert References
465
+
466
+ ```javascript
467
+ const elements = [
468
+ { _id: "...", name: "Product 1", category: "507f..." },
469
+ { _id: "...", name: "Product 2", category: "608a..." }
470
+ ];
471
+
472
+ const ref_fields = [
473
+ { name: "category", ref: "category" }
474
+ ];
475
+
476
+ const converted = await entity.convert_ref_attrs(elements, ref_fields);
477
+ // converted[0].category is now "Electronics" instead of ObjectId
478
+ ```
479
+
480
+ ### 4.4 Get Reference Labels
481
+
482
+ ```javascript
483
+ // Get ref_label values for given IDs
484
+ const ids = ["507f...", "608a..."];
485
+ const labels = await entity.get_ref_labels(ids);
486
+ // Returns array of objects with _id and ref_label
487
+ ```
488
+
489
+ ---
490
+
491
+ ## 5. GridFS File Storage
492
+
493
+ ### 5.1 Overview
494
+
495
+ GridFS stores files in MongoDB as chunks, suitable for files larger than 16MB BSON size limit. The Hola framework provides a simplified API for file operations.
496
+
497
+ ### 5.2 Import
498
+
499
+ ```javascript
500
+ const {
501
+ save_file, read_file, pipe_file, delete_file,
502
+ save_file_fields_to_db, set_file_fields
503
+ } = require("hola-server/db/gridfs");
504
+ ```
505
+
506
+ ### 5.3 Save File
507
+
508
+ **Method:** `save_file(collection, filename, filepath)`
509
+
510
+ Uploads a file to GridFS, replacing any existing file with the same name.
511
+
512
+ **Parameters:**
513
+ - `collection` (string): Bucket name (typically entity collection name)
514
+ - `filename` (string): File identifier
515
+ - `filepath` (string): Source file path or readable stream
516
+
517
+ ```javascript
518
+ await save_file("product", "iphone_image_01", "/tmp/upload_xyz.jpg");
519
+ ```
520
+
521
+ ### 5.4 Read File
522
+
523
+ **Method:** `read_file(collection, filename, response)`
524
+
525
+ Streams a file directly to HTTP response.
526
+
527
+ **Parameters:**
528
+ - `collection` (string): Bucket name
529
+ - `filename` (string): File identifier
530
+ - `response` (Response): Express response object
531
+
532
+ ```javascript
533
+ // Express route
534
+ app.get('/files/:collection/:filename', async (req, res) => {
535
+ await read_file(
536
+ req.params.collection,
537
+ req.params.filename,
538
+ res
539
+ );
540
+ });
541
+ ```
542
+
543
+ ### 5.5 Pipe File
544
+
545
+ **Method:** `pipe_file(collection, filename, dest_path)`
546
+
547
+ Downloads a file from GridFS to local disk.
548
+
549
+ ```javascript
550
+ await pipe_file(
551
+ "product",
552
+ "iphone_image_01",
553
+ "./downloads/product_image.jpg"
554
+ );
555
+ ```
556
+
557
+ ### 5.6 Delete File
558
+
559
+ **Method:** `delete_file(collection, filename)`
560
+
561
+ Removes a file from GridFS.
562
+
563
+ ```javascript
564
+ await delete_file("product", "iphone_image_01");
565
+ ```
566
+
567
+ ---
568
+
569
+ ## 6. File Field Integration
570
+
571
+ ### 6.1 Entity with File Fields
572
+
573
+ Define file fields in meta:
574
+
575
+ ```javascript
576
+ fields: [
577
+ { name: "sku", type: "string", required: true },
578
+ { name: "name", type: "string", required: true },
579
+ { name: "image", type: "file" },
580
+ { name: "manual", type: "file" }
581
+ ]
582
+ ```
583
+
584
+ ### 6.2 Handling File Uploads
585
+
586
+ In router create/update handlers:
587
+
588
+ ```javascript
589
+ const { set_file_fields, save_file_fields_to_db } = require("hola-server/db/gridfs");
590
+
591
+ // In create handler
592
+ router.post("/", upload.any(), async (req, res) => {
593
+ set_file_fields(meta, req, req.body);
594
+
595
+ const result = await entity.create_entity(req.body, "*");
596
+ if (result.code === SUCCESS) {
597
+ await save_file_fields_to_db(collection, meta.file_fields, req, req.body);
598
+ }
599
+
600
+ return res.json(result);
601
+ });
602
+ ```
603
+
604
+ **What happens:**
605
+ 1. `set_file_fields()` sets field values based on uploaded files (e.g., `"sku_image"`)
606
+ 2. `save_file_fields_to_db()` moves temp files to GridFS using the field values as filenames
607
+
608
+ ---
609
+
610
+ ## 7. Best Practices
611
+
612
+ ### 7.1 Use High-Level CRUD Methods
613
+
614
+ Prefer `create_entity()`, `update_entity()`, etc. over direct database operations for:
615
+ - Automatic validation
616
+ - Type conversion
617
+ - Reference resolution
618
+ - Lifecycle hooks
619
+ - Consistent error handling
620
+
621
+ ### 7.2 Handle Error Codes
622
+
623
+ Always check the `code` field in results:
624
+
625
+ ```javascript
626
+ const result = await entity.create_entity(data, "*");
627
+ return res.json(result);
628
+ // Entity methods return { code: SUCCESS } or { code: ERROR_CODE, err: [...] }
629
+ ```
630
+
631
+ ### 7.3 Use Views for Field Filtering
632
+
633
+ Define different views for different contexts:
634
+
635
+ ```javascript
636
+ // Admin view - all fields
637
+ await entity.read_entity(id, "name,price,cost,margin", "admin");
638
+
639
+ // Public view - limited fields
640
+ await entity.read_entity(id, "name,price", "public");
641
+ ```
642
+
643
+ ### 7.4 Optimize Queries
644
+
645
+ Use projections to limit returned fields:
646
+
647
+ ```javascript
648
+ // Good - only fetch needed fields
649
+ const users = await entity.find({ active: true }, { name: 1, email: 1 });
650
+
651
+ // Avoid - fetches all fields
652
+ const users = await entity.find({ active: true }, {});
653
+ ```
654
+
655
+ ### 7.5 File Storage Patterns
656
+
657
+ - Use entity primary key as base for file naming
658
+ - Clean up files when deleting entities
659
+ - Consider file versioning for updates
660
+
661
+ ```javascript
662
+ // Good file naming pattern
663
+ const filename = `${entity.primary_key}_${field_name}`;
664
+ await save_file(collection, filename, filepath);
665
+ ```
666
+
667
+ ---
668
+
669
+ ## 8. Common Patterns
670
+
671
+ ### 8.1 Create with File Upload
672
+
673
+ ```javascript
674
+ router.post("/", upload.any(), async (req, res) => {
675
+ set_file_fields(meta, req, req.body);
676
+
677
+ const result = await entity.create_entity(req.body, "*");
678
+
679
+ if (result.code === SUCCESS) {
680
+ await save_file_fields_to_db(collection, meta.file_fields, req, req.body);
681
+ return res.json({ code: SUCCESS });
682
+ }
683
+
684
+ return res.json(result);
685
+ });
686
+ ```
687
+
688
+ ### 8.2 Update with File Upload
689
+
690
+ ```javascript
691
+ router.put("/:id", upload.any(), async (req, res) => {
692
+ set_file_fields(meta, req, req.body);
693
+
694
+ const result = await entity.update_entity(req.params.id, req.body, "*");
695
+
696
+ if (result.code === SUCCESS) {
697
+ await save_file_fields_to_db(collection, meta.file_fields, req, req.body);
698
+ return res.json({ code: SUCCESS });
699
+ }
700
+
701
+ return res.json(result);
702
+ });
703
+ ```
704
+
705
+ ### 8.3 Delete with Cascade
706
+
707
+ ```javascript
708
+ router.delete("/", async (req, res) => {
709
+ const ids = req.body.ids; // Array of IDs
710
+
711
+ // Delete files first
712
+ for (const id of ids) {
713
+ const item = await entity.find_one(oid_query(id), {});
714
+ for (const field of meta.file_fields) {
715
+ if (item[field.name]) {
716
+ await delete_file(collection, item[field.name]);
717
+ }
718
+ }
719
+ }
720
+
721
+ // Then delete entity (will cascade to related entities)
722
+ const result = await entity.delete_entity(ids);
723
+ return res.json(result);
724
+ });
725
+ ```
726
+
727
+ ### 8.4 Search with References
728
+
729
+ ```javascript
730
+ // Client sends category name, server resolves to ID
731
+ const result = await entity.list_entity(
732
+ {
733
+ attr_names: "name,price,category",
734
+ page: 1,
735
+ limit: 20,
736
+ sort_by: "price",
737
+ desc: "false"
738
+ },
739
+ {},
740
+ { category: "Electronics" }, // Resolved to ObjectId automatically
741
+ "*"
742
+ );
743
+ ```