@effect-app/vue-components 4.0.0-beta.31 → 4.0.0-beta.310

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 (208) 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 +3 -1
  5. package/dist/types/components/FixedNuxtErrorBoundary.vue.d.ts +39 -0
  6. package/dist/types/components/OmegaForm/InputProps.d.ts +1 -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 +1 -1
  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 +6 -0
  13. package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +6 -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/errors.d.ts +33 -0
  19. package/dist/types/components/OmegaForm/getOmegaStore.d.ts +1 -1
  20. package/dist/types/components/OmegaForm/hocs.d.ts +3 -0
  21. package/dist/types/components/OmegaForm/index.d.ts +14 -3
  22. package/dist/types/components/OmegaForm/inputs.d.ts +4 -0
  23. package/dist/types/components/OmegaForm/meta/checks.d.ts +4 -0
  24. package/dist/types/components/OmegaForm/meta/createMeta.d.ts +33 -0
  25. package/dist/types/components/OmegaForm/meta/defaults.d.ts +21 -0
  26. package/dist/types/components/OmegaForm/meta/redacted.d.ts +2 -0
  27. package/dist/types/components/OmegaForm/meta/types.d.ts +56 -0
  28. package/dist/types/components/OmegaForm/meta/walker.d.ts +18 -0
  29. package/dist/types/components/OmegaForm/persistency.d.ts +58 -0
  30. package/dist/types/components/OmegaForm/submit.d.ts +60 -0
  31. package/dist/types/components/OmegaForm/types.d.ts +312 -0
  32. package/dist/types/components/OmegaForm/useOmegaForm.d.ts +9 -213
  33. package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
  34. package/dist/types/components/index.d.ts +1 -0
  35. package/dist/types/index.d.ts +0 -1
  36. package/dist/types/utils/index.d.ts +8 -8
  37. package/dist/vue-components.es.js +30 -44
  38. package/dist/vue-components10.es.js +5 -0
  39. package/dist/vue-components11.es.js +56 -0
  40. package/dist/vue-components12.es.js +5 -0
  41. package/dist/vue-components13.es.js +20 -0
  42. package/dist/vue-components14.es.js +49 -0
  43. package/dist/vue-components15.es.js +128 -0
  44. package/dist/vue-components16.es.js +65 -0
  45. package/dist/vue-components17.es.js +114 -0
  46. package/dist/vue-components18.es.js +22 -0
  47. package/dist/vue-components19.es.js +5 -0
  48. package/dist/vue-components2.es.js +11 -0
  49. package/dist/vue-components20.es.js +80 -0
  50. package/dist/vue-components21.es.js +93 -0
  51. package/dist/vue-components22.es.js +73 -0
  52. package/dist/vue-components23.es.js +12 -0
  53. package/dist/vue-components24.es.js +57 -0
  54. package/dist/vue-components25.es.js +5 -0
  55. package/dist/vue-components26.es.js +44 -0
  56. package/dist/vue-components27.es.js +5 -0
  57. package/dist/vue-components28.es.js +84 -0
  58. package/dist/vue-components3.es.js +98 -0
  59. package/dist/vue-components30.es.js +8 -0
  60. package/dist/vue-components31.es.js +9 -0
  61. package/dist/vue-components32.es.js +24 -0
  62. package/dist/vue-components33.es.js +300 -0
  63. package/dist/vue-components35.es.js +8 -0
  64. package/dist/vue-components36.es.js +73 -0
  65. package/dist/vue-components37.es.js +5 -0
  66. package/dist/vue-components38.es.js +64 -0
  67. package/dist/vue-components39.es.js +5 -0
  68. package/dist/vue-components4.es.js +5 -0
  69. package/dist/vue-components40.es.js +24 -0
  70. package/dist/vue-components41.es.js +5 -0
  71. package/dist/vue-components42.es.js +59 -0
  72. package/dist/vue-components43.es.js +5 -0
  73. package/dist/vue-components44.es.js +12 -0
  74. package/dist/vue-components45.es.js +22 -0
  75. package/dist/vue-components47.es.js +9 -0
  76. package/dist/vue-components48.es.js +4 -0
  77. package/dist/vue-components49.es.js +38 -0
  78. package/dist/vue-components5.es.js +24 -0
  79. package/dist/vue-components50.es.js +27 -0
  80. package/dist/vue-components51.es.js +28 -0
  81. package/dist/vue-components52.es.js +7 -0
  82. package/dist/vue-components53.es.js +18 -0
  83. package/dist/vue-components54.es.js +36 -0
  84. package/dist/vue-components55.es.js +18 -0
  85. package/dist/vue-components56.es.js +21 -0
  86. package/dist/vue-components57.es.js +30 -0
  87. package/dist/vue-components58.es.js +7 -0
  88. package/dist/vue-components59.es.js +9 -0
  89. package/dist/vue-components6.es.js +13 -0
  90. package/dist/vue-components60.es.js +38 -0
  91. package/dist/vue-components61.es.js +25 -0
  92. package/dist/vue-components62.es.js +128 -0
  93. package/dist/vue-components63.es.js +24 -0
  94. package/dist/vue-components64.es.js +21 -0
  95. package/dist/vue-components65.es.js +9 -0
  96. package/dist/vue-components66.es.js +19 -0
  97. package/dist/vue-components67.es.js +5 -0
  98. package/dist/vue-components68.es.js +29 -0
  99. package/dist/vue-components69.es.js +5 -0
  100. package/dist/vue-components7.es.js +13 -0
  101. package/dist/vue-components70.es.js +43 -0
  102. package/dist/vue-components71.es.js +101 -0
  103. package/dist/vue-components72.es.js +36 -0
  104. package/dist/vue-components73.es.js +19 -0
  105. package/dist/vue-components74.es.js +50 -0
  106. package/dist/vue-components8.es.js +35 -0
  107. package/dist/vue-components9.es.js +47 -0
  108. package/package.json +39 -33
  109. package/src/components/CommandButton.vue +96 -16
  110. package/src/components/Dialog.vue +0 -1
  111. package/src/components/FixedNuxtErrorBoundary.vue +112 -0
  112. package/src/components/OmegaForm/InputProps.ts +1 -1
  113. package/src/components/OmegaForm/OmegaArray.vue +8 -9
  114. package/src/components/OmegaForm/OmegaAutoGen.vue +3 -2
  115. package/src/components/OmegaForm/OmegaErrorsInternal.vue +1 -1
  116. package/src/components/OmegaForm/OmegaFormInput.vue +1 -1
  117. package/src/components/OmegaForm/OmegaInput.vue +26 -40
  118. package/src/components/OmegaForm/OmegaInputVuetify.vue +58 -8
  119. package/src/components/OmegaForm/OmegaInternalInput.vue +32 -13
  120. package/src/components/OmegaForm/OmegaTaggedUnion.vue +10 -3
  121. package/src/components/OmegaForm/OmegaTaggedUnionInternal.vue +6 -6
  122. package/src/components/OmegaForm/OmegaWrapper.vue +1 -1
  123. package/src/components/OmegaForm/blockDialog.ts +18 -6
  124. package/src/components/OmegaForm/createUseFormWithCustomInput.ts +16 -14
  125. package/src/components/OmegaForm/errors.ts +136 -0
  126. package/src/components/OmegaForm/getOmegaStore.ts +1 -1
  127. package/src/components/OmegaForm/hocs.ts +19 -0
  128. package/src/components/OmegaForm/index.ts +17 -4
  129. package/src/components/OmegaForm/inputs.ts +22 -0
  130. package/src/components/OmegaForm/meta/checks.ts +82 -0
  131. package/src/components/OmegaForm/meta/createMeta.ts +140 -0
  132. package/src/components/OmegaForm/meta/defaults.ts +262 -0
  133. package/src/components/OmegaForm/meta/redacted.ts +66 -0
  134. package/src/components/OmegaForm/meta/types.ts +78 -0
  135. package/src/components/OmegaForm/meta/walker.ts +248 -0
  136. package/src/components/OmegaForm/persistency.ts +247 -0
  137. package/src/components/OmegaForm/submit.ts +131 -0
  138. package/src/components/OmegaForm/types.ts +801 -0
  139. package/src/components/OmegaForm/useOmegaForm.ts +103 -896
  140. package/src/components/OmegaForm/useRegisterField.ts +1 -1
  141. package/src/components/OmegaForm/validation/localized.ts +203 -0
  142. package/src/components/index.ts +1 -0
  143. package/src/index.ts +0 -1
  144. package/src/reset.css +39 -38
  145. package/src/utils/index.ts +11 -8
  146. package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +0 -159
  147. package/dist/types/constants/index.d.ts +0 -1
  148. package/dist/vue-components.es10.js +0 -239
  149. package/dist/vue-components.es11.js +0 -32
  150. package/dist/vue-components.es12.js +0 -503
  151. package/dist/vue-components.es13.js +0 -49
  152. package/dist/vue-components.es14.js +0 -4
  153. package/dist/vue-components.es15.js +0 -4
  154. package/dist/vue-components.es16.js +0 -6
  155. package/dist/vue-components.es17.js +0 -13
  156. package/dist/vue-components.es18.js +0 -57
  157. package/dist/vue-components.es19.js +0 -56
  158. package/dist/vue-components.es2.js +0 -30
  159. package/dist/vue-components.es20.js +0 -8
  160. package/dist/vue-components.es21.js +0 -8
  161. package/dist/vue-components.es22.js +0 -5
  162. package/dist/vue-components.es23.js +0 -5
  163. package/dist/vue-components.es24.js +0 -4
  164. package/dist/vue-components.es25.js +0 -4
  165. package/dist/vue-components.es26.js +0 -4
  166. package/dist/vue-components.es27.js +0 -4
  167. package/dist/vue-components.es28.js +0 -19
  168. package/dist/vue-components.es29.js +0 -13
  169. package/dist/vue-components.es3.js +0 -17
  170. package/dist/vue-components.es30.js +0 -194
  171. package/dist/vue-components.es32.js +0 -31
  172. package/dist/vue-components.es33.js +0 -6
  173. package/dist/vue-components.es34.js +0 -4
  174. package/dist/vue-components.es35.js +0 -4
  175. package/dist/vue-components.es36.js +0 -113
  176. package/dist/vue-components.es38.js +0 -9
  177. package/dist/vue-components.es39.js +0 -34
  178. package/dist/vue-components.es4.js +0 -52
  179. package/dist/vue-components.es41.js +0 -6
  180. package/dist/vue-components.es42.js +0 -25
  181. package/dist/vue-components.es43.js +0 -7
  182. package/dist/vue-components.es44.js +0 -23
  183. package/dist/vue-components.es45.js +0 -32
  184. package/dist/vue-components.es46.js +0 -24
  185. package/dist/vue-components.es47.js +0 -14
  186. package/dist/vue-components.es48.js +0 -7
  187. package/dist/vue-components.es49.js +0 -21
  188. package/dist/vue-components.es5.js +0 -52
  189. package/dist/vue-components.es50.js +0 -11
  190. package/dist/vue-components.es51.js +0 -33
  191. package/dist/vue-components.es52.js +0 -50
  192. package/dist/vue-components.es53.js +0 -28
  193. package/dist/vue-components.es54.js +0 -13
  194. package/dist/vue-components.es55.js +0 -67
  195. package/dist/vue-components.es56.js +0 -58
  196. package/dist/vue-components.es57.js +0 -19
  197. package/dist/vue-components.es58.js +0 -35
  198. package/dist/vue-components.es59.js +0 -31
  199. package/dist/vue-components.es6.js +0 -69
  200. package/dist/vue-components.es60.js +0 -44
  201. package/dist/vue-components.es61.js +0 -4
  202. package/dist/vue-components.es62.js +0 -46
  203. package/dist/vue-components.es63.js +0 -4
  204. package/dist/vue-components.es7.js +0 -83
  205. package/dist/vue-components.es8.js +0 -63
  206. package/dist/vue-components.es9.js +0 -21
  207. package/src/components/OmegaForm/OmegaFormStuff.ts +0 -1276
  208. package/src/constants/index.ts +0 -1
@@ -0,0 +1,140 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type * as Effect from "effect-app/Effect"
3
+ import * as S from "effect-app/Schema"
4
+ import type * as Record from "effect/Record"
5
+ import { getTransformationFrom } from "../../../utils"
6
+ import type { FieldMeta, MetaRecord } from "./types"
7
+ import { classifyAndWalkUnion, leafMetaForAst, type ParentMeta, type WalkerContext, walkStruct } from "./walker"
8
+
9
+ export type FilterItems = {
10
+ items: readonly [string, ...string[]]
11
+ message:
12
+ | string
13
+ | Effect.Effect<string>
14
+ | { readonly message: string | Effect.Effect<string> }
15
+ }
16
+
17
+ export type CreateMeta =
18
+ & {
19
+ parent?: string
20
+ meta?: Record<string, any>
21
+ nullableOrUndefined?: false | "undefined" | "null"
22
+ }
23
+ & (
24
+ | {
25
+ propertySignatures: readonly S.AST.PropertySignature[]
26
+ property?: never
27
+ }
28
+ | {
29
+ propertySignatures?: never
30
+ property: S.AST.AST
31
+ }
32
+ )
33
+
34
+ export const unwrapDeclaration = (property: S.AST.AST): S.AST.AST => {
35
+ let current = getTransformationFrom(property)
36
+
37
+ while (S.AST.isDeclaration(current) && current.typeParameters.length > 0) {
38
+ current = getTransformationFrom(current.typeParameters[0])
39
+ }
40
+
41
+ return current
42
+ }
43
+
44
+ export const isNullableOrUndefined = (property: false | S.AST.AST | undefined) => {
45
+ if (!property || !S.AST.isUnion(property)) return false
46
+ if (property.types.find((_) => S.AST.isUndefined(_))) {
47
+ return "undefined"
48
+ }
49
+ if (property.types.find((_) => S.AST.isNull(_))) return "null"
50
+ return false
51
+ }
52
+
53
+ export const createMeta = <T = any>(
54
+ { meta = {}, parent = "", property, propertySignatures }: CreateMeta,
55
+ acc: Partial<MetaRecord<T>> = {}
56
+ ): MetaRecord<T> | FieldMeta => {
57
+ const ctx: WalkerContext<T> = { acc, unionMeta: {} }
58
+
59
+ if (propertySignatures) {
60
+ const parentMeta: ParentMeta = {
61
+ required: meta.required !== false,
62
+ nullableOrUndefined: meta.nullableOrUndefined ?? false
63
+ }
64
+ walkStruct(propertySignatures, parent, parentMeta, ctx)
65
+ return acc
66
+ }
67
+
68
+ if (property) {
69
+ const nullableOrUndefined = isNullableOrUndefined(property)
70
+ const unwrapped = unwrapDeclaration(property)
71
+ const required = !Object.hasOwnProperty.call(meta, "required")
72
+ ? !nullableOrUndefined
73
+ : (meta.required as boolean)
74
+
75
+ const parentMeta: ParentMeta = {
76
+ required,
77
+ nullableOrUndefined: (meta.nullableOrUndefined ?? nullableOrUndefined) as false | "null" | "undefined"
78
+ }
79
+
80
+ if (S.AST.isObjects(unwrapped)) {
81
+ walkStruct(unwrapped.propertySignatures, parent, parentMeta, ctx)
82
+ return acc
83
+ }
84
+
85
+ if (S.AST.isUnion(unwrapped)) {
86
+ // For property-mode, return a FieldMeta by running through classifyAndWalkUnion
87
+ // and then pulling out the result at `parent` key
88
+ const leafCtx: WalkerContext<T> = { acc: {}, unionMeta: {} }
89
+ classifyAndWalkUnion(unwrapped, parent, parentMeta, leafCtx)
90
+ const result = (leafCtx.acc as any)[parent]
91
+ if (result) return result as FieldMeta
92
+ }
93
+
94
+ return leafMetaForAst(unwrapped, parentMeta)
95
+ }
96
+
97
+ return acc
98
+ }
99
+
100
+ export const metadataFromAst = <From, To>(
101
+ schema: S.Codec<To, From>
102
+ ): {
103
+ meta: MetaRecord<To>
104
+ defaultValues: Record<string, any>
105
+ unionMeta: Record<string, MetaRecord<To>>
106
+ } => {
107
+ const ast = unwrapDeclaration(schema.ast)
108
+ const newMeta: Partial<MetaRecord<To>> = {}
109
+ const defaultValues: Record<string, any> = {}
110
+ const unionMeta: Record<string, MetaRecord<To>> = {}
111
+
112
+ const ctx: WalkerContext<To> = { acc: newMeta, unionMeta }
113
+
114
+ if (S.AST.isUnion(ast)) {
115
+ // Root-level discriminated union
116
+ classifyAndWalkUnion(ast, "", { required: true, nullableOrUndefined: false }, ctx)
117
+
118
+ return { meta: newMeta, defaultValues, unionMeta }
119
+ }
120
+
121
+ if (S.AST.isObjects(ast)) {
122
+ walkStruct(ast.propertySignatures, "", { required: true, nullableOrUndefined: false }, ctx)
123
+
124
+ return { meta: newMeta, defaultValues, unionMeta }
125
+ }
126
+
127
+ return { meta: newMeta, defaultValues, unionMeta }
128
+ }
129
+
130
+ export const generateMetaFromSchema = <From, To>(
131
+ schema: S.Codec<To, From>
132
+ ): {
133
+ schema: S.Codec<To, From>
134
+ meta: MetaRecord<To>
135
+ unionMeta: Record<string, MetaRecord<To>>
136
+ } => {
137
+ const { meta, unionMeta } = metadataFromAst(schema)
138
+
139
+ return { schema, meta, unionMeta }
140
+ }
@@ -0,0 +1,262 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import * as Effect from "effect-app/Effect"
3
+ import * as Option from "effect-app/Option"
4
+ import * as S from "effect-app/Schema"
5
+ import { isNullableOrUndefined, unwrapDeclaration } from "./createMeta"
6
+
7
+ const extractDefaultFromLink = (link: any): unknown | undefined => {
8
+ if (!link?.transformation?.decode?.run) return undefined
9
+ try {
10
+ // oxlint-disable-next-line typescript/no-unnecessary-type-assertion -- vue-tsc needs the decoded value narrowed at this boundary
11
+ const result = Effect.runSync(link.transformation.decode.run(Option.none())) as Option.Option<unknown>
12
+ return Option.isSome(result) ? result.value : undefined
13
+ } catch {
14
+ return undefined
15
+ }
16
+ }
17
+
18
+ const getDefaultFromAst = (property: S.AST.AST) => {
19
+ // 1. Check withConstructorDefault (stored in context.defaultValue)
20
+ const constructorLink = property.context?.defaultValue?.[0]
21
+ const constructorDefault = extractDefaultFromLink(constructorLink)
22
+ if (constructorDefault !== undefined) return constructorDefault
23
+
24
+ // 2. Check withDecodingDefault (stored in encoding)
25
+ const encodingLink = property.encoding?.[0]
26
+ if (encodingLink && property.context?.isOptional) {
27
+ return extractDefaultFromLink(encodingLink)
28
+ }
29
+
30
+ return undefined
31
+ }
32
+
33
+ type SchemaWithMembers = {
34
+ members: readonly S.Schema<any>[]
35
+ }
36
+
37
+ type UnknownRecord = Record<string, unknown>
38
+
39
+ function hasMembers(schema: any): schema is SchemaWithMembers {
40
+ return schema && "members" in schema && Array.isArray(schema.members)
41
+ }
42
+
43
+ const isRecord = (value: unknown): value is UnknownRecord =>
44
+ value !== null && typeof value === "object" && !Array.isArray(value)
45
+
46
+ const isNullishAst = (ast: S.AST.AST) => S.AST.isNull(ast) || S.AST.isUndefined(ast)
47
+
48
+ const unionMembers = (ast: S.AST.AST): readonly S.AST.AST[] => {
49
+ const resolved = unwrapDeclaration(ast)
50
+ return S.AST.isUnion(resolved)
51
+ ? resolved.types.flatMap(unionMembers)
52
+ : [resolved]
53
+ }
54
+
55
+ const literalValue = (ast: S.AST.AST): unknown => {
56
+ const resolved = unwrapDeclaration(ast)
57
+ if (S.AST.isLiteral(resolved)) return resolved.literal
58
+ if (S.AST.isUnion(resolved) && resolved.types.length === 1) {
59
+ return literalValue(resolved.types[0])
60
+ }
61
+ }
62
+
63
+ const findTaggedObjectMember = (
64
+ members: readonly S.AST.Objects[],
65
+ value: unknown
66
+ ): S.AST.Objects | undefined => {
67
+ if (!isRecord(value) || value._tag === undefined) return undefined
68
+
69
+ return members.find((member) => {
70
+ const tagProp = member.propertySignatures.find((prop) => prop.name.toString() === "_tag")
71
+ return tagProp ? literalValue(tagProp.type) === value._tag : false
72
+ })
73
+ }
74
+
75
+ // Internal implementation with WeakSet tracking
76
+ export const defaultsValueFromSchema = (
77
+ schema: S.Schema<any>,
78
+ record: Record<string, any> = {}
79
+ ): any => {
80
+ const ast = schema.ast
81
+ const defaultValue = getDefaultFromAst(ast)
82
+
83
+ if (defaultValue !== undefined) {
84
+ return defaultValue
85
+ }
86
+
87
+ if (isNullableOrUndefined(schema.ast) === "null") {
88
+ return null
89
+ }
90
+ if (isNullableOrUndefined(schema.ast) === "undefined") {
91
+ return undefined
92
+ }
93
+
94
+ // Handle structs via AST (covers plain structs, transformed schemas like decodeTo, Class, etc.)
95
+ const objectsAst = S.AST.isObjects(ast)
96
+ ? ast
97
+ : S.AST.isDeclaration(ast)
98
+ ? unwrapDeclaration(ast)
99
+ : undefined
100
+ if (objectsAst && S.AST.isObjects(objectsAst)) {
101
+ const result: Record<string, any> = {}
102
+
103
+ for (const prop of objectsAst.propertySignatures) {
104
+ const key = prop.name.toString()
105
+ const propType = prop.type
106
+
107
+ const propDefault = getDefaultFromAst(propType)
108
+ if (propDefault !== undefined) {
109
+ result[key] = propDefault
110
+ continue
111
+ }
112
+
113
+ const propSchema = S.make<any>(propType)
114
+ const propValue = defaultsValueFromSchema(propSchema, record[key] || {})
115
+
116
+ if (propValue !== undefined) {
117
+ result[key] = propValue
118
+ } else if (isNullableOrUndefined(propType) === "undefined") {
119
+ result[key] = undefined
120
+ }
121
+ }
122
+
123
+ return { ...result, ...record }
124
+ }
125
+
126
+ // Handle unions via AST or schema-level .members
127
+ const unionTypes = S.AST.isUnion(ast)
128
+ ? ast.types
129
+ : hasMembers(schema)
130
+ ? schema.members.map((m) => m.ast)
131
+ : undefined
132
+ if (unionTypes) {
133
+ const mergedFields: Record<string, { ast: S.AST.AST }> = {}
134
+
135
+ for (const memberAstRaw of unionTypes) {
136
+ const memberAst = unwrapDeclaration(memberAstRaw)
137
+ if (!S.AST.isObjects(memberAst)) continue
138
+
139
+ for (const prop of memberAst.propertySignatures) {
140
+ const key = prop.name.toString()
141
+ const fieldDefault = getDefaultFromAst(prop.type)
142
+ const existingDefault = mergedFields[key] ? getDefaultFromAst(mergedFields[key].ast) : undefined
143
+
144
+ if (!mergedFields[key] || (fieldDefault !== undefined && existingDefault === undefined)) {
145
+ mergedFields[key] = { ast: prop.type }
146
+ }
147
+ }
148
+ }
149
+
150
+ if (Object.keys(mergedFields).length === 0) {
151
+ return Object.keys(record).length > 0 ? record : undefined
152
+ }
153
+
154
+ return Object.entries(mergedFields).reduce((acc, [key, { ast: propAst }]) => {
155
+ acc[key] = defaultsValueFromSchema(S.make(propAst), record[key] || {})
156
+ return acc
157
+ }, record)
158
+ }
159
+
160
+ if (Object.keys(record).length === 0) {
161
+ if (S.AST.isString(ast)) {
162
+ return ""
163
+ }
164
+
165
+ if (S.AST.isBoolean(ast)) {
166
+ return false
167
+ }
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Deep-fills a partial form value with schema defaults for any nullable
173
+ * struct that has **materialised**.
174
+ *
175
+ * A nullable struct (`S.NullOr(S.Struct(...))`) left `null` stays `null`, but
176
+ * once it materialises — e.g. the user filled a single child — its untouched
177
+ * children are filled with their schema default (or `null` when nullable).
178
+ * Without this, strict `S.NullOr(...)` children reject the leftover
179
+ * `undefined` with a spurious "field must not be empty" error.
180
+ *
181
+ * Only children of a struct reached *through a nullable union* are filled;
182
+ * the always-present root struct keeps whatever fields it already has, so the
183
+ * form's own default-value priority is left untouched.
184
+ *
185
+ * Reference-preserving: returns `value` unchanged (same reference) when there
186
+ * is nothing to fill, so callers can detect a no-op with `===` and the result
187
+ * is idempotent (`fill(fill(v)) === fill(v)`).
188
+ */
189
+ export const fillNestedDefaults = (
190
+ ast: S.AST.AST,
191
+ value: unknown,
192
+ fillMissing = false
193
+ ): unknown => {
194
+ const resolved = unwrapDeclaration(ast)
195
+
196
+ switch (resolved._tag) {
197
+ case "Union": {
198
+ if (value === null || value === undefined) return value
199
+ const members = unionMembers(resolved)
200
+ const objectMembers = members.filter(S.AST.isObjects)
201
+ if (objectMembers.length === 0) return value
202
+
203
+ const hasNullishMember = members.some(isNullishAst)
204
+ const taggedMember = findTaggedObjectMember(objectMembers, value)
205
+ if (taggedMember) {
206
+ return fillNestedDefaults(taggedMember, value, fillMissing || hasNullishMember)
207
+ }
208
+
209
+ if (hasNullishMember && objectMembers.length === 1) {
210
+ return fillNestedDefaults(objectMembers[0], value, true)
211
+ }
212
+
213
+ return value
214
+ }
215
+
216
+ case "Arrays": {
217
+ if (!Array.isArray(value)) return value
218
+ const element = resolved.rest[0]
219
+ if (!element) return value
220
+ let changed = false
221
+ const next = value.map((item) => {
222
+ const filled = fillNestedDefaults(element, item, fillMissing)
223
+ if (filled !== item) changed = true
224
+ return filled
225
+ })
226
+ return changed ? next : value
227
+ }
228
+
229
+ case "Objects": {
230
+ if (!isRecord(value)) return value
231
+ let result: UnknownRecord = value
232
+ let changed = false
233
+ const set = (key: string, next: unknown) => {
234
+ if (!changed) {
235
+ result = { ...value }
236
+ changed = true
237
+ }
238
+ result[key] = next
239
+ }
240
+ for (const prop of resolved.propertySignatures) {
241
+ const key = prop.name.toString()
242
+ const current = value[key]
243
+ if (current !== undefined) {
244
+ const filled = fillNestedDefaults(prop.type, current, fillMissing)
245
+ if (filled !== current) set(key, filled)
246
+ continue
247
+ }
248
+ if (!fillMissing || prop.type.context?.isOptional === true) continue
249
+ const propDefault = getDefaultFromAst(prop.type)
250
+ if (propDefault !== undefined) {
251
+ set(key, propDefault)
252
+ } else if (isNullableOrUndefined(prop.type) === "null") {
253
+ set(key, null)
254
+ }
255
+ }
256
+ return result
257
+ }
258
+
259
+ default:
260
+ return value
261
+ }
262
+ }
@@ -0,0 +1,66 @@
1
+ import * as S from "effect-app/Schema"
2
+
3
+ /*
4
+ * Checks if an AST node is a S.Redacted Declaration without encoding.
5
+ * These need to be swapped to S.RedactedFromValue for form usage
6
+ * because S.Redacted expects Redacted objects, not plain strings.
7
+ */
8
+ const isRedactedWithoutEncoding = (ast: S.AST.AST): boolean =>
9
+ S.AST.isDeclaration(ast)
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Effect Schema AST annotations are loosely typed
11
+ && (ast.annotations as any)?.typeConstructor?._tag === "effect/Redacted"
12
+ && !ast.encoding
13
+
14
+ /*
15
+ * Creates a form-compatible schema by replacing S.Redacted(X) with
16
+ * S.RedactedFromValue(X). S.Redacted is a Declaration that expects
17
+ * Redacted<A> on both encoded and type sides, so form inputs (which
18
+ * produce plain strings) fail validation. S.RedactedFromValue accepts
19
+ * plain values on the encoded side and wraps them in Redacted on decode.
20
+ */
21
+ export const toFormSchema = <From, To>(
22
+ schema: S.Codec<To, From>
23
+ ): S.Codec<To, From> => {
24
+ const ast = schema.ast
25
+ const objAst = S.AST.isObjects(ast)
26
+ ? ast
27
+ : S.AST.isDeclaration(ast)
28
+ ? S.AST.toEncoded(ast)
29
+ : null
30
+
31
+ if (!objAst || !("propertySignatures" in objAst)) return schema
32
+
33
+ let hasRedacted = false
34
+ const props: Record<string, S.Struct.Fields[string]> = {}
35
+
36
+ for (const p of objAst.propertySignatures) {
37
+ if (isRedactedWithoutEncoding(p.type)) {
38
+ hasRedacted = true
39
+ const innerSchema = S.make((p.type as S.AST.Declaration).typeParameters[0])
40
+ props[p.name as string] = S.RedactedFromValue(innerSchema)
41
+ } else if (S.AST.isUnion(p.type)) {
42
+ const types = p.type.types
43
+ const redactedType = types.find(isRedactedWithoutEncoding)
44
+ if (redactedType) {
45
+ hasRedacted = true
46
+ const innerSchema = S.make((redactedType as S.AST.Declaration).typeParameters[0])
47
+ const hasNull = types.some(S.AST.isNull)
48
+ const hasUndefined = types.some(S.AST.isUndefined)
49
+ const base = S.RedactedFromValue(innerSchema)
50
+ props[p.name as string] = hasNull && hasUndefined
51
+ ? S.NullishOr(base)
52
+ : hasNull
53
+ ? S.NullOr(base)
54
+ : hasUndefined
55
+ ? S.UndefinedOr(base)
56
+ : base
57
+ } else {
58
+ props[p.name as string] = S.make(p.type)
59
+ }
60
+ } else {
61
+ props[p.name as string] = S.make(p.type)
62
+ }
63
+ }
64
+
65
+ return hasRedacted ? S.Struct(props) as unknown as S.Codec<To, From> : schema
66
+ }
@@ -0,0 +1,78 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import type { DeepKeys } from "@tanstack/vue-form"
3
+ import type * as S from "effect-app/Schema"
4
+ import type { Redacted } from "effect/Redacted"
5
+
6
+ // Recursively replace Redacted<A> with its inner type so DeepKeys treats it as a leaf
7
+ type StripRedacted<T> = T extends Redacted<any> ? string
8
+ : T extends ReadonlyArray<infer U> ? ReadonlyArray<StripRedacted<U>>
9
+ : T extends Record<string, any> ? { [K in keyof T]: StripRedacted<T[K]> }
10
+ : T
11
+
12
+ export type NestedKeyOf<T> = DeepKeys<StripRedacted<T>>
13
+
14
+ // Field metadata type definitions
15
+ export type BaseFieldMeta = {
16
+ required: boolean
17
+ nullableOrUndefined?: false | "undefined" | "null"
18
+ /**
19
+ * True when the schema property is `S.optionalKey` (AST
20
+ * `context.isOptional`) — i.e. the key should be ABSENT from the submitted
21
+ * object when empty, not present with `undefined`. Distinct from
22
+ * `required: false`, which may also mean "empty string is valid" for
23
+ * unconstrained `S.String` fields.
24
+ */
25
+ isOptionalKey?: boolean
26
+ }
27
+
28
+ export type StringFieldMeta = BaseFieldMeta & {
29
+ type: "string"
30
+ maxLength?: number
31
+ minLength?: number
32
+ format?: string
33
+ }
34
+
35
+ export type NumberFieldMeta = BaseFieldMeta & {
36
+ type: "number"
37
+ minimum?: number
38
+ maximum?: number
39
+ exclusiveMinimum?: number
40
+ exclusiveMaximum?: number
41
+ refinement?: "int"
42
+ }
43
+
44
+ export type SelectFieldMeta = BaseFieldMeta & {
45
+ type: "select"
46
+ members: any[] // TODO: should be non empty array?
47
+ }
48
+
49
+ export type MultipleFieldMeta = BaseFieldMeta & {
50
+ type: "multiple"
51
+ members: any[] // TODO: should be non empty array?
52
+ rest: readonly S.AST.AST[]
53
+ }
54
+
55
+ export type BooleanFieldMeta = BaseFieldMeta & {
56
+ type: "boolean"
57
+ }
58
+
59
+ export type DateFieldMeta = BaseFieldMeta & {
60
+ type: "date"
61
+ }
62
+
63
+ export type UnknownFieldMeta = BaseFieldMeta & {
64
+ type: "unknown"
65
+ }
66
+
67
+ export type FieldMeta =
68
+ | StringFieldMeta
69
+ | NumberFieldMeta
70
+ | SelectFieldMeta
71
+ | MultipleFieldMeta
72
+ | BooleanFieldMeta
73
+ | DateFieldMeta
74
+ | UnknownFieldMeta
75
+
76
+ export type MetaRecord<T = string> = {
77
+ [K in NestedKeyOf<T>]?: FieldMeta
78
+ }