@effect-app/vue-components 4.0.0-beta.15 → 4.0.0-beta.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/README.md +24 -0
  2. package/dist/reset.css +52 -0
  3. package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +24 -8
  4. package/dist/types/components/OmegaForm/useOmegaForm.d.ts +1 -1
  5. package/dist/types/utils/index.d.ts +6 -6
  6. package/dist/vue-components.es.js +21 -45
  7. package/dist/vue-components10.es.js +5 -0
  8. package/dist/vue-components11.es.js +13 -0
  9. package/dist/vue-components12.es.js +471 -0
  10. package/dist/vue-components13.es.js +4 -0
  11. package/dist/vue-components14.es.js +38 -0
  12. package/dist/vue-components15.es.js +27 -0
  13. package/dist/vue-components16.es.js +28 -0
  14. package/dist/vue-components17.es.js +7 -0
  15. package/dist/vue-components18.es.js +18 -0
  16. package/dist/vue-components19.es.js +36 -0
  17. package/dist/vue-components2.es.js +11 -0
  18. package/dist/vue-components20.es.js +18 -0
  19. package/dist/vue-components21.es.js +21 -0
  20. package/dist/vue-components22.es.js +30 -0
  21. package/dist/vue-components23.es.js +7 -0
  22. package/dist/vue-components24.es.js +9 -0
  23. package/dist/vue-components25.es.js +38 -0
  24. package/dist/vue-components26.es.js +25 -0
  25. package/dist/vue-components27.es.js +128 -0
  26. package/dist/vue-components28.es.js +24 -0
  27. package/dist/vue-components29.es.js +21 -0
  28. package/dist/vue-components3.es.js +54 -0
  29. package/dist/vue-components30.es.js +9 -0
  30. package/dist/vue-components31.es.js +19 -0
  31. package/dist/vue-components32.es.js +5 -0
  32. package/dist/vue-components33.es.js +29 -0
  33. package/dist/vue-components34.es.js +5 -0
  34. package/dist/vue-components35.es.js +29 -0
  35. package/dist/vue-components36.es.js +6 -0
  36. package/dist/vue-components37.es.js +18 -0
  37. package/dist/vue-components38.es.js +56 -0
  38. package/dist/vue-components39.es.js +5 -0
  39. package/dist/vue-components4.es.js +5 -0
  40. package/dist/vue-components40.es.js +44 -0
  41. package/dist/vue-components41.es.js +5 -0
  42. package/dist/vue-components42.es.js +84 -0
  43. package/dist/vue-components44.es.js +8 -0
  44. package/dist/vue-components45.es.js +9 -0
  45. package/dist/vue-components46.es.js +269 -0
  46. package/dist/vue-components48.es.js +8 -0
  47. package/dist/vue-components49.es.js +80 -0
  48. package/dist/vue-components5.es.js +24 -0
  49. package/dist/vue-components50.es.js +5 -0
  50. package/dist/vue-components51.es.js +73 -0
  51. package/dist/vue-components52.es.js +5 -0
  52. package/dist/vue-components53.es.js +24 -0
  53. package/dist/vue-components54.es.js +5 -0
  54. package/dist/vue-components55.es.js +59 -0
  55. package/dist/vue-components56.es.js +5 -0
  56. package/dist/vue-components57.es.js +12 -0
  57. package/dist/vue-components58.es.js +22 -0
  58. package/dist/vue-components6.es.js +13 -0
  59. package/dist/vue-components60.es.js +9 -0
  60. package/dist/vue-components61.es.js +235 -0
  61. package/dist/vue-components62.es.js +33 -0
  62. package/dist/vue-components63.es.js +8 -0
  63. package/dist/vue-components64.es.js +36 -0
  64. package/dist/vue-components7.es.js +28 -0
  65. package/dist/vue-components8.es.js +47 -0
  66. package/dist/vue-components9.es.js +5 -0
  67. package/package.json +33 -28
  68. package/src/components/CommandButton.vue +1 -1
  69. package/src/components/OmegaForm/OmegaFormStuff.ts +367 -129
  70. package/src/components/OmegaForm/OmegaInput.vue +36 -2
  71. package/src/components/OmegaForm/OmegaInputVuetify.vue +4 -1
  72. package/src/components/OmegaForm/OmegaInternalInput.vue +19 -5
  73. package/src/components/OmegaForm/useOmegaForm.ts +31 -8
  74. package/src/reset.css +52 -0
  75. package/src/utils/index.ts +10 -7
  76. package/dist/vue-components.es10.js +0 -239
  77. package/dist/vue-components.es11.js +0 -32
  78. package/dist/vue-components.es12.js +0 -481
  79. package/dist/vue-components.es13.js +0 -49
  80. package/dist/vue-components.es14.js +0 -4
  81. package/dist/vue-components.es15.js +0 -4
  82. package/dist/vue-components.es16.js +0 -13
  83. package/dist/vue-components.es17.js +0 -6
  84. package/dist/vue-components.es18.js +0 -13
  85. package/dist/vue-components.es19.js +0 -57
  86. package/dist/vue-components.es2.js +0 -31
  87. package/dist/vue-components.es20.js +0 -56
  88. package/dist/vue-components.es21.js +0 -8
  89. package/dist/vue-components.es22.js +0 -8
  90. package/dist/vue-components.es23.js +0 -5
  91. package/dist/vue-components.es24.js +0 -5
  92. package/dist/vue-components.es25.js +0 -4
  93. package/dist/vue-components.es26.js +0 -4
  94. package/dist/vue-components.es27.js +0 -4
  95. package/dist/vue-components.es28.js +0 -4
  96. package/dist/vue-components.es29.js +0 -19
  97. package/dist/vue-components.es3.js +0 -17
  98. package/dist/vue-components.es30.js +0 -31
  99. package/dist/vue-components.es31.js +0 -6
  100. package/dist/vue-components.es32.js +0 -4
  101. package/dist/vue-components.es33.js +0 -4
  102. package/dist/vue-components.es34.js +0 -113
  103. package/dist/vue-components.es36.js +0 -9
  104. package/dist/vue-components.es37.js +0 -34
  105. package/dist/vue-components.es39.js +0 -194
  106. package/dist/vue-components.es4.js +0 -52
  107. package/dist/vue-components.es41.js +0 -6
  108. package/dist/vue-components.es42.js +0 -25
  109. package/dist/vue-components.es43.js +0 -7
  110. package/dist/vue-components.es44.js +0 -23
  111. package/dist/vue-components.es45.js +0 -32
  112. package/dist/vue-components.es46.js +0 -24
  113. package/dist/vue-components.es47.js +0 -14
  114. package/dist/vue-components.es48.js +0 -7
  115. package/dist/vue-components.es49.js +0 -21
  116. package/dist/vue-components.es5.js +0 -52
  117. package/dist/vue-components.es50.js +0 -11
  118. package/dist/vue-components.es51.js +0 -33
  119. package/dist/vue-components.es52.js +0 -50
  120. package/dist/vue-components.es53.js +0 -28
  121. package/dist/vue-components.es54.js +0 -13
  122. package/dist/vue-components.es55.js +0 -67
  123. package/dist/vue-components.es56.js +0 -58
  124. package/dist/vue-components.es57.js +0 -19
  125. package/dist/vue-components.es58.js +0 -35
  126. package/dist/vue-components.es59.js +0 -31
  127. package/dist/vue-components.es6.js +0 -69
  128. package/dist/vue-components.es60.js +0 -44
  129. package/dist/vue-components.es61.js +0 -4
  130. package/dist/vue-components.es62.js +0 -46
  131. package/dist/vue-components.es63.js +0 -4
  132. package/dist/vue-components.es7.js +0 -83
  133. package/dist/vue-components.es8.js +0 -63
  134. 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 * as Fiber from "effect/Fiber"
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> | Fiber.Fiber<any, any> | Effect.Effect<unknown, any, never>
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
- export type NestedKeyOf<T> = DeepKeys<T>
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,15 @@ export type FieldValidators<T> = {
235
256
  export type BaseFieldMeta = {
236
257
  required: boolean
237
258
  nullableOrUndefined?: false | "undefined" | "null"
259
+ originalSchema?: StandardSchemaV1<any, any>
260
+ /**
261
+ * True when the schema property is `S.optionalKey` (AST
262
+ * `context.isOptional`) — i.e. the key should be ABSENT from the submitted
263
+ * object when empty, not present with `undefined`. Distinct from
264
+ * `required: false`, which may also mean "empty string is valid" for
265
+ * unconstrained `S.String` fields.
266
+ */
267
+ isOptionalKey?: boolean
238
268
  }
239
269
 
240
270
  export type StringFieldMeta = BaseFieldMeta & {
@@ -268,6 +298,10 @@ export type BooleanFieldMeta = BaseFieldMeta & {
268
298
  type: "boolean"
269
299
  }
270
300
 
301
+ export type DateFieldMeta = BaseFieldMeta & {
302
+ type: "date"
303
+ }
304
+
271
305
  export type UnknownFieldMeta = BaseFieldMeta & {
272
306
  type: "unknown"
273
307
  }
@@ -278,6 +312,7 @@ export type FieldMeta =
278
312
  | SelectFieldMeta
279
313
  | MultipleFieldMeta
280
314
  | BooleanFieldMeta
315
+ | DateFieldMeta
281
316
  | UnknownFieldMeta
282
317
 
283
318
  export type MetaRecord<T = string> = {
@@ -321,6 +356,17 @@ const unwrapDeclaration = (property: S.AST.AST): S.AST.AST => {
321
356
 
322
357
  const isNullishType = (property: S.AST.AST) => S.AST.isUndefined(property) || S.AST.isNull(property)
323
358
 
359
+ /**
360
+ * Unwrap a single-element Union to its inner type if it's a Literal.
361
+ * After AST.toType, S.Struct({ _tag: S.Literal("X") }) produces Union([Literal("X")])
362
+ * instead of bare Literal("X") like S.TaggedStruct does.
363
+ * TODO: remove after manual _tag deprecation
364
+ */
365
+ const unwrapSingleLiteralUnion = (ast: S.AST.AST): S.AST.AST =>
366
+ S.AST.isUnion(ast) && ast.types.length === 1 && S.AST.isLiteral(ast.types[0]!)
367
+ ? ast.types[0]!
368
+ : ast
369
+
324
370
  const getNullableOrUndefined = (property: S.AST.AST) =>
325
371
  S.AST.isUnion(property)
326
372
  ? property.types.find((_) => isNullishType(_))
@@ -360,13 +406,8 @@ const getJsonSchemaAnnotation = (property: S.AST.AST): Record<string, unknown> =
360
406
  return jsonSchema && typeof jsonSchema === "object" ? jsonSchema as Record<string, unknown> : {}
361
407
  }
362
408
 
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
-
409
+ const extractDefaultFromLink = (link: any): unknown | undefined => {
410
+ if (!link?.transformation?.decode?.run) return undefined
370
411
  try {
371
412
  const result = Effect.runSync(link.transformation.decode.run(Option.none())) as Option.Option<unknown>
372
413
  return Option.isSome(result) ? result.value : undefined
@@ -375,6 +416,21 @@ const getDefaultFromAst = (property: S.AST.AST) => {
375
416
  }
376
417
  }
377
418
 
419
+ const getDefaultFromAst = (property: S.AST.AST) => {
420
+ // 1. Check withConstructorDefault (stored in context.defaultValue)
421
+ const constructorLink = property.context?.defaultValue?.[0]
422
+ const constructorDefault = extractDefaultFromLink(constructorLink)
423
+ if (constructorDefault !== undefined) return constructorDefault
424
+
425
+ // 2. Check withDecodingDefault (stored in encoding)
426
+ const encodingLink = property.encoding?.[0]
427
+ if (encodingLink && property.context?.isOptional) {
428
+ return extractDefaultFromLink(encodingLink)
429
+ }
430
+
431
+ return undefined
432
+ }
433
+
378
434
  const getCheckMetas = (property: S.AST.AST): Array<Record<string, any>> => {
379
435
  const checks = property.checks ?? []
380
436
 
@@ -426,6 +482,10 @@ const getFieldMetadataFromAst = (property: S.AST.AST) => {
426
482
  case "isLessThanOrEqualTo":
427
483
  base.maximum = check.maximum
428
484
  break
485
+ case "isBetween":
486
+ base.minimum = check.minimum
487
+ base.maximum = check.maximum
488
+ break
429
489
  case "isGreaterThan":
430
490
  base.exclusiveMinimum = check.exclusiveMinimum
431
491
  break
@@ -436,6 +496,11 @@ const getFieldMetadataFromAst = (property: S.AST.AST) => {
436
496
  }
437
497
  } else if (S.AST.isBoolean(property)) {
438
498
  base.type = "boolean"
499
+ } else if (
500
+ S.AST.isDeclaration(property)
501
+ && (property.annotations as any)?.typeConstructor?._tag === "Date"
502
+ ) {
503
+ base.type = "date"
439
504
  } else {
440
505
  base.type = "unknown"
441
506
  }
@@ -445,7 +510,8 @@ const getFieldMetadataFromAst = (property: S.AST.AST) => {
445
510
 
446
511
  export const createMeta = <T = any>(
447
512
  { meta = {}, parent = "", property, propertySignatures }: CreateMeta,
448
- acc: Partial<MetaRecord<T>> = {}
513
+ acc: Partial<MetaRecord<T>> = {},
514
+ fieldAstByPath?: Record<string, S.AST.AST>
449
515
  ): MetaRecord<T> | FieldMeta => {
450
516
  if (property) {
451
517
  property = unwrapDeclaration(property)
@@ -463,8 +529,11 @@ export const createMeta = <T = any>(
463
529
  const key = parent ? `${parent}.${p.name.toString()}` : p.name.toString()
464
530
  const nullableOrUndefined = isNullableOrUndefined(p.type)
465
531
 
532
+ const isOptionalKey = (p.type as any).context?.isOptional === true
533
+
466
534
  // Determine if this field should be required:
467
535
  // - For nullable discriminated unions, only _tag should be non-required
536
+ // - optionalKey fields are not required
468
537
  // - All other fields should calculate their required status normally
469
538
  let isRequired: boolean
470
539
  if (meta._isNullableDiscriminatedUnion && p.name.toString() === "_tag") {
@@ -473,6 +542,8 @@ export const createMeta = <T = any>(
473
542
  } else if (meta.required === false) {
474
543
  // Explicitly set to non-required (legacy behavior for backwards compatibility)
475
544
  isRequired = false
545
+ } else if (isOptionalKey) {
546
+ isRequired = false
476
547
  } else {
477
548
  // Calculate from the property itself
478
549
  isRequired = !nullableOrUndefined
@@ -503,14 +574,28 @@ export const createMeta = <T = any>(
503
574
  // - All other fields maintain their normal required status based on their own types
504
575
  const isNullableDiscriminatedUnion = nullableOrUndefined && nonNullTypes.length > 1
505
576
 
506
- Object.assign(
507
- acc,
508
- createMeta<T>({
509
- parent: key,
510
- propertySignatures: nonNullType.propertySignatures,
511
- meta: isNullableDiscriminatedUnion ? { _isNullableDiscriminatedUnion: true } : {}
512
- })
513
- )
577
+ const branchMeta = createMeta<T>({
578
+ parent: key,
579
+ propertySignatures: nonNullType.propertySignatures,
580
+ meta: isNullableDiscriminatedUnion ? { _isNullableDiscriminatedUnion: true } : {}
581
+ })
582
+
583
+ // Merge branch metadata, combining select members for shared discriminator fields
584
+ for (const [metaKey, metaValue] of Object.entries(branchMeta)) {
585
+ const existing = acc[metaKey as NestedKeyOf<T>] as FieldMeta | undefined
586
+ if (
587
+ existing && existing.type === "select" && (metaValue as any)?.type === "select"
588
+ ) {
589
+ existing.members = [
590
+ ...existing.members,
591
+ ...(metaValue as SelectFieldMeta).members.filter(
592
+ (m: any) => !existing.members.includes(m)
593
+ )
594
+ ]
595
+ } else {
596
+ acc[metaKey as NestedKeyOf<T>] = metaValue as FieldMeta
597
+ }
598
+ }
514
599
  }
515
600
  }
516
601
  } else {
@@ -525,6 +610,9 @@ export const createMeta = <T = any>(
525
610
  required: isRequired,
526
611
  nullableOrUndefined
527
612
  } as FieldMeta
613
+ if (fieldAstByPath) {
614
+ fieldAstByPath[key] = p.type
615
+ }
528
616
 
529
617
  // If the array has struct elements, also create metadata for their properties
530
618
  if (arrayType.rest && arrayType.rest.length > 0) {
@@ -545,6 +633,9 @@ export const createMeta = <T = any>(
545
633
  // add to accumulator if valid
546
634
  if (propMeta && typeof propMeta === "object" && "type" in propMeta) {
547
635
  acc[propKey as NestedKeyOf<T>] = propMeta as FieldMeta
636
+ if (fieldAstByPath) {
637
+ fieldAstByPath[propKey] = prop.type
638
+ }
548
639
 
549
640
  if (
550
641
  propMeta.type === "multiple" && S.AST.isArrays(prop.type) && prop
@@ -568,6 +659,9 @@ export const createMeta = <T = any>(
568
659
  // add to accumulator if valid
569
660
  if (nestedPropMeta && typeof nestedPropMeta === "object" && "type" in nestedPropMeta) {
570
661
  acc[nestedPropKey as NestedKeyOf<T>] = nestedPropMeta as FieldMeta
662
+ if (fieldAstByPath) {
663
+ fieldAstByPath[nestedPropKey] = nestedProp.type
664
+ }
571
665
  }
572
666
  }
573
667
  }
@@ -584,17 +678,24 @@ export const createMeta = <T = any>(
584
678
  meta: { required: isRequired, nullableOrUndefined }
585
679
  })
586
680
  acc[key as NestedKeyOf<T>] = newMeta as FieldMeta
681
+ if (fieldAstByPath) {
682
+ fieldAstByPath[key] = p.type
683
+ }
587
684
  }
588
685
  }
589
686
  } else {
590
687
  if (S.AST.isObjects(typeToProcess)) {
591
688
  Object.assign(
592
689
  acc,
593
- createMeta<T>({
594
- parent: key,
595
- propertySignatures: typeToProcess.propertySignatures,
596
- meta: { required: isRequired, nullableOrUndefined }
597
- })
690
+ createMeta<T>(
691
+ {
692
+ parent: key,
693
+ propertySignatures: typeToProcess.propertySignatures,
694
+ meta: { required: isRequired, nullableOrUndefined }
695
+ },
696
+ {},
697
+ fieldAstByPath
698
+ )
598
699
  )
599
700
  } else if (S.AST.isArrays(p.type)) {
600
701
  // Check if it has struct elements
@@ -625,6 +726,9 @@ export const createMeta = <T = any>(
625
726
  }
626
727
  })
627
728
  acc[nestedKey as NestedKeyOf<T>] = nestedMeta as FieldMeta
729
+ if (fieldAstByPath) {
730
+ fieldAstByPath[nestedKey] = nestedProp.type
731
+ }
628
732
  }
629
733
  } else {
630
734
  // Array with primitive elements - create meta for the array itself
@@ -635,6 +739,9 @@ export const createMeta = <T = any>(
635
739
  required: !isNullableOrUndefined(prop.type),
636
740
  nullableOrUndefined: isNullableOrUndefined(prop.type)
637
741
  } as FieldMeta
742
+ if (fieldAstByPath) {
743
+ fieldAstByPath[propKey] = prop.type
744
+ }
638
745
  }
639
746
  } else {
640
747
  const fieldMeta = createMeta<T>({
@@ -646,6 +753,9 @@ export const createMeta = <T = any>(
646
753
  }
647
754
  })
648
755
  acc[propKey as NestedKeyOf<T>] = fieldMeta as FieldMeta
756
+ if (fieldAstByPath) {
757
+ fieldAstByPath[propKey] = prop.type
758
+ }
649
759
  }
650
760
  }
651
761
  }
@@ -658,6 +768,9 @@ export const createMeta = <T = any>(
658
768
  required: isRequired,
659
769
  nullableOrUndefined
660
770
  } as FieldMeta
771
+ if (fieldAstByPath) {
772
+ fieldAstByPath[key] = p.type
773
+ }
661
774
  }
662
775
  } else {
663
776
  const newMeta = createMeta<T>({
@@ -667,12 +780,16 @@ export const createMeta = <T = any>(
667
780
  // an empty string is valid for a S.String field, so we should not mark it as required
668
781
  // TODO: handle this better via the createMeta minLength parsing
669
782
  required: isRequired
670
- && (!S.AST.isString(typeToProcess) || !!getFieldMetadataFromAst(p.type).minLength),
671
- nullableOrUndefined
783
+ && (!S.AST.isString(typeToProcess) || !!getFieldMetadataFromAst(typeToProcess).minLength),
784
+ nullableOrUndefined,
785
+ ...(isOptionalKey ? { isOptionalKey: true } : {})
672
786
  }
673
787
  })
674
788
 
675
789
  acc[key as NestedKeyOf<T>] = newMeta as FieldMeta
790
+ if (fieldAstByPath) {
791
+ fieldAstByPath[key] = p.type
792
+ }
676
793
  }
677
794
  }
678
795
  }
@@ -689,7 +806,20 @@ export const createMeta = <T = any>(
689
806
 
690
807
  if (S.AST.isUnion(property)) {
691
808
  const unwrappedTypes = unwrapNestedUnions(property.types).map(unwrapDeclaration)
692
- const nonNullType = unwrappedTypes.find((t) => !isNullishType(t))!
809
+ const nonNullTypes = unwrappedTypes.filter((t) => !isNullishType(t))
810
+
811
+ // Unwrap single-element unions when the literal is a boolean
812
+ // (effect-app's S.Literal wraps as S.Literals([x]) → Union([Literal(x)]))
813
+ // Don't unwrap string/number literals — they may be discriminator values in a union
814
+ if (
815
+ nonNullTypes.length === 1
816
+ && S.AST.isLiteral(nonNullTypes[0]!)
817
+ && typeof nonNullTypes[0]!.literal === "boolean"
818
+ ) {
819
+ return createMeta<T>({ parent, meta, property: nonNullTypes[0]! })
820
+ }
821
+
822
+ const nonNullType = nonNullTypes[0]!
693
823
 
694
824
  if (S.AST.isObjects(nonNullType)) {
695
825
  return createMeta<T>({
@@ -699,11 +829,13 @@ export const createMeta = <T = any>(
699
829
  })
700
830
  }
701
831
 
702
- if (unwrappedTypes.every((_) => isNullishType(_) || S.AST.isLiteral(_))) {
832
+ // TODO: remove after manual _tag deprecation — unwrap legacy S.Struct({ _tag: S.Literal("X") }) pattern
833
+ const resolvedTypes = unwrappedTypes.map(unwrapSingleLiteralUnion)
834
+ if (resolvedTypes.every((_) => isNullishType(_) || S.AST.isLiteral(_))) {
703
835
  return {
704
836
  ...meta,
705
837
  type: "select",
706
- members: unwrappedTypes.filter(S.AST.isLiteral).map((t) => t.literal)
838
+ members: resolvedTypes.filter(S.AST.isLiteral).map((t) => t.literal)
707
839
  } as FieldMeta
708
840
  }
709
841
 
@@ -726,6 +858,14 @@ export const createMeta = <T = any>(
726
858
  } as FieldMeta
727
859
  }
728
860
 
861
+ if (S.AST.isLiteral(property)) {
862
+ return {
863
+ ...meta,
864
+ type: "select",
865
+ members: [property.literal]
866
+ } as FieldMeta
867
+ }
868
+
729
869
  meta = { ...getJsonSchemaAnnotation(property), ...getFieldMetadataFromAst(property), ...meta }
730
870
 
731
871
  return meta as FieldMeta
@@ -754,11 +894,50 @@ const flattenMeta = <T>(meta: MetaRecord<T> | FieldMeta, parentKey: string = "")
754
894
 
755
895
  const metadataFromAst = <From, To>(
756
896
  schema: S.Codec<To, From, never>
757
- ): { meta: MetaRecord<To>; defaultValues: Record<string, any>; unionMeta: Record<string, MetaRecord<To>> } => {
897
+ ): {
898
+ meta: MetaRecord<To>
899
+ defaultValues: Record<string, any>
900
+ unionMeta: Record<string, MetaRecord<To>>
901
+ } => {
758
902
  const ast = unwrapDeclaration(schema.ast)
759
903
  const newMeta: MetaRecord<To> = {}
760
904
  const defaultValues: Record<string, any> = {}
761
905
  const unionMeta: Record<string, MetaRecord<To>> = {}
906
+ const fieldAstByPath: Record<string, S.AST.AST> = {}
907
+
908
+ const toFieldStandardSchema = (
909
+ propertyAst: S.AST.AST,
910
+ required: boolean
911
+ ): StandardSchemaV1<any, any> => {
912
+ const base = S.make(propertyAst)
913
+ const fieldSchema = required ? base : S.NullishOr(base)
914
+ return S.toStandardSchemaV1(fieldSchema as any)
915
+ }
916
+
917
+ const attachOriginalSchemas = (metaRecord: MetaRecord<To>) => {
918
+ for (const [key, fieldAst] of Object.entries(fieldAstByPath)) {
919
+ const fieldMeta = metaRecord[key as NestedKeyOf<To>]
920
+ if (!fieldMeta) {
921
+ continue
922
+ }
923
+ try {
924
+ const required = fieldMeta.required ?? true
925
+ Object.defineProperty(fieldMeta, "originalSchema", {
926
+ value: toFieldStandardSchema(fieldAst, required),
927
+ enumerable: false,
928
+ configurable: true,
929
+ writable: true
930
+ })
931
+ } catch {
932
+ Object.defineProperty(fieldMeta, "originalSchema", {
933
+ value: S.toStandardSchemaV1(S.Unknown),
934
+ enumerable: false,
935
+ configurable: true,
936
+ writable: true
937
+ })
938
+ }
939
+ }
940
+ }
762
941
 
763
942
  // Handle root-level Union types (discriminated unions)
764
943
  if (S.AST.isUnion(ast)) {
@@ -781,15 +960,35 @@ const metadataFromAst = <From, To>(
781
960
  )
782
961
 
783
962
  let tagValue: string | null = null
784
- if (tagProp && S.AST.isLiteral(tagProp.type)) {
785
- tagValue = tagProp.type.literal as string
963
+ // TODO: remove after manual _tag deprecation — unwrap legacy S.Struct({ _tag: S.Literal("X") }) pattern
964
+ const resolvedTagType = tagProp ? unwrapSingleLiteralUnion(tagProp.type) : null
965
+ if (resolvedTagType && S.AST.isLiteral(resolvedTagType)) {
966
+ tagValue = resolvedTagType.literal as string
786
967
  discriminatorValues.push(tagValue)
968
+ // Warn if the tag was wrapped in a single-element Union (legacy pattern)
969
+ if (
970
+ tagProp
971
+ && S.AST.isUnion(tagProp.type)
972
+ && isDevelopmentEnvironment()
973
+ && tagValue != null
974
+ && !legacyTagWarningEmittedFor.has(tagValue)
975
+ ) {
976
+ legacyTagWarningEmittedFor.add(tagValue)
977
+ console.warn(
978
+ `[OmegaForm] Union member with _tag "${tagValue}" uses S.Struct({ _tag: S.Literal("${tagValue}"), ... }). `
979
+ + `Please migrate to S.TaggedStruct("${tagValue}", { ... }) for cleaner AST handling.`
980
+ )
981
+ }
787
982
  }
788
983
 
789
984
  // Create metadata for this member's properties
790
- const memberMeta = createMeta<To>({
791
- propertySignatures: memberType.propertySignatures
792
- })
985
+ const memberMeta = createMeta<To>(
986
+ {
987
+ propertySignatures: memberType.propertySignatures
988
+ },
989
+ {},
990
+ fieldAstByPath
991
+ )
793
992
 
794
993
  // Store per-tag metadata for reactive lookup
795
994
  if (tagValue) {
@@ -810,17 +1009,28 @@ const metadataFromAst = <From, To>(
810
1009
  } as FieldMeta
811
1010
  }
812
1011
 
1012
+ attachOriginalSchemas(newMeta)
813
1013
  return { meta: newMeta, defaultValues, unionMeta }
814
1014
  }
815
1015
  }
816
1016
 
817
1017
  if (S.AST.isObjects(ast)) {
818
- const meta = createMeta<To>({
819
- propertySignatures: ast.propertySignatures
820
- })
1018
+ const meta = createMeta<To>(
1019
+ {
1020
+ propertySignatures: ast.propertySignatures
1021
+ },
1022
+ {},
1023
+ fieldAstByPath
1024
+ )
821
1025
 
822
1026
  if (Object.values(meta).every((value) => value && "type" in value)) {
823
- return { meta: meta as MetaRecord<To>, defaultValues, unionMeta }
1027
+ const typedMeta = meta as MetaRecord<To>
1028
+ attachOriginalSchemas(typedMeta)
1029
+ return {
1030
+ meta: typedMeta,
1031
+ defaultValues,
1032
+ unionMeta
1033
+ }
824
1034
  }
825
1035
 
826
1036
  const flattenObject = (
@@ -840,9 +1050,74 @@ const metadataFromAst = <From, To>(
840
1050
  flattenObject(meta)
841
1051
  }
842
1052
 
1053
+ attachOriginalSchemas(newMeta)
843
1054
  return { meta: newMeta, defaultValues, unionMeta }
844
1055
  }
845
1056
 
1057
+ /*
1058
+ * Checks if an AST node is a S.Redacted Declaration without encoding.
1059
+ * These need to be swapped to S.RedactedFromValue for form usage
1060
+ * because S.Redacted expects Redacted objects, not plain strings.
1061
+ */
1062
+ const isRedactedWithoutEncoding = (ast: S.AST.AST): boolean =>
1063
+ S.AST.isDeclaration(ast)
1064
+ && (ast.annotations as any)?.typeConstructor?._tag === "effect/Redacted"
1065
+ && !ast.encoding
1066
+
1067
+ /*
1068
+ * Creates a form-compatible schema by replacing S.Redacted(X) with
1069
+ * S.RedactedFromValue(X). S.Redacted is a Declaration that expects
1070
+ * Redacted<A> on both encoded and type sides, so form inputs (which
1071
+ * produce plain strings) fail validation. S.RedactedFromValue accepts
1072
+ * plain values on the encoded side and wraps them in Redacted on decode.
1073
+ */
1074
+ export const toFormSchema = <From, To>(
1075
+ schema: S.Codec<To, From, never>
1076
+ ): S.Codec<To, From, never> => {
1077
+ const ast = schema.ast
1078
+ const objAst = S.AST.isObjects(ast)
1079
+ ? ast
1080
+ : S.AST.isDeclaration(ast)
1081
+ ? S.AST.toEncoded(ast)
1082
+ : null
1083
+
1084
+ if (!objAst || !("propertySignatures" in objAst)) return schema
1085
+
1086
+ let hasRedacted = false
1087
+ const props: Record<string, S.Struct.Fields[string]> = {}
1088
+
1089
+ for (const p of objAst.propertySignatures) {
1090
+ if (isRedactedWithoutEncoding(p.type)) {
1091
+ hasRedacted = true
1092
+ const innerSchema = S.make((p.type as S.AST.Declaration).typeParameters[0]!)
1093
+ props[p.name as string] = S.RedactedFromValue(innerSchema)
1094
+ } else if (S.AST.isUnion(p.type)) {
1095
+ const types = p.type.types
1096
+ const redactedType = types.find(isRedactedWithoutEncoding)
1097
+ if (redactedType) {
1098
+ hasRedacted = true
1099
+ const innerSchema = S.make((redactedType as S.AST.Declaration).typeParameters[0]!)
1100
+ const hasNull = types.some(S.AST.isNull)
1101
+ const hasUndefined = types.some(S.AST.isUndefined)
1102
+ const base = S.RedactedFromValue(innerSchema)
1103
+ props[p.name as string] = hasNull && hasUndefined
1104
+ ? S.NullishOr(base)
1105
+ : hasNull
1106
+ ? S.NullOr(base)
1107
+ : hasUndefined
1108
+ ? S.UndefinedOr(base)
1109
+ : base
1110
+ } else {
1111
+ props[p.name as string] = S.make(p.type)
1112
+ }
1113
+ } else {
1114
+ props[p.name as string] = S.make(p.type)
1115
+ }
1116
+ }
1117
+
1118
+ return hasRedacted ? S.Struct(props) as unknown as S.Codec<To, From, never> : schema
1119
+ }
1120
+
846
1121
  export const duplicateSchema = <From, To>(
847
1122
  schema: S.Codec<To, From, never>
848
1123
  ) => {
@@ -912,7 +1187,7 @@ export const generateInputStandardSchemaFromFieldMeta = (
912
1187
  message: trans("validation.integer.expected", { actualValue: "NaN" })
913
1188
  }))
914
1189
  } else {
915
- schema = S.Number.annotate({
1190
+ schema = S.Finite.annotate({
916
1191
  message: trans("validation.number.expected", { actualValue: "NaN" })
917
1192
  })
918
1193
 
@@ -979,6 +1254,10 @@ export const generateInputStandardSchemaFromFieldMeta = (
979
1254
  schema = S.Boolean
980
1255
  break
981
1256
 
1257
+ case "date":
1258
+ schema = S.Date
1259
+ break
1260
+
982
1261
  case "unknown":
983
1262
  schema = S.Unknown
984
1263
  break
@@ -996,11 +1275,6 @@ export const generateInputStandardSchemaFromFieldMeta = (
996
1275
  return result
997
1276
  }
998
1277
 
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
1278
  export type OmegaAutoGenMeta<
1005
1279
  From extends Record<PropertyKey, any>,
1006
1280
  To extends Record<PropertyKey, any>,
@@ -1043,20 +1317,10 @@ export function deepMerge(target: any, source: any) {
1043
1317
  return result
1044
1318
  }
1045
1319
 
1046
- // Type definitions for schemas with fields and members
1047
- type SchemaWithFields = {
1048
- fields: Record<string, S.Schema<any>>
1049
- }
1050
-
1051
1320
  type SchemaWithMembers = {
1052
1321
  members: readonly S.Schema<any>[]
1053
1322
  }
1054
1323
 
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
1324
  function hasMembers(schema: any): schema is SchemaWithMembers {
1061
1325
  return schema && "members" in schema && Array.isArray(schema.members)
1062
1326
  }
@@ -1080,99 +1344,73 @@ export const defaultsValueFromSchema = (
1080
1344
  return undefined
1081
1345
  }
1082
1346
 
1083
- // Check if schema has fields directly
1084
- if (hasFields(schema)) {
1085
- // Process fields and extract default values
1347
+ // Handle structs via AST (covers plain structs, transformed schemas like decodeTo, Class, etc.)
1348
+ const objectsAst = S.AST.isObjects(ast)
1349
+ ? ast
1350
+ : S.AST.isDeclaration(ast)
1351
+ ? unwrapDeclaration(ast)
1352
+ : undefined
1353
+ if (objectsAst && S.AST.isObjects(objectsAst)) {
1086
1354
  const result: Record<string, any> = {}
1087
1355
 
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
- }
1356
+ for (const prop of objectsAst.propertySignatures) {
1357
+ const key = prop.name.toString()
1358
+ const propType = prop.type
1359
+
1360
+ const propDefault = getDefaultFromAst(propType)
1361
+ if (propDefault !== undefined) {
1362
+ result[key] = propDefault
1363
+ continue
1098
1364
  }
1099
1365
 
1100
- // Recursively process the field
1101
- const fieldValue = defaultsValueFromSchema(fieldSchema as any, record[key] || {})
1102
- if (fieldValue !== undefined) {
1103
- result[key] = fieldValue
1366
+ const propSchema = S.make(propType)
1367
+ const propValue = defaultsValueFromSchema(propSchema, record[key] || {})
1368
+
1369
+ if (propValue !== undefined) {
1370
+ result[key] = propValue
1371
+ } else if (isNullableOrUndefined(propType) === "undefined") {
1372
+ result[key] = undefined
1104
1373
  }
1105
1374
  }
1106
1375
 
1107
1376
  return { ...result, ...record }
1108
1377
  }
1109
1378
 
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
- }
1379
+ // Handle unions via AST or schema-level .members
1380
+ const unionTypes = S.AST.isUnion(ast)
1381
+ ? ast.types
1382
+ : hasMembers(schema)
1383
+ ? schema.members.map((m) => m.ast)
1384
+ : undefined
1385
+ if (unionTypes) {
1386
+ const mergedFields: Record<string, { ast: S.AST.AST }> = {}
1114
1387
 
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
1388
+ for (const memberAstRaw of unionTypes) {
1389
+ const memberAst = unwrapDeclaration(memberAstRaw)
1390
+ if (!S.AST.isObjects(memberAst)) continue
1391
+
1392
+ for (const prop of memberAst.propertySignatures) {
1393
+ const key = prop.name.toString()
1394
+ const fieldDefault = getDefaultFromAst(prop.type)
1395
+ const existingDefault = mergedFields[key] ? getDefaultFromAst(mergedFields[key]!.ast) : undefined
1396
+
1397
+ if (!mergedFields[key] || (fieldDefault !== undefined && existingDefault === undefined)) {
1398
+ mergedFields[key] = { ast: prop.type }
1399
+ }
1131
1400
  }
1132
- return acc
1133
- }, {} as Record<string, any>)
1401
+ }
1134
1402
 
1135
- if (Object.keys(mergedMembers).length === 0) {
1403
+ if (Object.keys(mergedFields).length === 0) {
1136
1404
  return Object.keys(record).length > 0 ? record : undefined
1137
1405
  }
1138
1406
 
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] || {})
1407
+ return Object.entries(mergedFields).reduce((acc, [key, { ast: propAst }]) => {
1408
+ acc[key] = defaultsValueFromSchema(S.make(propAst), record[key] || {})
1142
1409
  return acc
1143
1410
  }, record)
1144
1411
  }
1145
1412
 
1146
1413
  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
1414
  if (S.AST.isString(ast)) {
1177
1415
  return ""
1178
1416
  }