@vidyano-labs/virtual-service 0.1.1 → 0.3.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 (4) hide show
  1. package/README.md +148 -2
  2. package/index.d.ts +28 -6
  3. package/index.js +97 -36
  4. 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,12 +359,152 @@ 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
365
398
 
399
+ ### Translating Validation Messages
400
+
401
+ Provide custom translations for validation error messages to support multiple languages:
402
+
403
+ ```typescript
404
+ import { VirtualService } from "@vidyano-labs/virtual-service";
405
+
406
+ // Define your translations
407
+ const translations: Record<string, string> = {
408
+ "Required": "Dit veld is verplicht",
409
+ "NotEmpty": "Dit veld mag niet leeg zijn",
410
+ "IsEmail": "E-mailformaat is ongeldig",
411
+ "MaxLength": "Maximale lengte is {0} tekens",
412
+ "MinLength": "Minimale lengte is {0} tekens",
413
+ "MaxValue": "Maximale waarde is {0}",
414
+ "MinValue": "Minimale waarde is {0}"
415
+ };
416
+
417
+ // Create service and register translation function
418
+ const service = new VirtualService();
419
+ service.registerMessageTranslator((key: string, ...params: any[]) => {
420
+ const template = translations[key] || key;
421
+ // Use String.format from @vidyano/core for {0}, {1} placeholders
422
+ return String.format(template, ...params);
423
+ });
424
+
425
+ service.registerPersistentObject({
426
+ type: "Person",
427
+ attributes: [
428
+ {
429
+ name: "Email",
430
+ type: "String",
431
+ rules: "Required; MaxLength(100); IsEmail"
432
+ }
433
+ ]
434
+ });
435
+
436
+ await service.initialize();
437
+
438
+ const person = await service.getPersistentObject(null, "Person", null, true);
439
+ await person.save();
440
+
441
+ // Error message is now translated
442
+ console.log(person.getAttribute("Email").validationError);
443
+ // "Dit veld is verplicht"
444
+ ```
445
+
446
+ **Translation function signature:**
447
+ ```typescript
448
+ type TranslateFunction = (key: string, ...params: any[]) => string;
449
+ ```
450
+
451
+ **Parameters:**
452
+ - `key` - The validation rule name (e.g., "Required", "MaxLength")
453
+ - `params` - Positional parameters for the message (e.g., max length value)
454
+
455
+ **How it works:**
456
+ 1. Built-in validators call `translate(key, ...params)` instead of using hardcoded messages
457
+ 2. Your translate function receives the rule name and any parameters
458
+ 3. Return the translated message with parameters interpolated
459
+ 4. If no translate function is provided, default English messages are used
460
+
461
+ **Custom rules can also use translation:**
462
+
463
+ ```typescript
464
+ const service = new VirtualService();
465
+ service.registerMessageTranslator((key: string, ...params: any[]) => {
466
+ const translations: Record<string, string> = {
467
+ "MinimumAge": "L'âge minimum est {0}",
468
+ "MatchesPassword": "Les mots de passe ne correspondent pas"
469
+ };
470
+ const template = translations[key] || key;
471
+ return String.format(template, ...params);
472
+ });
473
+
474
+ // Custom rule using translation
475
+ service.registerBusinessRule("MinimumAge", (value: any, context: RuleValidationContext, minAge: number) => {
476
+ if (!value)
477
+ return;
478
+
479
+ const age = Number(value);
480
+ if (age < minAge)
481
+ throw new Error(context.translate("MinimumAge", minAge));
482
+ });
483
+
484
+ // Custom rule can still throw direct errors (backward compatible)
485
+ service.registerBusinessRule("IsPhoneNumber", (value: any, _context: RuleValidationContext) => {
486
+ if (!value)
487
+ return;
488
+
489
+ const phoneRegex = /^\+?[\d\s-()]+$/;
490
+ if (!phoneRegex.test(String(value)))
491
+ throw new Error("Invalid phone number format"); // Not translated
492
+ });
493
+ ```
494
+
495
+ **Built-in rule keys:**
496
+ - `Required` - Field is required (no params)
497
+ - `NotEmpty` - Field cannot be empty (no params)
498
+ - `IsEmail` - Invalid email format (no params)
499
+ - `IsUrl` - Invalid URL format (no params)
500
+ - `MaxLength` - Maximum length exceeded (param: max length)
501
+ - `MinLength` - Minimum length not met (param: min length)
502
+ - `MaxValue` - Maximum value exceeded (param: max value)
503
+ - `MinValue` - Minimum value not met (param: min value)
504
+ - `IsBase64` - Invalid base64 string (no params)
505
+ - `IsRegex` - Invalid regex pattern (no params)
506
+ - `IsWord` - Invalid word characters (no params)
507
+
366
508
  ## Queries
367
509
 
368
510
  ### Basic Query Registration
@@ -1058,6 +1200,7 @@ test("search and sort query results", async () => {
1058
1200
  | `registerAction(config)` | Register a custom action |
1059
1201
  | `registerBusinessRule(name, validator)` | Register a validation rule |
1060
1202
  | `registerPersistentObjectActions(type, Class)` | Register lifecycle handlers |
1203
+ | `registerMessageTranslator(translateFn)` | Register message translator for system messages |
1061
1204
  | `initialize()` | Finalize registrations |
1062
1205
 
1063
1206
  ### VirtualPersistentObjectActions
@@ -1097,7 +1240,10 @@ import type {
1097
1240
  ActionHandler,
1098
1241
  ActionArgs,
1099
1242
  ActionContext,
1100
- RuleValidatorFn
1243
+ RuleValidatorFn,
1244
+ RuleValidationContext,
1245
+ TranslateFunction,
1246
+ TranslateFunction
1101
1247
  } from "@vidyano-labs/virtual-service";
1102
1248
  ```
1103
1249
 
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
  */
@@ -237,6 +233,17 @@ type ActionContext = {
237
233
  */
238
234
  setNotification: (message: string, type: Dto.NotificationType, duration?: number) => void;
239
235
  };
236
+ /**
237
+ * Translation function for system messages
238
+ * @param key - The message key (e.g., "Required", "MaxLength")
239
+ * @param params - Positional parameters for the message
240
+ * @returns Translated message with parameters interpolated
241
+ *
242
+ * Example:
243
+ * translate("MaxLength", 50) => "Maximum length is 50 characters"
244
+ * translate("MinValue", 18) => "Minimum value is 18"
245
+ */
246
+ type TranslateFunction = (key: string, ...params: any[]) => string;
240
247
  /**
241
248
  * Context provided to business rule validators for accessing the persistent object
242
249
  */
@@ -249,6 +256,10 @@ type RuleValidationContext = {
249
256
  * The attribute being validated (wrapped with helper methods)
250
257
  */
251
258
  attribute: VirtualPersistentObjectAttribute;
259
+ /**
260
+ * Translation function for system messages
261
+ */
262
+ translate: TranslateFunction;
252
263
  };
253
264
  /**
254
265
  * PersistentObject configuration - converted to PersistentObjectDto
@@ -464,6 +475,10 @@ declare class VirtualPersistentObjectActions {
464
475
  declare class VirtualServiceHooks extends ServiceHooks {
465
476
  #private;
466
477
  constructor();
478
+ /**
479
+ * Sets the translation function for system messages
480
+ */
481
+ setTranslate(translate: TranslateFunction): void;
467
482
  /**
468
483
  * Intercepts fetch requests and routes them to virtual handlers
469
484
  */
@@ -516,7 +531,7 @@ declare class VirtualService extends Service {
516
531
  #private;
517
532
  /**
518
533
  * Creates a new VirtualService instance.
519
- * @param hooks - Optional custom VirtualServiceHooks. If not provided, a default instance is created.
534
+ * @param hooks - Optional custom hooks instance.
520
535
  */
521
536
  constructor(hooks?: VirtualServiceHooks);
522
537
  /**
@@ -566,7 +581,14 @@ declare class VirtualService extends Service {
566
581
  * @throws Error if called after initialize().
567
582
  */
568
583
  registerPersistentObjectActions(type: string, ActionsClass: typeof VirtualPersistentObjectActions): void;
584
+ /**
585
+ * Registers a message translator for translating system messages.
586
+ * Must be called before initialize().
587
+ * @param translate - The translation function.
588
+ * @throws Error if called after initialize().
589
+ */
590
+ registerMessageTranslator(translate: TranslateFunction): void;
569
591
  }
570
592
 
571
593
  export { VirtualPersistentObjectActions, VirtualService, VirtualServiceHooks };
572
- export type { ActionArgs, ActionConfig, ActionContext, ActionHandler, RuleValidationContext, RuleValidatorFn, VirtualPersistentObject, VirtualPersistentObjectAttribute, VirtualPersistentObjectAttributeConfig, VirtualPersistentObjectConfig, VirtualQueryConfig, VirtualQueryExecuteResult };
594
+ export type { ActionArgs, ActionConfig, ActionContext, ActionHandler, RuleValidationContext, RuleValidatorFn, TranslateFunction, VirtualPersistentObject, VirtualPersistentObjectAttribute, VirtualPersistentObjectAttributeConfig, VirtualPersistentObjectConfig, VirtualQueryConfig, VirtualQueryExecuteResult };
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 and isRequired
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
- // Create a DTO with the rules and isRequired from config
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.isRequired || false
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: config.isRequired || false,
377
+ isRequired,
359
378
  isReadOnly: config.isReadOnly || false,
360
379
  rules: config.rules,
361
380
  visibility: config.visibility || "Always",
@@ -1209,7 +1228,9 @@ class VirtualPersistentObjectActionsRegistry {
1209
1228
  class BusinessRuleValidator {
1210
1229
  #builtInRules = new Map();
1211
1230
  #customRules = new Map();
1231
+ #translate;
1212
1232
  constructor() {
1233
+ this.#translate = this.#defaultTranslate;
1213
1234
  // Register all built-in rules
1214
1235
  this.#builtInRules.set("IsBase64", this.#validateIsBase64.bind(this));
1215
1236
  this.#builtInRules.set("IsEmail", this.#validateIsEmail.bind(this));
@@ -1221,7 +1242,33 @@ class BusinessRuleValidator {
1221
1242
  this.#builtInRules.set("MinLength", this.#validateMinLength.bind(this));
1222
1243
  this.#builtInRules.set("MinValue", this.#validateMinValue.bind(this));
1223
1244
  this.#builtInRules.set("NotEmpty", this.#validateNotEmpty.bind(this));
1224
- this.#builtInRules.set("Required", this.#validateNotEmpty.bind(this)); // Required is same as NotEmpty
1245
+ this.#builtInRules.set("Required", this.#validateRequired.bind(this));
1246
+ }
1247
+ /**
1248
+ * Sets the translation function
1249
+ */
1250
+ setTranslate(translate) {
1251
+ this.#translate = translate;
1252
+ }
1253
+ /**
1254
+ * Default English translations - used when no translate function provided
1255
+ */
1256
+ #defaultTranslate(key, ...params) {
1257
+ const defaults = {
1258
+ "Required": () => "This field is required",
1259
+ "NotEmpty": () => "This field cannot be empty",
1260
+ "IsEmail": () => "Email format is invalid",
1261
+ "IsUrl": () => "Value must be a valid URL",
1262
+ "MaxLength": (max) => `Maximum length is ${max} characters`,
1263
+ "MinLength": (min) => `Minimum length is ${min} characters`,
1264
+ "MaxValue": (max) => `Maximum value is ${max}`,
1265
+ "MinValue": (min) => `Minimum value is ${min}`,
1266
+ "IsBase64": () => "Value must be a valid base64 string",
1267
+ "IsRegex": () => "Value must be a valid regular expression",
1268
+ "IsWord": () => "Value must contain only word characters"
1269
+ };
1270
+ const translator = defaults[key];
1271
+ return translator ? translator(...params) : key;
1225
1272
  }
1226
1273
  /**
1227
1274
  * Register a custom business rule
@@ -1253,20 +1300,14 @@ class BusinessRuleValidator {
1253
1300
  // Create validation context
1254
1301
  const context = {
1255
1302
  persistentObject: wrappedPo,
1256
- attribute: wrappedAttr
1303
+ attribute: wrappedAttr,
1304
+ translate: this.#translate
1257
1305
  };
1258
1306
  // Get the converted value (e.g., boolean from "True"/"False", number from string)
1259
1307
  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
1308
  // Parse and validate rules string
1309
+ // Note: isRequired is auto-set from rules for UI purposes only (e.g., showing asterisks)
1310
+ // All validation logic is handled by the rules themselves
1270
1311
  if (!attr.rules)
1271
1312
  return null;
1272
1313
  const rules = this.parseRules(attr.rules);
@@ -1333,83 +1374,87 @@ class BusinessRuleValidator {
1333
1374
  return fromServiceValue(attr.value, attr.type);
1334
1375
  }
1335
1376
  // Built-in validators - throw errors instead of returning strings
1336
- #validateIsBase64(value, context) {
1377
+ #validateIsBase64(value, _context) {
1337
1378
  if (value == null || value === "")
1338
1379
  return;
1339
1380
  const base64Regex = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
1340
1381
  if (!base64Regex.test(String(value)))
1341
- throw new Error("Value must be a valid base64 string");
1382
+ throw new Error(this.#translate("IsBase64"));
1342
1383
  }
1343
- #validateIsEmail(value, context) {
1384
+ #validateIsEmail(value, _context) {
1344
1385
  if (value == null || value === "")
1345
1386
  return;
1346
1387
  // Only allow ASCII characters in email addresses
1347
1388
  const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
1348
1389
  if (!emailRegex.test(String(value)))
1349
- throw new Error("Email format is invalid");
1390
+ throw new Error(this.#translate("IsEmail"));
1350
1391
  }
1351
- #validateIsRegex(value, context) {
1392
+ #validateIsRegex(value, _context) {
1352
1393
  if (value == null || value === "")
1353
1394
  return;
1354
1395
  try {
1355
1396
  new RegExp(String(value));
1356
1397
  }
1357
1398
  catch {
1358
- throw new Error("Value must be a valid regular expression");
1399
+ throw new Error(this.#translate("IsRegex"));
1359
1400
  }
1360
1401
  }
1361
- #validateIsUrl(value, context) {
1402
+ #validateIsUrl(value, _context) {
1362
1403
  if (value == null || value === "")
1363
1404
  return;
1364
1405
  try {
1365
1406
  new URL(String(value));
1366
1407
  }
1367
1408
  catch {
1368
- throw new Error("Value must be a valid URL");
1409
+ throw new Error(this.#translate("IsUrl"));
1369
1410
  }
1370
1411
  }
1371
- #validateIsWord(value, context) {
1412
+ #validateIsWord(value, _context) {
1372
1413
  if (value == null || value === "")
1373
1414
  return;
1374
1415
  const wordRegex = /^\w+$/;
1375
1416
  if (!wordRegex.test(String(value)))
1376
- throw new Error("Value must contain only word characters");
1417
+ throw new Error(this.#translate("IsWord"));
1377
1418
  }
1378
- #validateMaxLength(value, context, maxLength) {
1419
+ #validateMaxLength(value, _context, maxLength) {
1379
1420
  if (value == null || value === "")
1380
1421
  return;
1381
1422
  const length = String(value).length;
1382
1423
  if (length > maxLength)
1383
- throw new Error(`Maximum length is ${maxLength} characters`);
1424
+ throw new Error(this.#translate("MaxLength", maxLength));
1384
1425
  }
1385
- #validateMaxValue(value, context, maximum) {
1426
+ #validateMaxValue(value, _context, maximum) {
1386
1427
  if (value == null || value === "")
1387
1428
  return;
1388
1429
  const num = Number(value);
1389
1430
  if (isNaN(num))
1390
1431
  throw new Error("Value must be a number");
1391
1432
  if (num > maximum)
1392
- throw new Error(`Maximum value is ${maximum}`);
1433
+ throw new Error(this.#translate("MaxValue", maximum));
1393
1434
  }
1394
- #validateMinLength(value, context, minLength) {
1435
+ #validateMinLength(value, _context, minLength) {
1395
1436
  if (value == null || value === "")
1396
1437
  return;
1397
1438
  const length = String(value).length;
1398
1439
  if (length < minLength)
1399
- throw new Error(`Minimum length is ${minLength} characters`);
1440
+ throw new Error(this.#translate("MinLength", minLength));
1400
1441
  }
1401
- #validateMinValue(value, context, minimum) {
1442
+ #validateMinValue(value, _context, minimum) {
1402
1443
  if (value == null || value === "")
1403
1444
  return;
1404
1445
  const num = Number(value);
1405
1446
  if (isNaN(num))
1406
1447
  throw new Error("Value must be a number");
1407
1448
  if (num < minimum)
1408
- throw new Error(`Minimum value is ${minimum}`);
1449
+ throw new Error(this.#translate("MinValue", minimum));
1409
1450
  }
1410
- #validateNotEmpty(value, context) {
1451
+ #validateRequired(value, _context) {
1452
+ if (value == null)
1453
+ throw new Error(this.#translate("Required"));
1454
+ }
1455
+ #validateNotEmpty(value, _context) {
1411
1456
  if (value == null || value === "" || (typeof value === "string" && value.trim() === ""))
1412
- throw new Error("This field is required");
1457
+ throw new Error(this.#translate("NotEmpty"));
1413
1458
  }
1414
1459
  }
1415
1460
 
@@ -1445,6 +1490,12 @@ class VirtualServiceHooks extends ServiceHooks {
1445
1490
  this.#actionDefinitions.set("Save", { name: "Save", displayName: "Save", isPinned: false });
1446
1491
  this.#actionDefinitions.set("SelectReference", { name: "SelectReference", displayName: "Select", isPinned: false });
1447
1492
  }
1493
+ /**
1494
+ * Sets the translation function for system messages
1495
+ */
1496
+ setTranslate(translate) {
1497
+ this.#validator.setTranslate(translate);
1498
+ }
1448
1499
  /**
1449
1500
  * Intercepts fetch requests and routes them to virtual handlers
1450
1501
  */
@@ -1953,7 +2004,7 @@ class VirtualService extends Service {
1953
2004
  #isInitialized = false;
1954
2005
  /**
1955
2006
  * Creates a new VirtualService instance.
1956
- * @param hooks - Optional custom VirtualServiceHooks. If not provided, a default instance is created.
2007
+ * @param hooks - Optional custom hooks instance.
1957
2008
  */
1958
2009
  constructor(hooks) {
1959
2010
  super("http://virtual.local", hooks ?? new VirtualServiceHooks(), true);
@@ -2020,6 +2071,16 @@ class VirtualService extends Service {
2020
2071
  this.#ensureNotInitialized();
2021
2072
  this.virtualHooks.registerPersistentObjectActions(type, ActionsClass);
2022
2073
  }
2074
+ /**
2075
+ * Registers a message translator for translating system messages.
2076
+ * Must be called before initialize().
2077
+ * @param translate - The translation function.
2078
+ * @throws Error if called after initialize().
2079
+ */
2080
+ registerMessageTranslator(translate) {
2081
+ this.#ensureNotInitialized();
2082
+ this.virtualHooks.setTranslate(translate);
2083
+ }
2023
2084
  /**
2024
2085
  * Throws an error if the service has already been initialized.
2025
2086
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vidyano-labs/virtual-service",
3
- "version": "0.1.1",
3
+ "version": "0.3.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": "c69d76d2dd9e0289838ca50f1cc89a81fa9a89c3"
23
+ "gitHash": "db4cd7380e2b729aedf28a81ab348823aa338c4b"
24
24
  }