@effect-app/vue-components 4.0.0-beta.14 → 4.0.0-beta.141
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 +24 -0
- package/dist/reset.css +52 -0
- package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +22 -7
- package/dist/types/components/OmegaForm/useOmegaForm.d.ts +1 -1
- package/dist/types/utils/index.d.ts +6 -6
- package/dist/vue-components.es.js +21 -45
- package/dist/vue-components10.es.js +5 -0
- package/dist/vue-components11.es.js +13 -0
- package/dist/vue-components12.es.js +445 -0
- package/dist/vue-components13.es.js +4 -0
- package/dist/vue-components14.es.js +38 -0
- package/dist/vue-components15.es.js +27 -0
- package/dist/vue-components16.es.js +28 -0
- package/dist/vue-components17.es.js +7 -0
- package/dist/vue-components18.es.js +18 -0
- package/dist/vue-components19.es.js +36 -0
- package/dist/vue-components2.es.js +11 -0
- package/dist/vue-components20.es.js +18 -0
- package/dist/vue-components21.es.js +21 -0
- package/dist/vue-components22.es.js +30 -0
- package/dist/vue-components23.es.js +7 -0
- package/dist/vue-components24.es.js +9 -0
- package/dist/vue-components25.es.js +38 -0
- package/dist/vue-components26.es.js +25 -0
- package/dist/vue-components27.es.js +128 -0
- package/dist/vue-components28.es.js +24 -0
- package/dist/vue-components29.es.js +21 -0
- package/dist/vue-components3.es.js +54 -0
- package/dist/vue-components30.es.js +9 -0
- package/dist/vue-components31.es.js +19 -0
- package/dist/vue-components32.es.js +5 -0
- package/dist/vue-components33.es.js +29 -0
- package/dist/vue-components34.es.js +5 -0
- package/dist/vue-components35.es.js +29 -0
- package/dist/vue-components36.es.js +6 -0
- package/dist/vue-components37.es.js +18 -0
- package/dist/vue-components38.es.js +56 -0
- package/dist/vue-components39.es.js +5 -0
- package/dist/vue-components4.es.js +5 -0
- package/dist/vue-components40.es.js +44 -0
- package/dist/vue-components41.es.js +5 -0
- package/dist/vue-components42.es.js +84 -0
- package/dist/vue-components44.es.js +8 -0
- package/dist/vue-components45.es.js +9 -0
- package/dist/vue-components46.es.js +269 -0
- package/dist/vue-components48.es.js +8 -0
- package/dist/vue-components49.es.js +80 -0
- package/dist/vue-components5.es.js +24 -0
- package/dist/vue-components50.es.js +5 -0
- package/dist/vue-components51.es.js +66 -0
- package/dist/vue-components52.es.js +5 -0
- package/dist/vue-components53.es.js +24 -0
- package/dist/vue-components54.es.js +5 -0
- package/dist/vue-components55.es.js +59 -0
- package/dist/vue-components56.es.js +5 -0
- package/dist/vue-components57.es.js +12 -0
- package/dist/vue-components58.es.js +22 -0
- package/dist/vue-components6.es.js +13 -0
- package/dist/vue-components60.es.js +9 -0
- package/dist/vue-components61.es.js +235 -0
- package/dist/vue-components62.es.js +33 -0
- package/dist/vue-components63.es.js +8 -0
- package/dist/vue-components64.es.js +36 -0
- package/dist/vue-components7.es.js +28 -0
- package/dist/vue-components8.es.js +47 -0
- package/dist/vue-components9.es.js +5 -0
- package/package.json +32 -27
- package/src/components/CommandButton.vue +1 -1
- package/src/components/OmegaForm/OmegaFormStuff.ts +265 -115
- package/src/components/OmegaForm/OmegaInput.vue +1 -1
- package/src/components/OmegaForm/OmegaInputVuetify.vue +4 -1
- package/src/components/OmegaForm/OmegaInternalInput.vue +19 -5
- package/src/components/OmegaForm/useOmegaForm.ts +30 -7
- package/src/reset.css +52 -0
- package/src/utils/index.ts +10 -7
- 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 -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
|
@@ -2,14 +2,29 @@ import { Effect, Option, type Record, S } from "effect-app"
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
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
4
|
import { isObject } from "@vueuse/core"
|
|
5
|
-
import type
|
|
5
|
+
import type { Fiber as EffectFiber } from "effect/Fiber"
|
|
6
|
+
import type { Redacted } from "effect/Redacted"
|
|
6
7
|
import { getTransformationFrom, useIntl } from "../../utils"
|
|
7
8
|
import { type OmegaFieldInternalApi } from "./InputProps"
|
|
8
9
|
import { type OF, type OmegaFormReturn } from "./useOmegaForm"
|
|
9
10
|
|
|
11
|
+
const legacyTagWarningEmittedFor = new Set<string>()
|
|
12
|
+
type GlobalThisWithOptionalProcess = typeof globalThis & {
|
|
13
|
+
process?: {
|
|
14
|
+
env?: {
|
|
15
|
+
NODE_ENV?: string
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const isDevelopmentEnvironment = () => {
|
|
21
|
+
const process = (globalThis as GlobalThisWithOptionalProcess).process
|
|
22
|
+
return process?.env?.NODE_ENV !== "production"
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
export type FieldPath<T> = unknown extends T ? string
|
|
11
26
|
// technically we cannot have primitive at the root
|
|
12
|
-
: T extends string | boolean | number | null | undefined | symbol | bigint ? ""
|
|
27
|
+
: T extends string | boolean | number | null | undefined | symbol | bigint | Redacted<any> ? ""
|
|
13
28
|
// technically we cannot have array at the root
|
|
14
29
|
: T extends ReadonlyArray<infer U> ? FieldPath_<U, `[${number}]`>
|
|
15
30
|
: {
|
|
@@ -17,7 +32,7 @@ export type FieldPath<T> = unknown extends T ? string
|
|
|
17
32
|
}[keyof T]
|
|
18
33
|
|
|
19
34
|
export type FieldPath_<T, Path extends string> = unknown extends T ? string
|
|
20
|
-
: T extends string | boolean | number | null | undefined | symbol | bigint ? Path
|
|
35
|
+
: T extends string | boolean | number | null | undefined | symbol | bigint | Redacted<any> ? Path
|
|
21
36
|
: T extends ReadonlyArray<infer U> ? FieldPath_<U, `${Path}[${number}]`> | Path
|
|
22
37
|
: {
|
|
23
38
|
[K in keyof T]: FieldPath_<T[K], `${Path}.${K & string}`>
|
|
@@ -144,7 +159,7 @@ export type FormProps<From, To> =
|
|
|
144
159
|
formApi: OmegaFormParams<From, To>
|
|
145
160
|
meta: any
|
|
146
161
|
value: To
|
|
147
|
-
}) => Promise<any> |
|
|
162
|
+
}) => Promise<any> | EffectFiber<any, any> | Effect.Effect<unknown, any, never>
|
|
148
163
|
}
|
|
149
164
|
|
|
150
165
|
export type OmegaFormParams<From, To> = FormApi<
|
|
@@ -222,7 +237,13 @@ export type PrefixFromDepth<
|
|
|
222
237
|
_TDepth extends any[]
|
|
223
238
|
> = K
|
|
224
239
|
|
|
225
|
-
|
|
240
|
+
// Recursively replace Redacted<A> with its inner type so DeepKeys treats it as a leaf
|
|
241
|
+
type StripRedacted<T> = T extends Redacted<any> ? string
|
|
242
|
+
: T extends ReadonlyArray<infer U> ? ReadonlyArray<StripRedacted<U>>
|
|
243
|
+
: T extends Record<string, any> ? { [K in keyof T]: StripRedacted<T[K]> }
|
|
244
|
+
: T
|
|
245
|
+
|
|
246
|
+
export type NestedKeyOf<T> = DeepKeys<StripRedacted<T>>
|
|
226
247
|
|
|
227
248
|
export type FieldValidators<T> = {
|
|
228
249
|
onChangeAsync?: FieldAsyncValidateOrFn<T, any, any>
|
|
@@ -235,6 +256,14 @@ export type FieldValidators<T> = {
|
|
|
235
256
|
export type BaseFieldMeta = {
|
|
236
257
|
required: boolean
|
|
237
258
|
nullableOrUndefined?: false | "undefined" | "null"
|
|
259
|
+
/**
|
|
260
|
+
* True when the schema property is `S.optionalKey` (AST
|
|
261
|
+
* `context.isOptional`) — i.e. the key should be ABSENT from the submitted
|
|
262
|
+
* object when empty, not present with `undefined`. Distinct from
|
|
263
|
+
* `required: false`, which may also mean "empty string is valid" for
|
|
264
|
+
* unconstrained `S.String` fields.
|
|
265
|
+
*/
|
|
266
|
+
isOptionalKey?: boolean
|
|
238
267
|
}
|
|
239
268
|
|
|
240
269
|
export type StringFieldMeta = BaseFieldMeta & {
|
|
@@ -268,6 +297,10 @@ export type BooleanFieldMeta = BaseFieldMeta & {
|
|
|
268
297
|
type: "boolean"
|
|
269
298
|
}
|
|
270
299
|
|
|
300
|
+
export type DateFieldMeta = BaseFieldMeta & {
|
|
301
|
+
type: "date"
|
|
302
|
+
}
|
|
303
|
+
|
|
271
304
|
export type UnknownFieldMeta = BaseFieldMeta & {
|
|
272
305
|
type: "unknown"
|
|
273
306
|
}
|
|
@@ -278,6 +311,7 @@ export type FieldMeta =
|
|
|
278
311
|
| SelectFieldMeta
|
|
279
312
|
| MultipleFieldMeta
|
|
280
313
|
| BooleanFieldMeta
|
|
314
|
+
| DateFieldMeta
|
|
281
315
|
| UnknownFieldMeta
|
|
282
316
|
|
|
283
317
|
export type MetaRecord<T = string> = {
|
|
@@ -321,6 +355,17 @@ const unwrapDeclaration = (property: S.AST.AST): S.AST.AST => {
|
|
|
321
355
|
|
|
322
356
|
const isNullishType = (property: S.AST.AST) => S.AST.isUndefined(property) || S.AST.isNull(property)
|
|
323
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Unwrap a single-element Union to its inner type if it's a Literal.
|
|
360
|
+
* After AST.toType, S.Struct({ _tag: S.Literal("X") }) produces Union([Literal("X")])
|
|
361
|
+
* instead of bare Literal("X") like S.TaggedStruct does.
|
|
362
|
+
* TODO: remove after manual _tag deprecation
|
|
363
|
+
*/
|
|
364
|
+
const unwrapSingleLiteralUnion = (ast: S.AST.AST): S.AST.AST =>
|
|
365
|
+
S.AST.isUnion(ast) && ast.types.length === 1 && S.AST.isLiteral(ast.types[0]!)
|
|
366
|
+
? ast.types[0]!
|
|
367
|
+
: ast
|
|
368
|
+
|
|
324
369
|
const getNullableOrUndefined = (property: S.AST.AST) =>
|
|
325
370
|
S.AST.isUnion(property)
|
|
326
371
|
? property.types.find((_) => isNullishType(_))
|
|
@@ -360,13 +405,8 @@ const getJsonSchemaAnnotation = (property: S.AST.AST): Record<string, unknown> =
|
|
|
360
405
|
return jsonSchema && typeof jsonSchema === "object" ? jsonSchema as Record<string, unknown> : {}
|
|
361
406
|
}
|
|
362
407
|
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (!link?.transformation?.decode?.run) {
|
|
367
|
-
return undefined
|
|
368
|
-
}
|
|
369
|
-
|
|
408
|
+
const extractDefaultFromLink = (link: any): unknown | undefined => {
|
|
409
|
+
if (!link?.transformation?.decode?.run) return undefined
|
|
370
410
|
try {
|
|
371
411
|
const result = Effect.runSync(link.transformation.decode.run(Option.none())) as Option.Option<unknown>
|
|
372
412
|
return Option.isSome(result) ? result.value : undefined
|
|
@@ -375,6 +415,21 @@ const getDefaultFromAst = (property: S.AST.AST) => {
|
|
|
375
415
|
}
|
|
376
416
|
}
|
|
377
417
|
|
|
418
|
+
const getDefaultFromAst = (property: S.AST.AST) => {
|
|
419
|
+
// 1. Check withConstructorDefault (stored in context.defaultValue)
|
|
420
|
+
const constructorLink = property.context?.defaultValue?.[0]
|
|
421
|
+
const constructorDefault = extractDefaultFromLink(constructorLink)
|
|
422
|
+
if (constructorDefault !== undefined) return constructorDefault
|
|
423
|
+
|
|
424
|
+
// 2. Check withDecodingDefault (stored in encoding)
|
|
425
|
+
const encodingLink = property.encoding?.[0]
|
|
426
|
+
if (encodingLink && property.context?.isOptional) {
|
|
427
|
+
return extractDefaultFromLink(encodingLink)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return undefined
|
|
431
|
+
}
|
|
432
|
+
|
|
378
433
|
const getCheckMetas = (property: S.AST.AST): Array<Record<string, any>> => {
|
|
379
434
|
const checks = property.checks ?? []
|
|
380
435
|
|
|
@@ -426,6 +481,10 @@ const getFieldMetadataFromAst = (property: S.AST.AST) => {
|
|
|
426
481
|
case "isLessThanOrEqualTo":
|
|
427
482
|
base.maximum = check.maximum
|
|
428
483
|
break
|
|
484
|
+
case "isBetween":
|
|
485
|
+
base.minimum = check.minimum
|
|
486
|
+
base.maximum = check.maximum
|
|
487
|
+
break
|
|
429
488
|
case "isGreaterThan":
|
|
430
489
|
base.exclusiveMinimum = check.exclusiveMinimum
|
|
431
490
|
break
|
|
@@ -436,6 +495,11 @@ const getFieldMetadataFromAst = (property: S.AST.AST) => {
|
|
|
436
495
|
}
|
|
437
496
|
} else if (S.AST.isBoolean(property)) {
|
|
438
497
|
base.type = "boolean"
|
|
498
|
+
} else if (
|
|
499
|
+
S.AST.isDeclaration(property)
|
|
500
|
+
&& (property.annotations as any)?.typeConstructor?._tag === "Date"
|
|
501
|
+
) {
|
|
502
|
+
base.type = "date"
|
|
439
503
|
} else {
|
|
440
504
|
base.type = "unknown"
|
|
441
505
|
}
|
|
@@ -463,8 +527,11 @@ export const createMeta = <T = any>(
|
|
|
463
527
|
const key = parent ? `${parent}.${p.name.toString()}` : p.name.toString()
|
|
464
528
|
const nullableOrUndefined = isNullableOrUndefined(p.type)
|
|
465
529
|
|
|
530
|
+
const isOptionalKey = (p.type as any).context?.isOptional === true
|
|
531
|
+
|
|
466
532
|
// Determine if this field should be required:
|
|
467
533
|
// - For nullable discriminated unions, only _tag should be non-required
|
|
534
|
+
// - optionalKey fields are not required
|
|
468
535
|
// - All other fields should calculate their required status normally
|
|
469
536
|
let isRequired: boolean
|
|
470
537
|
if (meta._isNullableDiscriminatedUnion && p.name.toString() === "_tag") {
|
|
@@ -473,6 +540,8 @@ export const createMeta = <T = any>(
|
|
|
473
540
|
} else if (meta.required === false) {
|
|
474
541
|
// Explicitly set to non-required (legacy behavior for backwards compatibility)
|
|
475
542
|
isRequired = false
|
|
543
|
+
} else if (isOptionalKey) {
|
|
544
|
+
isRequired = false
|
|
476
545
|
} else {
|
|
477
546
|
// Calculate from the property itself
|
|
478
547
|
isRequired = !nullableOrUndefined
|
|
@@ -503,14 +572,28 @@ export const createMeta = <T = any>(
|
|
|
503
572
|
// - All other fields maintain their normal required status based on their own types
|
|
504
573
|
const isNullableDiscriminatedUnion = nullableOrUndefined && nonNullTypes.length > 1
|
|
505
574
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
)
|
|
575
|
+
const branchMeta = createMeta<T>({
|
|
576
|
+
parent: key,
|
|
577
|
+
propertySignatures: nonNullType.propertySignatures,
|
|
578
|
+
meta: isNullableDiscriminatedUnion ? { _isNullableDiscriminatedUnion: true } : {}
|
|
579
|
+
})
|
|
580
|
+
|
|
581
|
+
// Merge branch metadata, combining select members for shared discriminator fields
|
|
582
|
+
for (const [metaKey, metaValue] of Object.entries(branchMeta)) {
|
|
583
|
+
const existing = acc[metaKey as NestedKeyOf<T>] as FieldMeta | undefined
|
|
584
|
+
if (
|
|
585
|
+
existing && existing.type === "select" && (metaValue as any)?.type === "select"
|
|
586
|
+
) {
|
|
587
|
+
existing.members = [
|
|
588
|
+
...existing.members,
|
|
589
|
+
...(metaValue as SelectFieldMeta).members.filter(
|
|
590
|
+
(m: any) => !existing.members.includes(m)
|
|
591
|
+
)
|
|
592
|
+
]
|
|
593
|
+
} else {
|
|
594
|
+
acc[metaKey as NestedKeyOf<T>] = metaValue as FieldMeta
|
|
595
|
+
}
|
|
596
|
+
}
|
|
514
597
|
}
|
|
515
598
|
}
|
|
516
599
|
} else {
|
|
@@ -667,8 +750,9 @@ export const createMeta = <T = any>(
|
|
|
667
750
|
// an empty string is valid for a S.String field, so we should not mark it as required
|
|
668
751
|
// TODO: handle this better via the createMeta minLength parsing
|
|
669
752
|
required: isRequired
|
|
670
|
-
&& (!S.AST.isString(typeToProcess) || !!getFieldMetadataFromAst(
|
|
671
|
-
nullableOrUndefined
|
|
753
|
+
&& (!S.AST.isString(typeToProcess) || !!getFieldMetadataFromAst(typeToProcess).minLength),
|
|
754
|
+
nullableOrUndefined,
|
|
755
|
+
...(isOptionalKey ? { isOptionalKey: true } : {})
|
|
672
756
|
}
|
|
673
757
|
})
|
|
674
758
|
|
|
@@ -689,7 +773,20 @@ export const createMeta = <T = any>(
|
|
|
689
773
|
|
|
690
774
|
if (S.AST.isUnion(property)) {
|
|
691
775
|
const unwrappedTypes = unwrapNestedUnions(property.types).map(unwrapDeclaration)
|
|
692
|
-
const
|
|
776
|
+
const nonNullTypes = unwrappedTypes.filter((t) => !isNullishType(t))
|
|
777
|
+
|
|
778
|
+
// Unwrap single-element unions when the literal is a boolean
|
|
779
|
+
// (effect-app's S.Literal wraps as S.Literals([x]) → Union([Literal(x)]))
|
|
780
|
+
// Don't unwrap string/number literals — they may be discriminator values in a union
|
|
781
|
+
if (
|
|
782
|
+
nonNullTypes.length === 1
|
|
783
|
+
&& S.AST.isLiteral(nonNullTypes[0]!)
|
|
784
|
+
&& typeof nonNullTypes[0]!.literal === "boolean"
|
|
785
|
+
) {
|
|
786
|
+
return createMeta<T>({ parent, meta, property: nonNullTypes[0]! })
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const nonNullType = nonNullTypes[0]!
|
|
693
790
|
|
|
694
791
|
if (S.AST.isObjects(nonNullType)) {
|
|
695
792
|
return createMeta<T>({
|
|
@@ -699,11 +796,13 @@ export const createMeta = <T = any>(
|
|
|
699
796
|
})
|
|
700
797
|
}
|
|
701
798
|
|
|
702
|
-
|
|
799
|
+
// TODO: remove after manual _tag deprecation — unwrap legacy S.Struct({ _tag: S.Literal("X") }) pattern
|
|
800
|
+
const resolvedTypes = unwrappedTypes.map(unwrapSingleLiteralUnion)
|
|
801
|
+
if (resolvedTypes.every((_) => isNullishType(_) || S.AST.isLiteral(_))) {
|
|
703
802
|
return {
|
|
704
803
|
...meta,
|
|
705
804
|
type: "select",
|
|
706
|
-
members:
|
|
805
|
+
members: resolvedTypes.filter(S.AST.isLiteral).map((t) => t.literal)
|
|
707
806
|
} as FieldMeta
|
|
708
807
|
}
|
|
709
808
|
|
|
@@ -726,6 +825,14 @@ export const createMeta = <T = any>(
|
|
|
726
825
|
} as FieldMeta
|
|
727
826
|
}
|
|
728
827
|
|
|
828
|
+
if (S.AST.isLiteral(property)) {
|
|
829
|
+
return {
|
|
830
|
+
...meta,
|
|
831
|
+
type: "select",
|
|
832
|
+
members: [property.literal]
|
|
833
|
+
} as FieldMeta
|
|
834
|
+
}
|
|
835
|
+
|
|
729
836
|
meta = { ...getJsonSchemaAnnotation(property), ...getFieldMetadataFromAst(property), ...meta }
|
|
730
837
|
|
|
731
838
|
return meta as FieldMeta
|
|
@@ -781,9 +888,25 @@ const metadataFromAst = <From, To>(
|
|
|
781
888
|
)
|
|
782
889
|
|
|
783
890
|
let tagValue: string | null = null
|
|
784
|
-
|
|
785
|
-
|
|
891
|
+
// TODO: remove after manual _tag deprecation — unwrap legacy S.Struct({ _tag: S.Literal("X") }) pattern
|
|
892
|
+
const resolvedTagType = tagProp ? unwrapSingleLiteralUnion(tagProp.type) : null
|
|
893
|
+
if (resolvedTagType && S.AST.isLiteral(resolvedTagType)) {
|
|
894
|
+
tagValue = resolvedTagType.literal as string
|
|
786
895
|
discriminatorValues.push(tagValue)
|
|
896
|
+
// Warn if the tag was wrapped in a single-element Union (legacy pattern)
|
|
897
|
+
if (
|
|
898
|
+
tagProp
|
|
899
|
+
&& S.AST.isUnion(tagProp.type)
|
|
900
|
+
&& isDevelopmentEnvironment()
|
|
901
|
+
&& tagValue != null
|
|
902
|
+
&& !legacyTagWarningEmittedFor.has(tagValue)
|
|
903
|
+
) {
|
|
904
|
+
legacyTagWarningEmittedFor.add(tagValue)
|
|
905
|
+
console.warn(
|
|
906
|
+
`[OmegaForm] Union member with _tag "${tagValue}" uses S.Struct({ _tag: S.Literal("${tagValue}"), ... }). `
|
|
907
|
+
+ `Please migrate to S.TaggedStruct("${tagValue}", { ... }) for cleaner AST handling.`
|
|
908
|
+
)
|
|
909
|
+
}
|
|
787
910
|
}
|
|
788
911
|
|
|
789
912
|
// Create metadata for this member's properties
|
|
@@ -843,6 +966,70 @@ const metadataFromAst = <From, To>(
|
|
|
843
966
|
return { meta: newMeta, defaultValues, unionMeta }
|
|
844
967
|
}
|
|
845
968
|
|
|
969
|
+
/*
|
|
970
|
+
* Checks if an AST node is a S.Redacted Declaration without encoding.
|
|
971
|
+
* These need to be swapped to S.RedactedFromValue for form usage
|
|
972
|
+
* because S.Redacted expects Redacted objects, not plain strings.
|
|
973
|
+
*/
|
|
974
|
+
const isRedactedWithoutEncoding = (ast: S.AST.AST): boolean =>
|
|
975
|
+
S.AST.isDeclaration(ast)
|
|
976
|
+
&& (ast.annotations as any)?.typeConstructor?._tag === "effect/Redacted"
|
|
977
|
+
&& !ast.encoding
|
|
978
|
+
|
|
979
|
+
/*
|
|
980
|
+
* Creates a form-compatible schema by replacing S.Redacted(X) with
|
|
981
|
+
* S.RedactedFromValue(X). S.Redacted is a Declaration that expects
|
|
982
|
+
* Redacted<A> on both encoded and type sides, so form inputs (which
|
|
983
|
+
* produce plain strings) fail validation. S.RedactedFromValue accepts
|
|
984
|
+
* plain values on the encoded side and wraps them in Redacted on decode.
|
|
985
|
+
*/
|
|
986
|
+
export const toFormSchema = <From, To>(
|
|
987
|
+
schema: S.Codec<To, From, never>
|
|
988
|
+
): S.Codec<To, From, never> => {
|
|
989
|
+
const ast = schema.ast
|
|
990
|
+
const objAst = S.AST.isObjects(ast)
|
|
991
|
+
? ast
|
|
992
|
+
: S.AST.isDeclaration(ast)
|
|
993
|
+
? S.AST.toEncoded(ast)
|
|
994
|
+
: null
|
|
995
|
+
|
|
996
|
+
if (!objAst || !("propertySignatures" in objAst)) return schema
|
|
997
|
+
|
|
998
|
+
let hasRedacted = false
|
|
999
|
+
const props: Record<string, S.Struct.Fields[string]> = {}
|
|
1000
|
+
|
|
1001
|
+
for (const p of objAst.propertySignatures) {
|
|
1002
|
+
if (isRedactedWithoutEncoding(p.type)) {
|
|
1003
|
+
hasRedacted = true
|
|
1004
|
+
const innerSchema = S.make((p.type as S.AST.Declaration).typeParameters[0]!)
|
|
1005
|
+
props[p.name as string] = S.RedactedFromValue(innerSchema)
|
|
1006
|
+
} else if (S.AST.isUnion(p.type)) {
|
|
1007
|
+
const types = p.type.types
|
|
1008
|
+
const redactedType = types.find(isRedactedWithoutEncoding)
|
|
1009
|
+
if (redactedType) {
|
|
1010
|
+
hasRedacted = true
|
|
1011
|
+
const innerSchema = S.make((redactedType as S.AST.Declaration).typeParameters[0]!)
|
|
1012
|
+
const hasNull = types.some(S.AST.isNull)
|
|
1013
|
+
const hasUndefined = types.some(S.AST.isUndefined)
|
|
1014
|
+
const base = S.RedactedFromValue(innerSchema)
|
|
1015
|
+
props[p.name as string] = hasNull && hasUndefined
|
|
1016
|
+
? S.NullishOr(base)
|
|
1017
|
+
: hasNull
|
|
1018
|
+
? S.NullOr(base)
|
|
1019
|
+
: hasUndefined
|
|
1020
|
+
? S.UndefinedOr(base)
|
|
1021
|
+
: base
|
|
1022
|
+
} else {
|
|
1023
|
+
props[p.name as string] = S.make(p.type)
|
|
1024
|
+
}
|
|
1025
|
+
} else {
|
|
1026
|
+
props[p.name as string] = S.make(p.type)
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
return hasRedacted ? S.Struct(props) as unknown as S.Codec<To, From, never> : schema
|
|
1031
|
+
}
|
|
1032
|
+
|
|
846
1033
|
export const duplicateSchema = <From, To>(
|
|
847
1034
|
schema: S.Codec<To, From, never>
|
|
848
1035
|
) => {
|
|
@@ -912,7 +1099,7 @@ export const generateInputStandardSchemaFromFieldMeta = (
|
|
|
912
1099
|
message: trans("validation.integer.expected", { actualValue: "NaN" })
|
|
913
1100
|
}))
|
|
914
1101
|
} else {
|
|
915
|
-
schema = S.
|
|
1102
|
+
schema = S.Finite.annotate({
|
|
916
1103
|
message: trans("validation.number.expected", { actualValue: "NaN" })
|
|
917
1104
|
})
|
|
918
1105
|
|
|
@@ -979,6 +1166,10 @@ export const generateInputStandardSchemaFromFieldMeta = (
|
|
|
979
1166
|
schema = S.Boolean
|
|
980
1167
|
break
|
|
981
1168
|
|
|
1169
|
+
case "date":
|
|
1170
|
+
schema = S.Date
|
|
1171
|
+
break
|
|
1172
|
+
|
|
982
1173
|
case "unknown":
|
|
983
1174
|
schema = S.Unknown
|
|
984
1175
|
break
|
|
@@ -996,11 +1187,6 @@ export const generateInputStandardSchemaFromFieldMeta = (
|
|
|
996
1187
|
return result
|
|
997
1188
|
}
|
|
998
1189
|
|
|
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
1190
|
export type OmegaAutoGenMeta<
|
|
1005
1191
|
From extends Record<PropertyKey, any>,
|
|
1006
1192
|
To extends Record<PropertyKey, any>,
|
|
@@ -1043,20 +1229,10 @@ export function deepMerge(target: any, source: any) {
|
|
|
1043
1229
|
return result
|
|
1044
1230
|
}
|
|
1045
1231
|
|
|
1046
|
-
// Type definitions for schemas with fields and members
|
|
1047
|
-
type SchemaWithFields = {
|
|
1048
|
-
fields: Record<string, S.Schema<any>>
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
1232
|
type SchemaWithMembers = {
|
|
1052
1233
|
members: readonly S.Schema<any>[]
|
|
1053
1234
|
}
|
|
1054
1235
|
|
|
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
1236
|
function hasMembers(schema: any): schema is SchemaWithMembers {
|
|
1061
1237
|
return schema && "members" in schema && Array.isArray(schema.members)
|
|
1062
1238
|
}
|
|
@@ -1080,99 +1256,73 @@ export const defaultsValueFromSchema = (
|
|
|
1080
1256
|
return undefined
|
|
1081
1257
|
}
|
|
1082
1258
|
|
|
1083
|
-
//
|
|
1084
|
-
|
|
1085
|
-
|
|
1259
|
+
// Handle structs via AST (covers plain structs, transformed schemas like decodeTo, ExtendedClass, etc.)
|
|
1260
|
+
const objectsAst = S.AST.isObjects(ast)
|
|
1261
|
+
? ast
|
|
1262
|
+
: S.AST.isDeclaration(ast)
|
|
1263
|
+
? unwrapDeclaration(ast)
|
|
1264
|
+
: undefined
|
|
1265
|
+
if (objectsAst && S.AST.isObjects(objectsAst)) {
|
|
1086
1266
|
const result: Record<string, any> = {}
|
|
1087
1267
|
|
|
1088
|
-
for (const
|
|
1089
|
-
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
// If defaultValue() throws, fall through to recursive processing
|
|
1097
|
-
}
|
|
1268
|
+
for (const prop of objectsAst.propertySignatures) {
|
|
1269
|
+
const key = prop.name.toString()
|
|
1270
|
+
const propType = prop.type
|
|
1271
|
+
|
|
1272
|
+
const propDefault = getDefaultFromAst(propType)
|
|
1273
|
+
if (propDefault !== undefined) {
|
|
1274
|
+
result[key] = propDefault
|
|
1275
|
+
continue
|
|
1098
1276
|
}
|
|
1099
1277
|
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1278
|
+
const propSchema = S.make(propType)
|
|
1279
|
+
const propValue = defaultsValueFromSchema(propSchema, record[key] || {})
|
|
1280
|
+
|
|
1281
|
+
if (propValue !== undefined) {
|
|
1282
|
+
result[key] = propValue
|
|
1283
|
+
} else if (isNullableOrUndefined(propType) === "undefined") {
|
|
1284
|
+
result[key] = undefined
|
|
1104
1285
|
}
|
|
1105
1286
|
}
|
|
1106
1287
|
|
|
1107
1288
|
return { ...result, ...record }
|
|
1108
1289
|
}
|
|
1109
1290
|
|
|
1110
|
-
//
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1291
|
+
// Handle unions via AST or schema-level .members
|
|
1292
|
+
const unionTypes = S.AST.isUnion(ast)
|
|
1293
|
+
? ast.types
|
|
1294
|
+
: hasMembers(schema)
|
|
1295
|
+
? schema.members.map((m) => m.ast)
|
|
1296
|
+
: undefined
|
|
1297
|
+
if (unionTypes) {
|
|
1298
|
+
const mergedFields: Record<string, { ast: S.AST.AST }> = {}
|
|
1114
1299
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
}
|
|
1128
|
-
// If both have defaults or neither have defaults, keep the first one (existing)
|
|
1129
|
-
})
|
|
1130
|
-
return acc
|
|
1300
|
+
for (const memberAstRaw of unionTypes) {
|
|
1301
|
+
const memberAst = unwrapDeclaration(memberAstRaw)
|
|
1302
|
+
if (!S.AST.isObjects(memberAst)) continue
|
|
1303
|
+
|
|
1304
|
+
for (const prop of memberAst.propertySignatures) {
|
|
1305
|
+
const key = prop.name.toString()
|
|
1306
|
+
const fieldDefault = getDefaultFromAst(prop.type)
|
|
1307
|
+
const existingDefault = mergedFields[key] ? getDefaultFromAst(mergedFields[key]!.ast) : undefined
|
|
1308
|
+
|
|
1309
|
+
if (!mergedFields[key] || (fieldDefault !== undefined && existingDefault === undefined)) {
|
|
1310
|
+
mergedFields[key] = { ast: prop.type }
|
|
1311
|
+
}
|
|
1131
1312
|
}
|
|
1132
|
-
|
|
1133
|
-
}, {} as Record<string, any>)
|
|
1313
|
+
}
|
|
1134
1314
|
|
|
1135
|
-
if (Object.keys(
|
|
1315
|
+
if (Object.keys(mergedFields).length === 0) {
|
|
1136
1316
|
return Object.keys(record).length > 0 ? record : undefined
|
|
1137
1317
|
}
|
|
1138
1318
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
acc[key] = defaultsValueFromSchema(value, record[key] || {})
|
|
1319
|
+
return Object.entries(mergedFields).reduce((acc, [key, { ast: propAst }]) => {
|
|
1320
|
+
acc[key] = defaultsValueFromSchema(S.make(propAst), record[key] || {})
|
|
1142
1321
|
return acc
|
|
1143
1322
|
}, record)
|
|
1144
1323
|
}
|
|
1145
1324
|
|
|
1146
1325
|
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
1326
|
if (S.AST.isString(ast)) {
|
|
1177
1327
|
return ""
|
|
1178
1328
|
}
|
|
@@ -119,6 +119,7 @@ const isFalsyButNotZero = (value: unknown): boolean => {
|
|
|
119
119
|
// we remove value and errors when the field is empty and not required
|
|
120
120
|
// convert nullish value to null or undefined based on schema
|
|
121
121
|
const handleChange: OmegaFieldInternalApi<From, Name>["handleChange"] = (value) => {
|
|
122
|
+
let fieldDeleted = false
|
|
122
123
|
if (isFalsyButNotZero(value) && props.meta?.type !== "boolean") {
|
|
123
124
|
// Only convert to null/undefined if the field is actually nullable or optional
|
|
124
125
|
if (props.meta?.nullableOrUndefined) {
|
|
@@ -128,6 +129,14 @@ const handleChange: OmegaFieldInternalApi<From, Name>["handleChange"] = (value)
|
|
|
128
129
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
129
130
|
: null as any
|
|
130
131
|
)
|
|
132
|
+
} else if (props.meta?.isOptionalKey) {
|
|
133
|
+
// `S.optionalKey` expects the key to be ABSENT from the submitted
|
|
134
|
+
// object, not present-with-undefined. Remove it from form state
|
|
135
|
+
// rather than setting it to `undefined`. Note: this is distinct
|
|
136
|
+
// from `required: false`, which may also just mean "empty string
|
|
137
|
+
// is valid" for unconstrained `S.String` fields.
|
|
138
|
+
props.field.form.deleteField(props.field.name)
|
|
139
|
+
fieldDeleted = true
|
|
131
140
|
} else {
|
|
132
141
|
// Keep the actual value (e.g., empty string for S.String fields)
|
|
133
142
|
props.field.handleChange(value)
|
|
@@ -138,7 +147,10 @@ const handleChange: OmegaFieldInternalApi<From, Name>["handleChange"] = (value)
|
|
|
138
147
|
|
|
139
148
|
// whenever we change the field, regardless if we set it to null, we should reset onSubmit.
|
|
140
149
|
// not sure why this is not the case in tanstack form.
|
|
141
|
-
|
|
150
|
+
// Skip when the field was deleted — its meta no longer exists in the form store.
|
|
151
|
+
if (!fieldDeleted) {
|
|
152
|
+
props.field.setMeta((m) => ({ ...m, errorMap: { ...m.errorMap, onSubmit: undefined } }))
|
|
153
|
+
}
|
|
142
154
|
}
|
|
143
155
|
|
|
144
156
|
// Note: Default value normalization (converting empty strings to null/undefined for nullable fields)
|
|
@@ -165,11 +177,13 @@ const inputProps: ComputedRef<InputProps<From, Name>> = computed(() => ({
|
|
|
165
177
|
minLength: props.meta?.type === "string" && props.meta?.minLength,
|
|
166
178
|
maxLength: props.meta?.type === "string" && props.meta?.maxLength,
|
|
167
179
|
max: (props.meta?.type === "number")
|
|
168
|
-
|
|
169
|
-
?? (typeof props.meta?.exclusiveMaximum === "number"
|
|
180
|
+
? (props.meta?.maximum
|
|
181
|
+
?? (typeof props.meta?.exclusiveMaximum === "number" ? props.meta.exclusiveMaximum - 1 : undefined))
|
|
182
|
+
: undefined,
|
|
170
183
|
min: (props.meta?.type === "number")
|
|
171
|
-
|
|
172
|
-
?? (typeof props.meta?.exclusiveMinimum === "number"
|
|
184
|
+
? (props.meta?.minimum
|
|
185
|
+
?? (typeof props.meta?.exclusiveMinimum === "number" ? props.meta.exclusiveMinimum + 1 : undefined))
|
|
186
|
+
: undefined,
|
|
173
187
|
errorMessages: errors.value,
|
|
174
188
|
error: !!errors.value.length,
|
|
175
189
|
type: fieldType.value,
|