-
Notifications
You must be signed in to change notification settings - Fork 0
/
mutations.ts
151 lines (129 loc) · 3.92 KB
/
mutations.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import type { DomainFunction } from 'domain-functions'
import { inputFromForm, errorMessagesForSchema } from 'domain-functions'
import type { z } from 'zod'
import { coerceValue } from './coercions'
import type { FormSchema } from './prelude'
import { objectFromSchema } from './prelude'
type RedirectFunction = (url: string, init?: number | ResponseInit) => Response
type JsonFunction = <Data>(data: Data, init?: number | ResponseInit) => Response
type FormActionFailure<SchemaType> = {
errors: FormErrors<SchemaType>
values: FormValues<SchemaType>
}
type FormValues<SchemaType> = Partial<Record<keyof SchemaType, any>>
type FormErrors<SchemaType> = Partial<
Record<keyof SchemaType | '_global', string[]>
>
type PerformMutation<SchemaType, D extends unknown> =
| ({ success: false } & FormActionFailure<SchemaType>)
| { success: true; data: D }
type Callback = (request: Request) => Promise<Response | void>
type PerformMutationProps<Schema extends FormSchema, D extends unknown> = {
request: Request
schema: Schema
mutation: DomainFunction<D>
environment?: unknown
transformValues?: (
values: FormValues<z.infer<Schema>>,
) => Record<string, unknown>
}
type FormActionProps<Schema extends FormSchema, D extends unknown> = {
beforeAction?: Callback
beforeSuccess?: Callback
successPath?: string | ((data: D) => string)
} & PerformMutationProps<Schema, D>
async function getFormValues<Schema extends FormSchema>(
request: Request,
schema: Schema,
): Promise<FormValues<z.infer<Schema>>> {
const shape = objectFromSchema(schema).shape
const input = await inputFromForm(request)
let values: FormValues<z.infer<Schema>> = {}
for (const key in shape) {
const value = input[key]
values[key as keyof z.infer<Schema>] = coerceValue(value, shape[key])
}
return values
}
async function performMutation<Schema extends FormSchema, D extends unknown>({
request,
schema,
mutation,
environment,
transformValues = (values) => values,
}: PerformMutationProps<Schema, D>): Promise<
PerformMutation<z.infer<Schema>, D>
> {
const values = await getFormValues(request, schema)
const result = await mutation(transformValues(values), environment)
if (result.success) {
return { success: true, data: result.data }
} else {
return {
success: false,
errors: {
...errorMessagesForSchema(result.inputErrors, schema),
_global:
result.errors.length || result.environmentErrors.length
? [...result.errors, ...result.environmentErrors].map(
(error) => error.message,
)
: undefined,
} as FormErrors<Schema>,
values,
}
}
}
function createFormAction({
redirect,
json,
}: {
redirect: RedirectFunction
json: JsonFunction
}) {
async function formAction<Schema extends FormSchema, D extends unknown>({
request,
schema,
mutation,
environment,
transformValues,
beforeAction,
beforeSuccess,
successPath,
}: FormActionProps<Schema, D>): Promise<Response> {
if (beforeAction) {
const beforeActionResponse = await beforeAction(request)
if (beforeActionResponse) return beforeActionResponse
}
const result = await performMutation({
request,
schema,
mutation,
environment,
transformValues,
})
if (result.success) {
if (beforeSuccess) {
const beforeSuccessResponse = await beforeSuccess(request)
if (beforeSuccessResponse) return beforeSuccessResponse
}
const path =
typeof successPath === 'function'
? successPath(result.data)
: successPath
return path ? redirect(path) : json(result.data)
} else {
return json({ errors: result.errors, values: result.values })
}
}
return formAction
}
export type {
FormValues,
FormErrors,
PerformMutation,
Callback,
PerformMutationProps,
FormActionProps,
}
export { performMutation, createFormAction }