@effect-app/vue-components 4.0.0-beta.26 → 4.0.0-beta.261
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/README.md +13 -9
- package/dist/reset.css +39 -38
- package/dist/types/components/CommandButton.vue.d.ts +24 -5
- package/dist/types/components/OmegaForm/InputProps.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaArray.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +2 -2
- 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/OmegaTaggedUnion.vue.d.ts +2 -2
- package/dist/types/components/OmegaForm/OmegaTaggedUnionInternal.vue.d.ts +3 -3
- 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 +33 -0
- package/dist/types/components/OmegaForm/meta/defaults.d.ts +21 -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 +289 -0
- package/dist/types/components/OmegaForm/useOmegaForm.d.ts +7 -213
- package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/utils/index.d.ts +8 -8
- package/dist/vue-components.es.js +29 -44
- package/dist/vue-components10.es.js +5 -0
- package/dist/vue-components11.es.js +20 -0
- package/dist/vue-components12.es.js +49 -0
- package/dist/vue-components13.es.js +128 -0
- package/dist/vue-components14.es.js +65 -0
- package/dist/vue-components15.es.js +114 -0
- package/dist/vue-components16.es.js +22 -0
- package/dist/vue-components17.es.js +5 -0
- package/dist/vue-components18.es.js +80 -0
- package/dist/vue-components19.es.js +93 -0
- package/dist/vue-components2.es.js +11 -0
- package/dist/vue-components20.es.js +73 -0
- package/dist/vue-components21.es.js +12 -0
- package/dist/vue-components22.es.js +56 -0
- package/dist/vue-components23.es.js +5 -0
- package/dist/vue-components24.es.js +44 -0
- package/dist/vue-components25.es.js +5 -0
- package/dist/vue-components26.es.js +84 -0
- package/dist/vue-components28.es.js +8 -0
- package/dist/vue-components29.es.js +9 -0
- package/dist/vue-components3.es.js +98 -0
- package/dist/vue-components30.es.js +269 -0
- package/dist/vue-components32.es.js +8 -0
- package/dist/vue-components33.es.js +73 -0
- package/dist/vue-components34.es.js +5 -0
- package/dist/vue-components35.es.js +52 -0
- package/dist/vue-components36.es.js +5 -0
- package/dist/vue-components37.es.js +24 -0
- package/dist/vue-components38.es.js +5 -0
- package/dist/vue-components39.es.js +59 -0
- package/dist/vue-components4.es.js +5 -0
- package/dist/vue-components40.es.js +5 -0
- package/dist/vue-components41.es.js +12 -0
- package/dist/vue-components42.es.js +22 -0
- package/dist/vue-components44.es.js +9 -0
- package/dist/vue-components45.es.js +4 -0
- package/dist/vue-components46.es.js +38 -0
- package/dist/vue-components47.es.js +27 -0
- package/dist/vue-components48.es.js +28 -0
- package/dist/vue-components49.es.js +7 -0
- package/dist/vue-components5.es.js +24 -0
- package/dist/vue-components50.es.js +18 -0
- package/dist/vue-components51.es.js +36 -0
- package/dist/vue-components52.es.js +18 -0
- package/dist/vue-components53.es.js +21 -0
- package/dist/vue-components54.es.js +30 -0
- package/dist/vue-components55.es.js +7 -0
- package/dist/vue-components56.es.js +9 -0
- package/dist/vue-components57.es.js +38 -0
- package/dist/vue-components58.es.js +25 -0
- package/dist/vue-components59.es.js +128 -0
- package/dist/vue-components6.es.js +13 -0
- package/dist/vue-components60.es.js +24 -0
- package/dist/vue-components61.es.js +21 -0
- package/dist/vue-components62.es.js +9 -0
- package/dist/vue-components63.es.js +19 -0
- package/dist/vue-components64.es.js +5 -0
- package/dist/vue-components65.es.js +29 -0
- package/dist/vue-components66.es.js +5 -0
- package/dist/vue-components67.es.js +43 -0
- package/dist/vue-components68.es.js +100 -0
- package/dist/vue-components69.es.js +33 -0
- package/dist/vue-components7.es.js +13 -0
- package/dist/vue-components70.es.js +19 -0
- package/dist/vue-components71.es.js +48 -0
- package/dist/vue-components8.es.js +35 -0
- package/dist/vue-components9.es.js +47 -0
- package/package.json +30 -30
- package/src/components/CommandButton.vue +96 -16
- package/src/components/OmegaForm/InputProps.ts +1 -1
- package/src/components/OmegaForm/OmegaArray.vue +8 -9
- package/src/components/OmegaForm/OmegaAutoGen.vue +3 -2
- package/src/components/OmegaForm/OmegaErrorsInternal.vue +1 -1
- package/src/components/OmegaForm/OmegaFormInput.vue +1 -1
- package/src/components/OmegaForm/OmegaInput.vue +15 -38
- package/src/components/OmegaForm/OmegaInputVuetify.vue +5 -2
- package/src/components/OmegaForm/OmegaInternalInput.vue +17 -5
- package/src/components/OmegaForm/OmegaTaggedUnion.vue +10 -3
- package/src/components/OmegaForm/OmegaTaggedUnionInternal.vue +6 -6
- package/src/components/OmegaForm/OmegaWrapper.vue +1 -1
- package/src/components/OmegaForm/blockDialog.ts +18 -6
- 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 +82 -0
- package/src/components/OmegaForm/meta/createMeta.ts +140 -0
- package/src/components/OmegaForm/meta/defaults.ts +261 -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 +248 -0
- package/src/components/OmegaForm/persistency.ts +247 -0
- package/src/components/OmegaForm/submit.ts +131 -0
- package/src/components/OmegaForm/types.ts +759 -0
- package/src/components/OmegaForm/useOmegaForm.ts +99 -893
- package/src/components/OmegaForm/useRegisterField.ts +1 -1
- package/src/components/OmegaForm/validation/localized.ts +203 -0
- package/src/index.ts +0 -1
- package/src/reset.css +39 -38
- package/src/utils/index.ts +11 -8
- package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +0 -159
- package/dist/types/constants/index.d.ts +0 -1
- package/dist/vue-components.es10.js +0 -239
- package/dist/vue-components.es11.js +0 -32
- package/dist/vue-components.es12.js +0 -503
- package/dist/vue-components.es13.js +0 -49
- package/dist/vue-components.es14.js +0 -4
- package/dist/vue-components.es15.js +0 -4
- package/dist/vue-components.es16.js +0 -6
- package/dist/vue-components.es17.js +0 -13
- package/dist/vue-components.es18.js +0 -57
- package/dist/vue-components.es19.js +0 -56
- package/dist/vue-components.es2.js +0 -30
- package/dist/vue-components.es20.js +0 -8
- package/dist/vue-components.es21.js +0 -8
- package/dist/vue-components.es22.js +0 -5
- package/dist/vue-components.es23.js +0 -5
- package/dist/vue-components.es24.js +0 -4
- package/dist/vue-components.es25.js +0 -4
- package/dist/vue-components.es26.js +0 -4
- package/dist/vue-components.es27.js +0 -4
- package/dist/vue-components.es28.js +0 -19
- package/dist/vue-components.es29.js +0 -13
- package/dist/vue-components.es3.js +0 -17
- package/dist/vue-components.es30.js +0 -194
- package/dist/vue-components.es32.js +0 -31
- package/dist/vue-components.es33.js +0 -6
- package/dist/vue-components.es34.js +0 -4
- package/dist/vue-components.es35.js +0 -4
- package/dist/vue-components.es36.js +0 -113
- package/dist/vue-components.es38.js +0 -9
- package/dist/vue-components.es39.js +0 -34
- package/dist/vue-components.es4.js +0 -52
- package/dist/vue-components.es41.js +0 -6
- package/dist/vue-components.es42.js +0 -25
- package/dist/vue-components.es43.js +0 -7
- package/dist/vue-components.es44.js +0 -23
- package/dist/vue-components.es45.js +0 -32
- package/dist/vue-components.es46.js +0 -24
- package/dist/vue-components.es47.js +0 -14
- package/dist/vue-components.es48.js +0 -7
- package/dist/vue-components.es49.js +0 -21
- package/dist/vue-components.es5.js +0 -52
- package/dist/vue-components.es50.js +0 -11
- package/dist/vue-components.es51.js +0 -33
- package/dist/vue-components.es52.js +0 -50
- package/dist/vue-components.es53.js +0 -28
- package/dist/vue-components.es54.js +0 -13
- package/dist/vue-components.es55.js +0 -67
- package/dist/vue-components.es56.js +0 -58
- package/dist/vue-components.es57.js +0 -19
- package/dist/vue-components.es58.js +0 -35
- package/dist/vue-components.es59.js +0 -31
- package/dist/vue-components.es6.js +0 -69
- package/dist/vue-components.es60.js +0 -44
- package/dist/vue-components.es61.js +0 -4
- package/dist/vue-components.es62.js +0 -46
- package/dist/vue-components.es63.js +0 -4
- package/dist/vue-components.es7.js +0 -83
- package/dist/vue-components.es8.js +0 -63
- package/dist/vue-components.es9.js +0 -21
- package/src/components/OmegaForm/OmegaFormStuff.ts +0 -1276
- 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 as MetaRecord<To>, defaultValues, unionMeta }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (S.AST.isObjects(ast)) {
|
|
122
|
+
walkStruct(ast.propertySignatures, "", { required: true, nullableOrUndefined: false }, ctx)
|
|
123
|
+
|
|
124
|
+
return { meta: newMeta as MetaRecord<To>, defaultValues, unionMeta }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { meta: newMeta as MetaRecord<To>, 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,261 @@
|
|
|
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
|
+
const result = Effect.runSync(link.transformation.decode.run(Option.none())) as Option.Option<unknown>
|
|
11
|
+
return Option.isSome(result) ? result.value : undefined
|
|
12
|
+
} catch {
|
|
13
|
+
return undefined
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const getDefaultFromAst = (property: S.AST.AST) => {
|
|
18
|
+
// 1. Check withConstructorDefault (stored in context.defaultValue)
|
|
19
|
+
const constructorLink = property.context?.defaultValue?.[0]
|
|
20
|
+
const constructorDefault = extractDefaultFromLink(constructorLink)
|
|
21
|
+
if (constructorDefault !== undefined) return constructorDefault
|
|
22
|
+
|
|
23
|
+
// 2. Check withDecodingDefault (stored in encoding)
|
|
24
|
+
const encodingLink = property.encoding?.[0]
|
|
25
|
+
if (encodingLink && property.context?.isOptional) {
|
|
26
|
+
return extractDefaultFromLink(encodingLink)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return undefined
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type SchemaWithMembers = {
|
|
33
|
+
members: readonly S.Schema<any>[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type UnknownRecord = Record<string, unknown>
|
|
37
|
+
|
|
38
|
+
function hasMembers(schema: any): schema is SchemaWithMembers {
|
|
39
|
+
return schema && "members" in schema && Array.isArray(schema.members)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const isRecord = (value: unknown): value is UnknownRecord =>
|
|
43
|
+
value !== null && typeof value === "object" && !Array.isArray(value)
|
|
44
|
+
|
|
45
|
+
const isNullishAst = (ast: S.AST.AST) => S.AST.isNull(ast) || S.AST.isUndefined(ast)
|
|
46
|
+
|
|
47
|
+
const unionMembers = (ast: S.AST.AST): readonly S.AST.AST[] => {
|
|
48
|
+
const resolved = unwrapDeclaration(ast)
|
|
49
|
+
return S.AST.isUnion(resolved)
|
|
50
|
+
? resolved.types.flatMap(unionMembers)
|
|
51
|
+
: [resolved]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const literalValue = (ast: S.AST.AST): unknown => {
|
|
55
|
+
const resolved = unwrapDeclaration(ast)
|
|
56
|
+
if (S.AST.isLiteral(resolved)) return resolved.literal
|
|
57
|
+
if (S.AST.isUnion(resolved) && resolved.types.length === 1) {
|
|
58
|
+
return literalValue(resolved.types[0])
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const findTaggedObjectMember = (
|
|
63
|
+
members: readonly S.AST.Objects[],
|
|
64
|
+
value: unknown
|
|
65
|
+
): S.AST.Objects | undefined => {
|
|
66
|
+
if (!isRecord(value) || value._tag === undefined) return undefined
|
|
67
|
+
|
|
68
|
+
return members.find((member) => {
|
|
69
|
+
const tagProp = member.propertySignatures.find((prop) => prop.name.toString() === "_tag")
|
|
70
|
+
return tagProp ? literalValue(tagProp.type) === value._tag : false
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Internal implementation with WeakSet tracking
|
|
75
|
+
export const defaultsValueFromSchema = (
|
|
76
|
+
schema: S.Schema<any>,
|
|
77
|
+
record: Record<string, any> = {}
|
|
78
|
+
): any => {
|
|
79
|
+
const ast = schema.ast
|
|
80
|
+
const defaultValue = getDefaultFromAst(ast)
|
|
81
|
+
|
|
82
|
+
if (defaultValue !== undefined) {
|
|
83
|
+
return defaultValue
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isNullableOrUndefined(schema.ast) === "null") {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
if (isNullableOrUndefined(schema.ast) === "undefined") {
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Handle structs via AST (covers plain structs, transformed schemas like decodeTo, Class, etc.)
|
|
94
|
+
const objectsAst = S.AST.isObjects(ast)
|
|
95
|
+
? ast
|
|
96
|
+
: S.AST.isDeclaration(ast)
|
|
97
|
+
? unwrapDeclaration(ast)
|
|
98
|
+
: undefined
|
|
99
|
+
if (objectsAst && S.AST.isObjects(objectsAst)) {
|
|
100
|
+
const result: Record<string, any> = {}
|
|
101
|
+
|
|
102
|
+
for (const prop of objectsAst.propertySignatures) {
|
|
103
|
+
const key = prop.name.toString()
|
|
104
|
+
const propType = prop.type
|
|
105
|
+
|
|
106
|
+
const propDefault = getDefaultFromAst(propType)
|
|
107
|
+
if (propDefault !== undefined) {
|
|
108
|
+
result[key] = propDefault
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const propSchema = S.make(propType)
|
|
113
|
+
const propValue = defaultsValueFromSchema(propSchema, record[key] || {})
|
|
114
|
+
|
|
115
|
+
if (propValue !== undefined) {
|
|
116
|
+
result[key] = propValue
|
|
117
|
+
} else if (isNullableOrUndefined(propType) === "undefined") {
|
|
118
|
+
result[key] = undefined
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { ...result, ...record }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Handle unions via AST or schema-level .members
|
|
126
|
+
const unionTypes = S.AST.isUnion(ast)
|
|
127
|
+
? ast.types
|
|
128
|
+
: hasMembers(schema)
|
|
129
|
+
? schema.members.map((m) => m.ast)
|
|
130
|
+
: undefined
|
|
131
|
+
if (unionTypes) {
|
|
132
|
+
const mergedFields: Record<string, { ast: S.AST.AST }> = {}
|
|
133
|
+
|
|
134
|
+
for (const memberAstRaw of unionTypes) {
|
|
135
|
+
const memberAst = unwrapDeclaration(memberAstRaw)
|
|
136
|
+
if (!S.AST.isObjects(memberAst)) continue
|
|
137
|
+
|
|
138
|
+
for (const prop of memberAst.propertySignatures) {
|
|
139
|
+
const key = prop.name.toString()
|
|
140
|
+
const fieldDefault = getDefaultFromAst(prop.type)
|
|
141
|
+
const existingDefault = mergedFields[key] ? getDefaultFromAst(mergedFields[key].ast) : undefined
|
|
142
|
+
|
|
143
|
+
if (!mergedFields[key] || (fieldDefault !== undefined && existingDefault === undefined)) {
|
|
144
|
+
mergedFields[key] = { ast: prop.type }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (Object.keys(mergedFields).length === 0) {
|
|
150
|
+
return Object.keys(record).length > 0 ? record : undefined
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return Object.entries(mergedFields).reduce((acc, [key, { ast: propAst }]) => {
|
|
154
|
+
acc[key] = defaultsValueFromSchema(S.make(propAst), record[key] || {})
|
|
155
|
+
return acc
|
|
156
|
+
}, record)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (Object.keys(record).length === 0) {
|
|
160
|
+
if (S.AST.isString(ast)) {
|
|
161
|
+
return ""
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (S.AST.isBoolean(ast)) {
|
|
165
|
+
return false
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Deep-fills a partial form value with schema defaults for any nullable
|
|
172
|
+
* struct that has **materialised**.
|
|
173
|
+
*
|
|
174
|
+
* A nullable struct (`S.NullOr(S.Struct(...))`) left `null` stays `null`, but
|
|
175
|
+
* once it materialises — e.g. the user filled a single child — its untouched
|
|
176
|
+
* children are filled with their schema default (or `null` when nullable).
|
|
177
|
+
* Without this, strict `S.NullOr(...)` children reject the leftover
|
|
178
|
+
* `undefined` with a spurious "field must not be empty" error.
|
|
179
|
+
*
|
|
180
|
+
* Only children of a struct reached *through a nullable union* are filled;
|
|
181
|
+
* the always-present root struct keeps whatever fields it already has, so the
|
|
182
|
+
* form's own default-value priority is left untouched.
|
|
183
|
+
*
|
|
184
|
+
* Reference-preserving: returns `value` unchanged (same reference) when there
|
|
185
|
+
* is nothing to fill, so callers can detect a no-op with `===` and the result
|
|
186
|
+
* is idempotent (`fill(fill(v)) === fill(v)`).
|
|
187
|
+
*/
|
|
188
|
+
export const fillNestedDefaults = (
|
|
189
|
+
ast: S.AST.AST,
|
|
190
|
+
value: unknown,
|
|
191
|
+
fillMissing = false
|
|
192
|
+
): unknown => {
|
|
193
|
+
const resolved = unwrapDeclaration(ast)
|
|
194
|
+
|
|
195
|
+
switch (resolved._tag) {
|
|
196
|
+
case "Union": {
|
|
197
|
+
if (value === null || value === undefined) return value
|
|
198
|
+
const members = unionMembers(resolved)
|
|
199
|
+
const objectMembers = members.filter(S.AST.isObjects)
|
|
200
|
+
if (objectMembers.length === 0) return value
|
|
201
|
+
|
|
202
|
+
const hasNullishMember = members.some(isNullishAst)
|
|
203
|
+
const taggedMember = findTaggedObjectMember(objectMembers, value)
|
|
204
|
+
if (taggedMember) {
|
|
205
|
+
return fillNestedDefaults(taggedMember, value, fillMissing || hasNullishMember)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (hasNullishMember && objectMembers.length === 1) {
|
|
209
|
+
return fillNestedDefaults(objectMembers[0], value, true)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return value
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
case "Arrays": {
|
|
216
|
+
if (!Array.isArray(value)) return value
|
|
217
|
+
const element = resolved.rest[0]
|
|
218
|
+
if (!element) return value
|
|
219
|
+
let changed = false
|
|
220
|
+
const next = value.map((item) => {
|
|
221
|
+
const filled = fillNestedDefaults(element, item, fillMissing)
|
|
222
|
+
if (filled !== item) changed = true
|
|
223
|
+
return filled
|
|
224
|
+
})
|
|
225
|
+
return changed ? next : value
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
case "Objects": {
|
|
229
|
+
if (!isRecord(value)) return value
|
|
230
|
+
let result: UnknownRecord = value
|
|
231
|
+
let changed = false
|
|
232
|
+
const set = (key: string, next: unknown) => {
|
|
233
|
+
if (!changed) {
|
|
234
|
+
result = { ...value }
|
|
235
|
+
changed = true
|
|
236
|
+
}
|
|
237
|
+
result[key] = next
|
|
238
|
+
}
|
|
239
|
+
for (const prop of resolved.propertySignatures) {
|
|
240
|
+
const key = prop.name.toString()
|
|
241
|
+
const current = value[key]
|
|
242
|
+
if (current !== undefined) {
|
|
243
|
+
const filled = fillNestedDefaults(prop.type, current, fillMissing)
|
|
244
|
+
if (filled !== current) set(key, filled)
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
if (!fillMissing || prop.type.context?.isOptional === true) continue
|
|
248
|
+
const propDefault = getDefaultFromAst(prop.type)
|
|
249
|
+
if (propDefault !== undefined) {
|
|
250
|
+
set(key, propDefault)
|
|
251
|
+
} else if (isNullableOrUndefined(prop.type) === "null") {
|
|
252
|
+
set(key, null)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return result
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
default:
|
|
259
|
+
return value
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -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
|
+
}
|