@effect-app/vue-components 4.0.0-beta.158 → 4.0.0-beta.159
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/dist/types/components/OmegaForm/OmegaArray.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaErrorsInternal.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaFormInput.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +2 -1
- package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/createUseFormWithCustomInput.d.ts +2 -2
- package/dist/types/components/OmegaForm/errors.d.ts +33 -0
- package/dist/types/components/OmegaForm/getOmegaStore.d.ts +1 -1
- package/dist/types/components/OmegaForm/hocs.d.ts +3 -0
- package/dist/types/components/OmegaForm/index.d.ts +13 -3
- package/dist/types/components/OmegaForm/inputs.d.ts +4 -0
- package/dist/types/components/OmegaForm/meta/checks.d.ts +4 -0
- package/dist/types/components/OmegaForm/meta/createMeta.d.ts +32 -0
- package/dist/types/components/OmegaForm/meta/defaults.d.ts +2 -0
- package/dist/types/components/OmegaForm/meta/redacted.d.ts +2 -0
- package/dist/types/components/OmegaForm/meta/types.d.ts +56 -0
- package/dist/types/components/OmegaForm/meta/walker.d.ts +18 -0
- package/dist/types/components/OmegaForm/persistency.d.ts +58 -0
- package/dist/types/components/OmegaForm/submit.d.ts +60 -0
- package/dist/types/components/OmegaForm/types.d.ts +281 -0
- package/dist/types/components/OmegaForm/useOmegaForm.d.ts +6 -212
- package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
- package/dist/vue-components.es.js +24 -16
- package/dist/vue-components10.es.js +4 -4
- package/dist/vue-components11.es.js +19 -12
- package/dist/vue-components12.es.js +22 -444
- package/dist/vue-components13.es.js +126 -3
- package/dist/vue-components14.es.js +61 -34
- package/dist/vue-components15.es.js +57 -24
- package/dist/vue-components16.es.js +20 -26
- package/dist/vue-components17.es.js +4 -6
- package/dist/vue-components18.es.js +78 -16
- package/dist/vue-components19.es.js +86 -30
- package/dist/vue-components20.es.js +72 -17
- package/dist/vue-components21.es.js +10 -19
- package/dist/vue-components22.es.js +54 -28
- package/dist/vue-components23.es.js +4 -6
- package/dist/vue-components24.es.js +43 -8
- package/dist/vue-components25.es.js +4 -37
- package/dist/vue-components26.es.js +83 -24
- package/dist/vue-components28.es.js +6 -22
- package/dist/vue-components29.es.js +8 -20
- package/dist/vue-components3.es.js +2 -2
- package/dist/vue-components30.es.js +267 -7
- package/dist/vue-components32.es.js +7 -4
- package/dist/vue-components33.es.js +71 -27
- package/dist/vue-components34.es.js +4 -4
- package/dist/vue-components35.es.js +50 -27
- package/dist/vue-components36.es.js +4 -5
- package/dist/vue-components37.es.js +23 -17
- package/dist/vue-components38.es.js +4 -55
- package/dist/vue-components39.es.js +57 -3
- package/dist/vue-components40.es.js +4 -43
- package/dist/vue-components41.es.js +11 -4
- package/dist/vue-components42.es.js +17 -79
- package/dist/vue-components44.es.js +8 -7
- package/dist/vue-components45.es.js +3 -8
- package/dist/vue-components46.es.js +36 -267
- package/dist/vue-components47.es.js +27 -0
- package/dist/vue-components48.es.js +27 -7
- package/dist/vue-components49.es.js +6 -79
- package/dist/vue-components50.es.js +17 -4
- package/dist/vue-components51.es.js +32 -69
- package/dist/vue-components52.es.js +17 -4
- package/dist/vue-components53.es.js +19 -22
- package/dist/vue-components54.es.js +29 -4
- package/dist/vue-components55.es.js +6 -58
- package/dist/vue-components56.es.js +8 -4
- package/dist/vue-components57.es.js +37 -11
- package/dist/vue-components58.es.js +24 -21
- package/dist/{vue-components27.es.js → vue-components59.es.js} +2 -2
- package/dist/vue-components6.es.js +11 -11
- package/dist/vue-components60.es.js +23 -8
- package/dist/vue-components61.es.js +18 -232
- package/dist/vue-components62.es.js +7 -31
- package/dist/vue-components63.es.js +19 -8
- package/dist/vue-components64.es.js +4 -35
- package/dist/vue-components65.es.js +29 -0
- package/dist/vue-components66.es.js +5 -0
- package/dist/vue-components67.es.js +29 -0
- package/dist/vue-components68.es.js +6 -0
- package/dist/vue-components69.es.js +18 -0
- package/dist/vue-components7.es.js +11 -26
- package/dist/vue-components70.es.js +40 -0
- package/dist/vue-components71.es.js +81 -0
- package/dist/vue-components72.es.js +33 -0
- package/dist/vue-components73.es.js +19 -0
- package/dist/vue-components74.es.js +48 -0
- package/dist/vue-components8.es.js +33 -45
- package/dist/vue-components9.es.js +46 -4
- package/package.json +7 -7
- package/src/components/CommandButton.vue +3 -1
- package/src/components/OmegaForm/OmegaArray.vue +1 -1
- package/src/components/OmegaForm/OmegaAutoGen.vue +2 -1
- package/src/components/OmegaForm/OmegaErrorsInternal.vue +1 -1
- package/src/components/OmegaForm/OmegaFormInput.vue +1 -1
- package/src/components/OmegaForm/OmegaInput.vue +6 -68
- package/src/components/OmegaForm/OmegaInputVuetify.vue +1 -1
- package/src/components/OmegaForm/OmegaInternalInput.vue +5 -11
- package/src/components/OmegaForm/OmegaTaggedUnion.vue +2 -1
- package/src/components/OmegaForm/OmegaWrapper.vue +1 -1
- package/src/components/OmegaForm/blockDialog.ts +10 -1
- package/src/components/OmegaForm/createUseFormWithCustomInput.ts +2 -1
- package/src/components/OmegaForm/errors.ts +136 -0
- package/src/components/OmegaForm/getOmegaStore.ts +1 -1
- package/src/components/OmegaForm/hocs.ts +19 -0
- package/src/components/OmegaForm/index.ts +16 -4
- package/src/components/OmegaForm/inputs.ts +22 -0
- package/src/components/OmegaForm/meta/checks.ts +81 -0
- package/src/components/OmegaForm/meta/createMeta.ts +138 -0
- package/src/components/OmegaForm/meta/defaults.ts +132 -0
- package/src/components/OmegaForm/meta/redacted.ts +66 -0
- package/src/components/OmegaForm/meta/types.ts +78 -0
- package/src/components/OmegaForm/meta/walker.ts +247 -0
- package/src/components/OmegaForm/persistency.ts +247 -0
- package/src/components/OmegaForm/submit.ts +128 -0
- package/src/components/OmegaForm/types.ts +751 -0
- package/src/components/OmegaForm/useOmegaForm.ts +49 -913
- package/src/components/OmegaForm/validation/localized.ts +202 -0
- package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +0 -173
- package/dist/vue-components31.es.js +0 -19
- package/src/components/OmegaForm/OmegaFormStuff.ts +0 -1422
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { isObject } from "@vueuse/core"
|
|
3
|
+
import { computed, type ComputedRef, onBeforeUnmount, onMounted, onUnmounted } from "vue"
|
|
4
|
+
import { type MetaRecord } from "./meta/types"
|
|
5
|
+
|
|
6
|
+
export type Policies = "local" | "session" | "querystring"
|
|
7
|
+
export type DefaultValuesPriorityUnion = "tanstack" | "persistency" | "schema"
|
|
8
|
+
|
|
9
|
+
// Backward-compatible alias for the legacy lowercased-prefix type name.
|
|
10
|
+
export type defaultValuesPriorityUnion = DefaultValuesPriorityUnion
|
|
11
|
+
|
|
12
|
+
export interface PersistencyConfig {
|
|
13
|
+
/** Order of importance:
|
|
14
|
+
* - "querystring": Highest priority when persisting
|
|
15
|
+
* - "local" and then "session": Lower priority storage options
|
|
16
|
+
*/
|
|
17
|
+
policies?: ReadonlyArray<Policies>
|
|
18
|
+
overrideDefaultValues?: "deprecated: use defaultValuesPriority"
|
|
19
|
+
id?: string
|
|
20
|
+
keys?: ReadonlyArray<string> | "You should only use one of banKeys or keys, not both, moron"
|
|
21
|
+
banKeys?: ReadonlyArray<string> | "You should only use one of banKeys or keys, not both, moron"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function deepMerge(target: any, source: any) {
|
|
25
|
+
const result = { ...target }
|
|
26
|
+
for (const key in source) {
|
|
27
|
+
if (Array.isArray(source[key])) {
|
|
28
|
+
// Arrays should be copied directly, not deep merged
|
|
29
|
+
result[key] = source[key]
|
|
30
|
+
} else if (source[key] && isObject(source[key])) {
|
|
31
|
+
result[key] = deepMerge(result[key], source[key])
|
|
32
|
+
} else {
|
|
33
|
+
result[key] = source[key]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const includesPolicy = (arr: ReadonlyArray<Policies>, policy: Policies) => {
|
|
40
|
+
return arr.includes(policy)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface UsePersistencyOptions<From> {
|
|
44
|
+
meta: MetaRecord<From>
|
|
45
|
+
persistency?: PersistencyConfig
|
|
46
|
+
preventWindowExit?: "prevent" | "prevent-and-reset" | "nope"
|
|
47
|
+
defaultValuesPriority?: DefaultValuesPriorityUnion[] | readonly DefaultValuesPriorityUnion[]
|
|
48
|
+
/** Tanstack-provided default values (highest priority by default). */
|
|
49
|
+
tanstackDefaultValues?: any
|
|
50
|
+
/** Lazy schema-derived defaults factory. */
|
|
51
|
+
schemaDefaultValues: () => any
|
|
52
|
+
/**
|
|
53
|
+
* Lazy accessor for the form. Lazy because persistency is created BEFORE
|
|
54
|
+
* the form (its `defaultValues` are passed into `useForm`), but the
|
|
55
|
+
* persistence callbacks (`persistData`, `saveDataInUrl`, the
|
|
56
|
+
* `beforeunload` listener) only run later and need the live form.
|
|
57
|
+
*/
|
|
58
|
+
getForm: () => {
|
|
59
|
+
store: { state: { values: any; isDirty: boolean } }
|
|
60
|
+
getFieldValue: (path: any) => any
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface UsePersistencyReturn {
|
|
65
|
+
defaultValues: ComputedRef<any>
|
|
66
|
+
persistencyKey: ComputedRef<string>
|
|
67
|
+
persistData: () => void
|
|
68
|
+
saveDataInUrl: () => void
|
|
69
|
+
clearUrlParams: () => void
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Encapsulates form-data persistency: loading default values from
|
|
74
|
+
* localStorage / sessionStorage / querystring, persisting them on unmount
|
|
75
|
+
* or window blur, and the optional `preventWindowExit` warning listener.
|
|
76
|
+
*
|
|
77
|
+
* The `prevent-and-reset` reset-on-success behavior is intentionally NOT
|
|
78
|
+
* owned here — the consumer wires that to its own form submit lifecycle.
|
|
79
|
+
*/
|
|
80
|
+
export const usePersistency = <From>(opts: UsePersistencyOptions<From>): UsePersistencyReturn => {
|
|
81
|
+
const { getForm, meta, persistency, preventWindowExit, schemaDefaultValues, tanstackDefaultValues } = opts
|
|
82
|
+
|
|
83
|
+
const persistencyKey = computed(() => {
|
|
84
|
+
if (persistency?.id) {
|
|
85
|
+
return persistency.id
|
|
86
|
+
}
|
|
87
|
+
const path = window.location.pathname
|
|
88
|
+
const keys = Object.keys(meta)
|
|
89
|
+
return `${path}-${keys.join("-")}`
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const clearUrlParams = () => {
|
|
93
|
+
const params = new URLSearchParams(window.location.search)
|
|
94
|
+
params.delete(persistencyKey.value)
|
|
95
|
+
const url = new URL(window.location.href)
|
|
96
|
+
url.search = params.toString()
|
|
97
|
+
window.history.replaceState({}, "", url.toString())
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const defaultValues = computed(() => {
|
|
101
|
+
// will contain what we get from querystring or local/session storage
|
|
102
|
+
let persistencyDefaultValues
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
// query string has higher priority than local/session storage
|
|
106
|
+
persistency?.policies
|
|
107
|
+
&& !persistencyDefaultValues
|
|
108
|
+
&& (includesPolicy(persistency.policies, "local")
|
|
109
|
+
|| includesPolicy(persistency.policies, "session"))
|
|
110
|
+
) {
|
|
111
|
+
const storage = includesPolicy(persistency.policies, "local")
|
|
112
|
+
? localStorage
|
|
113
|
+
: sessionStorage
|
|
114
|
+
if (storage) {
|
|
115
|
+
try {
|
|
116
|
+
const value = JSON.parse(
|
|
117
|
+
storage.getItem(persistencyKey.value) || "{}"
|
|
118
|
+
)
|
|
119
|
+
storage.removeItem(persistencyKey.value)
|
|
120
|
+
persistencyDefaultValues = value
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(error)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (persistency?.policies && includesPolicy(persistency.policies, "querystring")) {
|
|
127
|
+
try {
|
|
128
|
+
const params = new URLSearchParams(window.location.search)
|
|
129
|
+
const value = params.get(persistencyKey.value)
|
|
130
|
+
clearUrlParams()
|
|
131
|
+
if (value) {
|
|
132
|
+
persistencyDefaultValues = deepMerge(persistencyDefaultValues || {}, JSON.parse(value))
|
|
133
|
+
}
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error(error)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// to be sure we have a valid object at the end of the gathering process
|
|
140
|
+
persistencyDefaultValues ??= {}
|
|
141
|
+
|
|
142
|
+
const defaults: Record<DefaultValuesPriorityUnion, any> = {
|
|
143
|
+
tanstack: tanstackDefaultValues || {},
|
|
144
|
+
persistency: persistencyDefaultValues,
|
|
145
|
+
schema: schemaDefaultValues()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return [...(opts.defaultValuesPriority || ["tanstack", "persistency", "schema"] as const)].reverse().reduce(
|
|
149
|
+
(acc: any, m: DefaultValuesPriorityUnion) => {
|
|
150
|
+
if (!Object.keys(acc).length) {
|
|
151
|
+
return defaults[m]
|
|
152
|
+
}
|
|
153
|
+
return deepMerge(acc, defaults[m])
|
|
154
|
+
},
|
|
155
|
+
{}
|
|
156
|
+
)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const createNestedObjectFromPaths = (paths: string[]) =>
|
|
160
|
+
paths.reduce((result, path) => {
|
|
161
|
+
const parts = path.split(".")
|
|
162
|
+
parts.reduce((acc, part, i) => {
|
|
163
|
+
if (i === parts.length - 1) {
|
|
164
|
+
acc[part] = getForm().getFieldValue(path as any)
|
|
165
|
+
} else {
|
|
166
|
+
acc[part] = acc[part] ?? {}
|
|
167
|
+
}
|
|
168
|
+
return acc[part]
|
|
169
|
+
}, result)
|
|
170
|
+
return result
|
|
171
|
+
}, {} as Record<string, any>)
|
|
172
|
+
|
|
173
|
+
const persistFilter = (p: PersistencyConfig | undefined) => {
|
|
174
|
+
if (!p) return
|
|
175
|
+
const { banKeys, keys } = p
|
|
176
|
+
if (Array.isArray(keys)) {
|
|
177
|
+
return createNestedObjectFromPaths(keys as string[])
|
|
178
|
+
}
|
|
179
|
+
if (Array.isArray(banKeys)) {
|
|
180
|
+
const subs = Object.keys(meta).filter((metakey) => banKeys.includes(metakey))
|
|
181
|
+
return createNestedObjectFromPaths(subs)
|
|
182
|
+
}
|
|
183
|
+
return getForm().store.state.values
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const persistData = () => {
|
|
187
|
+
if (!persistency?.policies || persistency.policies.length === 0) {
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
if (
|
|
191
|
+
includesPolicy(persistency.policies, "local")
|
|
192
|
+
|| includesPolicy(persistency.policies, "session")
|
|
193
|
+
) {
|
|
194
|
+
const storage = includesPolicy(persistency.policies, "local")
|
|
195
|
+
? localStorage
|
|
196
|
+
: sessionStorage
|
|
197
|
+
if (!storage) return
|
|
198
|
+
const values = persistFilter(persistency)
|
|
199
|
+
return storage.setItem(persistencyKey.value, JSON.stringify(values))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const saveDataInUrl = () => {
|
|
204
|
+
if (!persistency?.policies || persistency.policies.length === 0) {
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
if (includesPolicy(persistency.policies, "querystring")) {
|
|
208
|
+
const values = persistFilter(persistency)
|
|
209
|
+
const searchParams = new URLSearchParams(window.location.search)
|
|
210
|
+
searchParams.set(persistencyKey.value, JSON.stringify(values))
|
|
211
|
+
const url = new URL(window.location.href)
|
|
212
|
+
url.search = searchParams.toString()
|
|
213
|
+
window.history.replaceState({}, "", url.toString())
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const preventWindowExitListener = (e: BeforeUnloadEvent) => {
|
|
218
|
+
if (getForm().store.state.isDirty) {
|
|
219
|
+
e.preventDefault()
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
onUnmounted(persistData)
|
|
224
|
+
|
|
225
|
+
onMounted(() => {
|
|
226
|
+
window.addEventListener("beforeunload", persistData)
|
|
227
|
+
window.addEventListener("blur", saveDataInUrl)
|
|
228
|
+
if (preventWindowExit && preventWindowExit !== "nope") {
|
|
229
|
+
window.addEventListener("beforeunload", preventWindowExitListener)
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
onBeforeUnmount(() => {
|
|
233
|
+
window.removeEventListener("beforeunload", persistData)
|
|
234
|
+
window.removeEventListener("blur", saveDataInUrl)
|
|
235
|
+
if (preventWindowExit && preventWindowExit !== "nope") {
|
|
236
|
+
window.removeEventListener("beforeunload", preventWindowExitListener)
|
|
237
|
+
}
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
return {
|
|
241
|
+
defaultValues,
|
|
242
|
+
persistencyKey,
|
|
243
|
+
persistData,
|
|
244
|
+
saveDataInUrl,
|
|
245
|
+
clearUrlParams
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
import * as api from "@opentelemetry/api"
|
|
4
|
+
import type { DeepKeys, StandardSchemaV1Issue, ValidationError, ValidationErrorMap } from "@tanstack/vue-form"
|
|
5
|
+
import { Data, Effect, Fiber, Option } from "effect-app"
|
|
6
|
+
import { runtimeFiberAsPromise } from "effect-app/utils"
|
|
7
|
+
import type { Fiber as EffectFiber } from "effect/Fiber"
|
|
8
|
+
import type { OmegaFormApi, OmegaFormParams } from "./types"
|
|
9
|
+
|
|
10
|
+
export class FormErrors<From> extends Data.TaggedError("FormErrors")<{
|
|
11
|
+
form: {
|
|
12
|
+
// TODO: error shapes seem off, with `undefined` etc..
|
|
13
|
+
errors: (Record<string, StandardSchemaV1Issue[]> | undefined)[]
|
|
14
|
+
errorMap: ValidationErrorMap<
|
|
15
|
+
undefined,
|
|
16
|
+
undefined,
|
|
17
|
+
Record<string, StandardSchemaV1Issue[]>,
|
|
18
|
+
undefined,
|
|
19
|
+
undefined,
|
|
20
|
+
undefined,
|
|
21
|
+
undefined,
|
|
22
|
+
undefined,
|
|
23
|
+
undefined,
|
|
24
|
+
undefined
|
|
25
|
+
>
|
|
26
|
+
}
|
|
27
|
+
fields: Record<DeepKeys<From>, {
|
|
28
|
+
errors: ValidationError[]
|
|
29
|
+
errorMap: ValidationErrorMap
|
|
30
|
+
}>
|
|
31
|
+
}> {}
|
|
32
|
+
|
|
33
|
+
export const wrapWithSpan = (span: api.Span | undefined, toWrap: () => any) => {
|
|
34
|
+
return span ? api.context.with(api.trace.setSpan(api.context.active(), span), toWrap) : toWrap()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type UserOnSubmit<From, To> = (props: {
|
|
38
|
+
formApi: OmegaFormParams<From, To>
|
|
39
|
+
meta: any
|
|
40
|
+
value: To
|
|
41
|
+
}) => Promise<any> | EffectFiber<any, any> | Effect.Effect<unknown, any, never>
|
|
42
|
+
|
|
43
|
+
export type RunPromise = <A, E>(eff: Effect.Effect<A, E, never>) => Promise<A>
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Wraps the user's `onSubmit` to:
|
|
47
|
+
* - run inside the OpenTelemetry span passed via `meta.currentSpan`
|
|
48
|
+
* - decode the raw form `value` (validators only validate, they don't transform)
|
|
49
|
+
* - normalize Promise / Effect / Fiber return values to a Promise
|
|
50
|
+
*
|
|
51
|
+
* Returns `undefined` when `userOnSubmit` is `undefined` (so callers can pass it
|
|
52
|
+
* directly to `useForm({ onSubmit })` without changing semantics).
|
|
53
|
+
*/
|
|
54
|
+
export const wrapOnSubmit = <From, To>(
|
|
55
|
+
userOnSubmit: UserOnSubmit<From, To> | undefined,
|
|
56
|
+
decode: (value: From) => Effect.Effect<To, any, never>,
|
|
57
|
+
runPromise: RunPromise
|
|
58
|
+
) => {
|
|
59
|
+
if (!userOnSubmit) return undefined
|
|
60
|
+
return ({ formApi, meta, value }: { formApi: OmegaFormParams<From, To>; meta: any; value: From }) =>
|
|
61
|
+
wrapWithSpan(meta?.currentSpan, async () => {
|
|
62
|
+
// validators only validate, they don't actually transform, so we have to do that manually here.
|
|
63
|
+
const parsedValue = await runPromise(decode(value))
|
|
64
|
+
const r = userOnSubmit({
|
|
65
|
+
formApi: formApi as OmegaFormApi<From, To>,
|
|
66
|
+
meta,
|
|
67
|
+
value: parsedValue
|
|
68
|
+
})
|
|
69
|
+
if (Fiber.isFiber(r)) {
|
|
70
|
+
return await runtimeFiberAsPromise(r)
|
|
71
|
+
}
|
|
72
|
+
if (Effect.isEffect(r)) {
|
|
73
|
+
const effectResult = await runPromise(r)
|
|
74
|
+
return Fiber.isFiber(effectResult)
|
|
75
|
+
? await runtimeFiberAsPromise(effectResult)
|
|
76
|
+
: effectResult
|
|
77
|
+
}
|
|
78
|
+
return r
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Builds the public submit handlers from a `useForm`-returned `form`:
|
|
84
|
+
* - `handleSubmit` injects the current OpenTelemetry span as `meta.currentSpan`.
|
|
85
|
+
* - `handleSubmitEffect` runs `handleSubmit` inside an Effect that picks up the
|
|
86
|
+
* ambient `Effect.currentSpan`. With `checkErrors: true`, it fails with
|
|
87
|
+
* `FormErrors<From>` when validation produced errors.
|
|
88
|
+
*/
|
|
89
|
+
export const makeSubmitHandlers = <From, To>(
|
|
90
|
+
form: OmegaFormApi<From, To>
|
|
91
|
+
) => {
|
|
92
|
+
const hs = form.handleSubmit
|
|
93
|
+
|
|
94
|
+
const handleSubmitInner: typeof form.handleSubmit = async (meta?: Record<string, any>) => {
|
|
95
|
+
return await hs(meta)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const handleSubmit = (meta?: Record<string, any>) => {
|
|
99
|
+
const span = api.trace.getSpan(api.context.active())
|
|
100
|
+
return handleSubmitInner({ currentSpan: span, ...meta })
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const handleSubmitEffect_ = (meta?: Record<string, any>) =>
|
|
104
|
+
Effect.currentSpan.pipe(
|
|
105
|
+
Effect.option,
|
|
106
|
+
Effect
|
|
107
|
+
.flatMap((span) =>
|
|
108
|
+
Effect.promise(() => handleSubmitInner(Option.isSome(span) ? { currentSpan: span.value, ...meta } : meta))
|
|
109
|
+
)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
const handleSubmitEffect: {
|
|
113
|
+
(options: { checkErrors: true; meta?: Record<string, any> }): Effect.Effect<void, FormErrors<From>>
|
|
114
|
+
(options?: { meta?: Record<string, any> }): Effect.Effect<void>
|
|
115
|
+
} = (
|
|
116
|
+
options?: { meta?: Record<string, any>; checkErrors?: true }
|
|
117
|
+
): any =>
|
|
118
|
+
options?.checkErrors
|
|
119
|
+
? handleSubmitEffect_(options?.meta).pipe(Effect.flatMap(Effect.fnUntraced(function*() {
|
|
120
|
+
const errors = form.getAllErrors()
|
|
121
|
+
if (Object.keys(errors.fields).length || errors.form.errors.length) {
|
|
122
|
+
return yield* Effect.fail(new FormErrors({ form: errors.form, fields: errors.fields }))
|
|
123
|
+
}
|
|
124
|
+
})))
|
|
125
|
+
: handleSubmitEffect_(options?.meta)
|
|
126
|
+
|
|
127
|
+
return { handleSubmit, handleSubmitEffect }
|
|
128
|
+
}
|