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