@qubit-ltd/common-decorator 3.8.11 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # js-common-decorator
1
+ # js-common-decorator
2
2
 
3
3
  [![npm package](https://img.shields.io/npm/v/@qubit-ltd/common-decorator.svg)](https://npmjs.com/package/@qubit-ltd/common-decorator)
4
4
  [![License](https://img.shields.io/badge/License-Apache-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
@@ -6,10 +6,35 @@
6
6
  [![CircleCI](https://dl.circleci.com/status-badge/img/gh/Haixing-Hu/js-common-decorator/tree/master.svg?style=shield)](https://dl.circleci.com/status-badge/redirect/gh/Haixing-Hu/js-common-decorator/tree/master)
7
7
  [![Coverage Status](https://coveralls.io/repos/github/Haixing-Hu/js-common-decorator/badge.svg?branch=master)](https://coveralls.io/github/Haixing-Hu/js-common-decorator?branch=master)
8
8
 
9
- [@qubit-ltd/common-decorator] is a JavaScript library of common decorators,
10
- provides decorators to add common methods to domain classes. The library
11
- supports the most recent (currently May 2023)
12
- [stage 3 proposal of JavaScript decorators].
9
+ ## Overview
10
+
11
+ [@qubit-ltd/common-decorator] is a JavaScript library of common decorators that provides powerful tools to enhance your domain classes. The library supports the most recent (as of November 2023) [stage 3 proposal of JavaScript decorators].
12
+
13
+ With this library, you can easily add common methods to your domain classes, implement Java-like enum functionality, add validation and normalization capabilities, and much more - all using the modern decorator syntax.
14
+
15
+ ## Features
16
+
17
+ - **Modern Decorator Support**: Compatible with the latest Stage 3 proposal for JavaScript decorators
18
+ - **Model Enhancement**: `@Model` decorator adds common methods to domain model classes
19
+ - **Enum Implementation**: `@Enum` decorator provides Java-like enumeration capabilities
20
+ - **Validation Support**: `@Validatable` decorator enables field validation
21
+ - **Normalization Support**: `@Normalizable` decorator enables field normalization
22
+ - **Type Safety**: `@Type` and `@ElementType` decorators for type checking
23
+ - **Serialization Utilities**: Built-in JSON serialization/deserialization support
24
+ - **High Test Coverage**: Comprehensive test suite ensuring reliability of all features
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ # Using npm
30
+ npm install @qubit-ltd/common-decorator
31
+
32
+ # Using yarn
33
+ yarn add @qubit-ltd/common-decorator
34
+
35
+ # Using pnpm
36
+ pnpm add @qubit-ltd/common-decorator
37
+ ```
13
38
 
14
39
  ## <span id="content">Table of Contents</span>
15
40
 
@@ -54,6 +79,7 @@ supports the most recent (currently May 2023)
54
79
  - [Configuration](#configuration)
55
80
  - [Bundling with webpack](#webpack)
56
81
  - [Bundling with vite](#vite)
82
+ - [Recent Updates](#recent-updates)
57
83
  - [Contributing](#contributing)
58
84
  - [License](#license)
59
85
 
@@ -1004,14 +1030,14 @@ expect(opt3.convertNaming).toBe(false);
1004
1030
 
1005
1031
  ## <span id="configuration">Configuration</span>
1006
1032
 
1007
- This library uses the most recent (currently May 2023)
1033
+ This library uses the most recent (currently November 2023)
1008
1034
  [stage 3 proposal of JavaScript decorators]. Therefore, you must configure
1009
1035
  [Babel] with [@babel/plugin-transform-class-properties] and the
1010
1036
  [@babel/plugin-proposal-decorators] plugins.
1011
1037
 
1012
- **NOTE:** To support the [stage 3 proposal of JavaScript decorator metadata],
1013
- the version of the [Babel] plugin [@babel/plugin-proposal-decorators] must be
1014
- at least `7.23.0`.
1038
+ **NOTE:** To support the [stage 3 proposal of JavaScript decorator metadata]
1039
+ at November 2023, the version of the [Babel] plugin [@babel/plugin-proposal-decorators]
1040
+ must be at least `7.24.0`.
1015
1041
 
1016
1042
  ### <span id="webpack">Bundling with [webpack]</span>
1017
1043
 
@@ -1031,7 +1057,7 @@ at least `7.23.0`.
1031
1057
  ],
1032
1058
  "plugins": [
1033
1059
  "@babel/plugin-transform-runtime",
1034
- ["@babel/plugin-proposal-decorators", { "version": "2023-05" }],
1060
+ ["@babel/plugin-proposal-decorators", { "version": "2023-11" }],
1035
1061
  "@babel/plugin-transform-class-properties"
1036
1062
  ]
1037
1063
  }
@@ -1055,7 +1081,7 @@ at least `7.23.0`.
1055
1081
  ],
1056
1082
  "plugins": [
1057
1083
  "@babel/plugin-transform-runtime",
1058
- ["@babel/plugin-proposal-decorators", { "version": "2023-05" }],
1084
+ ["@babel/plugin-proposal-decorators", { "version": "2023-11" }],
1059
1085
  "@babel/plugin-transform-class-properties"
1060
1086
  ]
1061
1087
  }
@@ -1132,6 +1158,20 @@ at least `7.23.0`.
1132
1158
  });
1133
1159
  ```
1134
1160
 
1161
+ ## <span id="recent-updates">Recent Updates</span>
1162
+
1163
+ ### Test Coverage Enhancements (December 2023)
1164
+
1165
+ - **Enum Clone Hook Test**: Added comprehensive tests to verify the behavior of enum clone hooks, ensuring that enumerator objects are properly handled during cloning operations.
1166
+ - **Field Validation Coverage**: Enhanced test coverage for field validation, specifically targeting edge cases to ensure robust validation in all scenarios.
1167
+ - **Model Implementation Tests**: Added additional tests for various model implementation functions to achieve higher code coverage.
1168
+
1169
+ The latest updates focus on improving test coverage and reliability, with particular attention to:
1170
+
1171
+ - Ensuring enumerator objects maintain their singleton pattern during cloning
1172
+ - Validating empty fields with proper error handling
1173
+ - Verifying that model implementation functions work correctly in all edge cases
1174
+
1135
1175
  ## <span id="contributing">Contributing</span>
1136
1176
 
1137
1177
  If you find any issues or have suggestions for improvements, please feel free
@@ -1154,3 +1194,608 @@ See the [LICENSE](LICENSE) file for more details.
1154
1194
  [vite-plugin-vue]: https://www.npmjs.com/package/@vitejs/plugin-vue
1155
1195
  [vite-plugin-babel]: https://www.npmjs.com/package/vite-plugin-babel
1156
1196
  [our version of vite-plugin-babel]: https://npmjs.com/package/@qubit-ltd/vite-plugin-babel
1197
+
1198
+ ## Front-end and Back-end Data Exchange Example
1199
+
1200
+ In real-world applications, data exchange between front-end and back-end is a common scenario. The following example demonstrates how to use this library to process data from RESTful APIs and send it back to the server.
1201
+
1202
+ ### Complete Example
1203
+
1204
+ Suppose we have an e-commerce application that needs to handle order data. The back-end API returns data in snake_case naming convention (e.g., `order_id`), while the front-end code uses camelCase (e.g., `orderId`). Additionally, order IDs use Java's Long type (which may exceed JavaScript's safe integer range).
1205
+
1206
+ First, let's define our domain models:
1207
+
1208
+ ```javascript
1209
+ import { Model, Type, ElementType, Normalizable, Validatable, NonEmpty } from '@qubit-ltd/common-decorator';
1210
+
1211
+ // Configure default options for naming style conversion
1212
+ DefaultOptions.set('assign', {
1213
+ normalize: true,
1214
+ convertNaming: true,
1215
+ sourceNamingStyle: 'LOWER_UNDERSCORE', // JSON data format from back-end
1216
+ targetNamingStyle: 'LOWER_CAMEL' // Format used in front-end
1217
+ });
1218
+
1219
+ DefaultOptions.set('toJSON', {
1220
+ normalize: true,
1221
+ removeEmptyFields: true, // Automatically remove empty fields
1222
+ convertNaming: true,
1223
+ sourceNamingStyle: 'LOWER_CAMEL', // Format used in front-end
1224
+ targetNamingStyle: 'LOWER_UNDERSCORE' // Format to send to back-end
1225
+ });
1226
+
1227
+ // Define the OrderItem model
1228
+ @Model
1229
+ class OrderItem {
1230
+ constructor() {
1231
+ this.id = null; // Java Long type, automatically converted to BigInt
1232
+ this.productId = null; // Front-end uses camelCase, corresponding to product_id in back-end
1233
+ this.productName = '';
1234
+ this.quantity = 0;
1235
+ this.unitPrice = 0;
1236
+ }
1237
+
1238
+ @Normalizable
1239
+ @Validatable
1240
+ @Type(String)
1241
+ get productName() {
1242
+ return this._productName;
1243
+ }
1244
+
1245
+ set productName(value) {
1246
+ this._productName = value;
1247
+ }
1248
+
1249
+ @Normalizable
1250
+ @Validatable
1251
+ @Type(Number)
1252
+ get quantity() {
1253
+ return this._quantity;
1254
+ }
1255
+
1256
+ set quantity(value) {
1257
+ this._quantity = value;
1258
+ }
1259
+
1260
+ @Normalizable
1261
+ @Validatable
1262
+ @Type(Number)
1263
+ get unitPrice() {
1264
+ return this._unitPrice;
1265
+ }
1266
+
1267
+ set unitPrice(value) {
1268
+ this._unitPrice = value;
1269
+ }
1270
+
1271
+ // Calculate total price
1272
+ getTotalPrice() {
1273
+ return this.quantity * this.unitPrice;
1274
+ }
1275
+ }
1276
+
1277
+ // Define the Order model
1278
+ @Model
1279
+ class Order {
1280
+ constructor() {
1281
+ this.id = null; // Java Long type, automatically converted to BigInt
1282
+ this.orderNumber = ''; // Front-end uses camelCase, corresponding to order_number in back-end
1283
+ this.customerId = null;
1284
+ this.customerName = '';
1285
+ this.orderDate = null;
1286
+ this.orderItems = []; // Array of order items
1287
+ this.totalAmount = 0;
1288
+ this.status = '';
1289
+ this.note = ''; // Optional field, empty values will be removed when sent to back-end
1290
+ }
1291
+
1292
+ @Normalizable
1293
+ @Validatable
1294
+ @NonEmpty
1295
+ @Type(String)
1296
+ get orderNumber() {
1297
+ return this._orderNumber;
1298
+ }
1299
+
1300
+ set orderNumber(value) {
1301
+ this._orderNumber = value;
1302
+ }
1303
+
1304
+ @Normalizable
1305
+ @Validatable
1306
+ @Type(String)
1307
+ get customerName() {
1308
+ return this._customerName;
1309
+ }
1310
+
1311
+ set customerName(value) {
1312
+ this._customerName = value;
1313
+ }
1314
+
1315
+ @Normalizable
1316
+ @Validatable
1317
+ @Type(Date)
1318
+ get orderDate() {
1319
+ return this._orderDate;
1320
+ }
1321
+
1322
+ set orderDate(value) {
1323
+ this._orderDate = value;
1324
+ }
1325
+
1326
+ @Normalizable
1327
+ @Validatable
1328
+ @ElementType(OrderItem)
1329
+ get orderItems() {
1330
+ return this._orderItems;
1331
+ }
1332
+
1333
+ set orderItems(value) {
1334
+ this._orderItems = value;
1335
+ }
1336
+ }
1337
+
1338
+ // Example usage - Fetching data from back-end
1339
+ async function fetchOrder(orderId) {
1340
+ try {
1341
+ // Assume this is the response from a back-end API
1342
+ const response = await fetch(`/api/orders/${orderId}`);
1343
+ const data = await response.json();
1344
+
1345
+ // Sample data (in snake_case naming style)
1346
+ // {
1347
+ // "id": "9223372036854775807", // Note: Max value of Java Long, exceeds JS Number safe range
1348
+ // "order_number": "ORD-2023-001",
1349
+ // "customer_id": "5678",
1350
+ // "customer_name": "John Doe",
1351
+ // "order_date": "2023-08-15T14:30:00.000Z",
1352
+ // "order_items": [
1353
+ // {
1354
+ // "id": "8345678912345678901", // Another large integer
1355
+ // "product_id": "101",
1356
+ // "product_name": "Laptop",
1357
+ // "quantity": 1,
1358
+ // "unit_price": 999.99
1359
+ // },
1360
+ // {
1361
+ // "id": "8345678912345678902",
1362
+ // "product_id": "202",
1363
+ // "product_name": "Wireless Mouse",
1364
+ // "quantity": 2,
1365
+ // "unit_price": 29.99
1366
+ // }
1367
+ // ],
1368
+ // "total_amount": 1059.97,
1369
+ // "status": "PENDING",
1370
+ // "note": null
1371
+ // }
1372
+
1373
+ // Create domain object using Order.create(), automatically handling naming style conversion and large integers
1374
+ const order = Order.create(data);
1375
+
1376
+ console.log(order.id); // Output: 9223372036854775807n (BigInt type)
1377
+ console.log(order.orderNumber); // Output: "ORD-2023-001" (converted to camelCase)
1378
+ console.log(order.orderItems[0].productName); // Output: "Laptop"
1379
+
1380
+ // Validation and normalization
1381
+ order.normalize();
1382
+ const validationResult = order.validate();
1383
+ if (!validationResult.valid) {
1384
+ console.error('Order data validation failed:', validationResult.message);
1385
+ }
1386
+
1387
+ return order;
1388
+ } catch (error) {
1389
+ console.error('Failed to fetch order:', error);
1390
+ throw error;
1391
+ }
1392
+ }
1393
+
1394
+ // Sending data back to the back-end
1395
+ async function updateOrder(order) {
1396
+ try {
1397
+ // Use toJSON to convert domain object to plain JavaScript object
1398
+ // Automatically handles: 1. Naming style conversion 2. Empty field removal 3. BigInt conversion
1399
+ const orderData = order.toJSON();
1400
+
1401
+ // Example of converted data format:
1402
+ // {
1403
+ // "id": "9223372036854775807", // BigInt converted to string, without 'n' suffix
1404
+ // "order_number": "ORD-2023-001",
1405
+ // "customer_id": "5678",
1406
+ // "customer_name": "John Doe",
1407
+ // "order_date": "2023-08-15T14:30:00.000Z",
1408
+ // "order_items": [
1409
+ // {
1410
+ // "id": "8345678912345678901",
1411
+ // "product_id": "101",
1412
+ // "product_name": "Laptop",
1413
+ // "quantity": 1,
1414
+ // "unit_price": 999.99
1415
+ // },
1416
+ // // ...other order items
1417
+ // ],
1418
+ // "total_amount": 1059.97,
1419
+ // "status": "PENDING"
1420
+ // // Note: 'note' field was null and has been automatically removed
1421
+ // }
1422
+
1423
+ const response = await fetch(`/api/orders/${order.id}`, {
1424
+ method: 'PUT',
1425
+ headers: {
1426
+ 'Content-Type': 'application/json'
1427
+ },
1428
+ body: JSON.stringify(orderData) // No additional processing needed, already in the right format
1429
+ });
1430
+
1431
+ if (!response.ok) {
1432
+ throw new Error(`Failed to update order: ${response.status}`);
1433
+ }
1434
+
1435
+ return await response.json();
1436
+ } catch (error) {
1437
+ console.error('Failed to update order:', error);
1438
+ throw error;
1439
+ }
1440
+ }
1441
+
1442
+ // Example usage
1443
+ async function example() {
1444
+ // Fetch order data and modify it
1445
+ const order = await fetchOrder('123');
1446
+ order.status = 'COMPLETED';
1447
+ order.note = ''; // Empty string, will be removed when sent to back-end
1448
+
1449
+ // Add a new item to the order
1450
+ const newItem = new OrderItem();
1451
+ newItem.productId = '303';
1452
+ newItem.productName = 'Headphones';
1453
+ newItem.quantity = 1;
1454
+ newItem.unitPrice = 79.99;
1455
+ order.orderItems.push(newItem);
1456
+
1457
+ // Recalculate total amount
1458
+ order.totalAmount = order.orderItems.reduce(
1459
+ (sum, item) => sum + item.getTotalPrice(), 0
1460
+ );
1461
+
1462
+ // Send the updated order back to the server
1463
+ await updateOrder(order);
1464
+ }
1465
+
1466
+ ```
1467
+
1468
+ ### Key Points
1469
+
1470
+ 1. **Automatic Naming Style Conversion**:
1471
+ - Data from the back-end uses snake_case format (e.g., `order_number`)
1472
+ - Front-end domain objects use camelCase format (e.g., `orderNumber`)
1473
+ - Through `DefaultOptions.set('assign', {...})` configuration, `Model.create()` and `model.assign()` automatically convert naming styles
1474
+ - Through `DefaultOptions.set('toJSON', {...})` configuration, `model.toJSON()` automatically converts camelCase back to snake_case
1475
+
1476
+ 2. **Large Integer Handling**:
1477
+ - Back-end uses Java Long type IDs (e.g., `9223372036854775807`) that exceed JavaScript's safe integer range
1478
+ - `Model.create()` and `model.assign()` automatically convert these large integers to JavaScript's BigInt type
1479
+ - `model.toJSON()` automatically converts BigInt to the correct JSON format (without the 'n' suffix)
1480
+
1481
+ 3. **Empty Field Handling**:
1482
+ - With `removeEmptyFields: true` configuration, `model.toJSON()` automatically removes null, undefined, and empty string properties
1483
+ - In the example, the 'note' field with null or empty string value won't be sent to the back-end
1484
+
1485
+ 4. **Type Conversion and Validation**:
1486
+ - Use `@Type` and `@ElementType` decorators to ensure type safety
1487
+ - Use `model.normalize()` for data normalization
1488
+ - Use `model.validate()` to validate data integrity
1489
+
1490
+ This complete example demonstrates how to use the features of this library to easily handle various challenges in front-end and back-end data exchange in real-world applications.
1491
+
1492
+ ## Advanced Usage
1493
+
1494
+ ### Combining Multiple Decorators
1495
+
1496
+ You can combine multiple decorators to add rich functionality to your classes:
1497
+
1498
+ ```javascript
1499
+ import {
1500
+ Model,
1501
+ Type,
1502
+ ElementType,
1503
+ Normalizable,
1504
+ Validatable,
1505
+ NonEmpty
1506
+ } from '@qubit-ltd/common-decorator';
1507
+
1508
+ @Model
1509
+ class Product {
1510
+ constructor() {
1511
+ this.id = null;
1512
+ this.name = '';
1513
+ this.price = 0;
1514
+ this.tags = [];
1515
+ this.createdAt = null;
1516
+ }
1517
+
1518
+ @NonEmpty
1519
+ @Validatable
1520
+ @Normalizable
1521
+ @Type(String)
1522
+ get name() {
1523
+ return this._name;
1524
+ }
1525
+
1526
+ set name(value) {
1527
+ this._name = value;
1528
+ }
1529
+
1530
+ @Validatable
1531
+ @Normalizable
1532
+ @Type(Number)
1533
+ get price() {
1534
+ return this._price;
1535
+ }
1536
+
1537
+ set price(value) {
1538
+ this._price = value;
1539
+ }
1540
+
1541
+ @Normalizable
1542
+ @ElementType(String)
1543
+ get tags() {
1544
+ return this._tags;
1545
+ }
1546
+
1547
+ set tags(value) {
1548
+ this._tags = value;
1549
+ }
1550
+ }
1551
+
1552
+ // Usage
1553
+ const product = new Product();
1554
+ product.assign({
1555
+ name: ' Product Name ',
1556
+ price: '99.99',
1557
+ tags: ['tag1', 2, 'tag3']
1558
+ });
1559
+
1560
+ // After normalization, product.name will be trimmed,
1561
+ // product.price will be a Number,
1562
+ // and all elements in product.tags will be strings
1563
+ product.normalize();
1564
+
1565
+ console.log(product.validate()); // Checks if name is not empty and all types match
1566
+ ```
1567
+
1568
+ ### Custom Validation and Normalization
1569
+
1570
+ You can implement custom validation and normalization logic:
1571
+
1572
+ ```javascript
1573
+ import { Model, Normalizable, Validatable } from '@qubit-ltd/common-decorator';
1574
+
1575
+ @Model
1576
+ class EmailSubscription {
1577
+ constructor() {
1578
+ this.email = '';
1579
+ this.subscribed = false;
1580
+ }
1581
+
1582
+ @Normalizable((value) => {
1583
+ // Custom normalizer that converts email to lowercase and trims whitespace
1584
+ return typeof value === 'string' ? value.toLowerCase().trim() : value;
1585
+ })
1586
+ @Validatable((value) => {
1587
+ // Custom validator that checks if email is valid
1588
+ if (typeof value !== 'string' || !value.includes('@')) {
1589
+ return {
1590
+ valid: false,
1591
+ message: 'Invalid email address',
1592
+ };
1593
+ }
1594
+ return { valid: true };
1595
+ })
1596
+ get email() {
1597
+ return this._email;
1598
+ }
1599
+
1600
+ set email(value) {
1601
+ this._email = value;
1602
+ }
1603
+ }
1604
+ ```
1605
+
1606
+ ### Working with DefaultOptions
1607
+
1608
+ You can configure default options for various operations:
1609
+
1610
+ ```javascript
1611
+ import { DefaultOptions, Model } from '@qubit-ltd/common-decorator';
1612
+
1613
+ // Configure default options for JSON serialization
1614
+ DefaultOptions.set('toJSON', {
1615
+ normalize: true,
1616
+ removeEmptyFields: true,
1617
+ convertNaming: true,
1618
+ sourceNamingStyle: 'LOWER_CAMEL',
1619
+ targetNamingStyle: 'LOWER_UNDERSCORE'
1620
+ });
1621
+
1622
+ @Model
1623
+ class Order {
1624
+ constructor() {
1625
+ this.orderId = null;
1626
+ this.customerName = '';
1627
+ this.orderItems = [];
1628
+ }
1629
+ }
1630
+
1631
+ const order = new Order();
1632
+ order.assign({
1633
+ orderId: '12345',
1634
+ customerName: 'John Doe',
1635
+ orderItems: [
1636
+ { itemId: 1, name: 'Product 1', quantity: 2 }
1637
+ ]
1638
+ });
1639
+
1640
+ // Will use the configured default options for serialization
1641
+ const json = order.toJsonString();
1642
+ console.log(json);
1643
+ // Output will use lower_underscore naming:
1644
+ // {"order_id":"12345","customer_name":"John Doe","order_items":[{"item_id":1,"name":"Product 1","quantity":2}]}
1645
+ ```
1646
+
1647
+ ## Integration Examples
1648
+
1649
+ ### Using with Vue.js
1650
+
1651
+ ```javascript
1652
+ import { Model, Normalizable, Validatable } from '@qubit-ltd/common-decorator';
1653
+ import { defineComponent, ref } from 'vue';
1654
+
1655
+ @Model
1656
+ class UserProfile {
1657
+ constructor() {
1658
+ this.username = '';
1659
+ this.email = '';
1660
+ this.bio = '';
1661
+ }
1662
+
1663
+ @Normalizable
1664
+ @Validatable
1665
+ get username() {
1666
+ return this._username;
1667
+ }
1668
+
1669
+ set username(value) {
1670
+ this._username = value;
1671
+ }
1672
+
1673
+ // Other getters and setters...
1674
+ }
1675
+
1676
+ export default defineComponent({
1677
+ setup() {
1678
+ const profile = ref(new UserProfile());
1679
+
1680
+ const updateProfile = (formData) => {
1681
+ profile.value.assign(formData);
1682
+ profile.value.normalize();
1683
+ const validation = profile.value.validate();
1684
+
1685
+ if (validation.valid) {
1686
+ // Save profile
1687
+ } else {
1688
+ // Handle validation errors
1689
+ console.error(validation.message);
1690
+ }
1691
+ };
1692
+
1693
+ return {
1694
+ profile,
1695
+ updateProfile
1696
+ };
1697
+ }
1698
+ });
1699
+ ```
1700
+
1701
+ ### Using with Express.js
1702
+
1703
+ ```javascript
1704
+ import express from 'express';
1705
+ import { Model, Normalizable, Validatable, NonEmpty } from '@qubit-ltd/common-decorator';
1706
+
1707
+ const app = express();
1708
+ app.use(express.json());
1709
+
1710
+ @Model
1711
+ class NewUserRequest {
1712
+ constructor() {
1713
+ this.username = '';
1714
+ this.email = '';
1715
+ this.password = '';
1716
+ }
1717
+
1718
+ @NonEmpty
1719
+ @Normalizable
1720
+ @Validatable
1721
+ get username() {
1722
+ return this._username;
1723
+ }
1724
+
1725
+ set username(value) {
1726
+ this._username = value;
1727
+ }
1728
+
1729
+ // Other getters and setters...
1730
+ }
1731
+
1732
+ app.post('/api/users', (req, res) => {
1733
+ try {
1734
+ const userRequest = NewUserRequest.create(req.body);
1735
+ userRequest.normalize();
1736
+ const validation = userRequest.validate();
1737
+
1738
+ if (!validation.valid) {
1739
+ return res.status(400).json({ error: validation.message });
1740
+ }
1741
+
1742
+ // Create user in database
1743
+ return res.status(201).json({ message: 'User created successfully' });
1744
+ } catch (error) {
1745
+ return res.status(500).json({ error: error.message });
1746
+ }
1747
+ });
1748
+
1749
+ app.listen(3000, () => {
1750
+ console.log('Server is running on port 3000');
1751
+ });
1752
+ ```
1753
+
1754
+ ## Compatibility and Requirements
1755
+
1756
+ - **Node.js**: 14.x or higher
1757
+ - **ECMAScript**: ES2022 or higher
1758
+ - **Decorator Support**: Requires babel configuration for Stage 3 decorators
1759
+ - **Browser Support**: Modern browsers with ES6+ support
1760
+
1761
+ ## Best Practices
1762
+
1763
+ ### Project Structure
1764
+
1765
+ When using this library, we recommend organizing your domain models in a structured way:
1766
+
1767
+ ```
1768
+ src/
1769
+ ├── models/
1770
+ │ ├── base/
1771
+ │ │ └── BaseModel.js
1772
+ │ ├── User.js
1773
+ │ ├── Product.js
1774
+ │ └── Order.js
1775
+ ├── enums/
1776
+ │ ├── Status.js
1777
+ │ └── Role.js
1778
+ └── app.js
1779
+ ```
1780
+
1781
+ ### Performance Considerations
1782
+
1783
+ - Use `normalize()` only when necessary, not on every operation
1784
+ - Consider caching validation results for frequently accessed objects
1785
+ - For large collections, use `createArray()` method instead of mapping each item
1786
+
1787
+ ### Type Safety
1788
+
1789
+ While JavaScript is dynamically typed, this library provides several ways to ensure type safety:
1790
+
1791
+ 1. Use the `@Type` decorator to enforce type checking for properties
1792
+ 2. Use the `@ElementType` decorator for arrays
1793
+ 3. Enable normalization to automatically convert values to the correct type
1794
+
1795
+ ### Memory Management
1796
+
1797
+ When working with large object graphs:
1798
+
1799
+ 1. Avoid deep cloning unnecessarily
1800
+ 2. Use the `removeEmptyFields` option in `toJSON` for large objects
1801
+ 3. Be cautious with circular references when serializing objects