@kaliber/forms 2.1.1 → 3.0.0-beta.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.
@@ -0,0 +1,164 @@
1
+ import {
2
+ useArrayFormField,
3
+ useBooleanFormField,
4
+ useForm,
5
+ useFormField,
6
+ useFormFieldSnapshot,
7
+ useFormFieldsValues,
8
+ useFormFieldValue,
9
+ useNumberFormField,
10
+ useObjectFormField
11
+ } from './hooks'
12
+ import { array, object } from './schema'
13
+ import { expectAssignable, expectNotAny, expectNotNever, Prepared } from './type.test.helpers.ts'
14
+ import { Field, Snapshot, State } from './types.ts'
15
+ import { optionalT } from './validation'
16
+
17
+ const { form } = useForm({
18
+ fields: {
19
+ a: optionalT('string'),
20
+ b: optionalT('number'),
21
+ c: optionalT('boolean'),
22
+ d: object({
23
+ a: optionalT('string'),
24
+ b: optionalT('number'),
25
+ c: optionalT('boolean'),
26
+ }),
27
+ e: array({
28
+ a: optionalT('string'),
29
+ b: optionalT('number'),
30
+ c: optionalT('boolean'),
31
+ })
32
+ },
33
+ onSubmit() {}
34
+ })
35
+
36
+ expectNotAny(form)
37
+ expectNotNever(form)
38
+ expectAssignable<
39
+ Field.Object<{
40
+ a: Field.Basic<string>,
41
+ b: Field.Basic<number>,
42
+ c: Field.Basic<boolean>,
43
+ d: Field.Object<{
44
+ a: Field.Basic<string>,
45
+ b: Field.Basic<number>,
46
+ c: Field.Basic<boolean>,
47
+ }>,
48
+ e: Field.Array<{
49
+ a: Field.Basic<string>,
50
+ b: Field.Basic<number>,
51
+ c: Field.Basic<boolean>,
52
+ }>
53
+ }>,
54
+ Prepared<typeof form>
55
+ >
56
+
57
+ const formFieldValue = useFormFieldValue(form.fields.a)
58
+ expectAssignable<string, Prepared<typeof formFieldValue>>
59
+
60
+ const formFieldsValues = useFormFieldsValues([form.fields.a, form.fields.b])
61
+ expectAssignable<[string, number], Prepared<typeof formFieldsValues>>
62
+
63
+ const formField = useFormField(form.fields.a)
64
+ expectAssignable<
65
+ {
66
+ name: string
67
+ state: State.Basic<string>
68
+ eventHandlers: {
69
+ onBlur(): void
70
+ onFocus(): void
71
+ onChange(eOrValue: string | Field.Event<string>): void
72
+ }
73
+ },
74
+ Prepared<typeof formField>
75
+ >
76
+
77
+ const numberFormField = useNumberFormField(form.fields.b)
78
+ expectAssignable<
79
+ {
80
+ name: string
81
+ state: State.Basic<string | number>
82
+ eventHandlers: {
83
+ onBlur(): void
84
+ onFocus(): void
85
+ onChange(e: { target: { value: string } }): void
86
+ }
87
+ },
88
+ Prepared<typeof numberFormField>
89
+ >
90
+
91
+ const booleanFormField = useBooleanFormField(form.fields.c)
92
+ expectAssignable<
93
+ {
94
+ name: string
95
+ state: State.Basic<boolean>
96
+ eventHandlers: {
97
+ onBlur(): void
98
+ onFocus(): void
99
+ onChange(e: { target: { checked: boolean } }): void
100
+ }
101
+ },
102
+ Prepared<typeof booleanFormField>
103
+ >
104
+
105
+ const objectFormField = useObjectFormField(form.fields.d)
106
+ expectAssignable<
107
+ {
108
+ name: string
109
+ state: State.Common
110
+ fields: {
111
+ a: Field.Basic<string>
112
+ b: Field.Basic<number>
113
+ c: Field.Basic<boolean>
114
+ }
115
+ },
116
+ Prepared<typeof objectFormField>
117
+ >
118
+
119
+ const arrayFormField = useArrayFormField(form.fields.e)
120
+ expectAssignable<
121
+ {
122
+ name: string
123
+ state: State.Array<Field.Object<{
124
+ a: Field.Basic<string>
125
+ b: Field.Basic<number>
126
+ c: Field.Basic<boolean>
127
+ }>>
128
+ helpers: {
129
+ add(initialValue: {
130
+ a?: string
131
+ b?: number
132
+ c?: boolean
133
+ }): void
134
+ remove(entry: Field.Object<{
135
+ a: Field.Basic<string>
136
+ b: Field.Basic<number>
137
+ c: Field.Basic<boolean>
138
+ }>): void
139
+ }
140
+ },
141
+ Prepared<typeof arrayFormField>
142
+ >
143
+
144
+ const formFieldSnapshot = useFormFieldSnapshot(form)
145
+ expectNotAny(formFieldSnapshot)
146
+ expectNotNever(formFieldSnapshot)
147
+ expectAssignable<
148
+ Snapshot.Object<{
149
+ a: Field.Basic<string>,
150
+ b: Field.Basic<number>,
151
+ c: Field.Basic<boolean>,
152
+ d: Field.Object<{
153
+ a: Field.Basic<string>,
154
+ b: Field.Basic<number>,
155
+ c: Field.Basic<boolean>,
156
+ }>,
157
+ e: Field.Array<{
158
+ a: Field.Basic<string>,
159
+ b: Field.Basic<number>,
160
+ c: Field.Basic<boolean>,
161
+ }>
162
+ }>,
163
+ Prepared<typeof formFieldSnapshot>
164
+ >
package/src/normalize.js CHANGED
@@ -1,5 +1,13 @@
1
- export function normalize(field, name) {
2
- return (
1
+ /** @import { FieldSchema, NormalizedField, Validate, ValidationContext, ValidationFunction } from './types.ts' */
2
+
3
+ /**
4
+ * @template {FieldSchema} T
5
+ *
6
+ * @arg {T} field
7
+ * @arg {string} [name]
8
+ */
9
+ export function normalize(field, name = '') {
10
+ return /** @type {NormalizedField.FromFieldSchema<T>} */ (
3
11
  convertValidationFunction(field, name) ||
4
12
  convertValidationArray(field, name) ||
5
13
  convertArrayField(field, name) ||
@@ -8,38 +16,83 @@ export function normalize(field, name) {
8
16
  )
9
17
  }
10
18
 
11
- function convertValidationFunction(x, name) {
12
- return x instanceof Function &&
13
- { type: 'basic', validate: toValidationFunction(x, name) }
19
+ /**
20
+ * @arg {FieldSchema} field
21
+ * @arg {string} name
22
+ * @returns {false | NormalizedField.Basic}
23
+ */
24
+ function convertValidationFunction(field, name) {
25
+ return field instanceof Function &&
26
+ { type: 'basic', validate: toValidationFunction(field, name) }
14
27
  }
15
- function convertValidationArray(x, name) {
16
- return x instanceof Array &&
17
- { type: 'basic', validate: toValidationFunction(x, name) }
28
+
29
+ /**
30
+ * @arg {FieldSchema} field
31
+ * @arg {string} name
32
+ * @returns {false | NormalizedField.Basic}
33
+ */
34
+ function convertValidationArray(field, name) {
35
+ return field instanceof Array &&
36
+ { type: 'basic', validate: toValidationFunction(field, name) }
18
37
  }
19
- function convertArrayField(x, name) {
20
- return x && 'type' in x && x.type === 'array' &&
21
- { type: 'array', validate: toValidationFunction(x.validate, name), fields: x.fields }
38
+
39
+ /**
40
+ * @arg {FieldSchema} field
41
+ * @arg {string} name
42
+ * @returns {null | false | NormalizedField.Array}
43
+ */
44
+ function convertArrayField(field, name) {
45
+ return field && 'type' in field && field.type === 'array' &&
46
+ { type: 'array', validate: toValidationFunction(field.validate, name), fields: field.fields }
22
47
  }
23
- function convertObjectField(x, name) {
24
- return x && 'type' in x && x.type === 'object' &&
25
- { type: 'object', validate: toValidationFunction(x.validate, name), fields: x.fields }
48
+
49
+ /**
50
+ * @arg {FieldSchema} field
51
+ * @arg {string} name
52
+ * @returns {null | false | NormalizedField.Object}
53
+ */
54
+ function convertObjectField(field, name) {
55
+ return field && 'type' in field && field.type === 'object' &&
56
+ { type: 'object', validate: toValidationFunction(field.validate, name), fields: field.fields }
26
57
  }
27
- function convertSimpleField(x, name) {
28
- return { type: 'basic', validate: toValidationFunction(x && x.validate, name) }
58
+
59
+ /**
60
+ * @arg {FieldSchema} field
61
+ * @arg {string} name
62
+ * @returns {NormalizedField.Basic}
63
+ */
64
+ function convertSimpleField(field, name) {
65
+ return { type: 'basic', validate: toValidationFunction(field && 'validate' in field && field.validate, name) }
29
66
  }
30
67
 
31
- function toValidationFunction(x = [], name) {
32
- const result = [].concat(x).reduce(
33
- (previous, next) => {
34
- const combined = previous && next && ((...args) => previous(...args) || next(...args))
35
- return combined || next || previous
36
- },
37
- null
38
- )
68
+ /**
69
+ * @arg {false | undefined | Validate} fOrArrayOfF
70
+ * @param {string} name
71
+ * @returns {null | ValidationFunction}
72
+ */
73
+ function toValidationFunction(fOrArrayOfF, name) {
74
+ const result = /** @type {(false | undefined | null | ValidationFunction)[]} */ ([])
75
+ .concat(fOrArrayOfF)
76
+ .reduce(
77
+ /** @arg {null | ValidationFunction} previous */
78
+ (previous, next) => {
79
+ const combined = previous && next && (
80
+ /** @arg {any} value @arg {ValidationContext} context */
81
+ (value, context) => previous(value, context) || next(value, context)
82
+ )
83
+ return combined || next || previous
84
+ },
85
+ null
86
+ )
39
87
 
40
- return result && withBetterError(result, name)
88
+ return result ? withBetterError(result, name) : null
41
89
  }
42
90
 
91
+ /**
92
+ * @arg {ValidationFunction} f
93
+ * @arg {string} name
94
+ * @returns {ValidationFunction}
95
+ */
43
96
  function withBetterError(f, name) {
44
97
  return (...args) => {
45
98
  try {
@@ -0,0 +1,125 @@
1
+ import { normalize } from './normalize'
2
+ import { array, object } from './schema'
3
+ import { asConst } from './type-helpers'
4
+ import { expectAssignable, expectNotAny, expectNotNever, Prepared } from './type.test.helpers'
5
+ import { ValidationFunction } from './types'
6
+ import { email, number, optional, required } from './validation'
7
+
8
+ const noValidation = optional
9
+ const singleValidation = email
10
+ const multipleValidation = asConst([required, number])
11
+ const objectInput = {
12
+ noValidation,
13
+ singleValidation,
14
+ multipleValidation,
15
+ }
16
+ const simpleObjectSchema = object(objectInput)
17
+ const simpleArraySchema = array(objectInput)
18
+ const simpleObjectWithValidationSchema = object(value => validate(value), objectInput)
19
+ const simpleArrayWithValidationSchema = array(value => validate(value), objectInput)
20
+ type BaseObjectNormalizedField = {
21
+ type: 'object',
22
+ fields: {
23
+ noValidation: null,
24
+ singleValidation: ValidationFunction<string>,
25
+ multipleValidation: readonly [ValidationFunction, ValidationFunction<number>],
26
+ }
27
+ }
28
+ type BaseArrayNormalizedField = {
29
+ type: 'array',
30
+ fields: {
31
+ noValidation: null,
32
+ singleValidation: ValidationFunction<string>,
33
+ multipleValidation: readonly [ValidationFunction, ValidationFunction<number>],
34
+ }
35
+ }
36
+ type BasicNormalizedField<T> = {
37
+ type: 'basic',
38
+ validate: T
39
+ }
40
+
41
+ {
42
+ const noValidationNormalizedField = normalize(noValidation)
43
+ expectNotAny(noValidationNormalizedField)
44
+ expectNotNever(noValidationNormalizedField)
45
+ expectAssignable<
46
+ BasicNormalizedField<null>,
47
+ Prepared<typeof noValidationNormalizedField>
48
+ >
49
+ }
50
+
51
+ {
52
+ const multipleValidationNormalizedField = normalize(multipleValidation)
53
+ expectNotAny(multipleValidationNormalizedField)
54
+ expectNotNever(multipleValidationNormalizedField)
55
+ expectAssignable<
56
+ BasicNormalizedField<ValidationFunction<number>>,
57
+ Prepared<typeof multipleValidationNormalizedField>
58
+ >
59
+ }
60
+
61
+ {
62
+ const singleValidationNormalizedField = normalize(singleValidation)
63
+ expectNotAny(singleValidationNormalizedField)
64
+ expectNotNever(singleValidationNormalizedField)
65
+ expectAssignable<
66
+ BasicNormalizedField<ValidationFunction<string>>,
67
+ Prepared<typeof singleValidationNormalizedField>
68
+ >
69
+ }
70
+
71
+ {
72
+ const simpleObjectNormalizedField = normalize(simpleObjectSchema)
73
+ expectNotAny(simpleObjectNormalizedField)
74
+ expectNotNever(simpleObjectNormalizedField)
75
+ expectAssignable<
76
+ BaseObjectNormalizedField & { validate: null },
77
+ Prepared<typeof simpleObjectNormalizedField>
78
+ >
79
+ }
80
+
81
+ {
82
+ const simpleObjectWithValidationNormalizedField = normalize(simpleObjectWithValidationSchema)
83
+ expectNotAny(simpleObjectWithValidationNormalizedField)
84
+ expectNotNever(simpleObjectWithValidationNormalizedField)
85
+ expectAssignable<
86
+ BaseObjectNormalizedField & {
87
+ validate: ValidationFunction<{
88
+ noValidation: unknown,
89
+ singleValidation: string,
90
+ multipleValidation: number,
91
+ }>
92
+ },
93
+ Prepared<typeof simpleObjectWithValidationNormalizedField>
94
+ >
95
+ }
96
+
97
+ {
98
+ const simpleArrayNormalizedField = normalize(simpleArraySchema)
99
+ expectNotAny(simpleArrayNormalizedField)
100
+ expectNotNever(simpleArrayNormalizedField)
101
+ expectAssignable<
102
+ BaseArrayNormalizedField & { validate: null },
103
+ Prepared<typeof simpleArrayNormalizedField>
104
+ >
105
+ }
106
+
107
+ {
108
+ const simpleArrayWithValidationNormalizedField = normalize(simpleArrayWithValidationSchema)
109
+ expectNotAny(simpleArrayWithValidationNormalizedField)
110
+ expectNotNever(simpleArrayWithValidationNormalizedField)
111
+ expectAssignable<
112
+ BaseArrayNormalizedField & {
113
+ validate: ValidationFunction<{
114
+ noValidation: unknown,
115
+ singleValidation: string,
116
+ multipleValidation: number,
117
+ }[]>
118
+ },
119
+ Prepared<typeof simpleArrayWithValidationNormalizedField>
120
+ >
121
+ }
122
+
123
+ function validate<T>(value: T) {
124
+ return value === 'failure' && { id: 'error' }
125
+ }
package/src/schema.js CHANGED
@@ -1,16 +1,40 @@
1
+ /** @import { Expand, FieldInput, Validate } from './types.ts' */
2
+
1
3
  /**
2
- * @template A, B
3
- * @param {A} fieldsOrValidate
4
- * @param {B} [fields]
4
+ * @template {Validate<FieldInput.ObjectToValue<B>>} const A
5
+ * @template {FieldInput.Object} const B
6
+ *
7
+ * @overload
8
+ * @arg {A} fieldsOrValidate
9
+ * @arg {B} fields
10
+ * @returns {ReturnType<typeof body<'object', A, B>>}
11
+ *
12
+ * @overload
13
+ * @arg {B} fieldsOrValidate
14
+ * @returns {ReturnType<typeof body<'object', B, undefined>>}
15
+ *
16
+ * @arg {A | B} fieldsOrValidate
17
+ * @arg {B} [fields]
5
18
  */
6
19
  export function object(fieldsOrValidate, fields) {
7
20
  return body('object', fieldsOrValidate, fields)
8
21
  }
9
22
 
10
23
  /**
11
- * @template A, B
12
- * @param {A} fieldsOrValidate
13
- * @param {B} [fields]
24
+ * @template {Validate<Expand<FieldInput.ArrayToValue<B>>>} const A
25
+ * @template {FieldInput.Array} const B
26
+ *
27
+ * @overload
28
+ * @arg {A} fieldsOrValidate
29
+ * @arg {B} fields
30
+ * @returns {ReturnType<typeof body<'array', A, B>>}
31
+ *
32
+ * @overload
33
+ * @arg {B} fieldsOrValidate
34
+ * @returns {ReturnType<typeof body<'array', B, undefined>>}
35
+ *
36
+ * @arg {A | B} fieldsOrValidate
37
+ * @arg {B} [fields]
14
38
  */
15
39
  export function array(fieldsOrValidate, fields) {
16
40
  return body('array', fieldsOrValidate, fields)
@@ -20,13 +44,17 @@ export function array(fieldsOrValidate, fields) {
20
44
  * @template {string} T
21
45
  * @template A, B
22
46
  *
23
- * @param {T} type
24
- * @param {A} fieldsOrValidate
25
- * @param {B} [fields]
47
+ * @arg {T} type
48
+ * @arg {A} fieldsOrValidate
49
+ * @arg {B} [fields]
26
50
  *
27
- * @returns {Expand<{ type: T } & IfAny<B, { fields: A }, { fields: B, validate: A }>>}
51
+ * @returns {{ type: T } & (
52
+ * B extends undefined ? { fields: A } :
53
+ * B extends infer X ? { fields: X, validate: A } :
54
+ * never
55
+ * )}
28
56
  */
29
57
  function body(type, fieldsOrValidate, fields) {
30
- // @ts-ignore - if you want to remove this @ts-ignore: good luck and please leave a comment afterwards *
58
+ // @ts-expect-error - if you want to remove this @ts-ignore: good luck and please leave a comment afterwards *
31
59
  return { type, fields: fields || fieldsOrValidate, validate: fields && fieldsOrValidate }
32
60
  }
@@ -0,0 +1,159 @@
1
+ import { email, number, optional, required } from './validation'
2
+ import { array, object } from './schema'
3
+ import { asConst } from './type-helpers'
4
+ import type { InitialValue, Validate } from './types.ts'
5
+ import { expectAssignable, expectNotAny, expectNotNever, Prepared } from './type.test.helpers.ts'
6
+
7
+ const simpleObjectInput = asConst({
8
+ noValidation: optional,
9
+ singleValidation: email,
10
+ multipleValidation: [required, number],
11
+ })
12
+ type ObjectSchema<Fields> = {
13
+ type: 'object',
14
+ fields: Fields,
15
+ }
16
+ type ArraySchema<Fields> = {
17
+ type: 'array',
18
+ fields: Fields,
19
+ }
20
+ type SimpleObjectSchema = ObjectSchema<SimpleObjectFields>
21
+
22
+ type SimpleObjectFields = {
23
+ noValidation: Validate<unknown>,
24
+ singleValidation: Validate<string>,
25
+ multipleValidation: readonly [Validate<any>, Validate<number>],
26
+ }
27
+ type SimpleObjectValues = {
28
+ noValidation: unknown,
29
+ singleValidation: string,
30
+ multipleValidation: number,
31
+ }
32
+
33
+ {
34
+ const simpleObjectSchema = object(simpleObjectInput)
35
+ expectNotAny(simpleObjectSchema)
36
+ expectAssignable<SimpleObjectSchema, Prepared<typeof simpleObjectSchema>>()
37
+ }
38
+
39
+ {
40
+ const simpleObjectWithValidationSchema = object(
41
+ (value) => {
42
+ expectNotNever(value)
43
+ expectNotAny(value)
44
+ expectAssignable<SimpleObjectValues, Prepared<typeof value>>()
45
+
46
+ return validate(value)
47
+ },
48
+ simpleObjectInput
49
+ )
50
+ expectNotAny(simpleObjectWithValidationSchema)
51
+ expectAssignable<
52
+ SimpleObjectSchema & { validate: Validate<SimpleObjectValues> },
53
+ Prepared<typeof simpleObjectWithValidationSchema>
54
+ >
55
+ }
56
+
57
+ const nestedObjectInput = { nested: object(simpleObjectInput) }
58
+ type NestedObjectSchema = ObjectSchema<{ nested: SimpleObjectSchema }>
59
+ {
60
+ const nestedObjectSchema = object(nestedObjectInput)
61
+ expectNotAny(nestedObjectSchema)
62
+ expectAssignable<NestedObjectSchema, Prepared<typeof nestedObjectSchema>>
63
+ }
64
+
65
+ {
66
+ const nestedObjectWithValidationSchema = object(
67
+ value => {
68
+ expectNotAny(value)
69
+ expectNotNever(value)
70
+ expectAssignable<{ nested: SimpleObjectValues }, Prepared<typeof value>>
71
+
72
+ return validate(value)
73
+ },
74
+ nestedObjectInput
75
+ )
76
+ expectNotAny(nestedObjectWithValidationSchema)
77
+ expectAssignable<
78
+ NestedObjectSchema & { validate: Validate<{ nested: SimpleObjectValues }> },
79
+ Prepared<typeof nestedObjectWithValidationSchema>
80
+ >
81
+ }
82
+
83
+ {
84
+ const simpleArraySchema = array(simpleObjectInput)
85
+ expectNotAny(simpleArraySchema)
86
+ expectAssignable<ArraySchema<SimpleObjectFields>, Prepared<typeof simpleArraySchema>>
87
+ }
88
+
89
+ {
90
+ const simpleArrayWithValidationSchema = array(
91
+ value => {
92
+ expectNotAny(value)
93
+ expectNotNever(value)
94
+ expectAssignable<SimpleObjectValues[], Prepared<typeof value>>
95
+
96
+ return validate(value)
97
+ },
98
+ simpleObjectInput
99
+ )
100
+ expectNotAny(simpleArrayWithValidationSchema)
101
+ expectAssignable<
102
+ ArraySchema<SimpleObjectFields> & { validate: Validate<SimpleObjectValues[]> },
103
+ Prepared<typeof simpleArrayWithValidationSchema>
104
+ >
105
+ }
106
+
107
+ const inputA = asConst({ type: validate<'a'>, a: object(simpleObjectInput) })
108
+ const inputB = asConst({ type: validate<'b'>, b: object(simpleObjectInput) })
109
+ type HeterogeneousInitialValues = InitialValue<typeof inputA | typeof inputB>
110
+
111
+ {
112
+ type InitialValues = InitialValue<typeof inputA | typeof inputB>
113
+
114
+ const heterogeneousArraySchema = array(
115
+ (initialValue: InitialValues) =>
116
+ initialValue.type === 'a' ? inputA : inputB
117
+ )
118
+ expectNotAny(heterogeneousArraySchema)
119
+ expectAssignable<
120
+ ArraySchema<(initialValues: InitialValues) =>
121
+ { type: Validate<'a'>, a: SimpleObjectSchema } |
122
+ { type: Validate<'b'>, b: SimpleObjectSchema }
123
+ >,
124
+ Prepared<typeof heterogeneousArraySchema>
125
+ >
126
+ }
127
+
128
+ {
129
+ const heterogeneousArrayWithValidationSchema = array(
130
+ value => {
131
+ expectNotAny(value)
132
+ expectNotNever(value)
133
+ expectAssignable<
134
+ ({ type: 'a', a: SimpleObjectValues } | { type: 'b', b: SimpleObjectValues })[],
135
+ Prepared<typeof value>
136
+ >
137
+
138
+ return validate(value)
139
+ },
140
+ (initialValue: HeterogeneousInitialValues) =>
141
+ initialValue.type === 'a' ? inputA :
142
+ initialValue.type === 'b' ? inputB :
143
+ throwError(`Unknown type: '${initialValue.type}'`)
144
+ )
145
+ expectNotAny(heterogeneousArrayWithValidationSchema)
146
+ expectAssignable<
147
+ ArraySchema<(initialValues: HeterogeneousInitialValues) =>
148
+ { type: Validate<'a'>, a: SimpleObjectSchema } |
149
+ { type: Validate<'b'>, b: SimpleObjectSchema }
150
+ > & { validate: Validate<({ type: 'a', a: SimpleObjectValues } | { type: 'b', b: SimpleObjectValues })[]> },
151
+ Prepared<typeof heterogeneousArrayWithValidationSchema>
152
+ >
153
+ }
154
+
155
+ function validate<T>(value: T) {
156
+ return value === 'failure' && { id: 'error' }
157
+ }
158
+
159
+ function throwError(m: string): never { throw new Error(m) }