@kaliber/forms 2.1.2 → 3.0.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/src/types.ts ADDED
@@ -0,0 +1,354 @@
1
+ /**
2
+ * While type declarations should be opaque, type hinting in visual studio still shows them, this
3
+ * is used to make the types from this library, exposed to the developer more friendly.
4
+ */
5
+ export type Expand<T> =
6
+ T extends infer O ? { [K in keyof O]: O[K] } : never
7
+
8
+ /**
9
+ * Typescript will (sometimes) forget the keys are strings when using `keyof T`, so this is
10
+ * Partial for keys that we need to be strings.
11
+ */
12
+ export type PartialWithStringKey<T extends { [key: string]: any}> =
13
+ {
14
+ [K in keyof T & string]?:
15
+ T[K] extends (infer U)[] ? (
16
+ U extends object ? PartialWithStringKey<U>[] : T[K]
17
+ ) :
18
+ T[K] extends object ? (
19
+ PartialWithStringKey<T[K]>
20
+ ) :
21
+ T[K]
22
+ }
23
+
24
+ export type Falsy = false | '' | 0 | 0n | null | undefined | void
25
+
26
+ /** Equivalent of `array.map(x => x[prop])` for tuples */
27
+ export type MapTuple<T extends [...unknown[]], prop extends string> =
28
+ T extends [infer X extends { [key in prop]: unknown }, ...infer Rest]
29
+ ? [X[prop], ...MapTuple<Rest, prop>]
30
+ : []
31
+
32
+ export type ValidateToValue<R> =
33
+ R extends ValidationFunction<infer T> ? T :
34
+ R extends readonly [] | null ? unknown :
35
+ R extends readonly [ValidationFunction<infer T>] ? T :
36
+ R extends readonly [ValidationFunction<infer T>, ...infer Rest] ? T & ValidateToValue<Rest> :
37
+ never
38
+
39
+ export type Validate<T = any> =
40
+ readonly [ValidationFunction<T>, ...ValidationFunction<T>[]] |
41
+ ValidationFunction<T> |
42
+ readonly ValidationFunction<T>[] |
43
+ null
44
+ export type ValidationFunction<T = any> = (value: T, context: ValidationContext) => ValidationResult
45
+ export type ValidationResult = Falsy | ValidationError
46
+ export type ValidationError = { id: string, params?: any[] }
47
+ export type ValidationContext = { form: any, parents: Field.Object[] }
48
+
49
+ export type InitialValue<T extends FieldInput.Object> =
50
+ PartialWithStringKey<NormalizedField.ToValue<NormalizedInitialValue<T>>>
51
+
52
+ export type NormalizedInitialValue<T extends FieldInput.Object> =
53
+ NormalizedField.FromFieldSchema<FieldSchema.Object<T>>
54
+
55
+ export namespace FieldInput {
56
+
57
+ export type ArrayToValue<T extends FieldInput.Array> =
58
+ FieldSchema.ToValue<FieldSchema.Array<T>>
59
+
60
+ export type ObjectToValue<T extends FieldInput.Object> =
61
+ FieldSchema.ToValue<FieldSchema.Object<T>>
62
+
63
+ export type Object = FieldSchema.ObjectFields
64
+
65
+ export type Array = Object | HeterogeneousArrayInput
66
+ export type HeterogeneousArrayInput = (initialValue: unknown) => Object
67
+ }
68
+
69
+ export type FieldSchema =
70
+ Validate |
71
+ FieldSchema.Object |
72
+ FieldSchema.Array |
73
+ { validate: Validate }
74
+
75
+ export namespace FieldSchema {
76
+
77
+ export type ToValue<T extends FieldSchema> =
78
+ T extends Object<infer X> ? ObjectFieldsToValues<X> :
79
+ T extends Array<infer X> ? ArrayFieldsToValues<X> :
80
+ T extends { validate: infer V } ? (
81
+ V extends Validate ? ValidateToValue<V> : ValidateToValue<T>) :
82
+ ValidateToValue<T>
83
+
84
+ export type ObjectFieldsToValues<T extends ObjectFields> =
85
+ T extends any ? { [K in keyof T & string]: ToValue<T[K]> } : never
86
+
87
+ export type ArrayFieldsToValues<T extends ArrayFields> =
88
+ ObjectFieldsToValues<ExtractObjectFields<T>>[]
89
+
90
+ export type ExtractObjectFields<T extends ArrayFields> =
91
+ T extends FieldSchema.ObjectFields ? T :
92
+ T extends FieldSchema.ArrayFieldsConstructor ? ReturnType<T> :
93
+ never
94
+
95
+ export type Object<T extends ObjectFields = ObjectFields> = {
96
+ type: 'object',
97
+ fields: T,
98
+ validate?: Validate,
99
+ }
100
+ export type ObjectFields = {
101
+ [name: string]: FieldSchema
102
+ }
103
+
104
+ export type Array<T extends ArrayFields = ArrayFields> = {
105
+ type: 'array',
106
+ fields: T,
107
+ validate?: Validate,
108
+ }
109
+ export type ArrayFields = FieldSchema.ObjectFields | ArrayFieldsConstructor
110
+ export type ArrayFieldsConstructor = (initialValues: any) => FieldSchema.ObjectFields
111
+ }
112
+
113
+ export type NormalizedField = NormalizedField.Basic | NormalizedField.Object | NormalizedField.Array
114
+
115
+ export namespace NormalizedField {
116
+ export type ToValue<T extends NormalizedField> =
117
+ T extends Object<infer X> ? FieldSchema.ObjectFieldsToValues<X> :
118
+ T extends Array<infer X> ? FieldSchema.ArrayFieldsToValues<X> :
119
+ T extends Basic<infer X> ? X :
120
+ never
121
+
122
+ export type FromFieldSchema<T extends FieldSchema> =
123
+ T extends FieldSchema.Object ? (
124
+ {
125
+ type: 'object',
126
+ validate: T extends { validate: any } ? ValidateToValidationFunction<T['validate']> : null,
127
+ fields: T['fields']
128
+ }
129
+ ) :
130
+ T extends FieldSchema.Array ? (
131
+ {
132
+ type: 'array',
133
+ validate: T extends { validate: any } ? ValidateToValidationFunction<T['validate']> : null,
134
+ fields: T['fields']
135
+ }
136
+ ) :
137
+ T extends null ? (
138
+ {
139
+ type: 'basic',
140
+ validate: null
141
+ }
142
+ ) :
143
+ T extends Validate<any> ? (
144
+ {
145
+ type: 'basic',
146
+ validate: ValidateToValidationFunction<T>
147
+ }
148
+ ) :
149
+ T extends { validate: infer X, type?: Exclude<infer Y, 'object' | 'array'> } ? (
150
+ {
151
+ type: 'basic',
152
+ validate: ValidateToValidationFunction<X>
153
+ }
154
+ ) :
155
+ never
156
+
157
+ export type ValidateToValidationFunction<T> =
158
+ T extends undefined ? null :
159
+ T extends null ? null :
160
+ T extends Validate ? ValidationFunction<ValidateToValue<T>> :
161
+ never
162
+
163
+ export type Basic<T = unknown> = {
164
+ type: 'basic',
165
+ validate: null | ValidationFunction<T>,
166
+ }
167
+
168
+ export type Object<T extends FieldSchema.ObjectFields = FieldSchema.ObjectFields> = {
169
+ type: 'object',
170
+ validate: null | ValidationFunction<FieldSchema.ObjectFieldsToValues<T>>,
171
+ fields: T,
172
+ }
173
+
174
+ export type Array<T extends FieldSchema.ArrayFields = FieldSchema.ArrayFields> = {
175
+ type: 'array',
176
+ validate: null | ValidationFunction<FieldSchema.ArrayFieldsToValues<T>>,
177
+ fields: T,
178
+ }
179
+ }
180
+
181
+ export type Field = Field.Basic | Field.Object | Field.Array
182
+
183
+ export namespace Field {
184
+ export type ToValue<T extends Field> =
185
+ T extends Object<infer X> ? ObjectFieldsToValues<X> :
186
+ T extends Array<infer X> ? ObjectFieldsToValues<X>[] :
187
+ T extends Basic<infer X> ? X :
188
+ never
189
+
190
+ export type ObjectFieldsToValues<T extends ObjectFields> =
191
+ T extends any ? { [K in keyof T & string]: ToValue<T[K]> } : never
192
+
193
+ export type ObjectFromObjectInput<T extends FieldInput.Object> =
194
+ FromNormalizedField<NormalizedField.FromFieldSchema<FieldSchema.Object<T>>>
195
+
196
+ export type FromNormalizedField<T extends NormalizedField> =
197
+ T extends NormalizedField.Object<infer X> ? Object<ObjectFieldsToFields<X>> :
198
+ T extends NormalizedField.Array<infer X> ? Array<ArrayFieldsToObjectFields<X>> :
199
+ T extends NormalizedField.Basic<infer X> ? Basic<X> :
200
+ never
201
+
202
+ export type ObjectFieldsToFields<T extends FieldSchema.ObjectFields> =
203
+ T extends any
204
+ ? { [K in keyof T & string]: FromNormalizedField<NormalizedField.FromFieldSchema<T[K]>>}
205
+ : never
206
+
207
+ export type ArrayFieldsToObjectFields<T extends FieldSchema.ArrayFields> =
208
+ FromNormalizedField<
209
+ NormalizedField.FromFieldSchema<
210
+ FieldSchema.Object<FieldSchema.ExtractObjectFields<T>>
211
+ >
212
+ >['fields']
213
+
214
+ export type BaseFieldProperties<T extends 'object' | 'array' | 'basic'> = {
215
+ type: T,
216
+ name: string,
217
+ validate(context: ValidationContext): void,
218
+ setSubmitted(isSubmitted: boolean): void,
219
+ reset(): void,
220
+ }
221
+
222
+ export type Basic<T = any> =
223
+ BaseFieldProperties<'basic'> &
224
+ {
225
+ value: State.ReadonlyWithHistory<T>,
226
+ state: State.ReadonlyWithHistory<State.Basic<T>>,
227
+ eventHandlers: {
228
+ onBlur(): void,
229
+ onFocus(): void,
230
+ onChange(eOrValue: Event<T> | T): void,
231
+ }
232
+ }
233
+
234
+ export type Event<T = any> = {
235
+ target: {
236
+ value?: T
237
+ }
238
+ }
239
+
240
+ export type Object<T extends ObjectFields = ObjectFields> =
241
+ BaseFieldProperties<'object'> &
242
+ {
243
+ value: State.Readonly<ObjectFieldsToValues<T>>,
244
+ state: State.ReadonlyWithHistory<State.Object>,
245
+ fields: T,
246
+ }
247
+
248
+ export type Array<T extends ObjectFields = ObjectFields> =
249
+ BaseFieldProperties<'array'> &
250
+ {
251
+ value: State.Readonly<ObjectFieldsToValues<T>[]>,
252
+ state: State.ReadonlyWithHistory<State.Array<Object<T>>>,
253
+ helpers: {
254
+ add(initialValue: PartialWithStringKey<ObjectFieldsToValues<T>>): void,
255
+ remove(entry: Object<T>): void,
256
+ }
257
+ }
258
+
259
+ export type ObjectFields = { [name: string]: Field }
260
+ }
261
+
262
+ export type State = State.Basic | State.Object | State.Array
263
+
264
+ export namespace State {
265
+ export type Common = {
266
+ isSubmitted: boolean,
267
+ isVisited: boolean,
268
+ hasFocus: boolean,
269
+ } & (Valid | Invalid)
270
+
271
+ export type Valid = {
272
+ error: Falsy,
273
+ invalid: false,
274
+ showError: false,
275
+ }
276
+
277
+ export type Invalid = {
278
+ error: ValidationError,
279
+ invalid: true,
280
+ showError: boolean,
281
+ }
282
+
283
+ export type Basic<T = any> = Common & {
284
+ value: T
285
+ }
286
+
287
+ export type Object = Common
288
+
289
+ export type Array<T = Field.Object> = Common & {
290
+ children: T[]
291
+ }
292
+
293
+ export type Readonly<T = any> = {
294
+ get(): T,
295
+ subscribe(f: Subscription<T>): Unsubscribe,
296
+ }
297
+ export type ReadonlyWithHistory<T = any> = {
298
+ get(): T,
299
+ subscribe(f: SubscriptionWithHistory<T>): Unsubscribe,
300
+ }
301
+ export type ReadWrite<T = any> = ReadonlyWithHistory<T> & {
302
+ update(f: (oldValue: T) => T): T
303
+ }
304
+ export type Subscription<T> = (newValue: T) => void
305
+ export type SubscriptionWithHistory<T> = (newValue: T, oldValue: T) => void
306
+ export type Unsubscribe = () => void
307
+
308
+ export type StateTupleToValueTuple<T extends [...unknown[]]> =
309
+ T extends [State.Readonly<infer X>, ...infer Rest]
310
+ ? [X, ...StateTupleToValueTuple<Rest>]
311
+ : []
312
+ }
313
+
314
+ export type Snapshot = Snapshot.Basic | Snapshot.Object | Snapshot.Array
315
+
316
+ export namespace Snapshot {
317
+ export type FromField<T extends Field> =
318
+ T extends Field.Object<infer X> ? Object<X> :
319
+ T extends Field.Array<infer X> ? Array<X> :
320
+ T extends Field.Basic<infer X> ? Basic<X> :
321
+ never
322
+
323
+ export type FromObjectFields<T extends FieldSchema.ObjectFields> =
324
+ Snapshot.Object<Field.ObjectFieldsToFields<T>>
325
+
326
+ export type Basic<T = any> =
327
+ Pick<State.Basic<T>, 'value' | 'invalid' | 'error'>
328
+
329
+ export type Object<T extends Field.ObjectFields = Field.ObjectFields> =
330
+ {
331
+ invalid: boolean,
332
+ value: MapToValues<T>,
333
+ error: {
334
+ self: Falsy | ValidationError,
335
+ children: MapToErrors<T>,
336
+ }
337
+ }
338
+
339
+ export type Array<T extends Field.ObjectFields = Field.ObjectFields> =
340
+ {
341
+ invalid: boolean,
342
+ value: MapToValues<T>[],
343
+ error: {
344
+ self: Falsy | ValidationError,
345
+ children: Object<T>['error'][],
346
+ }
347
+ }
348
+
349
+ export type MapToValues<T extends Field.ObjectFields> =
350
+ T extends any ? { [K in keyof T & string]: FromField<T[K]>['value'] } : never
351
+
352
+ export type MapToErrors<T extends Field.ObjectFields> =
353
+ T extends any ? { [K in keyof T & string]: FromField<T[K]>['error'] } : never
354
+ }
package/src/validation.js CHANGED
@@ -1,22 +1,106 @@
1
+ /** @import { Falsy, ValidationFunction } from './types.ts' */
2
+
3
+
4
+ /**
5
+ * @template {string | Constructable<any>} T
6
+ * @typedef {(
7
+ * T extends Constructable<infer R> ? R :
8
+ * T extends string ? TypeLookup<T> :
9
+ * never
10
+ * )} TypeFromLookupOrConstructable
11
+ */
12
+
13
+ /**
14
+ * @template T
15
+ * @typedef {abstract new (...args: any) => T} Constructable
16
+ */
17
+
18
+ /**
19
+ * @template {string} T
20
+ * @typedef {(
21
+ * T extends 'string' ? string :
22
+ * T extends 'number' ? number :
23
+ * T extends `${infer A} | ${infer B}` ? TypeLookup<A> | TypeLookup<B> :
24
+ * T extends 'boolean' ? boolean :
25
+ * T extends `${infer X}[]` ? TypeLookup<X>[] :
26
+ * never
27
+ * )} TypeLookup
28
+ */
29
+
30
+ /**
31
+ * @template T
32
+ * @param {T} type
33
+ * @returns {Constructable<T>}
34
+ */
35
+ export function withT(type) {
36
+ return /** @type {any} */(null)
37
+ }
38
+
39
+ /**
40
+ * @template const T
41
+ *
42
+ * @overload
43
+ * @arg {T} type
44
+ * @returns {(x: TypeFromLookupOrConstructable<T> | undefined) => never}
45
+ *
46
+ * @arg {any} type
47
+ * @returns {null | ((x: any) => never)}
48
+ */
49
+ export function optionalT(type) {
50
+ return null
51
+ }
52
+
53
+ /**
54
+ * @template const T
55
+ *
56
+ * @arg {T} type
57
+ * @returns {ValidationFunction<TypeFromLookupOrConstructable<T> >}
58
+ */
59
+ export function requiredT(type) {
60
+ return required
61
+ }
62
+
1
63
  export const optional = null
2
- export const required = x => !x && x !== false && x !== 0 && error('required')
64
+ export const required =
65
+ /** @template T @arg {T} x */
66
+ x => !x && x !== false && x !== 0 && error('required')
67
+
68
+ export const number =
69
+ /** @arg {number} x */
70
+ x => Number(x) !== x && error('number')
71
+
72
+ /** @arg {number} min */
73
+ export function min(min) {
74
+ /** @arg {number} x */
75
+ return x => (x || x === 0) && x < min && error('min', min)
76
+ }
3
77
 
4
- export const number = x => Number(x) !== x && error('number')
5
- /** @param {number} min */
6
- export function min(min) { return x => (x || x === 0) && x < min && error('min', min) }
7
- /** @param {number} max */
8
- export function max(max) { return x => (x || x === 0) && x > max && error('max', max) }
78
+ /** @arg {number} max */
79
+ export function max(max) {
80
+ /** @arg {number} x */
81
+ return x => (x || x === 0) && x > max && error('max', max)
82
+ }
9
83
 
10
- /** @param {number} min */
11
- export function minLength(min) { return x => x && x.length < min && error('minLength', min) }
12
- /** @param {number} max */
13
- export function maxLength(max) { return x => x && x.length > max && error('maxLength', max) }
84
+ /** @arg {number} min */
85
+ export function minLength(min) {
86
+ /** @arg {{ length: number }} x */
87
+ return x => x && x.length < min && error('minLength', min)
88
+ }
89
+ /** @arg {number} max */
90
+ export function maxLength(max) {
91
+ /** @arg {{ length: number }} x */
92
+ return x => x && x.length > max && error('maxLength', max)
93
+ }
14
94
 
15
95
  const emailRegex = /.+@.+\..+/
16
- export const email = x => x && !emailRegex.test(x) && error('email')
96
+ export const email =
97
+ /** @arg {string} x */
98
+ x => x && !emailRegex.test(x) && error('email')
17
99
 
18
100
  /**
19
101
  * @template {string} T
20
- * @param {T} id
102
+ * @template {[...any]} P
103
+ * @arg {T} id
104
+ * @arg {P} params
21
105
  */
22
106
  export function error(id, ...params) { return { id, params } }
package/validation.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export {
2
- required, optional,
2
+ required, requiredT, optional, optionalT, withT,
3
3
  number, min, max,
4
4
  minLength, maxLength,
5
5
  email,
package/src/types.d.ts DELETED
@@ -1,15 +0,0 @@
1
- // This file exists because you can not do conditional types in jsdoc
2
-
3
- /**
4
- * If you have an optional parameter which is generic, it is inferred as any, the IfAny helps to
5
- * detect that.
6
- *
7
- * When T === any return Y else return N
8
- */
9
- type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N
10
-
11
- /**
12
- * While type declarations should be opaque, type hinting in visual studio still shows them, this
13
- * is used to make the types from this library, exposed to the developer more friendly.
14
- */
15
- type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never