justus 0.0.0-alpha.0 → 0.0.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.
Files changed (61) hide show
  1. package/README.md +601 -6
  2. package/dist/index.cjs +747 -0
  3. package/dist/index.cjs.map +6 -0
  4. package/dist/index.d.ts +504 -7
  5. package/dist/index.mjs +697 -0
  6. package/dist/index.mjs.map +6 -0
  7. package/package.json +19 -7
  8. package/src/errors.ts +126 -0
  9. package/src/index.ts +48 -6
  10. package/src/schema.ts +112 -0
  11. package/src/types.ts +298 -0
  12. package/src/utilities.ts +49 -0
  13. package/src/validators/any.ts +11 -0
  14. package/src/validators/array.ts +108 -0
  15. package/src/validators/boolean.ts +13 -0
  16. package/src/validators/constant.ts +25 -0
  17. package/src/validators/date.ts +78 -0
  18. package/src/validators/number.ts +131 -0
  19. package/src/validators/object.ts +126 -0
  20. package/src/validators/string.ts +70 -0
  21. package/src/validators/tuple.ts +86 -0
  22. package/src/validators/union.ts +81 -0
  23. package/dist/arrays.d.ts +0 -30
  24. package/dist/arrays.d.ts.map +0 -1
  25. package/dist/arrays.js +0 -39
  26. package/dist/arrays.js.map +0 -1
  27. package/dist/basics.d.ts +0 -32
  28. package/dist/basics.d.ts.map +0 -1
  29. package/dist/basics.js +0 -58
  30. package/dist/basics.js.map +0 -1
  31. package/dist/index.d.ts.map +0 -1
  32. package/dist/index.js +0 -29
  33. package/dist/index.js.map +0 -1
  34. package/dist/objects.d.ts +0 -4
  35. package/dist/objects.d.ts.map +0 -1
  36. package/dist/objects.js +0 -13
  37. package/dist/objects.js.map +0 -1
  38. package/dist/primitives.d.ts +0 -41
  39. package/dist/primitives.d.ts.map +0 -1
  40. package/dist/primitives.js +0 -16
  41. package/dist/primitives.js.map +0 -1
  42. package/dist/schemas.d.ts +0 -60
  43. package/dist/schemas.d.ts.map +0 -1
  44. package/dist/schemas.js +0 -24
  45. package/dist/schemas.js.map +0 -1
  46. package/dist/unions.d.ts +0 -19
  47. package/dist/unions.d.ts.map +0 -1
  48. package/dist/unions.js +0 -24
  49. package/dist/unions.js.map +0 -1
  50. package/dist/utils.d.ts +0 -5
  51. package/dist/utils.d.ts.map +0 -1
  52. package/dist/utils.js +0 -28
  53. package/dist/utils.js.map +0 -1
  54. package/src/arrays.ts +0 -55
  55. package/src/basics.ts +0 -89
  56. package/src/objects.ts +0 -16
  57. package/src/primitives.ts +0 -62
  58. package/src/schemas.ts +0 -166
  59. package/src/unions.ts +0 -112
  60. package/src/utils.ts +0 -24
  61. package/tsconfig.json +0 -17
package/README.md CHANGED
@@ -7,19 +7,614 @@ JUSTUS
7
7
 
8
8
  > _"**justus**"_ (latin, adj.): _"proper"_ or _"correct"_.
9
9
 
10
- Justus is a very simple library for _validating_ JavaScript objects and
10
+ JUSTUS is a very simple library for _validating_ JavaScript objects and
11
11
  properly _annotating_ them with TypeScript types.
12
12
 
13
13
  It focuses in providing an _easy_ and _terse_ syntax to define a simple schema,
14
14
  used to ensure that an object is _**correct**_ and from which _**proper**_
15
15
  typing can be inferred.
16
16
 
17
+ * [Quick Start](#quick-start)
18
+ * Validators
19
+ * [Strings](#string-validator)
20
+ * [Numbers](#number-validator)
21
+ * [Booleans](#boolean-validator)
22
+ * [Constants](#constant-validator)
23
+ * [Any](#any-validator)
24
+ * [Arrays](#array-validator)
25
+ * [Dates](#date-validator)
26
+ * [Tuples](#tuple-validator)
27
+ * [Objects](#object-validator) (yes, this is the important one!!!)
28
+ * [Any of, all of](#union-validators)
29
+ * [A (slightly more) complex example](#a-complex-example)
30
+ * [Copyright Notice](NOTICE.md)
31
+ * [License](LICENSE.md)
32
+
33
+
34
+ Quick Start
35
+ -----------
36
+
37
+ You can use JUSTUS in your projects quite simply: import, write a schema and
38
+ validate. For example:
39
+
40
+ ```typescript
41
+ import { validate, object, string, number } from 'justus'
42
+
43
+ // Create a validator, validating _objects_ with a specific schema
44
+ const validator = object({
45
+
46
+ // The "foo" property in the objects to validate must be a "string"
47
+ // with a minimum length of one character
48
+ foo: string({ minLength: 1 }),
49
+
50
+ // The "bar" property in the objects to validate must be a "number"
51
+ bar: number,
52
+
53
+ // Always use `as const`: it correctly infers types for constants, tuples, ...
54
+ } as const)
55
+
56
+ // Use the validator to validate the object specified as the second argument
57
+ const validated = validate(validator, { foo: 'xyz', bar: 123 })
58
+
59
+ validated.foo // <-- its type will be "string"
60
+ validated.bar // <-- its type will be "number"
61
+ ```
17
62
 
18
- Work In Progress
63
+ Easy, terse, ultimately very readable... And all types are inferred!
64
+
65
+ #### Shorthand syntax
66
+
67
+ The `validate` function (or anywhere a _validation_ is needed) can accept a
68
+ _shorthand_ inline syntax. From our example above:
69
+
70
+ ```typescript
71
+ const validated = validate({
72
+ foo: string({ minLength: 1 }),
73
+ bar: number,
74
+ } as const, {
75
+ foo: 'xyz',
76
+ bar: 123,
77
+ })
78
+ ```
79
+
80
+ ... you get the drill! See below in each _validator_ for their shorthand syntax.
81
+
82
+
83
+ String Validator
19
84
  ----------------
20
85
 
21
- Please note that this repo is currently a work-in-progress. Don't expect
22
- anything to work.
86
+ String validators are created using the `string` function:
23
87
 
24
- * [Copyright Notice](NOTICE.md)
25
- * [License](LICENSE.md)
88
+ ```typescript
89
+ import { string } from 'justus'
90
+
91
+ const s1 = string() // validates any string
92
+ const s2 = string({ minLength: 1 }) // validate non empty strings
93
+ ```
94
+
95
+ #### Options
96
+
97
+ * `minLength?: number`: The _minimum_ length of a valid `string`
98
+ * `maxLength?: number`: The _maximum_ length of a valid `string`
99
+ * `pattern?: RegExp`: A `RegExp` enforcing a particular pattern for a valid `string`
100
+
101
+ #### Branded strings
102
+
103
+ Type _branding_ can be used for string primitives. For example:
104
+
105
+ ```typescript
106
+ import { string } from 'justus'
107
+
108
+ type UUID = string & { __brand_uuid: never }
109
+
110
+ const uuidValidator = string<UUID>({
111
+ pattern: /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/,
112
+ minLength: 36,
113
+ maxLength: 36,
114
+ })
115
+
116
+ const value = validate(uuidValidator, 'C274773D-1444-41E1-9D3A-9F9D584FE8B5')
117
+
118
+ value = 'foo' // <- will fail, as "foo" is a `string`, while "value" is a `UUID`
119
+ ```
120
+
121
+ #### Shorthand syntax
122
+
123
+ The shorthand syntax for string validators is simply `string`. For example:
124
+
125
+ ```typescript
126
+ import { string } from 'justus'
127
+
128
+ const validator = object({
129
+ foo: string // yep, no parenthesis, just "string"
130
+ })
131
+ ```
132
+
133
+
134
+ Number Validator
135
+ ----------------
136
+
137
+ Number validators are created using the `number` function:
138
+
139
+ ```typescript
140
+ import { number } from 'justus'
141
+
142
+ const n1 = number() // validates any number
143
+ const n2 = number({ minimum: 123 }) // validate numbers 123 and greater
144
+ ```
145
+
146
+ #### Options
147
+
148
+ * `multipleOf?: number`: The value for which a `number` must be multiple of for it to be valid
149
+ * `maximum?: number`: The _inclusive_ maximum value for a valid `number`
150
+ * `minimum?: number`: The _inclusive_ minimum value for a valid `number`
151
+ * `exclusiveMaximum?: number`: The _exclusive_ maximum value for a valid `number`
152
+ * `exclusiveMinimum?: number`: The _exclusive_ minimum value for a valid `number`
153
+ * `allowNaN?: boolean`: Whether to allow `NaN` or not (default: `false`)
154
+
155
+ #### Branded numbers
156
+
157
+ Type _branding_ can be used for number primitives. For example:
158
+
159
+ ```typescript
160
+ import { number } from 'justus'
161
+
162
+ type Price = string & { __brand_price: never }
163
+
164
+ const priceValidator = number<Price>({
165
+ multipleOf: 0.01, // cents, anyone? :-)
166
+ minimum: 0, // no negative prices, those are _discounts_
167
+ })
168
+
169
+ const value = validate(priceValidator, 123.45)
170
+
171
+ value = 432 // <- will fail, as 432 is a `number`, while "value" is a `Price`
172
+ ```
173
+
174
+ #### Shorthand syntax
175
+
176
+ The shorthand syntax for number validators is simply `number`. For example:
177
+
178
+ ```typescript
179
+ import { number } from 'justus'
180
+
181
+ const validator = object({
182
+ foo: number // yep, no parenthesis, just "number"
183
+ })
184
+ ```
185
+
186
+
187
+ Boolean Validator
188
+ -----------------
189
+
190
+ The boolean validator is represented by the `boolean` constant:
191
+
192
+ ```typescript
193
+ import { boolean, object } from 'justus'
194
+
195
+ const validator = object({
196
+ foo: boolean // it's a constant, no options!
197
+ })
198
+ ```
199
+
200
+
201
+ Constant Validator
202
+ ------------------
203
+
204
+ Cosntant validators are created using the `constant` function:
205
+
206
+ ```typescript
207
+ import { constant } from 'justus'
208
+
209
+ const c1 = constant('foo') // validates the `string` constant "foo"
210
+ const c2 = constant(12345) // validates the `number` constant 12345
211
+ const c3 = constant(false) // validates the `boolean` constant `false`
212
+ const c4 = constant(null) // validates the `null` constant
213
+ ```
214
+
215
+ The constant validator requires a `string`, `number`, `boolean` or `null`
216
+ constant.
217
+
218
+ #### Shorthand syntax
219
+
220
+ The shorthand syntax for constant validators is simply its value. For example:
221
+
222
+ ```typescript
223
+ import { object, validate } from 'justus'
224
+
225
+ const validator = object({
226
+ foo: 'foo', // the string "foo"
227
+ bar: 12345, // the number 12345
228
+ baz: false, // the boolean false
229
+ nil: null, // the null constant
230
+ } as const) // yep, don't forget "as const" to infer types correctly
231
+
232
+ const result = validate(validator, something)
233
+
234
+ result.foo // <- its type will be `"foo"` (or "string" if you didn't use "as const")
235
+ result.bar // <- its type will be `12345` (or "number" if you didn't use "as const")
236
+ result.baz // <- its type will be `false` (or "boolean" if you didn't use "as const")
237
+ result.nil // <- its type will be `null` (or "any" if you didn't use "as const")
238
+ ```
239
+
240
+
241
+ Any Validator
242
+ -------------
243
+
244
+ The _any_ validator is represented by the `any` constant:
245
+
246
+ ```typescript
247
+ import { any, object, validate } from 'justus'
248
+
249
+ const validator = object({
250
+ foo: any // it's a constant, no options!
251
+ })
252
+
253
+ const result = validate(validator, something)
254
+
255
+ result.foo // <- its type will be `any`
256
+ ```
257
+
258
+
259
+ Array Validator
260
+ ---------------
261
+
262
+ Array validators are created using the `array` or `arrayOf` functions:
263
+
264
+ ```typescript
265
+ import { array, arrayOf, number, string } from 'justus'
266
+
267
+ const a1 = array() // validates any array
268
+ const a2 = string({ maxItems: 10, items: string }) // array of strings
269
+ const a3 = arrayOf(number) // array of numbers
270
+ ```
271
+
272
+ #### Options
273
+
274
+ * `maxItems?: number`: The _maximum_ number of elements a valid `Array`
275
+ * `minItems?: number`: The _minimum_ number of elements a valid `Array`
276
+ * `uniqueItems?: boolean`: Indicates if the `Array`'s elements must be unique
277
+ * `items?: V`: The _type_ of each individual item in the `Array` */
278
+
279
+ #### Shorthand syntax
280
+
281
+ The shorthand syntax for string validators is simply `array`. For example:
282
+
283
+ ```typescript
284
+ import { array } from 'justus'
285
+
286
+ const validator = object({
287
+ foo: array // validate any array, of any length, containing anything
288
+ })
289
+ ```
290
+
291
+ The `arrayOf` function can also be considered a _shorthand_ of the full
292
+ `array({ items: ... })`. For example the two following declarations are
293
+ equivalent:
294
+
295
+ ```typescript
296
+ import { array, arrayOf } from 'justus'
297
+
298
+ const a1 = array({ items: string })
299
+ const a2 = arrayOf(string) // same as above, just more readable
300
+ ```
301
+
302
+
303
+ Date Validator
304
+ --------------
305
+
306
+ Date validators are created using the `date` function:
307
+
308
+ ```typescript
309
+ import { date } from 'justus'
310
+
311
+ const d1 = date() // validates any date
312
+ const d2 = date({ format: 'iso' }) // validate ISO dates
313
+ ```
314
+
315
+ > **NOTE:** Date validators also _convert_ dates (in string format), or
316
+ > timestamps (milliseconds from the epoch) into proper `Date` instances.
317
+
318
+ #### Options
319
+
320
+ * `format?: 'iso' | 'timestamp'`: The format for dates, `iso` for _ISO Dates_
321
+ (as outlined in RFC 3339) or `timestamp` for the number of milliseconds since
322
+ the epoch
323
+ * `from?: Date`: The earliest value a date can have
324
+ * `until?: Date`: The latest value a date can have
325
+
326
+ #### Shorthand syntax
327
+
328
+ The shorthand syntax for number validators is simply `date`. For example:
329
+
330
+ ```typescript
331
+ import { date } from 'justus'
332
+
333
+ const validator = object({
334
+ foo: date // anything that can be converted to `Date` will be!
335
+ })
336
+ ```
337
+
338
+
339
+ Tuple Validator
340
+ ---------------
341
+
342
+ A _tuple_ is (by definition) _a finite ordered list (sequence) of elements_.
343
+
344
+ Tuple validators are created using the `tuple` function:
345
+
346
+ ```typescript
347
+ import { tuple, string, number, boolean } from 'justus'
348
+
349
+ // Validates 3 elements tuple: (in order) a string, a number and a boolean
350
+ const t1 = tuple([ string, number, boolean ])
351
+
352
+ // Validates a tuple whose first element is a string, followed by zero or more
353
+ // numbers, and wholse last element is a boolean
354
+ const t2 = tuple([ string, ...number, boolean ]) // yay! rest parameters!
355
+ ```
356
+
357
+ As shown above, any `Validator` (or one of its shorthands) can be used as a
358
+ _rest parameter_ implying zero or more elements of the specified kind.
359
+
360
+ A more complext example:
361
+
362
+ ```typescript
363
+ import { tuple, string, number, object } from 'justus'
364
+
365
+ const myObject = object({
366
+ version: number,
367
+ title: string,
368
+ })
369
+
370
+ // This is the silliest tuple ever written, but outlines our intentions:
371
+ const sillyTuple = tuple([ 'start', ...myObject, 'end' ] as const)
372
+
373
+ // Validate using our tuple:
374
+ validate(tuple, [
375
+ 'start', // yep, a constant
376
+ { version: 1, title: 'Hello world' },
377
+ { version: 2, title: 'Foo, bar and baz' },
378
+ 'end', // the last
379
+ ])
380
+ ```
381
+
382
+
383
+ Object Validator
384
+ ----------------
385
+
386
+ As seen in the examples above, object validators are created using the
387
+ `object` function:
388
+
389
+ ```typescript
390
+ import { object, string, number, boolean } from 'justus'
391
+
392
+ const o1 = object() // any object (excluding null - darn JavaScript)
393
+ const o2 = object({
394
+ foo: string, // any string
395
+ bar: number, // any number
396
+ baz: 'Hello, world!', // the constant "Hello, world!"
397
+ } as const)
398
+ ```
399
+
400
+ #### Shorthand syntax
401
+
402
+ The shorthand syntax for object validators is simply `object`. For example:
403
+
404
+ ```typescript
405
+ import { arrayOf, object } from 'justus'
406
+
407
+ const validator = arrayOf(object) // won't validate if the array has numbers, strings, ...
408
+ ```
409
+
410
+ #### Allow additional properties
411
+
412
+ Sometimes it's necessary to allow additional properties in an object.
413
+
414
+ Destructuring `...allowAdditionalProperties` in an objects does the trick!
415
+
416
+ ```typescript
417
+ import { object, string, number, boolean } from 'justus'
418
+
419
+ const o1 = object({
420
+ foo: string, // any string
421
+ bar: number, // any number
422
+ ...allowAdditionalProperties, // any other key will be "any"
423
+ })
424
+
425
+ const result1 = validate(o1, ... some object ...)
426
+
427
+ result1.foo // <-- this will be a "string"
428
+ result1.bar // <-- this will be a "number"
429
+ result1.baz // <-- additional property, this will be "any"
430
+
431
+ // additional properties with a type
432
+
433
+ const o2 = object({
434
+ foo: string, // any string
435
+ bar: number, // any number
436
+ ...allowAdditionalProperties(boolean), // any other key will be "boolean"
437
+ })
438
+
439
+ const result2 = validate(o2, ... some object ...)
440
+
441
+ result2.foo // <-- this will be a "string"
442
+ result2.bar // <-- this will be a "number"
443
+ result2.baz // <-- additional property, this will be "boolean"
444
+
445
+ ```
446
+
447
+ Here `allowAdditionalProperties` is also a function, which can take some
448
+ parameters to configure its behaviour:
449
+
450
+ * `...allowAdditionalProperties`: default shorthand, allows additional
451
+ properties and will infer the `any` type for them.
452
+ * `...allowAdditionalProperties()`: as a function, and same as above, it allows
453
+ additional properties and will infer the `any` type for them.
454
+ * `...allowAdditionalProperties(true)`: as a function, and same as above, it
455
+ allows additional properties and will infer the `any` type for them.
456
+ * `...allowAdditionalProperties(false)`: as a function, it _forbids_ any
457
+ additional property in objects, useful when extending objects (see below)
458
+ * `...allowAdditionalProperties(... type ...)`: as a function, it allows
459
+ additional properties in objects and ensures their type is correct
460
+
461
+ #### Extending objects
462
+
463
+ Simply destructure one into another. For example:
464
+
465
+ ```typescript
466
+ import { object, string, number, boolean } from 'justus'
467
+
468
+ const o1 = object({
469
+ foo: string, // any string
470
+ bar: number, // any number
471
+ })
472
+
473
+ const o2 = object({
474
+ ...o1, // anything part of "o1" will be here as well!
475
+ bar: boolean, // here "bar" is no longer a number, but a boolean
476
+ baz: number, // add the "baz" property as a number
477
+ } as const)
478
+ ```
479
+
480
+ A slightly more complex scenario arises when considering additional properties
481
+ in the base object, but forcedly forbidding them in an extend one.
482
+
483
+ To do so, simply override in the extended object as follows:
484
+
485
+ Simply destructure one into another. For example:
486
+
487
+ ```typescript
488
+ import { object, string, number, boolean } from 'justus'
489
+
490
+ const o1 = object({
491
+ foo: string, // any string
492
+ bar: number, // any number
493
+ ...allowAdditionalProperties(boolean), // any other property is a boolean
494
+ })
495
+
496
+ const o2 = object({
497
+ ...o1, // anything part of "o1" will be here as well!
498
+ baz: boolean, // add "baz" to "o1", forcing it to be a "boolean"
499
+ ...allowAdditionaProperties(false), // no more additional properties here!
500
+ } as const)
501
+ ```
502
+
503
+ #### Optional and read-only properties
504
+
505
+ Optional and read-only properties can also be declared in objects:
506
+
507
+ ```typescript
508
+ import { object, readonly, optional, string, number, boolean } from 'justus'
509
+
510
+ const o1 = object({
511
+ foo: string, // any string, but must be a string
512
+ bar: optional(number), // optional property as "number | undefined"
513
+ baz: readonly(boolean), // read-only property as "readonly boolean"
514
+ xxx: readonly(optional(string)) // ... guess what it'll be?
515
+ })
516
+ ```
517
+
518
+
519
+ Union Validators
520
+ ----------------
521
+
522
+ Unions (either _all_ or _any_) are defined using the `allOf` or `oneOf`
523
+ functions.
524
+
525
+ To make sure all validations pass use `allOf`:
526
+
527
+ ```typescript
528
+ import { allOf, object, string, number } from 'justus'
529
+
530
+ const o1 = object({ foo: string })
531
+ const o2 = object({ bar: number })
532
+
533
+ const result = validate(allOf(o1, o2), ... some object ...)
534
+ // result here will have the type of what's inferred by o1 _and_ o2
535
+
536
+ result.foo // <-- this is a "string"
537
+ result.bar // <-- this is a "number"
538
+
539
+ // be careful about never!
540
+ const result2 = validate(allOf(number, string), ... some primitive ...)
541
+ // obviously "result2" will be of type "never" as "number" and "string" do not match!
542
+ ```
543
+
544
+ More useful, to make sure all validations pass use `oneOf`:
545
+
546
+ ```typescript
547
+ import { oneOf, string, number } from 'justus'
548
+
549
+ const result = validate(oneOf(number, string), ... some primitive ...)
550
+
551
+ result // <-- its type will be "number | string"
552
+ ```
553
+
554
+
555
+ A complex example
556
+ -----------------
557
+
558
+ This example is not complicated at all, but it outlines the simplicity
559
+ of the syntax intended for JUSTUS.
560
+
561
+ Let's assume we have some _time series_ data, but we can expect this in a
562
+ couple of different flavors, either V1 or V2 with some subtle differences:
563
+
564
+ ```typescript
565
+ import {
566
+ arrayOf,
567
+ date,
568
+ number,
569
+ object,
570
+ oneOf,
571
+ string,
572
+ tuple,
573
+ validate,
574
+ } from '../src'
575
+
576
+ // Our V1 time-series tuple is simply a timestamp followed by a numeric value
577
+ const entryv1 = tuple([ date, number ] as const)
578
+
579
+ // Our V1 response from the time series database declares the version and data
580
+ const responsev1 = object({
581
+ version: 1,
582
+ entries: arrayOf(entryv1),
583
+ } as const)
584
+
585
+ // Our V2 time-series tuple is a timestamp, followed by a number and zero or
586
+ // more strings indicating some remarks on the measurements
587
+ const entryv2 = tuple([ date, number, ...string ] as const)
588
+
589
+ // Response for V2 is the same as V1, with some extra stuff
590
+ const responsev2 = object({
591
+ version: 2,
592
+ entries: arrayOf(entryv2),
593
+ average: number, // this is extra!
594
+ } as const)
595
+
596
+ // Our combined response is either V1 or V2
597
+ const response = oneOf(responsev1, responsev2)
598
+
599
+ // GO! Validate!
600
+ const result = validate(response, {})
601
+
602
+ if (result.version === 1) {
603
+ result.version // the type here will be the literal number 1
604
+ result.entries.forEach((entry) => {
605
+ entry[0] // this will be a `Date` instance
606
+ entry[1] // this will be a "number"
607
+ // entry[2] // will generate a typescript error
608
+ })
609
+ // response.average // will generate a typescript error
610
+ } else {
611
+ result.version // the type here will be the literal number 2
612
+ result.entries.forEach((entry) => {
613
+ entry[0] // this will be a `Date` instance
614
+ entry[1] // this will be a "number"
615
+ entry[2] // this will be a "string"
616
+ entry[999] // this too will be a "string"
617
+ })
618
+ result.average // this will be a "number""
619
+ }
620
+ ```