@decaf-ts/decorator-validation 1.11.16 → 1.11.18

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 (40) hide show
  1. package/README.md +72 -421
  2. package/dist/decorator-validation.cjs +1 -1
  3. package/dist/decorator-validation.cjs.map +1 -1
  4. package/dist/decorator-validation.js +1 -1
  5. package/dist/decorator-validation.js.map +1 -1
  6. package/lib/constants/errors.cjs +6 -4
  7. package/lib/constants/errors.d.ts +6 -0
  8. package/lib/constants/errors.js.map +1 -1
  9. package/lib/constants/validation.cjs +9 -2
  10. package/lib/constants/validation.d.ts +9 -2
  11. package/lib/constants/validation.js.map +1 -1
  12. package/lib/esm/constants/errors.d.ts +6 -0
  13. package/lib/esm/constants/errors.js +6 -4
  14. package/lib/esm/constants/errors.js.map +1 -1
  15. package/lib/esm/constants/validation.d.ts +9 -2
  16. package/lib/esm/constants/validation.js +9 -2
  17. package/lib/esm/constants/validation.js.map +1 -1
  18. package/lib/esm/index.d.ts +1 -1
  19. package/lib/esm/index.js +1 -1
  20. package/lib/esm/model/decorators.d.ts +8 -0
  21. package/lib/esm/model/decorators.js +8 -0
  22. package/lib/esm/model/decorators.js.map +1 -1
  23. package/lib/esm/overrides/types.d.ts +8 -0
  24. package/lib/esm/validation/decorators.js +1 -1
  25. package/lib/esm/validation/decorators.js.map +1 -1
  26. package/lib/esm/validation/index.d.ts +5 -0
  27. package/lib/esm/validation/index.js +5 -0
  28. package/lib/esm/validation/index.js.map +1 -1
  29. package/lib/index.cjs +1 -1
  30. package/lib/index.d.ts +1 -1
  31. package/lib/model/decorators.cjs +8 -0
  32. package/lib/model/decorators.d.ts +8 -0
  33. package/lib/model/decorators.js.map +1 -1
  34. package/lib/overrides/types.d.ts +8 -0
  35. package/lib/validation/decorators.cjs +1 -1
  36. package/lib/validation/decorators.js.map +1 -1
  37. package/lib/validation/index.cjs +5 -0
  38. package/lib/validation/index.d.ts +5 -0
  39. package/lib/validation/index.js.map +1 -1
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -10,6 +10,14 @@ A TypeScript decorator-driven validation and model framework. It lets you:
10
10
 
11
11
  > Release docs refreshed on 2025-11-26. See [workdocs/reports/RELEASE_NOTES.md](./workdocs/reports/RELEASE_NOTES.md) for ticket summaries.
12
12
 
13
+ ### Core Concepts
14
+
15
+ * **`@model()`**: A class decorator that turns a regular class into a validatable model.
16
+ * **Validation Decorators**: A rich set of decorators for defining validation rules on properties (e.g., `@required`, `@email`, `@minLength`).
17
+ * **`Model` Class**: An abstract base class that provides validation, serialization, hashing, and comparison methods.
18
+ * **`Validation` Class**: A static class for managing the validator registry and creating custom validation logic.
19
+ * **Builders and Registries**: Pluggable systems for controlling how models are constructed, serialized, and hashed.
20
+
13
21
  ![Licence](https://img.shields.io/github/license/decaf-ts/decorator-validation.svg?style=plastic)
14
22
  ![GitHub language count](https://img.shields.io/github/languages/count/decaf-ts/decorator-validation?style=plastic)
15
23
  ![GitHub top language](https://img.shields.io/github/languages/top/decaf-ts/decorator-validation?style=plastic)
@@ -80,498 +88,141 @@ Design principles
80
88
  - Separation of concerns: Decorators express intent; Validator classes implement behavior; Model provides lifecycle utilities.
81
89
 
82
90
 
83
- # How to Use Decorator Validation
84
-
85
- This guide shows concrete, TypeScript-accurate examples for the main public APIs exported by the library. Examples are inspired by patterns used across the repo tests and typical usage of decorator-driven validation and models.
86
-
87
- Notes
88
- - Import paths assume a consumer importing from the package entry and submodules as re-exported by src/index.ts.
89
- - All snippets are valid TypeScript.
90
-
91
-
92
- ## Model decorators and model lifecycle
93
-
94
- ### @model()
95
- Description: Marks a class as a model. The constructor is wrapped to bind model utilities and run a global builder (if any).
96
-
97
- ```typescript
98
- import { model, Model, ModelArg } from "@decaf-ts/decorator-validation";
99
-
100
- @model()
101
- class User extends Model {
102
- @prop()
103
- id!: string;
104
- @prop()
105
- name!: string;
106
-
107
- constructor(arg?: ModelArg<User>) {
108
- super(arg);
109
- }
110
- }
111
-
112
- const u = new User();
113
- ```
114
-
115
- Extending from the Model class is optional but highly recommended. Regardless, if decorated with `@model()`, the constructor signature must be compatible.
116
-
117
- When using a class for validation, eg `hasErrors()` only, @model() is not required.
118
-
119
- #### Model construction
120
-
121
- When a class is decorated with @model(), the framework invokes a model building function right after the instance is constructed. This builder initializes your model from the argument you pass to the constructor.
122
-
123
- There are two builder strategies:
124
- - Model.fromObject: Accepts a plain object and performs a more permissive, best-effort mapping.
125
- - Does not enforce nested Model classes
126
- - Model.fromModel (default): does the same as fromObject, but also instantiates nested Model classes
127
-
128
- Your model constructors should accept an optional argument and pass it to super so the builder can use it.
129
-
130
- You can change between builder functions by using:
131
-
132
- ```typescript
133
- import { required, Model, model, ModelArg } from "@decaf-ts/decorator-validation";
134
-
135
- @model()
136
- class Child extends Model {
137
- @required()
138
- name!: string;
139
-
140
- constructor(arg?: ModelArg<Child>) {
141
- super(arg);
142
- }
143
- }
144
-
145
- @model()
146
- class Parent extends Model {
147
- @required()
148
- name!: string;
149
- @required()
150
- child!: Child;
151
- constructor(arg?: ModelArg<Parent>) {
152
- super(arg);
153
- }
154
- }
155
- // Default builder is Model.fromModel
156
-
157
- let parent = new Parent({
158
- child: {
159
- name: "child"
160
- }
161
- })
162
-
163
- parent.child instanceof Child; // true
164
-
165
- Model.setBuilder(Model.fromObject);
166
-
167
- parent = new Parent({
168
- child: {
169
- name: "child"
170
- }
171
- })
172
-
173
- parent.child instanceof Child; // false
174
- ```
175
-
176
- ### @hashedBy(algorithm, ...args)
177
- Description: Declares which hashing strategy to use when hashing model instances.
91
+ # How to Use
178
92
 
179
- ```typescript
180
- import { model, hashedBy, ModelArg, prop } from "@decaf-ts/decorator-validation";
93
+ This guide provides examples of how to use the main features of the `@decaf-ts/decorator-validation` library.
181
94
 
182
- @model()
183
- @hashedBy("sha256", "utf8")
184
- class FileInfo extends Model {
185
- @prop()
186
- path!: string;
187
- @prop()
188
- size!: number;
189
-
190
- constructor(arg?: ModelArg<FileInfo>) {
191
- super(arg)
192
- }
193
- }
194
- ```
95
+ ## Creating a Model
195
96
 
196
- ### @serializedBy(serializer, ...args)
197
- Description: Declares which serializer to use for (de)serializing model instances.
97
+ The `@model()` decorator is the entry point for creating a validatable model.
198
98
 
199
99
  ```typescript
200
- import { prop, ModelArg, model, serializedBy } from "@decaf-ts/decorator-validation";
100
+ import { model, Model } from '@decaf-ts/decorator-validation';
201
101
 
202
102
  @model()
203
- @serializedBy("json")
204
- class Settings extends Model {
205
- @prop()
206
- theme!: string;
207
- @prop()
208
- locale!: string;
209
-
210
- constructor(arg?: ModelArg<Settings>){
211
- super(arg)
212
- }
213
- }
214
- ```
215
-
216
- ### @description(text)
217
- Description: Applies textual documentation metadata to a class, property, or method.
218
-
219
- ```typescript
220
- import { model, description, Model } from "@decaf-ts/decorator-validation";
221
-
222
- @model()
223
- @description("Represents an application user")
224
103
  class User extends Model {
225
- @description("Unique identifier")
226
- id!: string;
227
104
  // ...
228
105
  }
229
106
  ```
230
107
 
108
+ ## Validation Decorators
231
109
 
232
- ## Validation decorators (property-level)
233
- Each decorator writes metadata for a corresponding Validator. Use them on model fields.
234
-
235
-
236
- ### @prop()
237
- Description: Registers a property with the model system. This is required for model construction so the property participates in metadata, serialization, hashing, validation discovery, etc.
238
-
239
- Important
240
- - All other property decorators (e.g., @required, @min, @email, etc.) already apply @prop under the hood.
241
- - Therefore, you only need to use @prop when a property has no other decorators.
242
-
243
-
244
- ### @required(message?)
245
- Description: Field must be present and non-empty.
110
+ The library provides a rich set of decorators for defining validation rules on properties.
246
111
 
247
112
  ```typescript
248
- import { Model, required, model } from "@decaf-ts/decorator-validation";
113
+ import { model, Model, required, email, minLength } from '@decaf-ts/decorator-validation';
249
114
 
250
115
  @model()
251
116
  class User extends Model {
252
117
  @required()
253
- username!: string;
254
- //...
255
- }
256
- ```
257
-
258
- ### @min(value, message?) and @max(value, message?)
259
- Description: Numeric or date boundaries.
260
-
261
- ```typescript
262
- import { model } from "@decaf-ts/decorator-validation";
263
- import { min } from "@decaf-ts/decorator-validation";
264
- import { max } from "@decaf-ts/decorator-validation";
265
-
266
- @model()
267
- class Product extends Model {
268
- @min(0)
269
- @max(1000)
270
- price!: number;
271
- }
272
- ```
273
-
274
- ### @minLength(n, message?) and @maxLength(n, message?)
275
- Description: String length boundaries.
276
-
277
- ```typescript
278
- import { model, Model } from "@decaf-ts/decorator-validation";
279
- import { minLength, maxLength } from "@decaf-ts/decorator-validation";
280
-
281
- @model()
282
- class Credentials extends Model {
283
- @minLength(8)
284
- @maxLength(64)
285
- password!: string;
286
- }
287
- ```
288
-
289
- ### @pattern(regex | string, message?)
290
- Description: String must match a pattern.
291
-
292
- ```typescript
293
- import { model } from "@decaf-ts/decorator-validation";
294
- import { pattern, model } from "@decaf-ts/decorator-validation";
295
-
296
- @model()
297
- class Vehicle extends Model {
298
- @pattern(/^[A-Z]{2}-\d{2}-[A-Z]{2}$/)
299
- plate!: string;
300
- }
301
- ```
302
-
303
- ### @email(message?)
304
- Description: Must be a valid email.
305
-
306
- ```typescript
307
- import { model } from "@decaf-ts/decorator-validation";
308
- import { email } from "@decaf-ts/decorator-validation";
118
+ name!: string;
309
119
 
310
- @model()
311
- class Contact extends Model {
120
+ @required()
312
121
  @email()
313
122
  email!: string;
314
- }
315
- ```
316
-
317
- ### @url(message?)
318
- Description: Must be a valid URL.
319
-
320
- ```typescript
321
- import { model } from "@decaf-ts/decorator-validation";
322
- import { url } from "@decaf-ts/decorator-validation";
323
123
 
324
- @model()
325
- class Link extends Model {
326
- @url()
327
- href!: string;
124
+ @minLength(8)
125
+ password!: string;
328
126
  }
329
127
  ```
330
128
 
331
- ### @type(T, message?)
332
- Description: Enforces a runtime type match (e.g., Number, String, Date).
129
+ ## Validating a Model
333
130
 
334
- ```typescript
335
- import { model } from "@decaf-ts/decorator-validation";
336
- import { type } from "@decaf-ts/decorator-validation";
337
-
338
- @model()
339
- class Measurement extends Model {
340
- @type(Number)
341
- value!: number;
342
- }
343
- ```
344
-
345
- ### @equals(otherValueOrPath, message?)
346
- Description: Value must equal the provided value or another property.
131
+ You can validate a model instance using the `hasErrors()` method.
347
132
 
348
133
  ```typescript
349
- import { model } from "@decaf-ts/decorator-validation";
350
- import { equals } from "@decaf-ts/decorator-validation";
134
+ const user = new User();
135
+ const errors = user.hasErrors();
351
136
 
352
- @model()
353
- class Confirmation extends Model {
354
- password!: string;
355
- @equals(":password")
356
- confirm!: string;
137
+ if (errors) {
138
+ console.log('Validation errors:', errors);
357
139
  }
358
140
  ```
359
141
 
360
- ### @greaterThan(x) / @greaterThanOrEqual(x) / @lessThan(x) / @lessThanOrEqual(x)
361
- Description: Numeric or date comparisons.
142
+ ### Ignoring Properties
362
143
 
363
- ```typescript
364
- import { model } from "@decaf-ts/decorator-validation";
365
- import { greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual } from "@decaf-ts/decorator-validation";
366
-
367
- @model()
368
- class Range extends Model {
369
- @greaterThan(0)
370
- @lessThanOrEqual(100)
371
- ratio!: number;
372
- }
373
- ```
374
-
375
- ### @step(step, message?)
376
- Description: Numeric step constraints.
144
+ You can ignore properties during validation by passing their names to `hasErrors()`.
377
145
 
378
146
  ```typescript
379
- import { model, Model } from "@decaf-ts/decorator-validation";
380
- import { step } from "@decaf-ts/decorator-validation";
381
-
382
- @model()
383
- class Slider extends Model {
384
- @step(0.5)
385
- value!: number;
386
- }
147
+ const user = new User();
148
+ // Ignore the 'password' property during validation
149
+ const errors = user.hasErrors('password');
387
150
  ```
388
151
 
389
- ### @list(values, message?)
390
- Description: Constrains value to be one of the provided list.
391
-
392
- ```typescript
393
- import { model, Model } from "@decaf-ts/decorator-validation";
394
- import { list } from "@decaf-ts/decorator-validation";
395
-
396
- @model()
397
- class ThemeSettingm extends Model {
398
- @list(["light", "dark", "system"])
399
- theme!: "light" | "dark" | "system";
400
- }
401
- ```
152
+ ## Serialization and Hashing
402
153
 
403
- ### @diff(propertyPath, message?)
404
- Description: Must be different from another property.
154
+ The `Model` class provides methods for serializing and hashing instances.
405
155
 
406
156
  ```typescript
407
- import { model, Model } from "@decaf-ts/decorator-validation";
408
- import { diff } from "@decaf-ts/decorator-validation";
157
+ const user = new User({ name: 'John Doe', email: 'john.doe@example.com' });
409
158
 
410
- @model()
411
- class Credentials extends Model {
412
- username!: string;
413
- @diff(":username")
414
- password!: string;
415
- }
159
+ const serializedUser = user.serialize();
160
+ const userHash = user.hash();
416
161
  ```
417
162
 
418
- ### @date({ min?, max? }, message?)
419
- Description: Date constraints for a date-typed field.
420
-
421
- ```typescript
422
- import { model, Model } from "@decaf-ts/decorator-validation";
423
- import { date } from "@decaf-ts/decorator-validation";
424
-
425
- @model()
426
- class Booking extends Model {
427
- @date({ min: new Date("2025-01-01"), max: new Date("2025-12-31") })
428
- start!: Date;
429
- }
430
- ```
163
+ ## Model Construction
431
164
 
432
- ### @password(options?, message?)
433
- Description: Password strength constraints (e.g., min length, uppercase, digits, symbols) depending on validator configuration.
165
+ The library provides a flexible system for constructing model instances.
434
166
 
435
167
  ```typescript
436
- import { model } from "@decaf-ts/decorator-validation";
437
- import { password, Model } from "@decaf-ts/decorator-validation";
438
-
439
- @model()
440
- class Account extends Model{
441
- @password({ minLength: 10 })
442
- password!: string;
443
- }
444
- ```
445
-
446
- ### async()
447
- Description: Marks a model as involving async validation rules (decorator flag helper).
168
+ import { Model } from '@decaf-ts/decorator-validation';
448
169
 
449
- ```typescript
450
- import { model } from "@decaf-ts/decorator-validation";
451
- import { async } from "@decaf-ts/decorator-validation";
170
+ // Create a new instance
171
+ const user1 = new User({ name: 'Jane Doe' });
452
172
 
453
- @model()
454
- @async()
455
- class Signup {
456
- // fields that may use async validators
457
- }
173
+ // Create an instance from a plain object
174
+ const user2 = Model.build({
175
+ [Model.ANCHOR]: 'User',
176
+ name: 'John Smith',
177
+ email: 'john.smith@example.com'
178
+ });
458
179
  ```
459
180
 
181
+ ## Custom Validators
460
182
 
461
- ## Running validation
462
- Use the model validation utilities to evaluate rules defined by decorators.
183
+ You can create custom validators by extending the `Validator` class and registering them with the `Validation` class.
463
184
 
464
185
  ```typescript
465
- import { model, required, email, validate, Model } from "@decaf-ts/decorator-validation";
186
+ import { Validator, Validation, metadata } from '@decaf-ts/decorator-validation';
466
187
 
467
- @model()
468
- class Contact extends Model {
469
- @required()
470
- @email()
471
- email!: string;
472
-
473
- constructor(arg?: ModelArg<Contact>) {
474
- super(arg);
188
+ // 1. Create a custom validator
189
+ class MyCustomValidator extends Validator {
190
+ constructor() {
191
+ super('my-custom-validator', 'Invalid value');
475
192
  }
476
- }
477
193
 
478
- const c = new Contact({
479
- email: "not-an-email"
480
- });
481
-
482
- let errs = c.hasErrors(); // resolves to a list of errors or undefined
483
-
484
- @async()
485
- @model()
486
- class User extends Model {
487
- @required()
488
- @email()
489
- email!: string;
490
-
491
- constructor(arg?: ModelArg<User>) {
492
- super(arg);
194
+ validate(value: any): boolean {
195
+ // Custom validation logic
196
+ return value === 'valid';
493
197
  }
494
198
  }
495
199
 
496
- const u = new User({
497
- email: "not-an-email"
498
- })
499
-
500
- errs = await u.hasErrors(); // resolves to a list of errors or undefined
501
-
502
- ```
503
-
504
-
505
- ## Validation registry APIs (Validation)
506
-
507
- ### Validation.setRegistry(registry, migration?)
508
- Description: Swap the active validator registry and optionally migrate validators.
509
-
510
- ```typescript
511
- import { Validation, ValidatorRegistry } from "@decaf-ts/decorator-validation";
512
-
513
- const custom = new ValidatorRegistry();
514
- Validation.setRegistry(custom, v => v); // trivial migration
515
- ```
516
-
517
- ### Validation.register(...validators)
518
- Description: Register one or more Validator implementations or definitions.
519
-
520
- ```typescript
521
- import { Validation, Validator, validator } from "@decaf-ts/decorator-validation";
200
+ // 2. Register the validator
201
+ Validation.register(new MyCustomValidator());
522
202
 
523
- @validator("ALWAYS_OK")
524
- class AlwaysOk extends Validator {
525
- hasErrors(...args: any[]) { return []; }
203
+ // 3. Create a decorator for the validator
204
+ function myCustom() {
205
+ return metadata(Validation.key('my-custom-validator'), {});
526
206
  }
527
207
 
528
- Validation.register(new AlwaysOk());
208
+ // 4. Use the decorator
209
+ @model()
210
+ class MyModel extends Model {
211
+ @myCustom()
212
+ myProp: string;
213
+ }
529
214
  ```
530
215
 
531
- ### Validation.get(key)
532
- Description: Retrieve a registered validator by key.
216
+ ## Date Operations
533
217
 
534
- ```typescript
535
- import { Validation, ValidationKeys } from "@decaf-ts/decorator-validation";
536
-
537
- const requiredValidator = Validation.get(ValidationKeys.REQUIRED);
538
- ```
539
-
540
- ### Validation.key(k) and Validation.keys()
541
- Description: Build a reflect-metadata key or list all registered keys.
218
+ The library includes a `DateBuilder` for creating and manipulating dates.
542
219
 
543
220
  ```typescript
544
- import { Validation } from "@decaf-ts/decorator-validation";
221
+ import { DateBuilder } from '@decaf-ts/decorator-validation';
545
222
 
546
- const metaKey = Validation.key("REQUIRED");
547
- const allKeys = Validation.keys();
223
+ const date = new DateBuilder().addDays(5).build();
548
224
  ```
549
225
 
550
- ## Notes on tests and validity
551
- - Patterns here reflect common test patterns found across the monorepo (e.g., model decoration, decorator application, registry customization).
552
- - Each snippet is valid TypeScript and aligns with the re-exports provided by the package entry.
553
-
554
-
555
- ## Coding Principles
556
-
557
- - group similar functionality in folders (analog to namespaces but without any namespace declaration)
558
- - one class per file;
559
- - one interface per file (unless interface is just used as a type);
560
- - group types as other interfaces in a types.ts file per folder;
561
- - group constants or enums in a constants.ts file per folder;
562
- - group decorators in a decorators.ts file per folder;
563
- - always import from the specific file, never from a folder or index file (exceptions for dependencies on other packages);
564
- - prefer the usage of established design patters where applicable:
565
- - Singleton (can be an anti-pattern. use with care);
566
- - factory;
567
- - observer;
568
- - strategy;
569
- - builder;
570
- - etc;
571
-
572
- ## Release Documentation Hooks
573
- Stay aligned with the automated release pipeline by reviewing [Release Notes](./workdocs/reports/RELEASE_NOTES.md) and [Dependencies](./workdocs/reports/DEPENDENCIES.md) after trying these recipes (updated on 2025-11-26).
574
-
575
226
 
576
227
  ### Related
577
228