@vidyano-labs/virtual-service 0.1.1 → 0.2.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 +36 -2
- package/index.d.ts +0 -4
- package/index.js +31 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -336,8 +336,10 @@ console.log(contact.getAttribute("Email").validationError);
|
|
|
336
336
|
Register your own validation rules for domain-specific requirements:
|
|
337
337
|
|
|
338
338
|
```typescript
|
|
339
|
+
import type { RuleValidationContext } from "@vidyano-labs/virtual-service";
|
|
340
|
+
|
|
339
341
|
// Register a custom rule (before registerPersistentObject)
|
|
340
|
-
service.registerBusinessRule("IsPhoneNumber", (value: any) => {
|
|
342
|
+
service.registerBusinessRule("IsPhoneNumber", (value: any, context: RuleValidationContext) => {
|
|
341
343
|
if (value == null || value === "") return;
|
|
342
344
|
const phoneRegex = /^\+?[\d\s-()]+$/;
|
|
343
345
|
if (!phoneRegex.test(String(value)))
|
|
@@ -357,8 +359,39 @@ service.registerPersistentObject({
|
|
|
357
359
|
});
|
|
358
360
|
```
|
|
359
361
|
|
|
362
|
+
**Validation context:**
|
|
363
|
+
The `RuleValidationContext` parameter provides access to:
|
|
364
|
+
- `context.persistentObject` - The persistent object being validated (wrapped with helper methods)
|
|
365
|
+
- `context.attribute` - The attribute being validated (wrapped with helper methods)
|
|
366
|
+
|
|
367
|
+
This allows cross-field validation:
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// Validate password confirmation matches password
|
|
371
|
+
service.registerBusinessRule("MatchesPassword", (value: any, context: RuleValidationContext) => {
|
|
372
|
+
if (!value) return;
|
|
373
|
+
|
|
374
|
+
const passwordValue = context.persistentObject.getAttributeValue("Password");
|
|
375
|
+
if (value !== passwordValue)
|
|
376
|
+
throw new Error("Passwords do not match");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
service.registerPersistentObject({
|
|
380
|
+
type: "User",
|
|
381
|
+
attributes: [
|
|
382
|
+
{ name: "Password", type: "String" },
|
|
383
|
+
{
|
|
384
|
+
name: "ConfirmPassword",
|
|
385
|
+
type: "String",
|
|
386
|
+
rules: "MatchesPassword"
|
|
387
|
+
}
|
|
388
|
+
]
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
360
392
|
**Custom rule requirements:**
|
|
361
393
|
- Must be registered before `registerPersistentObject()`
|
|
394
|
+
- Receives two parameters: `value` (the attribute value) and `context` (validation context)
|
|
362
395
|
- Throw an `Error` with a message if validation fails
|
|
363
396
|
- Return nothing (or undefined) if validation passes
|
|
364
397
|
- Cannot override built-in rules
|
|
@@ -1097,7 +1130,8 @@ import type {
|
|
|
1097
1130
|
ActionHandler,
|
|
1098
1131
|
ActionArgs,
|
|
1099
1132
|
ActionContext,
|
|
1100
|
-
RuleValidatorFn
|
|
1133
|
+
RuleValidatorFn,
|
|
1134
|
+
RuleValidationContext
|
|
1101
1135
|
} from "@vidyano-labs/virtual-service";
|
|
1102
1136
|
```
|
|
1103
1137
|
|
package/index.d.ts
CHANGED
|
@@ -77,10 +77,6 @@ type VirtualPersistentObjectAttributeConfig = {
|
|
|
77
77
|
* Initial value for the attribute.
|
|
78
78
|
*/
|
|
79
79
|
value?: any;
|
|
80
|
-
/**
|
|
81
|
-
* Indicates whether this attribute is required. Defaults to false.
|
|
82
|
-
*/
|
|
83
|
-
isRequired?: boolean;
|
|
84
80
|
/**
|
|
85
81
|
* Indicates whether the value of this attribute can be changed. Defaults to false.
|
|
86
82
|
*/
|
package/index.js
CHANGED
|
@@ -215,15 +215,15 @@ class VirtualPersistentObjectRegistry {
|
|
|
215
215
|
for (const attr of po.attributes) {
|
|
216
216
|
// Clear previous validation errors
|
|
217
217
|
attr.validationError = undefined;
|
|
218
|
-
// Find the attribute config to get rules
|
|
218
|
+
// Find the attribute config to get rules
|
|
219
219
|
const attrConfig = config.attributes.find(a => a.name === attr.name);
|
|
220
220
|
if (!attrConfig)
|
|
221
221
|
continue;
|
|
222
|
-
//
|
|
222
|
+
// Always use server config for rules and isRequired (never trust client values)
|
|
223
223
|
const attrWithRules = {
|
|
224
224
|
...attr,
|
|
225
225
|
rules: attrConfig.rules,
|
|
226
|
-
isRequired: attrConfig.
|
|
226
|
+
isRequired: hasRequiredRule(attrConfig.rules)
|
|
227
227
|
};
|
|
228
228
|
// Validate the attribute
|
|
229
229
|
const error = this.#validator.validateAttribute(attrWithRules, po);
|
|
@@ -345,17 +345,36 @@ async function buildPersistentObjectDto(config, queryRegistry, objectId, isNew)
|
|
|
345
345
|
queries
|
|
346
346
|
};
|
|
347
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* Checks if rules string contains NotEmpty or Required
|
|
350
|
+
*/
|
|
351
|
+
function hasRequiredRule(rules) {
|
|
352
|
+
if (!rules)
|
|
353
|
+
return false;
|
|
354
|
+
// Parse the rules string (semicolon-separated)
|
|
355
|
+
const ruleNames = rules
|
|
356
|
+
.split(";")
|
|
357
|
+
.map(rule => rule.trim())
|
|
358
|
+
.map(rule => {
|
|
359
|
+
// Extract just the rule name (before any parentheses)
|
|
360
|
+
const match = rule.match(/^(\w+)/);
|
|
361
|
+
return match ? match[1] : "";
|
|
362
|
+
});
|
|
363
|
+
return ruleNames.includes("NotEmpty") || ruleNames.includes("Required");
|
|
364
|
+
}
|
|
348
365
|
/**
|
|
349
366
|
* Builds a PersistentObjectAttributeDto from configuration
|
|
350
367
|
*/
|
|
351
368
|
async function buildAttributeDto(config, index, queryRegistry) {
|
|
369
|
+
// Automatically set isRequired if rules contain NotEmpty or Required
|
|
370
|
+
const isRequired = hasRequiredRule(config.rules);
|
|
352
371
|
const baseDto = {
|
|
353
372
|
id: config.id || crypto.randomUUID(),
|
|
354
373
|
name: config.name,
|
|
355
374
|
type: config.type || "String",
|
|
356
375
|
label: config.label || config.name,
|
|
357
376
|
value: config.value,
|
|
358
|
-
isRequired
|
|
377
|
+
isRequired,
|
|
359
378
|
isReadOnly: config.isReadOnly || false,
|
|
360
379
|
rules: config.rules,
|
|
361
380
|
visibility: config.visibility || "Always",
|
|
@@ -1221,7 +1240,7 @@ class BusinessRuleValidator {
|
|
|
1221
1240
|
this.#builtInRules.set("MinLength", this.#validateMinLength.bind(this));
|
|
1222
1241
|
this.#builtInRules.set("MinValue", this.#validateMinValue.bind(this));
|
|
1223
1242
|
this.#builtInRules.set("NotEmpty", this.#validateNotEmpty.bind(this));
|
|
1224
|
-
this.#builtInRules.set("Required", this.#
|
|
1243
|
+
this.#builtInRules.set("Required", this.#validateRequired.bind(this));
|
|
1225
1244
|
}
|
|
1226
1245
|
/**
|
|
1227
1246
|
* Register a custom business rule
|
|
@@ -1257,16 +1276,9 @@ class BusinessRuleValidator {
|
|
|
1257
1276
|
};
|
|
1258
1277
|
// Get the converted value (e.g., boolean from "True"/"False", number from string)
|
|
1259
1278
|
const convertedValue = this.#getConvertedValue(attr);
|
|
1260
|
-
// Check isRequired first
|
|
1261
|
-
if (attr.isRequired) {
|
|
1262
|
-
try {
|
|
1263
|
-
this.#validateNotEmpty(convertedValue, context);
|
|
1264
|
-
}
|
|
1265
|
-
catch (error) {
|
|
1266
|
-
return error instanceof Error ? error.message : String(error);
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
1279
|
// Parse and validate rules string
|
|
1280
|
+
// Note: isRequired is auto-set from rules for UI purposes only (e.g., showing asterisks)
|
|
1281
|
+
// All validation logic is handled by the rules themselves
|
|
1270
1282
|
if (!attr.rules)
|
|
1271
1283
|
return null;
|
|
1272
1284
|
const rules = this.parseRules(attr.rules);
|
|
@@ -1407,9 +1419,13 @@ class BusinessRuleValidator {
|
|
|
1407
1419
|
if (num < minimum)
|
|
1408
1420
|
throw new Error(`Minimum value is ${minimum}`);
|
|
1409
1421
|
}
|
|
1422
|
+
#validateRequired(value, context) {
|
|
1423
|
+
if (value == null)
|
|
1424
|
+
throw new Error("This field is required");
|
|
1425
|
+
}
|
|
1410
1426
|
#validateNotEmpty(value, context) {
|
|
1411
1427
|
if (value == null || value === "" || (typeof value === "string" && value.trim() === ""))
|
|
1412
|
-
throw new Error("This field
|
|
1428
|
+
throw new Error("This field cannot be empty");
|
|
1413
1429
|
}
|
|
1414
1430
|
}
|
|
1415
1431
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vidyano-labs/virtual-service",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Virtual service implementation for testing Vidyano applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -20,5 +20,5 @@
|
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
|
-
"gitHash": "
|
|
23
|
+
"gitHash": "a8a427169445d844bc42c226c93da215441e6242"
|
|
24
24
|
}
|