@effect-app/vue-components 4.0.0-beta.32 → 4.0.0-beta.321

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.
Files changed (211) hide show
  1. package/README.md +13 -9
  2. package/dist/reset.css +39 -38
  3. package/dist/types/components/CommandButton.vue.d.ts +24 -5
  4. package/dist/types/components/Dialog.vue.d.ts +4 -2
  5. package/dist/types/components/FixedNuxtErrorBoundary.vue.d.ts +39 -0
  6. package/dist/types/components/OmegaForm/InputProps.d.ts +2 -1
  7. package/dist/types/components/OmegaForm/OmegaArray.vue.d.ts +1 -1
  8. package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +2 -2
  9. package/dist/types/components/OmegaForm/OmegaErrorsInternal.vue.d.ts +2 -3
  10. package/dist/types/components/OmegaForm/OmegaFormInput.vue.d.ts +1 -1
  11. package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +1 -1
  12. package/dist/types/components/OmegaForm/OmegaInputVuetify.vue.d.ts +12 -0
  13. package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +7 -9
  14. package/dist/types/components/OmegaForm/OmegaTaggedUnion.vue.d.ts +2 -2
  15. package/dist/types/components/OmegaForm/OmegaTaggedUnionInternal.vue.d.ts +3 -3
  16. package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +1 -1
  17. package/dist/types/components/OmegaForm/createUseFormWithCustomInput.d.ts +4 -2
  18. package/dist/types/components/OmegaForm/decimalSeparatorInput.d.ts +13 -0
  19. package/dist/types/components/OmegaForm/errors.d.ts +33 -0
  20. package/dist/types/components/OmegaForm/getOmegaStore.d.ts +1 -1
  21. package/dist/types/components/OmegaForm/hocs.d.ts +3 -0
  22. package/dist/types/components/OmegaForm/index.d.ts +14 -3
  23. package/dist/types/components/OmegaForm/inputs.d.ts +4 -0
  24. package/dist/types/components/OmegaForm/meta/checks.d.ts +4 -0
  25. package/dist/types/components/OmegaForm/meta/createMeta.d.ts +33 -0
  26. package/dist/types/components/OmegaForm/meta/defaults.d.ts +21 -0
  27. package/dist/types/components/OmegaForm/meta/redacted.d.ts +2 -0
  28. package/dist/types/components/OmegaForm/meta/types.d.ts +56 -0
  29. package/dist/types/components/OmegaForm/meta/walker.d.ts +18 -0
  30. package/dist/types/components/OmegaForm/persistency.d.ts +58 -0
  31. package/dist/types/components/OmegaForm/submit.d.ts +60 -0
  32. package/dist/types/components/OmegaForm/types.d.ts +312 -0
  33. package/dist/types/components/OmegaForm/useOmegaForm.d.ts +9 -213
  34. package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
  35. package/dist/types/components/index.d.ts +1 -0
  36. package/dist/types/index.d.ts +0 -1
  37. package/dist/types/utils/index.d.ts +8 -8
  38. package/dist/vue-components.es.js +30 -44
  39. package/dist/vue-components10.es.js +5 -0
  40. package/dist/vue-components11.es.js +56 -0
  41. package/dist/vue-components12.es.js +5 -0
  42. package/dist/vue-components13.es.js +20 -0
  43. package/dist/vue-components14.es.js +50 -0
  44. package/dist/vue-components15.es.js +128 -0
  45. package/dist/vue-components16.es.js +65 -0
  46. package/dist/vue-components17.es.js +114 -0
  47. package/dist/vue-components18.es.js +26 -0
  48. package/dist/vue-components19.es.js +5 -0
  49. package/dist/vue-components2.es.js +11 -0
  50. package/dist/vue-components20.es.js +80 -0
  51. package/dist/vue-components21.es.js +115 -0
  52. package/dist/vue-components22.es.js +74 -0
  53. package/dist/vue-components23.es.js +12 -0
  54. package/dist/vue-components24.es.js +54 -0
  55. package/dist/vue-components25.es.js +5 -0
  56. package/dist/vue-components26.es.js +44 -0
  57. package/dist/vue-components27.es.js +5 -0
  58. package/dist/vue-components28.es.js +84 -0
  59. package/dist/vue-components3.es.js +98 -0
  60. package/dist/vue-components30.es.js +8 -0
  61. package/dist/vue-components31.es.js +9 -0
  62. package/dist/vue-components32.es.js +15 -0
  63. package/dist/vue-components33.es.js +24 -0
  64. package/dist/vue-components34.es.js +344 -0
  65. package/dist/vue-components36.es.js +8 -0
  66. package/dist/vue-components37.es.js +74 -0
  67. package/dist/vue-components38.es.js +5 -0
  68. package/dist/vue-components39.es.js +64 -0
  69. package/dist/vue-components4.es.js +5 -0
  70. package/dist/vue-components40.es.js +5 -0
  71. package/dist/vue-components41.es.js +24 -0
  72. package/dist/vue-components42.es.js +5 -0
  73. package/dist/vue-components43.es.js +59 -0
  74. package/dist/vue-components44.es.js +5 -0
  75. package/dist/vue-components45.es.js +12 -0
  76. package/dist/vue-components46.es.js +22 -0
  77. package/dist/vue-components48.es.js +9 -0
  78. package/dist/vue-components49.es.js +4 -0
  79. package/dist/vue-components5.es.js +24 -0
  80. package/dist/vue-components50.es.js +38 -0
  81. package/dist/vue-components51.es.js +27 -0
  82. package/dist/vue-components52.es.js +28 -0
  83. package/dist/vue-components53.es.js +7 -0
  84. package/dist/vue-components54.es.js +18 -0
  85. package/dist/vue-components55.es.js +36 -0
  86. package/dist/vue-components56.es.js +18 -0
  87. package/dist/vue-components57.es.js +21 -0
  88. package/dist/vue-components58.es.js +30 -0
  89. package/dist/vue-components59.es.js +7 -0
  90. package/dist/vue-components6.es.js +13 -0
  91. package/dist/vue-components60.es.js +9 -0
  92. package/dist/vue-components61.es.js +38 -0
  93. package/dist/vue-components62.es.js +25 -0
  94. package/dist/vue-components63.es.js +128 -0
  95. package/dist/vue-components64.es.js +24 -0
  96. package/dist/vue-components65.es.js +21 -0
  97. package/dist/vue-components66.es.js +9 -0
  98. package/dist/vue-components67.es.js +19 -0
  99. package/dist/vue-components68.es.js +5 -0
  100. package/dist/vue-components69.es.js +29 -0
  101. package/dist/vue-components7.es.js +13 -0
  102. package/dist/vue-components70.es.js +5 -0
  103. package/dist/vue-components71.es.js +43 -0
  104. package/dist/vue-components72.es.js +101 -0
  105. package/dist/vue-components73.es.js +36 -0
  106. package/dist/vue-components74.es.js +19 -0
  107. package/dist/vue-components75.es.js +50 -0
  108. package/dist/vue-components8.es.js +35 -0
  109. package/dist/vue-components9.es.js +47 -0
  110. package/package.json +39 -33
  111. package/src/components/CommandButton.vue +96 -16
  112. package/src/components/Dialog.vue +0 -1
  113. package/src/components/FixedNuxtErrorBoundary.vue +112 -0
  114. package/src/components/OmegaForm/InputProps.ts +2 -1
  115. package/src/components/OmegaForm/OmegaArray.vue +9 -23
  116. package/src/components/OmegaForm/OmegaAutoGen.vue +3 -2
  117. package/src/components/OmegaForm/OmegaErrorsInternal.vue +20 -17
  118. package/src/components/OmegaForm/OmegaFormInput.vue +1 -1
  119. package/src/components/OmegaForm/OmegaInput.vue +29 -43
  120. package/src/components/OmegaForm/OmegaInputVuetify.vue +122 -16
  121. package/src/components/OmegaForm/OmegaInternalInput.vue +33 -13
  122. package/src/components/OmegaForm/OmegaTaggedUnion.vue +10 -3
  123. package/src/components/OmegaForm/OmegaTaggedUnionInternal.vue +6 -6
  124. package/src/components/OmegaForm/OmegaWrapper.vue +1 -1
  125. package/src/components/OmegaForm/blockDialog.ts +18 -6
  126. package/src/components/OmegaForm/createUseFormWithCustomInput.ts +16 -14
  127. package/src/components/OmegaForm/decimalSeparatorInput.ts +38 -0
  128. package/src/components/OmegaForm/errors.ts +139 -0
  129. package/src/components/OmegaForm/getOmegaStore.ts +1 -1
  130. package/src/components/OmegaForm/hocs.ts +19 -0
  131. package/src/components/OmegaForm/index.ts +17 -4
  132. package/src/components/OmegaForm/inputs.ts +22 -0
  133. package/src/components/OmegaForm/meta/checks.ts +107 -0
  134. package/src/components/OmegaForm/meta/createMeta.ts +140 -0
  135. package/src/components/OmegaForm/meta/defaults.ts +263 -0
  136. package/src/components/OmegaForm/meta/redacted.ts +71 -0
  137. package/src/components/OmegaForm/meta/types.ts +78 -0
  138. package/src/components/OmegaForm/meta/walker.ts +248 -0
  139. package/src/components/OmegaForm/persistency.ts +247 -0
  140. package/src/components/OmegaForm/submit.ts +131 -0
  141. package/src/components/OmegaForm/types.ts +801 -0
  142. package/src/components/OmegaForm/useOmegaForm.ts +103 -896
  143. package/src/components/OmegaForm/useRegisterField.ts +1 -1
  144. package/src/components/OmegaForm/validation/localized.ts +242 -0
  145. package/src/components/index.ts +1 -0
  146. package/src/index.ts +0 -1
  147. package/src/reset.css +39 -38
  148. package/src/utils/index.ts +11 -8
  149. package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +0 -159
  150. package/dist/types/constants/index.d.ts +0 -1
  151. package/dist/vue-components.es10.js +0 -239
  152. package/dist/vue-components.es11.js +0 -32
  153. package/dist/vue-components.es12.js +0 -503
  154. package/dist/vue-components.es13.js +0 -49
  155. package/dist/vue-components.es14.js +0 -4
  156. package/dist/vue-components.es15.js +0 -4
  157. package/dist/vue-components.es16.js +0 -6
  158. package/dist/vue-components.es17.js +0 -13
  159. package/dist/vue-components.es18.js +0 -57
  160. package/dist/vue-components.es19.js +0 -56
  161. package/dist/vue-components.es2.js +0 -30
  162. package/dist/vue-components.es20.js +0 -8
  163. package/dist/vue-components.es21.js +0 -8
  164. package/dist/vue-components.es22.js +0 -5
  165. package/dist/vue-components.es23.js +0 -5
  166. package/dist/vue-components.es24.js +0 -4
  167. package/dist/vue-components.es25.js +0 -4
  168. package/dist/vue-components.es26.js +0 -4
  169. package/dist/vue-components.es27.js +0 -4
  170. package/dist/vue-components.es28.js +0 -19
  171. package/dist/vue-components.es29.js +0 -13
  172. package/dist/vue-components.es3.js +0 -17
  173. package/dist/vue-components.es30.js +0 -31
  174. package/dist/vue-components.es31.js +0 -6
  175. package/dist/vue-components.es32.js +0 -4
  176. package/dist/vue-components.es33.js +0 -4
  177. package/dist/vue-components.es34.js +0 -113
  178. package/dist/vue-components.es36.js +0 -9
  179. package/dist/vue-components.es37.js +0 -34
  180. package/dist/vue-components.es39.js +0 -194
  181. package/dist/vue-components.es4.js +0 -52
  182. package/dist/vue-components.es41.js +0 -6
  183. package/dist/vue-components.es42.js +0 -25
  184. package/dist/vue-components.es43.js +0 -7
  185. package/dist/vue-components.es44.js +0 -23
  186. package/dist/vue-components.es45.js +0 -32
  187. package/dist/vue-components.es46.js +0 -24
  188. package/dist/vue-components.es47.js +0 -14
  189. package/dist/vue-components.es48.js +0 -7
  190. package/dist/vue-components.es49.js +0 -21
  191. package/dist/vue-components.es5.js +0 -52
  192. package/dist/vue-components.es50.js +0 -11
  193. package/dist/vue-components.es51.js +0 -33
  194. package/dist/vue-components.es52.js +0 -50
  195. package/dist/vue-components.es53.js +0 -28
  196. package/dist/vue-components.es54.js +0 -13
  197. package/dist/vue-components.es55.js +0 -67
  198. package/dist/vue-components.es56.js +0 -58
  199. package/dist/vue-components.es57.js +0 -19
  200. package/dist/vue-components.es58.js +0 -35
  201. package/dist/vue-components.es59.js +0 -31
  202. package/dist/vue-components.es6.js +0 -69
  203. package/dist/vue-components.es60.js +0 -44
  204. package/dist/vue-components.es61.js +0 -4
  205. package/dist/vue-components.es62.js +0 -46
  206. package/dist/vue-components.es63.js +0 -4
  207. package/dist/vue-components.es7.js +0 -83
  208. package/dist/vue-components.es8.js +0 -63
  209. package/dist/vue-components.es9.js +0 -21
  210. package/src/components/OmegaForm/OmegaFormStuff.ts +0 -1276
  211. package/src/constants/index.ts +0 -1
@@ -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
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,131 @@
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 * as Effect from "effect-app/Effect"
6
+ import * as Option from "effect-app/Option"
7
+ import { runtimeFiberAsPromise } from "effect-app/utils"
8
+ import * as Data from "effect/Data"
9
+ import * as Fiber from "effect/Fiber"
10
+ import type { Fiber as EffectFiber } from "effect/Fiber"
11
+ import type { OmegaFormApi, OmegaFormParams } from "./types"
12
+
13
+ export class FormErrors<From> extends Data.TaggedError("FormErrors")<{
14
+ form: {
15
+ // TODO: error shapes seem off, with `undefined` etc..
16
+ errors: (Record<string, StandardSchemaV1Issue[]> | undefined)[]
17
+ errorMap: ValidationErrorMap<
18
+ undefined,
19
+ undefined,
20
+ Record<string, StandardSchemaV1Issue[]>,
21
+ undefined,
22
+ undefined,
23
+ undefined,
24
+ undefined,
25
+ undefined,
26
+ undefined,
27
+ undefined
28
+ >
29
+ }
30
+ fields: Record<DeepKeys<From>, {
31
+ errors: ValidationError[]
32
+ errorMap: ValidationErrorMap
33
+ }>
34
+ }> {}
35
+
36
+ export const wrapWithSpan = (span: api.Span | undefined, toWrap: () => any) => {
37
+ return span ? api.context.with(api.trace.setSpan(api.context.active(), span), toWrap) : toWrap()
38
+ }
39
+
40
+ export type UserOnSubmit<From, To> = (props: {
41
+ formApi: OmegaFormParams<From, To>
42
+ meta: any
43
+ value: To
44
+ }) => Promise<any> | EffectFiber<any, any> | Effect.Effect<unknown, any>
45
+
46
+ export type RunPromise = <A, E>(eff: Effect.Effect<A, E>) => Promise<A>
47
+
48
+ /**
49
+ * Wraps the user's `onSubmit` to:
50
+ * - run inside the OpenTelemetry span passed via `meta.currentSpan`
51
+ * - decode the raw form `value` (validators only validate, they don't transform)
52
+ * - normalize Promise / Effect / Fiber return values to a Promise
53
+ *
54
+ * Returns `undefined` when `userOnSubmit` is `undefined` (so callers can pass it
55
+ * directly to `useForm({ onSubmit })` without changing semantics).
56
+ */
57
+ export const wrapOnSubmit = <From, To>(
58
+ userOnSubmit: UserOnSubmit<From, To> | undefined,
59
+ decode: (value: From) => Effect.Effect<To, any>,
60
+ runPromise: RunPromise
61
+ ) => {
62
+ if (!userOnSubmit) return undefined
63
+ return ({ formApi, meta, value }: { formApi: OmegaFormParams<From, To>; meta: any; value: From }) =>
64
+ wrapWithSpan(meta?.currentSpan, async () => {
65
+ // validators only validate, they don't actually transform, so we have to do that manually here.
66
+ const parsedValue = await runPromise(decode(value))
67
+ const r = userOnSubmit({
68
+ formApi,
69
+ meta,
70
+ value: parsedValue
71
+ })
72
+ if (Fiber.isFiber(r)) {
73
+ return await runtimeFiberAsPromise(r)
74
+ }
75
+ if (Effect.isEffect(r)) {
76
+ const effectResult = await runPromise(r)
77
+ return Fiber.isFiber(effectResult)
78
+ ? await runtimeFiberAsPromise(effectResult)
79
+ : effectResult
80
+ }
81
+ return r
82
+ })
83
+ }
84
+
85
+ /**
86
+ * Builds the public submit handlers from a `useForm`-returned `form`:
87
+ * - `handleSubmit` injects the current OpenTelemetry span as `meta.currentSpan`.
88
+ * - `handleSubmitEffect` runs `handleSubmit` inside an Effect that picks up the
89
+ * ambient `Effect.currentSpan`. With `checkErrors: true`, it fails with
90
+ * `FormErrors<From>` when validation produced errors.
91
+ */
92
+ export const makeSubmitHandlers = <From, To>(
93
+ form: OmegaFormApi<From, To>
94
+ ) => {
95
+ const hs = form.handleSubmit.bind(form)
96
+
97
+ const handleSubmitInner: typeof form.handleSubmit = async (meta?: Record<string, any>) => {
98
+ return await hs(meta)
99
+ }
100
+
101
+ const handleSubmit = (meta?: Record<string, any>) => {
102
+ const span = api.trace.getSpan(api.context.active())
103
+ return handleSubmitInner({ currentSpan: span, ...meta })
104
+ }
105
+
106
+ const handleSubmitEffect_ = (meta?: Record<string, any>) =>
107
+ Effect.currentSpan.pipe(
108
+ Effect.option,
109
+ Effect
110
+ .flatMap((span) =>
111
+ Effect.promise(() => handleSubmitInner(Option.isSome(span) ? { currentSpan: span.value, ...meta } : meta))
112
+ )
113
+ )
114
+
115
+ const handleSubmitEffect: {
116
+ (options: { checkErrors: true; meta?: Record<string, any> }): Effect.Effect<void, FormErrors<From>>
117
+ (options?: { meta?: Record<string, any> }): Effect.Effect<void>
118
+ } = (
119
+ options?: { meta?: Record<string, any>; checkErrors?: true }
120
+ ): any =>
121
+ options?.checkErrors
122
+ ? handleSubmitEffect_(options?.meta).pipe(Effect.flatMap(Effect.fnUntraced(function*() {
123
+ const errors = form.getAllErrors()
124
+ if (Object.keys(errors.fields).length || errors.form.errors.length) {
125
+ return yield* Effect.fail(new FormErrors({ form: errors.form, fields: errors.fields }))
126
+ }
127
+ })))
128
+ : handleSubmitEffect_(options?.meta)
129
+
130
+ return { handleSubmit, handleSubmitEffect }
131
+ }