@effect-app/vue-components 4.0.0-beta.18 → 4.0.0-beta.180
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/OmegaForm/OmegaArray.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +2 -3
- package/dist/types/components/OmegaForm/OmegaErrorsInternal.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaFormInput.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaInput.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaInternalInput.vue.d.ts +2 -1
- package/dist/types/components/OmegaForm/OmegaWrapper.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/createUseFormWithCustomInput.d.ts +2 -2
- package/dist/types/components/OmegaForm/errors.d.ts +33 -0
- package/dist/types/components/OmegaForm/getOmegaStore.d.ts +1 -1
- package/dist/types/components/OmegaForm/hocs.d.ts +3 -0
- package/dist/types/components/OmegaForm/index.d.ts +13 -3
- package/dist/types/components/OmegaForm/inputs.d.ts +4 -0
- package/dist/types/components/OmegaForm/meta/checks.d.ts +4 -0
- package/dist/types/components/OmegaForm/meta/createMeta.d.ts +32 -0
- package/dist/types/components/OmegaForm/meta/defaults.d.ts +2 -0
- package/dist/types/components/OmegaForm/meta/redacted.d.ts +2 -0
- package/dist/types/components/OmegaForm/meta/types.d.ts +56 -0
- package/dist/types/components/OmegaForm/meta/walker.d.ts +18 -0
- package/dist/types/components/OmegaForm/persistency.d.ts +58 -0
- package/dist/types/components/OmegaForm/submit.d.ts +60 -0
- package/dist/types/components/OmegaForm/types.d.ts +281 -0
- package/dist/types/components/OmegaForm/useOmegaForm.d.ts +6 -212
- package/dist/types/components/OmegaForm/validation/localized.d.ts +10 -0
- package/dist/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 +127 -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 +54 -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 +34 -30
- package/src/components/CommandButton.vue +4 -2
- package/src/components/OmegaForm/OmegaArray.vue +7 -5
- package/src/components/OmegaForm/OmegaAutoGen.vue +5 -3
- package/src/components/OmegaForm/OmegaErrorsInternal.vue +1 -1
- package/src/components/OmegaForm/OmegaFormInput.vue +6 -2
- package/src/components/OmegaForm/OmegaInput.vue +12 -37
- package/src/components/OmegaForm/OmegaInputVuetify.vue +5 -2
- package/src/components/OmegaForm/OmegaInternalInput.vue +18 -10
- package/src/components/OmegaForm/OmegaTaggedUnion.vue +6 -3
- package/src/components/OmegaForm/OmegaTaggedUnionInternal.vue +5 -1
- package/src/components/OmegaForm/OmegaWrapper.vue +5 -3
- package/src/components/OmegaForm/blockDialog.ts +10 -1
- package/src/components/OmegaForm/createUseFormWithCustomInput.ts +2 -1
- package/src/components/OmegaForm/errors.ts +136 -0
- package/src/components/OmegaForm/getOmegaStore.ts +1 -1
- package/src/components/OmegaForm/hocs.ts +19 -0
- package/src/components/OmegaForm/index.ts +16 -4
- package/src/components/OmegaForm/inputs.ts +22 -0
- package/src/components/OmegaForm/meta/checks.ts +81 -0
- package/src/components/OmegaForm/meta/createMeta.ts +138 -0
- package/src/components/OmegaForm/meta/defaults.ts +132 -0
- package/src/components/OmegaForm/meta/redacted.ts +66 -0
- package/src/components/OmegaForm/meta/types.ts +78 -0
- package/src/components/OmegaForm/meta/walker.ts +247 -0
- package/src/components/OmegaForm/persistency.ts +247 -0
- package/src/components/OmegaForm/submit.ts +128 -0
- package/src/components/OmegaForm/types.ts +751 -0
- package/src/components/OmegaForm/useOmegaForm.ts +57 -892
- 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 -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 -31
- 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 -31
- package/dist/vue-components.es31.js +0 -6
- package/dist/vue-components.es32.js +0 -4
- package/dist/vue-components.es33.js +0 -4
- package/dist/vue-components.es34.js +0 -113
- package/dist/vue-components.es36.js +0 -9
- package/dist/vue-components.es37.js +0 -34
- package/dist/vue-components.es39.js +0 -194
- 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,5 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { S } from "effect-app"
|
|
2
|
+
|
|
3
|
+
export { getInputType, type SupportedInputs } from "./inputs"
|
|
4
|
+
export { createMeta, generateMetaFromSchema, isNullableOrUndefined, metadataFromAst } from "./meta/createMeta"
|
|
5
|
+
export type { CreateMeta, FilterItems } from "./meta/createMeta"
|
|
6
|
+
export { defaultsValueFromSchema } from "./meta/defaults"
|
|
7
|
+
export { toFormSchema } from "./meta/redacted"
|
|
8
|
+
export type { BaseFieldMeta, BooleanFieldMeta, DateFieldMeta, FieldMeta, MetaRecord, MultipleFieldMeta, NestedKeyOf, NumberFieldMeta, SelectFieldMeta, StringFieldMeta, UnknownFieldMeta } from "./meta/types"
|
|
9
|
+
export { deepMerge } from "./persistency"
|
|
10
|
+
export type { BaseProps, DefaultTypeProps, FieldPath, FieldPath_, FieldValidators, FormComponent, FormProps, FormType, OmegaArrayProps, OmegaAutoGenMeta, OmegaError, OmegaFormApi, OmegaFormParams, OmegaFormState, OmegaInputProps, OmegaInputPropsBase, PrefixFromDepth, TypeOverride, TypesWithOptions } from "./types"
|
|
11
|
+
export { makeStandardSchemaV1Hooks, toLocalizedStandardSchemaV1 } from "./validation/localized"
|
|
12
|
+
|
|
13
|
+
export { FormErrors, OmegaFormKey, useErrorLabel, useOmegaForm } from "./useOmegaForm"
|
|
14
|
+
export type { defaultValuesPriorityUnion, OF, OmegaConfig, OmegaFormReturn, Policies } from "./useOmegaForm"
|
|
3
15
|
|
|
4
16
|
export { type ExtractTagValue, type ExtractUnionBranch, type InputProps, type MergedInputProps, type TaggedUnionOption, type TaggedUnionOptionsArray, type TaggedUnionProps } from "./InputProps"
|
|
5
17
|
export { default as OmegaInput } from "./OmegaInput.vue"
|
|
@@ -9,6 +21,6 @@ export { default as OmegaTaggedUnionInternal } from "./OmegaTaggedUnionInternal.
|
|
|
9
21
|
|
|
10
22
|
export { useOnClose, usePreventClose } from "./blockDialog"
|
|
11
23
|
|
|
12
|
-
export { getInputType } from "./OmegaFormStuff"
|
|
13
|
-
|
|
14
24
|
export { createUseFormWithCustomInput } from "./createUseFormWithCustomInput"
|
|
25
|
+
|
|
26
|
+
export const duplicateSchema = <From, To>(schema: S.Codec<To, From, never>) => schema
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const supportedInputs = [
|
|
2
|
+
"button",
|
|
3
|
+
"checkbox",
|
|
4
|
+
"color",
|
|
5
|
+
"date",
|
|
6
|
+
"email",
|
|
7
|
+
"number",
|
|
8
|
+
"password",
|
|
9
|
+
"radio",
|
|
10
|
+
"range",
|
|
11
|
+
"search",
|
|
12
|
+
"submit",
|
|
13
|
+
"tel",
|
|
14
|
+
"text",
|
|
15
|
+
"time",
|
|
16
|
+
"url"
|
|
17
|
+
] as const
|
|
18
|
+
|
|
19
|
+
export type SupportedInputs = typeof supportedInputs[number]
|
|
20
|
+
|
|
21
|
+
export const getInputType = (input: string): SupportedInputs =>
|
|
22
|
+
(supportedInputs as readonly string[]).includes(input) ? input as SupportedInputs : "text"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { type Record, S } from "effect-app"
|
|
3
|
+
import type { FieldMeta } from "./types"
|
|
4
|
+
|
|
5
|
+
export const getCheckMetas = (property: S.AST.AST): Array<Record<string, any>> => {
|
|
6
|
+
const checks = property.checks ?? []
|
|
7
|
+
|
|
8
|
+
return checks.flatMap((check) => {
|
|
9
|
+
if (check._tag === "FilterGroup") {
|
|
10
|
+
return check.checks.flatMap((inner) => {
|
|
11
|
+
const meta = inner.annotations?.meta
|
|
12
|
+
return meta && typeof meta === "object" ? [meta as Record<string, any>] : []
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const meta = check.annotations?.meta
|
|
17
|
+
return meta && typeof meta === "object" ? [meta as Record<string, any>] : []
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const getFieldMetadataFromAst = (property: S.AST.AST) => {
|
|
22
|
+
const base: Partial<FieldMeta> & Record<string, unknown> = {
|
|
23
|
+
description: S.AST.resolveDescription(property)
|
|
24
|
+
}
|
|
25
|
+
const checks = getCheckMetas(property)
|
|
26
|
+
|
|
27
|
+
if (S.AST.isString(property)) {
|
|
28
|
+
base.type = "string"
|
|
29
|
+
for (const check of checks) {
|
|
30
|
+
switch (check._tag) {
|
|
31
|
+
case "isMinLength":
|
|
32
|
+
base.minLength = check.minLength
|
|
33
|
+
break
|
|
34
|
+
case "isMaxLength":
|
|
35
|
+
base.maxLength = check.maxLength
|
|
36
|
+
break
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const format = property.annotations?.["format"]
|
|
41
|
+
if (format === "email") {
|
|
42
|
+
base.format = "email"
|
|
43
|
+
}
|
|
44
|
+
} else if (S.AST.isNumber(property)) {
|
|
45
|
+
base.type = "number"
|
|
46
|
+
for (const check of checks) {
|
|
47
|
+
switch (check._tag) {
|
|
48
|
+
case "isInt":
|
|
49
|
+
base.refinement = "int"
|
|
50
|
+
break
|
|
51
|
+
case "isGreaterThanOrEqualTo":
|
|
52
|
+
base.minimum = check.minimum
|
|
53
|
+
break
|
|
54
|
+
case "isLessThanOrEqualTo":
|
|
55
|
+
base.maximum = check.maximum
|
|
56
|
+
break
|
|
57
|
+
case "isBetween":
|
|
58
|
+
base.minimum = check.minimum
|
|
59
|
+
base.maximum = check.maximum
|
|
60
|
+
break
|
|
61
|
+
case "isGreaterThan":
|
|
62
|
+
base.exclusiveMinimum = check.exclusiveMinimum
|
|
63
|
+
break
|
|
64
|
+
case "isLessThan":
|
|
65
|
+
base.exclusiveMaximum = check.exclusiveMaximum
|
|
66
|
+
break
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
} else if (S.AST.isBoolean(property)) {
|
|
70
|
+
base.type = "boolean"
|
|
71
|
+
} else if (
|
|
72
|
+
S.AST.isDeclaration(property)
|
|
73
|
+
&& (property.annotations as any)?.typeConstructor?._tag === "Date"
|
|
74
|
+
) {
|
|
75
|
+
base.type = "date"
|
|
76
|
+
} else {
|
|
77
|
+
base.type = "unknown"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return base
|
|
81
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { type Effect, type Record, S } from "effect-app"
|
|
3
|
+
import { getTransformationFrom } from "../../../utils"
|
|
4
|
+
import type { FieldMeta, MetaRecord } from "./types"
|
|
5
|
+
import { classifyAndWalkUnion, leafMetaForAst, type ParentMeta, type WalkerContext, walkStruct } from "./walker"
|
|
6
|
+
|
|
7
|
+
export type FilterItems = {
|
|
8
|
+
items: readonly [string, ...string[]]
|
|
9
|
+
message:
|
|
10
|
+
| string
|
|
11
|
+
| Effect.Effect<string, never, never>
|
|
12
|
+
| { readonly message: string | Effect.Effect<string> }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type CreateMeta =
|
|
16
|
+
& {
|
|
17
|
+
parent?: string
|
|
18
|
+
meta?: Record<string, any>
|
|
19
|
+
nullableOrUndefined?: false | "undefined" | "null"
|
|
20
|
+
}
|
|
21
|
+
& (
|
|
22
|
+
| {
|
|
23
|
+
propertySignatures: readonly S.AST.PropertySignature[]
|
|
24
|
+
property?: never
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
propertySignatures?: never
|
|
28
|
+
property: S.AST.AST
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
export const unwrapDeclaration = (property: S.AST.AST): S.AST.AST => {
|
|
33
|
+
let current = getTransformationFrom(property)
|
|
34
|
+
|
|
35
|
+
while (S.AST.isDeclaration(current) && current.typeParameters.length > 0) {
|
|
36
|
+
current = getTransformationFrom(current.typeParameters[0]!)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return current
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const isNullableOrUndefined = (property: false | S.AST.AST | undefined) => {
|
|
43
|
+
if (!property || !S.AST.isUnion(property)) return false
|
|
44
|
+
if (property.types.find((_) => S.AST.isUndefined(_))) {
|
|
45
|
+
return "undefined"
|
|
46
|
+
}
|
|
47
|
+
if (property.types.find((_) => S.AST.isNull(_))) return "null"
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const createMeta = <T = any>(
|
|
52
|
+
{ meta = {}, parent = "", property, propertySignatures }: CreateMeta,
|
|
53
|
+
acc: Partial<MetaRecord<T>> = {}
|
|
54
|
+
): MetaRecord<T> | FieldMeta => {
|
|
55
|
+
const ctx: WalkerContext<T> = { acc, unionMeta: {} }
|
|
56
|
+
|
|
57
|
+
if (propertySignatures) {
|
|
58
|
+
const parentMeta: ParentMeta = {
|
|
59
|
+
required: meta.required !== false,
|
|
60
|
+
nullableOrUndefined: meta.nullableOrUndefined ?? false
|
|
61
|
+
}
|
|
62
|
+
walkStruct(propertySignatures, parent, parentMeta, ctx)
|
|
63
|
+
return acc
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (property) {
|
|
67
|
+
const nullableOrUndefined = isNullableOrUndefined(property)
|
|
68
|
+
const unwrapped = unwrapDeclaration(property)
|
|
69
|
+
const required = !Object.hasOwnProperty.call(meta, "required")
|
|
70
|
+
? !nullableOrUndefined
|
|
71
|
+
: (meta.required as boolean)
|
|
72
|
+
|
|
73
|
+
const parentMeta: ParentMeta = {
|
|
74
|
+
required,
|
|
75
|
+
nullableOrUndefined: (meta.nullableOrUndefined ?? nullableOrUndefined) as false | "null" | "undefined"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (S.AST.isObjects(unwrapped)) {
|
|
79
|
+
walkStruct(unwrapped.propertySignatures, parent, parentMeta, ctx)
|
|
80
|
+
return acc
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (S.AST.isUnion(unwrapped)) {
|
|
84
|
+
// For property-mode, return a FieldMeta by running through classifyAndWalkUnion
|
|
85
|
+
// and then pulling out the result at `parent` key
|
|
86
|
+
const leafCtx: WalkerContext<T> = { acc: {}, unionMeta: {} }
|
|
87
|
+
classifyAndWalkUnion(unwrapped, parent, parentMeta, leafCtx)
|
|
88
|
+
const result = (leafCtx.acc as any)[parent]
|
|
89
|
+
if (result) return result as FieldMeta
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return leafMetaForAst(unwrapped, parentMeta)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return acc
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const metadataFromAst = <From, To>(
|
|
99
|
+
schema: S.Codec<To, From, never>
|
|
100
|
+
): {
|
|
101
|
+
meta: MetaRecord<To>
|
|
102
|
+
defaultValues: Record<string, any>
|
|
103
|
+
unionMeta: Record<string, MetaRecord<To>>
|
|
104
|
+
} => {
|
|
105
|
+
const ast = unwrapDeclaration(schema.ast)
|
|
106
|
+
const newMeta: Partial<MetaRecord<To>> = {}
|
|
107
|
+
const defaultValues: Record<string, any> = {}
|
|
108
|
+
const unionMeta: Record<string, MetaRecord<To>> = {}
|
|
109
|
+
|
|
110
|
+
const ctx: WalkerContext<To> = { acc: newMeta, unionMeta }
|
|
111
|
+
|
|
112
|
+
if (S.AST.isUnion(ast)) {
|
|
113
|
+
// Root-level discriminated union
|
|
114
|
+
classifyAndWalkUnion(ast, "", { required: true, nullableOrUndefined: false }, ctx)
|
|
115
|
+
|
|
116
|
+
return { meta: newMeta as MetaRecord<To>, defaultValues, unionMeta }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (S.AST.isObjects(ast)) {
|
|
120
|
+
walkStruct(ast.propertySignatures, "", { required: true, nullableOrUndefined: false }, ctx)
|
|
121
|
+
|
|
122
|
+
return { meta: newMeta as MetaRecord<To>, defaultValues, unionMeta }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { meta: newMeta as MetaRecord<To>, defaultValues, unionMeta }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const generateMetaFromSchema = <From, To>(
|
|
129
|
+
schema: S.Codec<To, From, never>
|
|
130
|
+
): {
|
|
131
|
+
schema: S.Codec<To, From, never>
|
|
132
|
+
meta: MetaRecord<To>
|
|
133
|
+
unionMeta: Record<string, MetaRecord<To>>
|
|
134
|
+
} => {
|
|
135
|
+
const { meta, unionMeta } = metadataFromAst(schema)
|
|
136
|
+
|
|
137
|
+
return { schema, meta, unionMeta }
|
|
138
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { Effect, Option, S } from "effect-app"
|
|
3
|
+
import { isNullableOrUndefined, unwrapDeclaration } from "./createMeta"
|
|
4
|
+
|
|
5
|
+
const extractDefaultFromLink = (link: any): unknown | undefined => {
|
|
6
|
+
if (!link?.transformation?.decode?.run) return undefined
|
|
7
|
+
try {
|
|
8
|
+
const result = Effect.runSync(link.transformation.decode.run(Option.none())) as Option.Option<unknown>
|
|
9
|
+
return Option.isSome(result) ? result.value : undefined
|
|
10
|
+
} catch {
|
|
11
|
+
return undefined
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const getDefaultFromAst = (property: S.AST.AST) => {
|
|
16
|
+
// 1. Check withConstructorDefault (stored in context.defaultValue)
|
|
17
|
+
const constructorLink = property.context?.defaultValue?.[0]
|
|
18
|
+
const constructorDefault = extractDefaultFromLink(constructorLink)
|
|
19
|
+
if (constructorDefault !== undefined) return constructorDefault
|
|
20
|
+
|
|
21
|
+
// 2. Check withDecodingDefault (stored in encoding)
|
|
22
|
+
const encodingLink = property.encoding?.[0]
|
|
23
|
+
if (encodingLink && property.context?.isOptional) {
|
|
24
|
+
return extractDefaultFromLink(encodingLink)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type SchemaWithMembers = {
|
|
31
|
+
members: readonly S.Schema<any>[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function hasMembers(schema: any): schema is SchemaWithMembers {
|
|
35
|
+
return schema && "members" in schema && Array.isArray(schema.members)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Internal implementation with WeakSet tracking
|
|
39
|
+
export const defaultsValueFromSchema = (
|
|
40
|
+
schema: S.Schema<any>,
|
|
41
|
+
record: Record<string, any> = {}
|
|
42
|
+
): any => {
|
|
43
|
+
const ast = schema.ast
|
|
44
|
+
const defaultValue = getDefaultFromAst(ast)
|
|
45
|
+
|
|
46
|
+
if (defaultValue !== undefined) {
|
|
47
|
+
return defaultValue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isNullableOrUndefined(schema.ast) === "null") {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
if (isNullableOrUndefined(schema.ast) === "undefined") {
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Handle structs via AST (covers plain structs, transformed schemas like decodeTo, Class, etc.)
|
|
58
|
+
const objectsAst = S.AST.isObjects(ast)
|
|
59
|
+
? ast
|
|
60
|
+
: S.AST.isDeclaration(ast)
|
|
61
|
+
? unwrapDeclaration(ast)
|
|
62
|
+
: undefined
|
|
63
|
+
if (objectsAst && S.AST.isObjects(objectsAst)) {
|
|
64
|
+
const result: Record<string, any> = {}
|
|
65
|
+
|
|
66
|
+
for (const prop of objectsAst.propertySignatures) {
|
|
67
|
+
const key = prop.name.toString()
|
|
68
|
+
const propType = prop.type
|
|
69
|
+
|
|
70
|
+
const propDefault = getDefaultFromAst(propType)
|
|
71
|
+
if (propDefault !== undefined) {
|
|
72
|
+
result[key] = propDefault
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const propSchema = S.make(propType)
|
|
77
|
+
const propValue = defaultsValueFromSchema(propSchema, record[key] || {})
|
|
78
|
+
|
|
79
|
+
if (propValue !== undefined) {
|
|
80
|
+
result[key] = propValue
|
|
81
|
+
} else if (isNullableOrUndefined(propType) === "undefined") {
|
|
82
|
+
result[key] = undefined
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { ...result, ...record }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle unions via AST or schema-level .members
|
|
90
|
+
const unionTypes = S.AST.isUnion(ast)
|
|
91
|
+
? ast.types
|
|
92
|
+
: hasMembers(schema)
|
|
93
|
+
? schema.members.map((m) => m.ast)
|
|
94
|
+
: undefined
|
|
95
|
+
if (unionTypes) {
|
|
96
|
+
const mergedFields: Record<string, { ast: S.AST.AST }> = {}
|
|
97
|
+
|
|
98
|
+
for (const memberAstRaw of unionTypes) {
|
|
99
|
+
const memberAst = unwrapDeclaration(memberAstRaw)
|
|
100
|
+
if (!S.AST.isObjects(memberAst)) continue
|
|
101
|
+
|
|
102
|
+
for (const prop of memberAst.propertySignatures) {
|
|
103
|
+
const key = prop.name.toString()
|
|
104
|
+
const fieldDefault = getDefaultFromAst(prop.type)
|
|
105
|
+
const existingDefault = mergedFields[key] ? getDefaultFromAst(mergedFields[key]!.ast) : undefined
|
|
106
|
+
|
|
107
|
+
if (!mergedFields[key] || (fieldDefault !== undefined && existingDefault === undefined)) {
|
|
108
|
+
mergedFields[key] = { ast: prop.type }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (Object.keys(mergedFields).length === 0) {
|
|
114
|
+
return Object.keys(record).length > 0 ? record : undefined
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return Object.entries(mergedFields).reduce((acc, [key, { ast: propAst }]) => {
|
|
118
|
+
acc[key] = defaultsValueFromSchema(S.make(propAst), record[key] || {})
|
|
119
|
+
return acc
|
|
120
|
+
}, record)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (Object.keys(record).length === 0) {
|
|
124
|
+
if (S.AST.isString(ast)) {
|
|
125
|
+
return ""
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (S.AST.isBoolean(ast)) {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { S } from "effect-app"
|
|
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, never>
|
|
23
|
+
): S.Codec<To, From, never> => {
|
|
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, never> : 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 { S } from "effect-app"
|
|
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
|
+
}
|