@effect-app/vue-components 4.0.0-beta.20 → 4.0.0-beta.201
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 +36 -8
- package/dist/reset.css +52 -0
- package/dist/types/components/CommandButton.vue.d.ts +6 -4
- 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 +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 +6 -6
- package/dist/vue-components.es.js +29 -45
- 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 +60 -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 +92 -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 +86 -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 +29 -0
- package/dist/vue-components68.es.js +6 -0
- package/dist/vue-components69.es.js +18 -0
- package/dist/vue-components7.es.js +13 -0
- 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 +35 -0
- package/dist/vue-components9.es.js +47 -0
- package/package.json +35 -31
- package/src/components/CommandButton.vue +55 -7
- package/src/components/OmegaForm/OmegaArray.vue +2 -4
- 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 +7 -36
- package/src/components/OmegaForm/OmegaInputVuetify.vue +5 -2
- package/src/components/OmegaForm/OmegaInternalInput.vue +18 -10
- package/src/components/OmegaForm/OmegaTaggedUnion.vue +2 -1
- package/src/components/OmegaForm/OmegaTaggedUnionInternal.vue +1 -1
- 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 +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 +248 -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 +58 -893
- package/src/components/OmegaForm/validation/localized.ts +202 -0
- package/src/index.ts +0 -1
- package/src/reset.css +52 -0
- package/src/utils/index.ts +10 -7
- package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +0 -157
- 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 -481
- 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 -13
- package/dist/vue-components.es17.js +0 -6
- package/dist/vue-components.es18.js +0 -13
- package/dist/vue-components.es19.js +0 -57
- package/dist/vue-components.es2.js +0 -31
- package/dist/vue-components.es20.js +0 -56
- package/dist/vue-components.es21.js +0 -8
- package/dist/vue-components.es22.js +0 -8
- package/dist/vue-components.es23.js +0 -5
- package/dist/vue-components.es24.js +0 -5
- 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 -4
- package/dist/vue-components.es29.js +0 -19
- 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 -1184
- package/src/constants/index.ts +0 -1
|
@@ -1,1184 +0,0 @@
|
|
|
1
|
-
import { Effect, Option, type Record, S } from "effect-app"
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
-
import { type DeepKeys, type DeepValue, type FieldAsyncValidateOrFn, type FieldValidateOrFn, type FormApi, type FormAsyncValidateOrFn, type FormOptions, type FormState, type FormValidateOrFn, type StandardSchemaV1, type VueFormApi } from "@tanstack/vue-form"
|
|
4
|
-
import { isObject } from "@vueuse/core"
|
|
5
|
-
import type * as Fiber from "effect/Fiber"
|
|
6
|
-
import { getTransformationFrom, useIntl } from "../../utils"
|
|
7
|
-
import { type OmegaFieldInternalApi } from "./InputProps"
|
|
8
|
-
import { type OF, type OmegaFormReturn } from "./useOmegaForm"
|
|
9
|
-
|
|
10
|
-
export type FieldPath<T> = unknown extends T ? string
|
|
11
|
-
// technically we cannot have primitive at the root
|
|
12
|
-
: T extends string | boolean | number | null | undefined | symbol | bigint ? ""
|
|
13
|
-
// technically we cannot have array at the root
|
|
14
|
-
: T extends ReadonlyArray<infer U> ? FieldPath_<U, `[${number}]`>
|
|
15
|
-
: {
|
|
16
|
-
[K in keyof T]: FieldPath_<T[K], `${K & string}`>
|
|
17
|
-
}[keyof T]
|
|
18
|
-
|
|
19
|
-
export type FieldPath_<T, Path extends string> = unknown extends T ? string
|
|
20
|
-
: T extends string | boolean | number | null | undefined | symbol | bigint ? Path
|
|
21
|
-
: T extends ReadonlyArray<infer U> ? FieldPath_<U, `${Path}[${number}]`> | Path
|
|
22
|
-
: {
|
|
23
|
-
[K in keyof T]: FieldPath_<T[K], `${Path}.${K & string}`>
|
|
24
|
-
}[keyof T]
|
|
25
|
-
|
|
26
|
-
export type BaseProps<From, TName extends FieldPath<From>> = {
|
|
27
|
-
/**
|
|
28
|
-
* Will fallback to i18n when not specified.
|
|
29
|
-
* Can also be provided via #label slot for custom HTML labels.
|
|
30
|
-
* When using the slot, it receives bindings: { required, id, label }
|
|
31
|
-
*/
|
|
32
|
-
label?: string
|
|
33
|
-
validators?: FieldValidators<From>
|
|
34
|
-
// Use FlexibleArrayPath: if name contains [], just use TName; otherwise intersect with Leaves<From>
|
|
35
|
-
name: TName
|
|
36
|
-
/**
|
|
37
|
-
* Optional class to apply to the input element.
|
|
38
|
-
* - If a string is provided, it will be used instead of the general class
|
|
39
|
-
* - If null is provided, no class will be applied (neither inputClass nor general class)
|
|
40
|
-
* - If undefined (not provided), the general class will be used
|
|
41
|
-
*/
|
|
42
|
-
inputClass?: string | null
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type TypesWithOptions = "radio" | "select" | "multiple" | "autocomplete" | "autocompletemultiple"
|
|
46
|
-
export type DefaultTypeProps = {
|
|
47
|
-
type?: TypeOverride
|
|
48
|
-
options?: undefined
|
|
49
|
-
} | {
|
|
50
|
-
type?: TypesWithOptions
|
|
51
|
-
// TODO: options should depend on `type`, but since there is auto-type, we can't currently enforce it.
|
|
52
|
-
// hence we allow it also for type? (undefined) atm
|
|
53
|
-
options?: {
|
|
54
|
-
title: string
|
|
55
|
-
value: unknown
|
|
56
|
-
}[]
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export type OmegaInputPropsBase<
|
|
60
|
-
From extends Record<PropertyKey, any>,
|
|
61
|
-
To extends Record<PropertyKey, any>,
|
|
62
|
-
Name extends DeepKeys<From>
|
|
63
|
-
> = {
|
|
64
|
-
form: OF<From, To> & {
|
|
65
|
-
meta: MetaRecord<From>
|
|
66
|
-
i18nNamespace?: string
|
|
67
|
-
}
|
|
68
|
-
} & BaseProps<From, Name>
|
|
69
|
-
|
|
70
|
-
export type OmegaInputProps<
|
|
71
|
-
From extends Record<PropertyKey, any>,
|
|
72
|
-
To extends Record<PropertyKey, any>,
|
|
73
|
-
Name extends DeepKeys<From>,
|
|
74
|
-
TypeProps = DefaultTypeProps
|
|
75
|
-
> = {
|
|
76
|
-
form: OmegaFormReturn<From, To, TypeProps> & {
|
|
77
|
-
meta: MetaRecord<From>
|
|
78
|
-
i18nNamespace?: string
|
|
79
|
-
}
|
|
80
|
-
} & BaseProps<From, Name>
|
|
81
|
-
|
|
82
|
-
export type OmegaArrayProps<
|
|
83
|
-
From extends Record<PropertyKey, any>,
|
|
84
|
-
To extends Record<PropertyKey, any>,
|
|
85
|
-
Name extends DeepKeys<From>
|
|
86
|
-
> =
|
|
87
|
-
& Omit<
|
|
88
|
-
OmegaInputProps<From, To, Name>,
|
|
89
|
-
"validators" | "options" | "label" | "type" | "items" | "name"
|
|
90
|
-
>
|
|
91
|
-
& {
|
|
92
|
-
name: DeepKeys<From>
|
|
93
|
-
defaultItems?: DeepValue<From, DeepKeys<From>>
|
|
94
|
-
// deprecated items, caused bugs in state update, use defaultItems instead. It's not a simple Never, because Volar explodes
|
|
95
|
-
items?: "please use `defaultItems` instead"
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export type TypeOverride =
|
|
99
|
-
| "string"
|
|
100
|
-
| "text"
|
|
101
|
-
| "number"
|
|
102
|
-
| "select"
|
|
103
|
-
| "multiple"
|
|
104
|
-
| "boolean"
|
|
105
|
-
| "radio"
|
|
106
|
-
| "autocomplete"
|
|
107
|
-
| "autocompletemultiple"
|
|
108
|
-
| "switch"
|
|
109
|
-
| "range"
|
|
110
|
-
| "password"
|
|
111
|
-
| "email"
|
|
112
|
-
| "date"
|
|
113
|
-
|
|
114
|
-
export interface OmegaError {
|
|
115
|
-
label: string
|
|
116
|
-
inputId: string
|
|
117
|
-
errors: readonly string[]
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export type FormProps<From, To> =
|
|
121
|
-
& Omit<
|
|
122
|
-
FormOptions<
|
|
123
|
-
From,
|
|
124
|
-
FormValidateOrFn<From> | undefined,
|
|
125
|
-
FormValidateOrFn<From> | undefined,
|
|
126
|
-
StandardSchemaV1<From, To>,
|
|
127
|
-
FormValidateOrFn<From> | undefined,
|
|
128
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
129
|
-
FormValidateOrFn<From> | undefined,
|
|
130
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
131
|
-
FormValidateOrFn<From> | undefined,
|
|
132
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
133
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
134
|
-
Record<string, any> | undefined // TODO
|
|
135
|
-
>,
|
|
136
|
-
| "onSubmit"
|
|
137
|
-
| "defaultValues"
|
|
138
|
-
>
|
|
139
|
-
& {
|
|
140
|
-
// when defaultValues are allowed to be undefined, then they should also be allowed to be partial
|
|
141
|
-
// this fixes validator issues where a defaultValue of "" leads to "requires at least 1 character", while manually emptying the field changes it to "is required"
|
|
142
|
-
defaultValues?: Partial<From>
|
|
143
|
-
onSubmit?: (props: {
|
|
144
|
-
formApi: OmegaFormParams<From, To>
|
|
145
|
-
meta: any
|
|
146
|
-
value: To
|
|
147
|
-
}) => Promise<any> | Fiber.Fiber<any, any> | Effect.Effect<unknown, any, never>
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export type OmegaFormParams<From, To> = FormApi<
|
|
151
|
-
From,
|
|
152
|
-
FormValidateOrFn<From> | undefined,
|
|
153
|
-
FormValidateOrFn<From> | undefined,
|
|
154
|
-
StandardSchemaV1<From, To>,
|
|
155
|
-
FormValidateOrFn<From> | undefined,
|
|
156
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
157
|
-
FormValidateOrFn<From> | undefined,
|
|
158
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
159
|
-
FormValidateOrFn<From> | undefined,
|
|
160
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
161
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
162
|
-
Record<string, any> | undefined
|
|
163
|
-
>
|
|
164
|
-
|
|
165
|
-
export type OmegaFormState<From, To> = FormState<
|
|
166
|
-
From,
|
|
167
|
-
FormValidateOrFn<From> | undefined,
|
|
168
|
-
FormValidateOrFn<From> | undefined,
|
|
169
|
-
StandardSchemaV1<From, To>,
|
|
170
|
-
FormValidateOrFn<From> | undefined,
|
|
171
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
172
|
-
FormValidateOrFn<From> | undefined,
|
|
173
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
174
|
-
FormValidateOrFn<From> | undefined,
|
|
175
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
176
|
-
FormAsyncValidateOrFn<From> | undefined
|
|
177
|
-
>
|
|
178
|
-
|
|
179
|
-
// TODO: stitch TSubmitMeta somehow
|
|
180
|
-
export type OmegaFormApi<From, To, TSubmitMeta = Record<string, any> | undefined> =
|
|
181
|
-
& OmegaFormParams<From, To>
|
|
182
|
-
& VueFormApi<
|
|
183
|
-
From,
|
|
184
|
-
FormValidateOrFn<From> | undefined,
|
|
185
|
-
FormValidateOrFn<From> | undefined,
|
|
186
|
-
StandardSchemaV1<From, To>,
|
|
187
|
-
FormValidateOrFn<From> | undefined,
|
|
188
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
189
|
-
FormValidateOrFn<From> | undefined,
|
|
190
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
191
|
-
FormValidateOrFn<From> | undefined,
|
|
192
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
193
|
-
FormAsyncValidateOrFn<From> | undefined,
|
|
194
|
-
TSubmitMeta
|
|
195
|
-
>
|
|
196
|
-
|
|
197
|
-
export type FormComponent<T, S> = VueFormApi<
|
|
198
|
-
T,
|
|
199
|
-
FormValidateOrFn<T> | undefined,
|
|
200
|
-
FormValidateOrFn<T> | undefined,
|
|
201
|
-
StandardSchemaV1<T, S>,
|
|
202
|
-
FormValidateOrFn<T> | undefined,
|
|
203
|
-
FormAsyncValidateOrFn<T> | undefined,
|
|
204
|
-
FormValidateOrFn<T> | undefined,
|
|
205
|
-
FormAsyncValidateOrFn<T> | undefined,
|
|
206
|
-
FormValidateOrFn<T> | undefined,
|
|
207
|
-
FormAsyncValidateOrFn<T> | undefined,
|
|
208
|
-
FormAsyncValidateOrFn<T> | undefined,
|
|
209
|
-
Record<string, any> | undefined
|
|
210
|
-
>
|
|
211
|
-
|
|
212
|
-
export type FormType<
|
|
213
|
-
From extends Record<PropertyKey, any>,
|
|
214
|
-
To extends Record<PropertyKey, any>,
|
|
215
|
-
Name extends DeepKeys<From>
|
|
216
|
-
> = OmegaFormApi<From, To> & {
|
|
217
|
-
Field: OmegaFieldInternalApi<From, Name>
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export type PrefixFromDepth<
|
|
221
|
-
K extends string | number,
|
|
222
|
-
_TDepth extends any[]
|
|
223
|
-
> = K
|
|
224
|
-
|
|
225
|
-
export type NestedKeyOf<T> = DeepKeys<T>
|
|
226
|
-
|
|
227
|
-
export type FieldValidators<T> = {
|
|
228
|
-
onChangeAsync?: FieldAsyncValidateOrFn<T, any, any>
|
|
229
|
-
onChange?: FieldValidateOrFn<T, any, any>
|
|
230
|
-
onBlur?: FieldValidateOrFn<T, any, any>
|
|
231
|
-
onBlurAsync?: FieldAsyncValidateOrFn<T, any, any>
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Field metadata type definitions
|
|
235
|
-
export type BaseFieldMeta = {
|
|
236
|
-
required: boolean
|
|
237
|
-
nullableOrUndefined?: false | "undefined" | "null"
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export type StringFieldMeta = BaseFieldMeta & {
|
|
241
|
-
type: "string"
|
|
242
|
-
maxLength?: number
|
|
243
|
-
minLength?: number
|
|
244
|
-
format?: string
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
export type NumberFieldMeta = BaseFieldMeta & {
|
|
248
|
-
type: "number"
|
|
249
|
-
minimum?: number
|
|
250
|
-
maximum?: number
|
|
251
|
-
exclusiveMinimum?: number
|
|
252
|
-
exclusiveMaximum?: number
|
|
253
|
-
refinement?: "int"
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export type SelectFieldMeta = BaseFieldMeta & {
|
|
257
|
-
type: "select"
|
|
258
|
-
members: any[] // TODO: should be non empty array?
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export type MultipleFieldMeta = BaseFieldMeta & {
|
|
262
|
-
type: "multiple"
|
|
263
|
-
members: any[] // TODO: should be non empty array?
|
|
264
|
-
rest: readonly S.AST.AST[]
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export type BooleanFieldMeta = BaseFieldMeta & {
|
|
268
|
-
type: "boolean"
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export type UnknownFieldMeta = BaseFieldMeta & {
|
|
272
|
-
type: "unknown"
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export type FieldMeta =
|
|
276
|
-
| StringFieldMeta
|
|
277
|
-
| NumberFieldMeta
|
|
278
|
-
| SelectFieldMeta
|
|
279
|
-
| MultipleFieldMeta
|
|
280
|
-
| BooleanFieldMeta
|
|
281
|
-
| UnknownFieldMeta
|
|
282
|
-
|
|
283
|
-
export type MetaRecord<T = string> = {
|
|
284
|
-
[K in NestedKeyOf<T>]?: FieldMeta
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
export type FilterItems = {
|
|
288
|
-
items: readonly [string, ...string[]]
|
|
289
|
-
message:
|
|
290
|
-
| string
|
|
291
|
-
| Effect.Effect<string, never, never>
|
|
292
|
-
| { readonly message: string | Effect.Effect<string> }
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export type CreateMeta =
|
|
296
|
-
& {
|
|
297
|
-
parent?: string
|
|
298
|
-
meta?: Record<string, any>
|
|
299
|
-
nullableOrUndefined?: false | "undefined" | "null"
|
|
300
|
-
}
|
|
301
|
-
& (
|
|
302
|
-
| {
|
|
303
|
-
propertySignatures: readonly S.AST.PropertySignature[]
|
|
304
|
-
property?: never
|
|
305
|
-
}
|
|
306
|
-
| {
|
|
307
|
-
propertySignatures?: never
|
|
308
|
-
property: S.AST.AST
|
|
309
|
-
}
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
const unwrapDeclaration = (property: S.AST.AST): S.AST.AST => {
|
|
313
|
-
let current = getTransformationFrom(property)
|
|
314
|
-
|
|
315
|
-
while (S.AST.isDeclaration(current) && current.typeParameters.length > 0) {
|
|
316
|
-
current = getTransformationFrom(current.typeParameters[0]!)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return current
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const isNullishType = (property: S.AST.AST) => S.AST.isUndefined(property) || S.AST.isNull(property)
|
|
323
|
-
|
|
324
|
-
const getNullableOrUndefined = (property: S.AST.AST) =>
|
|
325
|
-
S.AST.isUnion(property)
|
|
326
|
-
? property.types.find((_) => isNullishType(_))
|
|
327
|
-
: false
|
|
328
|
-
|
|
329
|
-
export const isNullableOrUndefined = (property: false | S.AST.AST | undefined) => {
|
|
330
|
-
if (!property || !S.AST.isUnion(property)) return false
|
|
331
|
-
if (property.types.find((_) => S.AST.isUndefined(_))) {
|
|
332
|
-
return "undefined"
|
|
333
|
-
}
|
|
334
|
-
if (property.types.find((_) => S.AST.isNull(_))) return "null"
|
|
335
|
-
return false
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Helper function to recursively unwrap nested unions (e.g., S.NullOr(S.NullOr(X)) -> X)
|
|
339
|
-
const unwrapNestedUnions = (types: readonly S.AST.AST[]): readonly S.AST.AST[] => {
|
|
340
|
-
const result: S.AST.AST[] = []
|
|
341
|
-
for (const type of types) {
|
|
342
|
-
if (S.AST.isUnion(type)) {
|
|
343
|
-
// Recursively unwrap nested unions
|
|
344
|
-
const unwrapped = unwrapNestedUnions(type.types)
|
|
345
|
-
result.push(...unwrapped)
|
|
346
|
-
} else {
|
|
347
|
-
result.push(type)
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return result
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const getNonNullTypes = (types: readonly S.AST.AST[]) =>
|
|
354
|
-
unwrapNestedUnions(types)
|
|
355
|
-
.map(unwrapDeclaration)
|
|
356
|
-
.filter((_) => !isNullishType(_))
|
|
357
|
-
|
|
358
|
-
const getJsonSchemaAnnotation = (property: S.AST.AST): Record<string, unknown> => {
|
|
359
|
-
const jsonSchema = S.AST.resolve(property)?.jsonSchema
|
|
360
|
-
return jsonSchema && typeof jsonSchema === "object" ? jsonSchema as Record<string, unknown> : {}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const getDefaultFromAst = (property: S.AST.AST) => {
|
|
364
|
-
const link = property.context?.defaultValue?.[0] as any
|
|
365
|
-
|
|
366
|
-
if (!link?.transformation?.decode?.run) {
|
|
367
|
-
return undefined
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
const result = Effect.runSync(link.transformation.decode.run(Option.none())) as Option.Option<unknown>
|
|
372
|
-
return Option.isSome(result) ? result.value : undefined
|
|
373
|
-
} catch {
|
|
374
|
-
return undefined
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const getCheckMetas = (property: S.AST.AST): Array<Record<string, any>> => {
|
|
379
|
-
const checks = property.checks ?? []
|
|
380
|
-
|
|
381
|
-
return checks.flatMap((check) => {
|
|
382
|
-
if (check._tag === "FilterGroup") {
|
|
383
|
-
return check.checks.flatMap((inner) => {
|
|
384
|
-
const meta = inner.annotations?.meta
|
|
385
|
-
return meta && typeof meta === "object" ? [meta as Record<string, any>] : []
|
|
386
|
-
})
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const meta = check.annotations?.meta
|
|
390
|
-
return meta && typeof meta === "object" ? [meta as Record<string, any>] : []
|
|
391
|
-
})
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const getFieldMetadataFromAst = (property: S.AST.AST) => {
|
|
395
|
-
const base: Partial<FieldMeta> & Record<string, unknown> = {
|
|
396
|
-
description: S.AST.resolveDescription(property)
|
|
397
|
-
}
|
|
398
|
-
const checks = getCheckMetas(property)
|
|
399
|
-
|
|
400
|
-
if (S.AST.isString(property)) {
|
|
401
|
-
base.type = "string"
|
|
402
|
-
for (const check of checks) {
|
|
403
|
-
switch (check._tag) {
|
|
404
|
-
case "isMinLength":
|
|
405
|
-
base.minLength = check.minLength
|
|
406
|
-
break
|
|
407
|
-
case "isMaxLength":
|
|
408
|
-
base.maxLength = check.maxLength
|
|
409
|
-
break
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (S.AST.resolveTitle(property) === "Email") {
|
|
414
|
-
base.format = "email"
|
|
415
|
-
}
|
|
416
|
-
} else if (S.AST.isNumber(property)) {
|
|
417
|
-
base.type = "number"
|
|
418
|
-
for (const check of checks) {
|
|
419
|
-
switch (check._tag) {
|
|
420
|
-
case "isInt":
|
|
421
|
-
base.refinement = "int"
|
|
422
|
-
break
|
|
423
|
-
case "isGreaterThanOrEqualTo":
|
|
424
|
-
base.minimum = check.minimum
|
|
425
|
-
break
|
|
426
|
-
case "isLessThanOrEqualTo":
|
|
427
|
-
base.maximum = check.maximum
|
|
428
|
-
break
|
|
429
|
-
case "isGreaterThan":
|
|
430
|
-
base.exclusiveMinimum = check.exclusiveMinimum
|
|
431
|
-
break
|
|
432
|
-
case "isLessThan":
|
|
433
|
-
base.exclusiveMaximum = check.exclusiveMaximum
|
|
434
|
-
break
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
} else if (S.AST.isBoolean(property)) {
|
|
438
|
-
base.type = "boolean"
|
|
439
|
-
} else {
|
|
440
|
-
base.type = "unknown"
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
return base
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
export const createMeta = <T = any>(
|
|
447
|
-
{ meta = {}, parent = "", property, propertySignatures }: CreateMeta,
|
|
448
|
-
acc: Partial<MetaRecord<T>> = {}
|
|
449
|
-
): MetaRecord<T> | FieldMeta => {
|
|
450
|
-
if (property) {
|
|
451
|
-
property = unwrapDeclaration(property)
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
if (property && S.AST.isObjects(property)) {
|
|
455
|
-
return createMeta<T>({
|
|
456
|
-
meta,
|
|
457
|
-
propertySignatures: property.propertySignatures
|
|
458
|
-
})
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (propertySignatures) {
|
|
462
|
-
for (const p of propertySignatures) {
|
|
463
|
-
const key = parent ? `${parent}.${p.name.toString()}` : p.name.toString()
|
|
464
|
-
const nullableOrUndefined = isNullableOrUndefined(p.type)
|
|
465
|
-
|
|
466
|
-
// Determine if this field should be required:
|
|
467
|
-
// - For nullable discriminated unions, only _tag should be non-required
|
|
468
|
-
// - All other fields should calculate their required status normally
|
|
469
|
-
let isRequired: boolean
|
|
470
|
-
if (meta._isNullableDiscriminatedUnion && p.name.toString() === "_tag") {
|
|
471
|
-
// _tag in a nullable discriminated union is not required
|
|
472
|
-
isRequired = false
|
|
473
|
-
} else if (meta.required === false) {
|
|
474
|
-
// Explicitly set to non-required (legacy behavior for backwards compatibility)
|
|
475
|
-
isRequired = false
|
|
476
|
-
} else {
|
|
477
|
-
// Calculate from the property itself
|
|
478
|
-
isRequired = !nullableOrUndefined
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const typeToProcess = unwrapDeclaration(p.type)
|
|
482
|
-
if (S.AST.isUnion(p.type)) {
|
|
483
|
-
const nonNullTypes = getNonNullTypes(p.type.types)
|
|
484
|
-
|
|
485
|
-
const hasStructMembers = nonNullTypes.some(S.AST.isObjects)
|
|
486
|
-
|
|
487
|
-
if (hasStructMembers) {
|
|
488
|
-
// Only create parent meta for non-NullOr unions to avoid duplicates
|
|
489
|
-
if (!nullableOrUndefined) {
|
|
490
|
-
const parentMeta = createMeta<T>({
|
|
491
|
-
parent: key,
|
|
492
|
-
property: p.type,
|
|
493
|
-
meta: { required: isRequired, nullableOrUndefined }
|
|
494
|
-
})
|
|
495
|
-
acc[key as NestedKeyOf<T>] = parentMeta as FieldMeta
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// Process each non-null type and merge their metadata
|
|
499
|
-
for (const nonNullType of nonNullTypes) {
|
|
500
|
-
if (S.AST.isObjects(nonNullType)) {
|
|
501
|
-
// For discriminated unions (multiple branches):
|
|
502
|
-
// - If the parent union is nullable, only _tag should be non-required
|
|
503
|
-
// - All other fields maintain their normal required status based on their own types
|
|
504
|
-
const isNullableDiscriminatedUnion = nullableOrUndefined && nonNullTypes.length > 1
|
|
505
|
-
|
|
506
|
-
Object.assign(
|
|
507
|
-
acc,
|
|
508
|
-
createMeta<T>({
|
|
509
|
-
parent: key,
|
|
510
|
-
propertySignatures: nonNullType.propertySignatures,
|
|
511
|
-
meta: isNullableDiscriminatedUnion ? { _isNullableDiscriminatedUnion: true } : {}
|
|
512
|
-
})
|
|
513
|
-
)
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
} else {
|
|
517
|
-
const arrayTypes = nonNullTypes.filter(S.AST.isArrays)
|
|
518
|
-
if (arrayTypes.length > 0) {
|
|
519
|
-
const arrayType = arrayTypes[0] // Take the first array type
|
|
520
|
-
|
|
521
|
-
acc[key as NestedKeyOf<T>] = {
|
|
522
|
-
type: "multiple",
|
|
523
|
-
members: arrayType.elements,
|
|
524
|
-
rest: arrayType.rest,
|
|
525
|
-
required: isRequired,
|
|
526
|
-
nullableOrUndefined
|
|
527
|
-
} as FieldMeta
|
|
528
|
-
|
|
529
|
-
// If the array has struct elements, also create metadata for their properties
|
|
530
|
-
if (arrayType.rest && arrayType.rest.length > 0) {
|
|
531
|
-
const restElement = unwrapDeclaration(arrayType.rest[0]!)
|
|
532
|
-
if (S.AST.isObjects(restElement)) {
|
|
533
|
-
for (const prop of restElement.propertySignatures) {
|
|
534
|
-
const propKey = `${key}.${prop.name.toString()}`
|
|
535
|
-
|
|
536
|
-
const propMeta = createMeta<T>({
|
|
537
|
-
parent: propKey,
|
|
538
|
-
property: prop.type,
|
|
539
|
-
meta: {
|
|
540
|
-
required: !isNullableOrUndefined(prop.type),
|
|
541
|
-
nullableOrUndefined: isNullableOrUndefined(prop.type)
|
|
542
|
-
}
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
// add to accumulator if valid
|
|
546
|
-
if (propMeta && typeof propMeta === "object" && "type" in propMeta) {
|
|
547
|
-
acc[propKey as NestedKeyOf<T>] = propMeta as FieldMeta
|
|
548
|
-
|
|
549
|
-
if (
|
|
550
|
-
propMeta.type === "multiple" && S.AST.isArrays(prop.type) && prop
|
|
551
|
-
.type
|
|
552
|
-
.rest && prop.type.rest.length > 0
|
|
553
|
-
) {
|
|
554
|
-
const nestedRestElement = unwrapDeclaration(prop.type.rest[0]!)
|
|
555
|
-
if (S.AST.isObjects(nestedRestElement)) {
|
|
556
|
-
for (const nestedProp of nestedRestElement.propertySignatures) {
|
|
557
|
-
const nestedPropKey = `${propKey}.${nestedProp.name.toString()}`
|
|
558
|
-
|
|
559
|
-
const nestedPropMeta = createMeta<T>({
|
|
560
|
-
parent: nestedPropKey,
|
|
561
|
-
property: nestedProp.type,
|
|
562
|
-
meta: {
|
|
563
|
-
required: !isNullableOrUndefined(nestedProp.type),
|
|
564
|
-
nullableOrUndefined: isNullableOrUndefined(nestedProp.type)
|
|
565
|
-
}
|
|
566
|
-
})
|
|
567
|
-
|
|
568
|
-
// add to accumulator if valid
|
|
569
|
-
if (nestedPropMeta && typeof nestedPropMeta === "object" && "type" in nestedPropMeta) {
|
|
570
|
-
acc[nestedPropKey as NestedKeyOf<T>] = nestedPropMeta as FieldMeta
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
} else {
|
|
580
|
-
// If no struct members and no arrays, process as regular union
|
|
581
|
-
const newMeta = createMeta<T>({
|
|
582
|
-
parent: key,
|
|
583
|
-
property: p.type,
|
|
584
|
-
meta: { required: isRequired, nullableOrUndefined }
|
|
585
|
-
})
|
|
586
|
-
acc[key as NestedKeyOf<T>] = newMeta as FieldMeta
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
} else {
|
|
590
|
-
if (S.AST.isObjects(typeToProcess)) {
|
|
591
|
-
Object.assign(
|
|
592
|
-
acc,
|
|
593
|
-
createMeta<T>({
|
|
594
|
-
parent: key,
|
|
595
|
-
propertySignatures: typeToProcess.propertySignatures,
|
|
596
|
-
meta: { required: isRequired, nullableOrUndefined }
|
|
597
|
-
})
|
|
598
|
-
)
|
|
599
|
-
} else if (S.AST.isArrays(p.type)) {
|
|
600
|
-
// Check if it has struct elements
|
|
601
|
-
const hasStructElements = p.type.rest.length > 0
|
|
602
|
-
&& S.AST.isObjects(unwrapDeclaration(p.type.rest[0]!))
|
|
603
|
-
|
|
604
|
-
if (hasStructElements) {
|
|
605
|
-
// For arrays with struct elements, only create meta for nested fields, not the array itself
|
|
606
|
-
const elementType = unwrapDeclaration(p.type.rest[0]!)
|
|
607
|
-
if (S.AST.isObjects(elementType)) {
|
|
608
|
-
// Process each property in the array element
|
|
609
|
-
for (const prop of elementType.propertySignatures) {
|
|
610
|
-
const propKey = `${key}.${prop.name.toString()}`
|
|
611
|
-
|
|
612
|
-
// Check if the property is another array
|
|
613
|
-
if (S.AST.isArrays(prop.type) && prop.type.rest.length > 0) {
|
|
614
|
-
const nestedElementType = unwrapDeclaration(prop.type.rest[0]!)
|
|
615
|
-
if (S.AST.isObjects(nestedElementType)) {
|
|
616
|
-
// Array with struct elements - process nested fields
|
|
617
|
-
for (const nestedProp of nestedElementType.propertySignatures) {
|
|
618
|
-
const nestedKey = `${propKey}.${nestedProp.name.toString()}`
|
|
619
|
-
const nestedMeta = createMeta<T>({
|
|
620
|
-
parent: nestedKey,
|
|
621
|
-
property: nestedProp.type,
|
|
622
|
-
meta: {
|
|
623
|
-
required: !isNullableOrUndefined(nestedProp.type),
|
|
624
|
-
nullableOrUndefined: isNullableOrUndefined(nestedProp.type)
|
|
625
|
-
}
|
|
626
|
-
})
|
|
627
|
-
acc[nestedKey as NestedKeyOf<T>] = nestedMeta as FieldMeta
|
|
628
|
-
}
|
|
629
|
-
} else {
|
|
630
|
-
// Array with primitive elements - create meta for the array itself
|
|
631
|
-
acc[propKey as NestedKeyOf<T>] = {
|
|
632
|
-
type: "multiple",
|
|
633
|
-
members: prop.type.elements,
|
|
634
|
-
rest: prop.type.rest,
|
|
635
|
-
required: !isNullableOrUndefined(prop.type),
|
|
636
|
-
nullableOrUndefined: isNullableOrUndefined(prop.type)
|
|
637
|
-
} as FieldMeta
|
|
638
|
-
}
|
|
639
|
-
} else {
|
|
640
|
-
const fieldMeta = createMeta<T>({
|
|
641
|
-
parent: propKey,
|
|
642
|
-
property: prop.type,
|
|
643
|
-
meta: {
|
|
644
|
-
required: !isNullableOrUndefined(prop.type),
|
|
645
|
-
nullableOrUndefined: isNullableOrUndefined(prop.type)
|
|
646
|
-
}
|
|
647
|
-
})
|
|
648
|
-
acc[propKey as NestedKeyOf<T>] = fieldMeta as FieldMeta
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
} else {
|
|
653
|
-
// For arrays with primitive elements, create the array meta
|
|
654
|
-
acc[key as NestedKeyOf<T>] = {
|
|
655
|
-
type: "multiple",
|
|
656
|
-
members: p.type.elements,
|
|
657
|
-
rest: p.type.rest,
|
|
658
|
-
required: isRequired,
|
|
659
|
-
nullableOrUndefined
|
|
660
|
-
} as FieldMeta
|
|
661
|
-
}
|
|
662
|
-
} else {
|
|
663
|
-
const newMeta = createMeta<T>({
|
|
664
|
-
parent: key,
|
|
665
|
-
property: p.type,
|
|
666
|
-
meta: {
|
|
667
|
-
// an empty string is valid for a S.String field, so we should not mark it as required
|
|
668
|
-
// TODO: handle this better via the createMeta minLength parsing
|
|
669
|
-
required: isRequired
|
|
670
|
-
&& (!S.AST.isString(typeToProcess) || !!getFieldMetadataFromAst(p.type).minLength),
|
|
671
|
-
nullableOrUndefined
|
|
672
|
-
}
|
|
673
|
-
})
|
|
674
|
-
|
|
675
|
-
acc[key as NestedKeyOf<T>] = newMeta as FieldMeta
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return acc
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
if (property) {
|
|
683
|
-
const nullableOrUndefined = getNullableOrUndefined(property)
|
|
684
|
-
property = unwrapDeclaration(property)
|
|
685
|
-
|
|
686
|
-
if (!Object.hasOwnProperty.call(meta, "required")) {
|
|
687
|
-
meta["required"] = !nullableOrUndefined
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
if (S.AST.isUnion(property)) {
|
|
691
|
-
const unwrappedTypes = unwrapNestedUnions(property.types).map(unwrapDeclaration)
|
|
692
|
-
const nonNullType = unwrappedTypes.find((t) => !isNullishType(t))!
|
|
693
|
-
|
|
694
|
-
if (S.AST.isObjects(nonNullType)) {
|
|
695
|
-
return createMeta<T>({
|
|
696
|
-
propertySignatures: nonNullType.propertySignatures,
|
|
697
|
-
parent,
|
|
698
|
-
meta
|
|
699
|
-
})
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
if (unwrappedTypes.every((_) => isNullishType(_) || S.AST.isLiteral(_))) {
|
|
703
|
-
return {
|
|
704
|
-
...meta,
|
|
705
|
-
type: "select",
|
|
706
|
-
members: unwrappedTypes.filter(S.AST.isLiteral).map((t) => t.literal)
|
|
707
|
-
} as FieldMeta
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
return {
|
|
711
|
-
...meta,
|
|
712
|
-
...createMeta<T>({
|
|
713
|
-
parent,
|
|
714
|
-
meta,
|
|
715
|
-
property: nonNullType
|
|
716
|
-
})
|
|
717
|
-
} as FieldMeta
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
if (S.AST.isArrays(property)) {
|
|
721
|
-
return {
|
|
722
|
-
...meta,
|
|
723
|
-
type: "multiple",
|
|
724
|
-
members: property.elements,
|
|
725
|
-
rest: property.rest
|
|
726
|
-
} as FieldMeta
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
meta = { ...getJsonSchemaAnnotation(property), ...getFieldMetadataFromAst(property), ...meta }
|
|
730
|
-
|
|
731
|
-
return meta as FieldMeta
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
return acc
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
// Helper to flatten nested meta structure into dot-notation keys
|
|
738
|
-
const flattenMeta = <T>(meta: MetaRecord<T> | FieldMeta, parentKey: string = ""): MetaRecord<T> => {
|
|
739
|
-
const result: MetaRecord<T> = {}
|
|
740
|
-
|
|
741
|
-
for (const key in meta) {
|
|
742
|
-
const value = (meta as any)[key]
|
|
743
|
-
const newKey = parentKey ? `${parentKey}.${key}` : key
|
|
744
|
-
|
|
745
|
-
if (value && typeof value === "object" && "type" in value) {
|
|
746
|
-
result[newKey as DeepKeys<T>] = value as FieldMeta
|
|
747
|
-
} else if (value && typeof value === "object") {
|
|
748
|
-
Object.assign(result, flattenMeta<T>(value, newKey))
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
return result
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
const metadataFromAst = <From, To>(
|
|
756
|
-
schema: S.Codec<To, From, never>
|
|
757
|
-
): { meta: MetaRecord<To>; defaultValues: Record<string, any>; unionMeta: Record<string, MetaRecord<To>> } => {
|
|
758
|
-
const ast = unwrapDeclaration(schema.ast)
|
|
759
|
-
const newMeta: MetaRecord<To> = {}
|
|
760
|
-
const defaultValues: Record<string, any> = {}
|
|
761
|
-
const unionMeta: Record<string, MetaRecord<To>> = {}
|
|
762
|
-
|
|
763
|
-
// Handle root-level Union types (discriminated unions)
|
|
764
|
-
if (S.AST.isUnion(ast)) {
|
|
765
|
-
// Filter out null/undefined types and unwrap transformations
|
|
766
|
-
const nonNullTypes = getNonNullTypes(ast.types)
|
|
767
|
-
|
|
768
|
-
// Check if this is a discriminated union (all members are structs)
|
|
769
|
-
const allStructs = nonNullTypes.every(S.AST.isObjects)
|
|
770
|
-
|
|
771
|
-
if (allStructs && nonNullTypes.length > 0) {
|
|
772
|
-
// Extract discriminator values from each union member
|
|
773
|
-
const discriminatorValues: any[] = []
|
|
774
|
-
|
|
775
|
-
// Store metadata for each union member by its tag value
|
|
776
|
-
for (const memberType of nonNullTypes) {
|
|
777
|
-
if (S.AST.isObjects(memberType)) {
|
|
778
|
-
// Find the discriminator field (usually _tag)
|
|
779
|
-
const tagProp = memberType.propertySignatures.find(
|
|
780
|
-
(p) => p.name.toString() === "_tag"
|
|
781
|
-
)
|
|
782
|
-
|
|
783
|
-
let tagValue: string | null = null
|
|
784
|
-
if (tagProp && S.AST.isLiteral(tagProp.type)) {
|
|
785
|
-
tagValue = tagProp.type.literal as string
|
|
786
|
-
discriminatorValues.push(tagValue)
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
// Create metadata for this member's properties
|
|
790
|
-
const memberMeta = createMeta<To>({
|
|
791
|
-
propertySignatures: memberType.propertySignatures
|
|
792
|
-
})
|
|
793
|
-
|
|
794
|
-
// Store per-tag metadata for reactive lookup
|
|
795
|
-
if (tagValue) {
|
|
796
|
-
unionMeta[tagValue] = flattenMeta<To>(memberMeta)
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Merge into result (for backward compatibility)
|
|
800
|
-
Object.assign(newMeta, memberMeta)
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// Create metadata for the discriminator field
|
|
805
|
-
if (discriminatorValues.length > 0) {
|
|
806
|
-
newMeta["_tag" as DeepKeys<To>] = {
|
|
807
|
-
type: "select",
|
|
808
|
-
members: discriminatorValues,
|
|
809
|
-
required: true
|
|
810
|
-
} as FieldMeta
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
return { meta: newMeta, defaultValues, unionMeta }
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
if (S.AST.isObjects(ast)) {
|
|
818
|
-
const meta = createMeta<To>({
|
|
819
|
-
propertySignatures: ast.propertySignatures
|
|
820
|
-
})
|
|
821
|
-
|
|
822
|
-
if (Object.values(meta).every((value) => value && "type" in value)) {
|
|
823
|
-
return { meta: meta as MetaRecord<To>, defaultValues, unionMeta }
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
const flattenObject = (
|
|
827
|
-
obj: Record<string, any>,
|
|
828
|
-
parentKey: string = ""
|
|
829
|
-
) => {
|
|
830
|
-
for (const key in obj) {
|
|
831
|
-
const newKey = parentKey ? `${parentKey}.${key}` : key
|
|
832
|
-
if (obj[key] && typeof obj[key] === "object" && "type" in obj[key]) {
|
|
833
|
-
newMeta[newKey as DeepKeys<To>] = obj[key] as FieldMeta
|
|
834
|
-
} else if (obj[key] && typeof obj[key] === "object") {
|
|
835
|
-
flattenObject(obj[key], newKey)
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
flattenObject(meta)
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
return { meta: newMeta, defaultValues, unionMeta }
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
export const duplicateSchema = <From, To>(
|
|
847
|
-
schema: S.Codec<To, From, never>
|
|
848
|
-
) => {
|
|
849
|
-
return schema
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
export const generateMetaFromSchema = <From, To>(
|
|
853
|
-
schema: S.Codec<To, From, never>
|
|
854
|
-
): {
|
|
855
|
-
schema: S.Codec<To, From, never>
|
|
856
|
-
meta: MetaRecord<To>
|
|
857
|
-
unionMeta: Record<string, MetaRecord<To>>
|
|
858
|
-
} => {
|
|
859
|
-
const { meta, unionMeta } = metadataFromAst(schema)
|
|
860
|
-
|
|
861
|
-
return { schema, meta, unionMeta }
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
export const generateInputStandardSchemaFromFieldMeta = (
|
|
865
|
-
meta: FieldMeta,
|
|
866
|
-
trans?: ReturnType<typeof useIntl>["trans"]
|
|
867
|
-
): StandardSchemaV1<any, any> => {
|
|
868
|
-
if (!trans) {
|
|
869
|
-
trans = useIntl().trans
|
|
870
|
-
}
|
|
871
|
-
let schema: any
|
|
872
|
-
switch (meta.type) {
|
|
873
|
-
case "string":
|
|
874
|
-
schema = meta.format === "email"
|
|
875
|
-
? S.Email.annotate({
|
|
876
|
-
message: trans("validation.email.invalid")
|
|
877
|
-
})
|
|
878
|
-
: S.String.annotate({
|
|
879
|
-
message: trans("validation.empty")
|
|
880
|
-
})
|
|
881
|
-
|
|
882
|
-
if (meta.required) {
|
|
883
|
-
schema = schema.check(S.isMinLength(1, {
|
|
884
|
-
message: trans("validation.empty")
|
|
885
|
-
}))
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
if (typeof meta.maxLength === "number") {
|
|
889
|
-
schema = schema.check(S.isMaxLength(meta.maxLength, {
|
|
890
|
-
message: trans("validation.string.maxLength", {
|
|
891
|
-
maxLength: meta.maxLength
|
|
892
|
-
})
|
|
893
|
-
}))
|
|
894
|
-
}
|
|
895
|
-
if (typeof meta.minLength === "number") {
|
|
896
|
-
schema = schema.check(S.isMinLength(meta.minLength, {
|
|
897
|
-
message: trans("validation.string.minLength", {
|
|
898
|
-
minLength: meta.minLength
|
|
899
|
-
})
|
|
900
|
-
}))
|
|
901
|
-
}
|
|
902
|
-
break
|
|
903
|
-
|
|
904
|
-
case "number":
|
|
905
|
-
if (meta.refinement === "int") {
|
|
906
|
-
schema = S
|
|
907
|
-
.Number
|
|
908
|
-
.annotate({
|
|
909
|
-
message: trans("validation.empty")
|
|
910
|
-
})
|
|
911
|
-
.check(S.isInt({
|
|
912
|
-
message: trans("validation.integer.expected", { actualValue: "NaN" })
|
|
913
|
-
}))
|
|
914
|
-
} else {
|
|
915
|
-
schema = S.Number.annotate({
|
|
916
|
-
message: trans("validation.number.expected", { actualValue: "NaN" })
|
|
917
|
-
})
|
|
918
|
-
|
|
919
|
-
if (meta.required) {
|
|
920
|
-
schema = schema.annotate({
|
|
921
|
-
message: trans("validation.empty")
|
|
922
|
-
})
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
if (typeof meta.minimum === "number") {
|
|
927
|
-
schema = schema.check(S.isGreaterThanOrEqualTo(meta.minimum, {
|
|
928
|
-
message: trans(meta.minimum === 0 ? "validation.number.positive" : "validation.number.min", {
|
|
929
|
-
minimum: meta.minimum,
|
|
930
|
-
isExclusive: true
|
|
931
|
-
})
|
|
932
|
-
}))
|
|
933
|
-
}
|
|
934
|
-
if (typeof meta.maximum === "number") {
|
|
935
|
-
schema = schema.check(S.isLessThanOrEqualTo(meta.maximum, {
|
|
936
|
-
message: trans("validation.number.max", {
|
|
937
|
-
maximum: meta.maximum,
|
|
938
|
-
isExclusive: true
|
|
939
|
-
})
|
|
940
|
-
}))
|
|
941
|
-
}
|
|
942
|
-
if (typeof meta.exclusiveMinimum === "number") {
|
|
943
|
-
schema = schema.check(S.isGreaterThan(meta.exclusiveMinimum, {
|
|
944
|
-
message: trans(meta.exclusiveMinimum === 0 ? "validation.number.positive" : "validation.number.min", {
|
|
945
|
-
minimum: meta.exclusiveMinimum,
|
|
946
|
-
isExclusive: false
|
|
947
|
-
})
|
|
948
|
-
}))
|
|
949
|
-
}
|
|
950
|
-
if (typeof meta.exclusiveMaximum === "number") {
|
|
951
|
-
schema = schema.check(S.isLessThan(meta.exclusiveMaximum, {
|
|
952
|
-
message: trans("validation.number.max", {
|
|
953
|
-
maximum: meta.exclusiveMaximum,
|
|
954
|
-
isExclusive: false
|
|
955
|
-
})
|
|
956
|
-
}))
|
|
957
|
-
}
|
|
958
|
-
break
|
|
959
|
-
case "select":
|
|
960
|
-
schema = S.Literals(meta.members as [any, ...any[]]).annotate({
|
|
961
|
-
message: trans("validation.not_a_valid", {
|
|
962
|
-
type: "select",
|
|
963
|
-
message: meta.members.join(", ")
|
|
964
|
-
})
|
|
965
|
-
})
|
|
966
|
-
|
|
967
|
-
break
|
|
968
|
-
|
|
969
|
-
case "multiple":
|
|
970
|
-
schema = S.Array(S.String).annotate({
|
|
971
|
-
message: trans("validation.not_a_valid", {
|
|
972
|
-
type: "multiple",
|
|
973
|
-
message: meta.members.join(", ")
|
|
974
|
-
})
|
|
975
|
-
})
|
|
976
|
-
break
|
|
977
|
-
|
|
978
|
-
case "boolean":
|
|
979
|
-
schema = S.Boolean
|
|
980
|
-
break
|
|
981
|
-
|
|
982
|
-
case "unknown":
|
|
983
|
-
schema = S.Unknown
|
|
984
|
-
break
|
|
985
|
-
|
|
986
|
-
default:
|
|
987
|
-
// For any unhandled types, use Unknown schema to prevent undefined errors
|
|
988
|
-
console.warn(`Unhandled field type: ${meta}`)
|
|
989
|
-
schema = S.Unknown
|
|
990
|
-
break
|
|
991
|
-
}
|
|
992
|
-
if (!meta.required) {
|
|
993
|
-
schema = S.NullishOr(schema)
|
|
994
|
-
}
|
|
995
|
-
const result = S.toStandardSchemaV1(schema as any)
|
|
996
|
-
return result
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
export const nullableInput = <A, I, R>(
|
|
1000
|
-
schema: S.Codec<A, I, R>,
|
|
1001
|
-
_defaultValue: () => A
|
|
1002
|
-
) => S.NullOr(schema) as any
|
|
1003
|
-
|
|
1004
|
-
export type OmegaAutoGenMeta<
|
|
1005
|
-
From extends Record<PropertyKey, any>,
|
|
1006
|
-
To extends Record<PropertyKey, any>,
|
|
1007
|
-
Name extends DeepKeys<From>
|
|
1008
|
-
> = Omit<OmegaInputProps<From, To, Name>, "form">
|
|
1009
|
-
|
|
1010
|
-
const supportedInputs = [
|
|
1011
|
-
"button",
|
|
1012
|
-
"checkbox",
|
|
1013
|
-
"color",
|
|
1014
|
-
"date",
|
|
1015
|
-
"email",
|
|
1016
|
-
"number",
|
|
1017
|
-
"password",
|
|
1018
|
-
"radio",
|
|
1019
|
-
"range",
|
|
1020
|
-
"search",
|
|
1021
|
-
"submit",
|
|
1022
|
-
"tel",
|
|
1023
|
-
"text",
|
|
1024
|
-
"time",
|
|
1025
|
-
"url"
|
|
1026
|
-
] as const
|
|
1027
|
-
export type SupportedInputs = typeof supportedInputs[number]
|
|
1028
|
-
export const getInputType = (input: string): SupportedInputs =>
|
|
1029
|
-
(supportedInputs as readonly string[]).includes(input) ? input as SupportedInputs : "text"
|
|
1030
|
-
|
|
1031
|
-
export function deepMerge(target: any, source: any) {
|
|
1032
|
-
const result = { ...target }
|
|
1033
|
-
for (const key in source) {
|
|
1034
|
-
if (Array.isArray(source[key])) {
|
|
1035
|
-
// Arrays should be copied directly, not deep merged
|
|
1036
|
-
result[key] = source[key]
|
|
1037
|
-
} else if (source[key] && isObject(source[key])) {
|
|
1038
|
-
result[key] = deepMerge(result[key], source[key])
|
|
1039
|
-
} else {
|
|
1040
|
-
result[key] = source[key]
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
return result
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
// Type definitions for schemas with fields and members
|
|
1047
|
-
type SchemaWithFields = {
|
|
1048
|
-
fields: Record<string, S.Schema<any>>
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
type SchemaWithMembers = {
|
|
1052
|
-
members: readonly S.Schema<any>[]
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
// Type guards to check schema types
|
|
1056
|
-
function hasFields(schema: any): schema is SchemaWithFields {
|
|
1057
|
-
return schema && "fields" in schema && typeof schema.fields === "object"
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
function hasMembers(schema: any): schema is SchemaWithMembers {
|
|
1061
|
-
return schema && "members" in schema && Array.isArray(schema.members)
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
// Internal implementation with WeakSet tracking
|
|
1065
|
-
export const defaultsValueFromSchema = (
|
|
1066
|
-
schema: S.Schema<any>,
|
|
1067
|
-
record: Record<string, any> = {}
|
|
1068
|
-
): any => {
|
|
1069
|
-
const ast = schema.ast
|
|
1070
|
-
const defaultValue = getDefaultFromAst(ast)
|
|
1071
|
-
|
|
1072
|
-
if (defaultValue !== undefined) {
|
|
1073
|
-
return defaultValue
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
if (isNullableOrUndefined(schema.ast) === "null") {
|
|
1077
|
-
return null
|
|
1078
|
-
}
|
|
1079
|
-
if (isNullableOrUndefined(schema.ast) === "undefined") {
|
|
1080
|
-
return undefined
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
// Check if schema has fields directly
|
|
1084
|
-
if (hasFields(schema)) {
|
|
1085
|
-
// Process fields and extract default values
|
|
1086
|
-
const result: Record<string, any> = {}
|
|
1087
|
-
|
|
1088
|
-
for (const [key, fieldSchema] of Object.entries(schema.fields)) {
|
|
1089
|
-
// Check if this field has a defaultValue in its AST
|
|
1090
|
-
const fieldAst = (fieldSchema as any)?.ast
|
|
1091
|
-
if (fieldAst?.defaultValue) {
|
|
1092
|
-
try {
|
|
1093
|
-
result[key] = fieldAst.defaultValue()
|
|
1094
|
-
continue
|
|
1095
|
-
} catch {
|
|
1096
|
-
// If defaultValue() throws, fall through to recursive processing
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
// Recursively process the field
|
|
1101
|
-
const fieldValue = defaultsValueFromSchema(fieldSchema as any, record[key] || {})
|
|
1102
|
-
if (fieldValue !== undefined) {
|
|
1103
|
-
result[key] = fieldValue
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
return { ...result, ...record }
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
// Check if schema has fields in from (for ExtendedClass and similar transformations)
|
|
1111
|
-
if ((schema as any)?.from && hasFields((schema as any).from)) {
|
|
1112
|
-
return defaultsValueFromSchema((schema as any).from, record)
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
if (hasMembers(schema)) {
|
|
1116
|
-
// Merge all member fields, giving precedence to fields with default values
|
|
1117
|
-
const mergedMembers = schema.members.reduce((acc, member) => {
|
|
1118
|
-
if (hasFields(member)) {
|
|
1119
|
-
// Check each field and give precedence to ones with default values
|
|
1120
|
-
Object.entries(member.fields).forEach(([key, fieldSchema]) => {
|
|
1121
|
-
const fieldAst: any = fieldSchema.ast
|
|
1122
|
-
const existingFieldAst: any = acc[key]?.ast
|
|
1123
|
-
|
|
1124
|
-
// If field doesn't exist yet, or new field has default and existing doesn't, use new field
|
|
1125
|
-
if (!acc[key] || (fieldAst?.defaultValue && !existingFieldAst?.defaultValue)) {
|
|
1126
|
-
acc[key] = fieldSchema
|
|
1127
|
-
}
|
|
1128
|
-
// If both have defaults or neither have defaults, keep the first one (existing)
|
|
1129
|
-
})
|
|
1130
|
-
return acc
|
|
1131
|
-
}
|
|
1132
|
-
return acc
|
|
1133
|
-
}, {} as Record<string, any>)
|
|
1134
|
-
|
|
1135
|
-
if (Object.keys(mergedMembers).length === 0) {
|
|
1136
|
-
return Object.keys(record).length > 0 ? record : undefined
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
// Use reduce to properly accumulate the merged fields
|
|
1140
|
-
return Object.entries(mergedMembers).reduce((acc, [key, value]) => {
|
|
1141
|
-
acc[key] = defaultsValueFromSchema(value, record[key] || {})
|
|
1142
|
-
return acc
|
|
1143
|
-
}, record)
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
if (Object.keys(record).length === 0) {
|
|
1147
|
-
if (S.AST.isObjects(ast)) {
|
|
1148
|
-
// Process TypeLiteral fields directly to build the result object
|
|
1149
|
-
const result: Record<string, any> = { ...record }
|
|
1150
|
-
|
|
1151
|
-
for (const prop of ast.propertySignatures) {
|
|
1152
|
-
const key = prop.name.toString()
|
|
1153
|
-
const propType = prop.type
|
|
1154
|
-
|
|
1155
|
-
const propDefault = getDefaultFromAst(propType)
|
|
1156
|
-
if (propDefault !== undefined) {
|
|
1157
|
-
result[key] = propDefault
|
|
1158
|
-
continue
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
// Create a schema from the property type and get its defaults
|
|
1162
|
-
const propSchema = S.make(propType)
|
|
1163
|
-
|
|
1164
|
-
// Recursively process the property - don't pas for prop processing
|
|
1165
|
-
// to allow proper unwrapping of nested structures
|
|
1166
|
-
const propValue = defaultsValueFromSchema(propSchema, record[key] || {})
|
|
1167
|
-
|
|
1168
|
-
if (propValue !== undefined) {
|
|
1169
|
-
result[key] = propValue
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
return result
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
if (S.AST.isString(ast)) {
|
|
1177
|
-
return ""
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
if (S.AST.isBoolean(ast)) {
|
|
1181
|
-
return false
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
}
|