@naturalcycles/nodejs-lib 15.43.0 → 15.43.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.
|
@@ -241,11 +241,14 @@ declare function object<IN extends AnyObject>(props: {
|
|
|
241
241
|
}): JsonSchemaObjectBuilder<IN, IN, false>;
|
|
242
242
|
declare function objectInfer<P extends Record<string, JsonSchemaAnyBuilder<any, any, any>>>(props: P): JsonSchemaObjectInferringBuilder<P, false>;
|
|
243
243
|
declare function objectDbEntity(props?: AnyObject): never;
|
|
244
|
-
declare function objectDbEntity<IN extends AnyObject>(props?:
|
|
244
|
+
declare function objectDbEntity<IN extends AnyObject>(props?: {
|
|
245
|
+
[key in keyof PartiallyOptional<IN, 'id' | 'created' | 'updated'>]: JsonSchemaAnyBuilder<any, IN[key], any>;
|
|
246
|
+
}): JsonSchemaObjectBuilder<IN, IN, false>;
|
|
245
247
|
type Expand<T> = T extends infer O ? {
|
|
246
248
|
[K in keyof O]: O[K];
|
|
247
249
|
} : never;
|
|
248
250
|
type ExactMatch<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
251
|
+
type PartiallyOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
249
252
|
type BuilderOutUnion<B extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
|
|
250
253
|
[K in keyof B]: B[K] extends JsonSchemaAnyBuilder<any, infer O, any> ? O : never;
|
|
251
254
|
}[number];
|
package/package.json
CHANGED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# `j`
|
|
2
|
+
|
|
3
|
+
## and Silent Bob
|
|
4
|
+
|
|
5
|
+
### validate The Data
|
|
6
|
+
|
|
7
|
+
In this document you can learn about how to use `j`, our new validation library.
|
|
8
|
+
|
|
9
|
+
### Why?
|
|
10
|
+
|
|
11
|
+
Yet another validation library. But why? Main reasons:
|
|
12
|
+
|
|
13
|
+
1. Faster validation
|
|
14
|
+
2. Better DX
|
|
15
|
+
3. Stricter type validation
|
|
16
|
+
4. New types
|
|
17
|
+
|
|
18
|
+
**Faster validation** means that we can now start validating data that we used to ignore, because
|
|
19
|
+
validating them were very-very slow. For example: OuraSleepData.
|
|
20
|
+
|
|
21
|
+
It also means that we are more prepared for the accumulation of data that will happen with our own
|
|
22
|
+
devices like B1 and R1.
|
|
23
|
+
|
|
24
|
+
**Better DX** comes from the discoverable API, which means that one does not need to remember what
|
|
25
|
+
kind of schemas we usually import or use.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const oldWay = objectSchema<SomeType>({
|
|
29
|
+
unix: unixTimestamp2000Schema(),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
// 👆 You needed to know about importing `objectSchema` and `unixTimestamp2000Schema`
|
|
33
|
+
// as opposed to... 👇
|
|
34
|
+
|
|
35
|
+
const newWay = j.object<SomeType>({
|
|
36
|
+
unix: j.number().unixTimestamp2000(),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// ... knowing to import `j`, and the rest is aided by auto-completion.
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Stricter type validation** (aka worse DX) means that the schema and the types need to match
|
|
43
|
+
exactly, unlike before where a required property could have had an optional schema.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
interface Foo {
|
|
47
|
+
foo: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const oldWay = objectSchema<Foo>({ foo: stringSchema.optional() }) // ✅ Worked
|
|
51
|
+
const newWay = j.object<Foo>({ foo: j.string().optional() }) // ❌ Does not work anymore
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
And we also have **new types** in the schema, e.g.: Buffer, Set.
|
|
55
|
+
|
|
56
|
+
The novelty is that the new types support serialization and de-serialization, i.e. you can use
|
|
57
|
+
`j.set()` and when you know that the incoming data (from Datastore or from a Request) is an array,
|
|
58
|
+
it will convert the incoming data to a Set. And the same for `j.buffer()` - should you ever need
|
|
59
|
+
that.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const schema = j.object.infer({
|
|
63
|
+
set: j.set(j.enum(DataFlags)), // accepts any Iterable input, output is Set<DataFlags> instance
|
|
64
|
+
buffer: j.buffer(), // accepts any valid input for Buffer, output is a Buffer instance
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### How to use `j` for validation?
|
|
69
|
+
|
|
70
|
+
While the API is very intuitive, there are some tips that can help with quick adoption:
|
|
71
|
+
|
|
72
|
+
1. When you want to use a specialized schema, first think about its underlying value:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const timestamp = j.number().unixTimestamp2000() // start with ".number"
|
|
76
|
+
const email = j.string().email() // start with ".string"
|
|
77
|
+
const date = j.string().isoDate() // start with ".string"
|
|
78
|
+
const dbRow = j.object.dbEntity({}) // start with ".object"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
2. Many branded values have no shortcut (on purpose), usually those that come with no actual
|
|
82
|
+
validation:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const accountId = j.string().accountId() // ❌
|
|
86
|
+
const accountId = j.string().branded<AccountId>() // ✅
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
3. Probably the most important: object schemas must have a type
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
const schema1 = j.object({ foo: j.string() }) // ❌
|
|
93
|
+
const schema2 = j.object<SomeType>({ bar: j.string(), nested: objectSchema1 }) // ✅
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
But because we do not always want to create a type or interface for every object schema, in those
|
|
97
|
+
cases it's possible to use inference via `j.object.infer()`:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const schema3 = j.object.infer({ foo: j.string() }) // { foo: string } is inferred
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
But these inferred schemas cannot be used for validation - only to be passed into other schemas. To
|
|
104
|
+
use inferred schemas in validation, you need to call `.ofType<SomeType>()` on them. If you forget,
|
|
105
|
+
there will be an error thrown when the first validation is about to happen.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const schema1 = j.object.infer({ foo: j.string() }) // ❌ Using `schema1` in validation would fail
|
|
109
|
+
|
|
110
|
+
const schema2 = j.object<SomeType>({ nestedProperty: schema1 }) // ✅ Using `schema1` inside another schema
|
|
111
|
+
|
|
112
|
+
const schema3 = j.object.infer({ foo: j.string() }).isOfType<{ foo: string }>() // ✅ Using `schema3` for validation
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This requirement is in place to enforce that we 1) have types for data that we validate, and 2) that
|
|
116
|
+
mismatches between types and schemas become visible as soon as possible.
|
|
117
|
+
|
|
118
|
+
If the typing has a mismatch, then the `schema`'s type will become `never`. When using
|
|
119
|
+
`j.object<SomeType>` the type error will be very helpful in identifying the mismatch. When using
|
|
120
|
+
`j.object.infer().isOfType()` the type error will be very unhelpful. Because of this,
|
|
121
|
+
`j.object<SomeType>` is the preferred choice.
|
|
122
|
+
|
|
123
|
+
4. In some cases you can specify a custom error message
|
|
124
|
+
|
|
125
|
+
When using regex validation, the resulting error message is generally not something we would want
|
|
126
|
+
the user to see. In many case, they are also not very helpful for developers either. So, when
|
|
127
|
+
running a regex validation, you can set a custom error message. This pattern can be extended to
|
|
128
|
+
other validator functions too, as we think it's necessary.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const schema = j.object({
|
|
132
|
+
foo: j.string().regex(/\[a-z]{2,}\d?.+/, { msg: 'not a valid OompaLoompa! ' }),
|
|
133
|
+
})
|
|
134
|
+
// will produce an error like "Object.foo is not a valid OompaLoompa!"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### More about `j`
|
|
138
|
+
|
|
139
|
+
`j` is a JSON Schema builder that is developed in-house.
|
|
140
|
+
|
|
141
|
+
The validation is done by `ajv` which stands for Another JsonSchema Validator, an insanely fast
|
|
142
|
+
validation library.
|
|
143
|
+
|
|
144
|
+
`ajv` is hidden under the hood, and developers will mostly interact with `j`.
|
|
@@ -840,13 +840,21 @@ function objectInfer<P extends Record<string, JsonSchemaAnyBuilder<any, any, any
|
|
|
840
840
|
}
|
|
841
841
|
|
|
842
842
|
function objectDbEntity(props?: AnyObject): never
|
|
843
|
-
function objectDbEntity<IN extends AnyObject>(
|
|
844
|
-
|
|
845
|
-
|
|
843
|
+
function objectDbEntity<IN extends AnyObject>(props?: {
|
|
844
|
+
[key in keyof PartiallyOptional<IN, 'id' | 'created' | 'updated'>]: JsonSchemaAnyBuilder<
|
|
845
|
+
any,
|
|
846
|
+
IN[key],
|
|
847
|
+
any
|
|
848
|
+
>
|
|
849
|
+
}): JsonSchemaObjectBuilder<IN, IN, false>
|
|
846
850
|
|
|
847
|
-
function objectDbEntity<IN extends AnyObject>(
|
|
848
|
-
|
|
849
|
-
|
|
851
|
+
function objectDbEntity<IN extends AnyObject>(props?: {
|
|
852
|
+
[key in keyof PartiallyOptional<IN, 'id' | 'created' | 'updated'>]: JsonSchemaAnyBuilder<
|
|
853
|
+
any,
|
|
854
|
+
IN[key],
|
|
855
|
+
any
|
|
856
|
+
>
|
|
857
|
+
}): JsonSchemaObjectBuilder<IN, IN, false> {
|
|
850
858
|
return j.object({
|
|
851
859
|
id: j.string(),
|
|
852
860
|
created: j.number().unixTimestamp2000(),
|
|
@@ -860,6 +868,8 @@ type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never
|
|
|
860
868
|
type ExactMatch<A, B> =
|
|
861
869
|
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
|
|
862
870
|
|
|
871
|
+
type PartiallyOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
|
|
872
|
+
|
|
863
873
|
type BuilderOutUnion<B extends readonly JsonSchemaAnyBuilder<any, any, any>[]> = {
|
|
864
874
|
[K in keyof B]: B[K] extends JsonSchemaAnyBuilder<any, infer O, any> ? O : never
|
|
865
875
|
}[number]
|