@effect-app/vue-components 4.0.0-beta.5 → 4.0.0-beta.52

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 (171) hide show
  1. package/README.md +24 -0
  2. package/dist/reset.css +51 -0
  3. package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +1 -1
  4. package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +22 -15
  5. package/dist/types/components/OmegaForm/useOmegaForm.d.ts +3 -5
  6. package/dist/types/utils/index.d.ts +3 -4
  7. package/dist/vue-components.es.js +21 -44
  8. package/dist/vue-components10.es.js +5 -0
  9. package/dist/vue-components11.es.js +13 -0
  10. package/dist/vue-components12.es.js +440 -0
  11. package/dist/vue-components13.es.js +4 -0
  12. package/dist/vue-components14.es.js +38 -0
  13. package/dist/vue-components15.es.js +27 -0
  14. package/dist/vue-components16.es.js +28 -0
  15. package/dist/vue-components17.es.js +7 -0
  16. package/dist/vue-components18.es.js +18 -0
  17. package/dist/vue-components19.es.js +36 -0
  18. package/dist/vue-components2.es.js +11 -0
  19. package/dist/vue-components20.es.js +18 -0
  20. package/dist/vue-components21.es.js +21 -0
  21. package/dist/vue-components22.es.js +30 -0
  22. package/dist/vue-components23.es.js +7 -0
  23. package/dist/vue-components24.es.js +9 -0
  24. package/dist/vue-components25.es.js +38 -0
  25. package/dist/vue-components26.es.js +25 -0
  26. package/dist/vue-components27.es.js +128 -0
  27. package/dist/vue-components28.es.js +24 -0
  28. package/dist/vue-components29.es.js +21 -0
  29. package/dist/vue-components3.es.js +54 -0
  30. package/dist/vue-components30.es.js +9 -0
  31. package/dist/vue-components31.es.js +19 -0
  32. package/dist/vue-components32.es.js +5 -0
  33. package/dist/vue-components33.es.js +29 -0
  34. package/dist/vue-components34.es.js +5 -0
  35. package/dist/vue-components35.es.js +29 -0
  36. package/dist/vue-components36.es.js +6 -0
  37. package/dist/vue-components37.es.js +18 -0
  38. package/dist/vue-components38.es.js +56 -0
  39. package/dist/vue-components39.es.js +5 -0
  40. package/dist/vue-components4.es.js +5 -0
  41. package/dist/vue-components40.es.js +44 -0
  42. package/dist/vue-components41.es.js +5 -0
  43. package/dist/vue-components42.es.js +84 -0
  44. package/dist/vue-components44.es.js +8 -0
  45. package/dist/vue-components45.es.js +7 -0
  46. package/dist/vue-components46.es.js +267 -0
  47. package/dist/vue-components48.es.js +6 -0
  48. package/dist/vue-components49.es.js +79 -0
  49. package/dist/vue-components5.es.js +24 -0
  50. package/dist/vue-components50.es.js +5 -0
  51. package/dist/vue-components51.es.js +66 -0
  52. package/dist/vue-components52.es.js +5 -0
  53. package/dist/vue-components53.es.js +24 -0
  54. package/dist/vue-components54.es.js +5 -0
  55. package/dist/vue-components55.es.js +59 -0
  56. package/dist/vue-components56.es.js +5 -0
  57. package/dist/vue-components57.es.js +12 -0
  58. package/dist/vue-components58.es.js +22 -0
  59. package/dist/vue-components6.es.js +13 -0
  60. package/dist/vue-components60.es.js +7 -0
  61. package/dist/vue-components61.es.js +235 -0
  62. package/dist/vue-components62.es.js +33 -0
  63. package/dist/vue-components63.es.js +8 -0
  64. package/dist/vue-components64.es.js +36 -0
  65. package/dist/vue-components7.es.js +28 -0
  66. package/dist/vue-components8.es.js +47 -0
  67. package/dist/vue-components9.es.js +5 -0
  68. package/package.json +29 -25
  69. package/src/components/OmegaForm/OmegaAutoGen.vue +25 -30
  70. package/src/components/OmegaForm/OmegaErrorsInternal.vue +2 -3
  71. package/src/components/OmegaForm/OmegaFormStuff.ts +495 -357
  72. package/src/components/OmegaForm/OmegaInternalInput.vue +9 -5
  73. package/src/components/OmegaForm/useOmegaForm.ts +57 -36
  74. package/src/reset.css +51 -0
  75. package/src/utils/index.ts +4 -8
  76. package/dist/vue-components.es10.js +0 -237
  77. package/dist/vue-components.es100.js +0 -4
  78. package/dist/vue-components.es11.js +0 -32
  79. package/dist/vue-components.es12.js +0 -439
  80. package/dist/vue-components.es13.js +0 -49
  81. package/dist/vue-components.es14.js +0 -4
  82. package/dist/vue-components.es15.js +0 -4
  83. package/dist/vue-components.es16.js +0 -725
  84. package/dist/vue-components.es17.js +0 -143
  85. package/dist/vue-components.es18.js +0 -6
  86. package/dist/vue-components.es19.js +0 -13
  87. package/dist/vue-components.es2.js +0 -30
  88. package/dist/vue-components.es20.js +0 -5
  89. package/dist/vue-components.es21.js +0 -26
  90. package/dist/vue-components.es22.js +0 -6
  91. package/dist/vue-components.es23.js +0 -10
  92. package/dist/vue-components.es24.js +0 -57
  93. package/dist/vue-components.es25.js +0 -71
  94. package/dist/vue-components.es26.js +0 -8
  95. package/dist/vue-components.es27.js +0 -8
  96. package/dist/vue-components.es28.js +0 -5
  97. package/dist/vue-components.es29.js +0 -5
  98. package/dist/vue-components.es3.js +0 -16
  99. package/dist/vue-components.es30.js +0 -4
  100. package/dist/vue-components.es31.js +0 -4
  101. package/dist/vue-components.es32.js +0 -4
  102. package/dist/vue-components.es33.js +0 -4
  103. package/dist/vue-components.es34.js +0 -19
  104. package/dist/vue-components.es35.js +0 -13
  105. package/dist/vue-components.es36.js +0 -40
  106. package/dist/vue-components.es37.js +0 -6
  107. package/dist/vue-components.es38.js +0 -85
  108. package/dist/vue-components.es39.js +0 -54
  109. package/dist/vue-components.es4.js +0 -52
  110. package/dist/vue-components.es40.js +0 -563
  111. package/dist/vue-components.es41.js +0 -43
  112. package/dist/vue-components.es42.js +0 -29
  113. package/dist/vue-components.es43.js +0 -7
  114. package/dist/vue-components.es44.js +0 -42
  115. package/dist/vue-components.es45.js +0 -316
  116. package/dist/vue-components.es46.js +0 -33
  117. package/dist/vue-components.es47.js +0 -6
  118. package/dist/vue-components.es48.js +0 -26
  119. package/dist/vue-components.es49.js +0 -77
  120. package/dist/vue-components.es5.js +0 -52
  121. package/dist/vue-components.es50.js +0 -101
  122. package/dist/vue-components.es51.js +0 -4
  123. package/dist/vue-components.es52.js +0 -320
  124. package/dist/vue-components.es53.js +0 -66
  125. package/dist/vue-components.es54.js +0 -4
  126. package/dist/vue-components.es55.js +0 -4
  127. package/dist/vue-components.es56.js +0 -113
  128. package/dist/vue-components.es58.js +0 -9
  129. package/dist/vue-components.es59.js +0 -34
  130. package/dist/vue-components.es6.js +0 -69
  131. package/dist/vue-components.es61.js +0 -194
  132. package/dist/vue-components.es63.js +0 -25
  133. package/dist/vue-components.es64.js +0 -7
  134. package/dist/vue-components.es65.js +0 -23
  135. package/dist/vue-components.es66.js +0 -32
  136. package/dist/vue-components.es67.js +0 -24
  137. package/dist/vue-components.es68.js +0 -14
  138. package/dist/vue-components.es69.js +0 -7
  139. package/dist/vue-components.es7.js +0 -83
  140. package/dist/vue-components.es70.js +0 -21
  141. package/dist/vue-components.es71.js +0 -11
  142. package/dist/vue-components.es72.js +0 -33
  143. package/dist/vue-components.es73.js +0 -50
  144. package/dist/vue-components.es74.js +0 -28
  145. package/dist/vue-components.es75.js +0 -103
  146. package/dist/vue-components.es76.js +0 -84
  147. package/dist/vue-components.es77.js +0 -17
  148. package/dist/vue-components.es78.js +0 -34
  149. package/dist/vue-components.es79.js +0 -23
  150. package/dist/vue-components.es8.js +0 -63
  151. package/dist/vue-components.es80.js +0 -14
  152. package/dist/vue-components.es81.js +0 -115
  153. package/dist/vue-components.es82.js +0 -5
  154. package/dist/vue-components.es83.js +0 -4
  155. package/dist/vue-components.es84.js +0 -4
  156. package/dist/vue-components.es85.js +0 -4
  157. package/dist/vue-components.es86.js +0 -17
  158. package/dist/vue-components.es87.js +0 -72
  159. package/dist/vue-components.es88.js +0 -18
  160. package/dist/vue-components.es89.js +0 -10
  161. package/dist/vue-components.es9.js +0 -21
  162. package/dist/vue-components.es90.js +0 -6
  163. package/dist/vue-components.es91.js +0 -13
  164. package/dist/vue-components.es92.js +0 -67
  165. package/dist/vue-components.es93.js +0 -58
  166. package/dist/vue-components.es94.js +0 -19
  167. package/dist/vue-components.es95.js +0 -35
  168. package/dist/vue-components.es96.js +0 -31
  169. package/dist/vue-components.es97.js +0 -44
  170. package/dist/vue-components.es98.js +0 -4
  171. package/dist/vue-components.es99.js +0 -46
@@ -1,16 +1,30 @@
1
- import type * as Effect from "effect/Effect"
2
- import * as AST from "effect/SchemaAST"
1
+ import { Effect, Option, type Record, S } from "effect-app"
3
2
  /* eslint-disable @typescript-eslint/no-explicit-any */
4
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"
5
4
  import { isObject } from "@vueuse/core"
6
- import * as S from "effect/Schema"
5
+ import type { Fiber as EffectFiber } from "effect/Fiber"
6
+ import type { Redacted } from "effect/Redacted"
7
7
  import { getTransformationFrom, useIntl } from "../../utils"
8
8
  import { type OmegaFieldInternalApi } from "./InputProps"
9
9
  import { type OF, type OmegaFormReturn } from "./useOmegaForm"
10
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
+
11
25
  export type FieldPath<T> = unknown extends T ? string
12
26
  // technically we cannot have primitive at the root
13
- : T extends string | boolean | number | null | undefined | symbol | bigint ? ""
27
+ : T extends string | boolean | number | null | undefined | symbol | bigint | Redacted<any> ? ""
14
28
  // technically we cannot have array at the root
15
29
  : T extends ReadonlyArray<infer U> ? FieldPath_<U, `[${number}]`>
16
30
  : {
@@ -18,7 +32,7 @@ export type FieldPath<T> = unknown extends T ? string
18
32
  }[keyof T]
19
33
 
20
34
  export type FieldPath_<T, Path extends string> = unknown extends T ? string
21
- : T extends string | boolean | number | null | undefined | symbol | bigint ? Path
35
+ : T extends string | boolean | number | null | undefined | symbol | bigint | Redacted<any> ? Path
22
36
  : T extends ReadonlyArray<infer U> ? FieldPath_<U, `${Path}[${number}]`> | Path
23
37
  : {
24
38
  [K in keyof T]: FieldPath_<T[K], `${Path}.${K & string}`>
@@ -145,7 +159,7 @@ export type FormProps<From, To> =
145
159
  formApi: OmegaFormParams<From, To>
146
160
  meta: any
147
161
  value: To
148
- }) => Promise<any> | Effect.Effect<unknown, any, never>
162
+ }) => Promise<any> | EffectFiber<any, any> | Effect.Effect<unknown, any, never>
149
163
  }
150
164
 
151
165
  export type OmegaFormParams<From, To> = FormApi<
@@ -223,7 +237,13 @@ export type PrefixFromDepth<
223
237
  _TDepth extends any[]
224
238
  > = K
225
239
 
226
- 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>>
227
247
 
228
248
  export type FieldValidators<T> = {
229
249
  onChangeAsync?: FieldAsyncValidateOrFn<T, any, any>
@@ -262,13 +282,17 @@ export type SelectFieldMeta = BaseFieldMeta & {
262
282
  export type MultipleFieldMeta = BaseFieldMeta & {
263
283
  type: "multiple"
264
284
  members: any[] // TODO: should be non empty array?
265
- rest: readonly AST.AST[]
285
+ rest: readonly S.AST.AST[]
266
286
  }
267
287
 
268
288
  export type BooleanFieldMeta = BaseFieldMeta & {
269
289
  type: "boolean"
270
290
  }
271
291
 
292
+ export type DateFieldMeta = BaseFieldMeta & {
293
+ type: "date"
294
+ }
295
+
272
296
  export type UnknownFieldMeta = BaseFieldMeta & {
273
297
  type: "unknown"
274
298
  }
@@ -279,6 +303,7 @@ export type FieldMeta =
279
303
  | SelectFieldMeta
280
304
  | MultipleFieldMeta
281
305
  | BooleanFieldMeta
306
+ | DateFieldMeta
282
307
  | UnknownFieldMeta
283
308
 
284
309
  export type MetaRecord<T = string> = {
@@ -301,34 +326,57 @@ export type CreateMeta =
301
326
  }
302
327
  & (
303
328
  | {
304
- propertySignatures: readonly AST.PropertySignature[]
329
+ propertySignatures: readonly S.AST.PropertySignature[]
305
330
  property?: never
306
331
  }
307
332
  | {
308
333
  propertySignatures?: never
309
- property: AST.AST
334
+ property: S.AST.AST
310
335
  }
311
336
  )
312
337
 
313
- const getNullableOrUndefined = (property: AST.AST) => {
314
- if (!AST.isUnion(property)) return false
315
- return property.types.find((_) => AST.isUndefined(_) || _ === S.Null.ast)
338
+ const unwrapDeclaration = (property: S.AST.AST): S.AST.AST => {
339
+ let current = getTransformationFrom(property)
340
+
341
+ while (S.AST.isDeclaration(current) && current.typeParameters.length > 0) {
342
+ current = getTransformationFrom(current.typeParameters[0]!)
343
+ }
344
+
345
+ return current
316
346
  }
317
347
 
318
- export const isNullableOrUndefined = (property: false | AST.AST | undefined) => {
319
- if (!property || !AST.isUnion(property)) return false
320
- if (property.types.find((_) => AST.isUndefined(_))) {
348
+ const isNullishType = (property: S.AST.AST) => S.AST.isUndefined(property) || S.AST.isNull(property)
349
+
350
+ /**
351
+ * Unwrap a single-element Union to its inner type if it's a Literal.
352
+ * After AST.toType, S.Struct({ _tag: S.Literal("X") }) produces Union([Literal("X")])
353
+ * instead of bare Literal("X") like S.TaggedStruct does.
354
+ * TODO: remove after manual _tag deprecation
355
+ */
356
+ const unwrapSingleLiteralUnion = (ast: S.AST.AST): S.AST.AST =>
357
+ S.AST.isUnion(ast) && ast.types.length === 1 && S.AST.isLiteral(ast.types[0]!)
358
+ ? ast.types[0]!
359
+ : ast
360
+
361
+ const getNullableOrUndefined = (property: S.AST.AST) =>
362
+ S.AST.isUnion(property)
363
+ ? property.types.find((_) => isNullishType(_))
364
+ : false
365
+
366
+ export const isNullableOrUndefined = (property: false | S.AST.AST | undefined) => {
367
+ if (!property || !S.AST.isUnion(property)) return false
368
+ if (property.types.find((_) => S.AST.isUndefined(_))) {
321
369
  return "undefined"
322
370
  }
323
- if (property.types.find((_) => _ === S.Null.ast)) return "null"
371
+ if (property.types.find((_) => S.AST.isNull(_))) return "null"
324
372
  return false
325
373
  }
326
374
 
327
375
  // Helper function to recursively unwrap nested unions (e.g., S.NullOr(S.NullOr(X)) -> X)
328
- const unwrapNestedUnions = (types: readonly AST.AST[]): readonly AST.AST[] => {
329
- const result: AST.AST[] = []
376
+ const unwrapNestedUnions = (types: readonly S.AST.AST[]): readonly S.AST.AST[] => {
377
+ const result: S.AST.AST[] = []
330
378
  for (const type of types) {
331
- if (AST.isUnion(type)) {
379
+ if (S.AST.isUnion(type)) {
332
380
  // Recursively unwrap nested unions
333
381
  const unwrapped = unwrapNestedUnions(type.types)
334
382
  result.push(...unwrapped)
@@ -339,31 +387,127 @@ const unwrapNestedUnions = (types: readonly AST.AST[]): readonly AST.AST[] => {
339
387
  return result
340
388
  }
341
389
 
390
+ const getNonNullTypes = (types: readonly S.AST.AST[]) =>
391
+ unwrapNestedUnions(types)
392
+ .map(unwrapDeclaration)
393
+ .filter((_) => !isNullishType(_))
394
+
395
+ const getJsonSchemaAnnotation = (property: S.AST.AST): Record<string, unknown> => {
396
+ const jsonSchema = S.AST.resolve(property)?.jsonSchema
397
+ return jsonSchema && typeof jsonSchema === "object" ? jsonSchema as Record<string, unknown> : {}
398
+ }
399
+
400
+ const extractDefaultFromLink = (link: any): unknown | undefined => {
401
+ if (!link?.transformation?.decode?.run) return undefined
402
+ try {
403
+ const result = Effect.runSync(link.transformation.decode.run(Option.none())) as Option.Option<unknown>
404
+ return Option.isSome(result) ? result.value : undefined
405
+ } catch {
406
+ return undefined
407
+ }
408
+ }
409
+
410
+ const getDefaultFromAst = (property: S.AST.AST) => {
411
+ // 1. Check withDefaultConstructor (stored in context.defaultValue)
412
+ const constructorLink = property.context?.defaultValue?.[0]
413
+ const constructorDefault = extractDefaultFromLink(constructorLink)
414
+ if (constructorDefault !== undefined) return constructorDefault
415
+
416
+ // 2. Check withDecodingDefault (stored in encoding)
417
+ const encodingLink = property.encoding?.[0]
418
+ if (encodingLink && property.context?.isOptional) {
419
+ return extractDefaultFromLink(encodingLink)
420
+ }
421
+
422
+ return undefined
423
+ }
424
+
425
+ const getCheckMetas = (property: S.AST.AST): Array<Record<string, any>> => {
426
+ const checks = property.checks ?? []
427
+
428
+ return checks.flatMap((check) => {
429
+ if (check._tag === "FilterGroup") {
430
+ return check.checks.flatMap((inner) => {
431
+ const meta = inner.annotations?.meta
432
+ return meta && typeof meta === "object" ? [meta as Record<string, any>] : []
433
+ })
434
+ }
435
+
436
+ const meta = check.annotations?.meta
437
+ return meta && typeof meta === "object" ? [meta as Record<string, any>] : []
438
+ })
439
+ }
440
+
441
+ const getFieldMetadataFromAst = (property: S.AST.AST) => {
442
+ const base: Partial<FieldMeta> & Record<string, unknown> = {
443
+ description: S.AST.resolveDescription(property)
444
+ }
445
+ const checks = getCheckMetas(property)
446
+
447
+ if (S.AST.isString(property)) {
448
+ base.type = "string"
449
+ for (const check of checks) {
450
+ switch (check._tag) {
451
+ case "isMinLength":
452
+ base.minLength = check.minLength
453
+ break
454
+ case "isMaxLength":
455
+ base.maxLength = check.maxLength
456
+ break
457
+ }
458
+ }
459
+
460
+ if (S.AST.resolveTitle(property) === "Email") {
461
+ base.format = "email"
462
+ }
463
+ } else if (S.AST.isNumber(property)) {
464
+ base.type = "number"
465
+ for (const check of checks) {
466
+ switch (check._tag) {
467
+ case "isInt":
468
+ base.refinement = "int"
469
+ break
470
+ case "isGreaterThanOrEqualTo":
471
+ base.minimum = check.minimum
472
+ break
473
+ case "isLessThanOrEqualTo":
474
+ base.maximum = check.maximum
475
+ break
476
+ case "isBetween":
477
+ base.minimum = check.minimum
478
+ base.maximum = check.maximum
479
+ break
480
+ case "isGreaterThan":
481
+ base.exclusiveMinimum = check.exclusiveMinimum
482
+ break
483
+ case "isLessThan":
484
+ base.exclusiveMaximum = check.exclusiveMaximum
485
+ break
486
+ }
487
+ }
488
+ } else if (S.AST.isBoolean(property)) {
489
+ base.type = "boolean"
490
+ } else if (
491
+ S.AST.isDeclaration(property)
492
+ && (property.annotations as any)?.typeConstructor?._tag === "Date"
493
+ ) {
494
+ base.type = "date"
495
+ } else {
496
+ base.type = "unknown"
497
+ }
498
+
499
+ return base
500
+ }
501
+
342
502
  export const createMeta = <T = any>(
343
503
  { meta = {}, parent = "", property, propertySignatures }: CreateMeta,
344
504
  acc: Partial<MetaRecord<T>> = {}
345
505
  ): MetaRecord<T> | FieldMeta => {
346
- // unwraps class (Class are transformations)
347
- // this calls createMeta recursively, so wrapped transformations are also unwrapped
348
- // BUT: check for Int title annotation first - S.Int and branded Int have title "Int" or "int"
349
- // and we don't want to lose that information by unwrapping
350
- if (property && AST.isDeclaration(property)) {
351
- const titleOnTransform = property.annotations?.title ?? ""
352
-
353
- // only unwrap if this is NOT an Int type
354
- if (titleOnTransform !== "Int" && titleOnTransform !== "int") {
355
- // In v4, Declaration doesn't have a 'from' property
356
- // Just return the property as-is
357
- return createMeta<T>({
358
- parent,
359
- meta,
360
- property
361
- })
362
- }
363
- // if it's Int, fall through to process it with the Int type
506
+ if (property) {
507
+ property = unwrapDeclaration(property)
364
508
  }
365
509
 
366
- if (property && AST.isObjects(property)) {
510
+ if (property && S.AST.isObjects(property)) {
367
511
  return createMeta<T>({
368
512
  meta,
369
513
  propertySignatures: property.propertySignatures
@@ -375,10 +519,6 @@ export const createMeta = <T = any>(
375
519
  const key = parent ? `${parent}.${p.name.toString()}` : p.name.toString()
376
520
  const nullableOrUndefined = isNullableOrUndefined(p.type)
377
521
 
378
- // Check if this property has title "Int" or "int" annotation (from Int brand wrapper)
379
- const propertyTitle = p.type.annotations?.title ?? ""
380
- const isIntField = propertyTitle === "Int" || propertyTitle === "int"
381
-
382
522
  // Determine if this field should be required:
383
523
  // - For nullable discriminated unions, only _tag should be non-required
384
524
  // - All other fields should calculate their required status normally
@@ -394,20 +534,11 @@ export const createMeta = <T = any>(
394
534
  isRequired = !nullableOrUndefined
395
535
  }
396
536
 
397
- const typeToProcess = p.type
398
- if (AST.isUnion(p.type)) {
399
- // First unwrap any nested unions, then filter out null/undefined
400
- const unwrappedTypes = unwrapNestedUnions(p.type.types)
401
- const nonNullTypes = unwrappedTypes
402
- .filter(
403
- (t) => !AST.isUndefined(t) && !AST.isNull(t)
404
- )
405
- // unwraps class (Class are transformations)
406
- .map(getTransformationFrom)
537
+ const typeToProcess = unwrapDeclaration(p.type)
538
+ if (S.AST.isUnion(p.type)) {
539
+ const nonNullTypes = getNonNullTypes(p.type.types)
407
540
 
408
- const hasStructMembers = nonNullTypes.some(
409
- (t) => AST.isObjects(t)
410
- )
541
+ const hasStructMembers = nonNullTypes.some(S.AST.isObjects)
411
542
 
412
543
  if (hasStructMembers) {
413
544
  // Only create parent meta for non-NullOr unions to avoid duplicates
@@ -422,25 +553,38 @@ export const createMeta = <T = any>(
422
553
 
423
554
  // Process each non-null type and merge their metadata
424
555
  for (const nonNullType of nonNullTypes) {
425
- if (AST.isObjects(nonNullType)) {
556
+ if (S.AST.isObjects(nonNullType)) {
426
557
  // For discriminated unions (multiple branches):
427
558
  // - If the parent union is nullable, only _tag should be non-required
428
559
  // - All other fields maintain their normal required status based on their own types
429
560
  const isNullableDiscriminatedUnion = nullableOrUndefined && nonNullTypes.length > 1
430
561
 
431
- Object.assign(
432
- acc,
433
- createMeta<T>({
434
- parent: key,
435
- propertySignatures: nonNullType.propertySignatures,
436
- meta: isNullableDiscriminatedUnion ? { _isNullableDiscriminatedUnion: true } : {}
437
- })
438
- )
562
+ const branchMeta = createMeta<T>({
563
+ parent: key,
564
+ propertySignatures: nonNullType.propertySignatures,
565
+ meta: isNullableDiscriminatedUnion ? { _isNullableDiscriminatedUnion: true } : {}
566
+ })
567
+
568
+ // Merge branch metadata, combining select members for shared discriminator fields
569
+ for (const [metaKey, metaValue] of Object.entries(branchMeta)) {
570
+ const existing = acc[metaKey as NestedKeyOf<T>] as FieldMeta | undefined
571
+ if (
572
+ existing && existing.type === "select" && (metaValue as any)?.type === "select"
573
+ ) {
574
+ existing.members = [
575
+ ...existing.members,
576
+ ...(metaValue as SelectFieldMeta).members.filter(
577
+ (m: any) => !existing.members.includes(m)
578
+ )
579
+ ]
580
+ } else {
581
+ acc[metaKey as NestedKeyOf<T>] = metaValue as FieldMeta
582
+ }
583
+ }
439
584
  }
440
585
  }
441
586
  } else {
442
- // Check if any of the union types are arrays
443
- const arrayTypes = nonNullTypes.filter(AST.isArrays)
587
+ const arrayTypes = nonNullTypes.filter(S.AST.isArrays)
444
588
  if (arrayTypes.length > 0) {
445
589
  const arrayType = arrayTypes[0] // Take the first array type
446
590
 
@@ -454,8 +598,8 @@ export const createMeta = <T = any>(
454
598
 
455
599
  // If the array has struct elements, also create metadata for their properties
456
600
  if (arrayType.rest && arrayType.rest.length > 0) {
457
- const restElement = arrayType.rest[0]
458
- if (AST.isObjects(restElement)) {
601
+ const restElement = unwrapDeclaration(arrayType.rest[0]!)
602
+ if (S.AST.isObjects(restElement)) {
459
603
  for (const prop of restElement.propertySignatures) {
460
604
  const propKey = `${key}.${prop.name.toString()}`
461
605
 
@@ -473,12 +617,12 @@ export const createMeta = <T = any>(
473
617
  acc[propKey as NestedKeyOf<T>] = propMeta as FieldMeta
474
618
 
475
619
  if (
476
- propMeta.type === "multiple" && AST.isArrays(prop.type) && prop
620
+ propMeta.type === "multiple" && S.AST.isArrays(prop.type) && prop
477
621
  .type
478
622
  .rest && prop.type.rest.length > 0
479
623
  ) {
480
- const nestedRestElement = prop.type.rest[0]
481
- if (AST.isObjects(nestedRestElement)) {
624
+ const nestedRestElement = unwrapDeclaration(prop.type.rest[0]!)
625
+ if (S.AST.isObjects(nestedRestElement)) {
482
626
  for (const nestedProp of nestedRestElement.propertySignatures) {
483
627
  const nestedPropKey = `${propKey}.${nestedProp.name.toString()}`
484
628
 
@@ -513,34 +657,32 @@ export const createMeta = <T = any>(
513
657
  }
514
658
  }
515
659
  } else {
516
- // Unwrap transformations (like ExtendedClass) to check for propertySignatures
517
- const unwrappedTypeToProcess = getTransformationFrom(typeToProcess)
518
- if (AST.isObjects(unwrappedTypeToProcess)) {
660
+ if (S.AST.isObjects(typeToProcess)) {
519
661
  Object.assign(
520
662
  acc,
521
663
  createMeta<T>({
522
664
  parent: key,
523
- propertySignatures: unwrappedTypeToProcess.propertySignatures,
665
+ propertySignatures: typeToProcess.propertySignatures,
524
666
  meta: { required: isRequired, nullableOrUndefined }
525
667
  })
526
668
  )
527
- } else if (AST.isArrays(p.type)) {
669
+ } else if (S.AST.isArrays(p.type)) {
528
670
  // Check if it has struct elements
529
671
  const hasStructElements = p.type.rest.length > 0
530
- && AST.isObjects(p.type.rest[0])
672
+ && S.AST.isObjects(unwrapDeclaration(p.type.rest[0]!))
531
673
 
532
674
  if (hasStructElements) {
533
675
  // For arrays with struct elements, only create meta for nested fields, not the array itself
534
- const elementType = p.type.rest[0]
535
- if (AST.isObjects(elementType)) {
676
+ const elementType = unwrapDeclaration(p.type.rest[0]!)
677
+ if (S.AST.isObjects(elementType)) {
536
678
  // Process each property in the array element
537
679
  for (const prop of elementType.propertySignatures) {
538
680
  const propKey = `${key}.${prop.name.toString()}`
539
681
 
540
682
  // Check if the property is another array
541
- if (AST.isArrays(prop.type) && prop.type.rest.length > 0) {
542
- const nestedElementType = prop.type.rest[0]
543
- if (AST.isObjects(nestedElementType)) {
683
+ if (S.AST.isArrays(prop.type) && prop.type.rest.length > 0) {
684
+ const nestedElementType = unwrapDeclaration(prop.type.rest[0]!)
685
+ if (S.AST.isObjects(nestedElementType)) {
544
686
  // Array with struct elements - process nested fields
545
687
  for (const nestedProp of nestedElementType.propertySignatures) {
546
688
  const nestedKey = `${propKey}.${nestedProp.name.toString()}`
@@ -592,9 +734,11 @@ export const createMeta = <T = any>(
592
734
  parent: key,
593
735
  property: p.type,
594
736
  meta: {
595
- required: isRequired,
596
- nullableOrUndefined,
597
- ...(isIntField && { refinement: "int" })
737
+ // an empty string is valid for a S.String field, so we should not mark it as required
738
+ // TODO: handle this better via the createMeta minLength parsing
739
+ required: isRequired
740
+ && (!S.AST.isString(typeToProcess) || !!getFieldMetadataFromAst(typeToProcess).minLength),
741
+ nullableOrUndefined
598
742
  }
599
743
  })
600
744
 
@@ -607,19 +751,30 @@ export const createMeta = <T = any>(
607
751
 
608
752
  if (property) {
609
753
  const nullableOrUndefined = getNullableOrUndefined(property)
754
+ property = unwrapDeclaration(property)
610
755
 
611
756
  if (!Object.hasOwnProperty.call(meta, "required")) {
612
757
  meta["required"] = !nullableOrUndefined
613
758
  }
614
759
 
615
- if (AST.isUnion(property)) {
616
- // First unwrap any nested unions, then filter out null/undefined
617
- const unwrappedTypes = unwrapNestedUnions(property.types)
618
- const nonNullType = unwrappedTypes.find(
619
- (t) => !AST.isUndefined(t) && !AST.isNull(t)
620
- )!
760
+ if (S.AST.isUnion(property)) {
761
+ const unwrappedTypes = unwrapNestedUnions(property.types).map(unwrapDeclaration)
762
+ const nonNullTypes = unwrappedTypes.filter((t) => !isNullishType(t))
763
+
764
+ // Unwrap single-element unions when the literal is a boolean
765
+ // (effect-app's S.Literal wraps as S.Literals([x]) → Union([Literal(x)]))
766
+ // Don't unwrap string/number literals — they may be discriminator values in a union
767
+ if (
768
+ nonNullTypes.length === 1
769
+ && S.AST.isLiteral(nonNullTypes[0]!)
770
+ && typeof nonNullTypes[0]!.literal === "boolean"
771
+ ) {
772
+ return createMeta<T>({ parent, meta, property: nonNullTypes[0]! })
773
+ }
621
774
 
622
- if (AST.isObjects(nonNullType)) {
775
+ const nonNullType = nonNullTypes[0]!
776
+
777
+ if (S.AST.isObjects(nonNullType)) {
623
778
  return createMeta<T>({
624
779
  propertySignatures: nonNullType.propertySignatures,
625
780
  parent,
@@ -627,11 +782,13 @@ export const createMeta = <T = any>(
627
782
  })
628
783
  }
629
784
 
630
- if (unwrappedTypes.every(AST.isLiteral)) {
785
+ // TODO: remove after manual _tag deprecation — unwrap legacy S.Struct({ _tag: S.Literal("X") }) pattern
786
+ const resolvedTypes = unwrappedTypes.map(unwrapSingleLiteralUnion)
787
+ if (resolvedTypes.every((_) => isNullishType(_) || S.AST.isLiteral(_))) {
631
788
  return {
632
789
  ...meta,
633
790
  type: "select",
634
- members: unwrappedTypes.map((t) => t.literal)
791
+ members: resolvedTypes.filter(S.AST.isLiteral).map((t) => t.literal)
635
792
  } as FieldMeta
636
793
  }
637
794
 
@@ -645,7 +802,7 @@ export const createMeta = <T = any>(
645
802
  } as FieldMeta
646
803
  }
647
804
 
648
- if (AST.isArrays(property)) {
805
+ if (S.AST.isArrays(property)) {
649
806
  return {
650
807
  ...meta,
651
808
  type: "multiple",
@@ -654,38 +811,7 @@ export const createMeta = <T = any>(
654
811
  } as FieldMeta
655
812
  }
656
813
 
657
- const JSONAnnotation = (property.annotations?.jsonSchema ?? {}) as Record<string, unknown>
658
-
659
- meta = { ...JSONAnnotation, ...meta }
660
-
661
- // check the title annotation BEFORE following "from" to detect refinements like S.Int
662
- let titleType = property.annotations?.title ?? "unknown"
663
-
664
- // Detect basic types from AST if no title annotation
665
- if (titleType === "unknown") {
666
- if (AST.isString(property)) {
667
- titleType = "string"
668
- } else if (AST.isNumber(property)) {
669
- titleType = "number"
670
- } else if (AST.isBoolean(property)) {
671
- titleType = "boolean"
672
- }
673
- }
674
-
675
- // if this is S.Int (a refinement), set the type and skip following "from"
676
- // otherwise we'd lose the "Int" information and get "number" instead
677
- if (titleType === "Int" || titleType === "int") {
678
- meta["type"] = "number"
679
- meta["refinement"] = "int"
680
- // don't follow "from" for Int refinements
681
- } else {
682
- meta["type"] = titleType
683
- }
684
-
685
- // Always ensure required is set before returning
686
- if (!Object.hasOwnProperty.call(meta, "required")) {
687
- meta["required"] = !nullableOrUndefined
688
- }
814
+ meta = { ...getJsonSchemaAnnotation(property), ...getFieldMetadataFromAst(property), ...meta }
689
815
 
690
816
  return meta as FieldMeta
691
817
  }
@@ -711,27 +837,21 @@ const flattenMeta = <T>(meta: MetaRecord<T> | FieldMeta, parentKey: string = "")
711
837
  return result
712
838
  }
713
839
 
714
- const _schemaFromAst = (ast: AST.AST): S.Codec<any> => S.make(ast)
715
-
716
- const metadataFromAst = <_From, To>(
717
- schema: any // v4 Schema type is complex, use any for now
840
+ const metadataFromAst = <From, To>(
841
+ schema: S.Codec<To, From, never>
718
842
  ): { meta: MetaRecord<To>; defaultValues: Record<string, any>; unionMeta: Record<string, MetaRecord<To>> } => {
719
- const ast = schema.ast
843
+ const ast = unwrapDeclaration(schema.ast)
720
844
  const newMeta: MetaRecord<To> = {}
721
845
  const defaultValues: Record<string, any> = {}
722
846
  const unionMeta: Record<string, MetaRecord<To>> = {}
723
847
 
724
848
  // Handle root-level Union types (discriminated unions)
725
- if (AST.isUnion(ast)) {
726
- const types = ast.types
727
-
849
+ if (S.AST.isUnion(ast)) {
728
850
  // Filter out null/undefined types and unwrap transformations
729
- const nonNullTypes = types
730
- .filter((t: any) => !AST.isUndefined(t) && !AST.isNull(t))
731
- .map(getTransformationFrom)
851
+ const nonNullTypes = getNonNullTypes(ast.types)
732
852
 
733
853
  // Check if this is a discriminated union (all members are structs)
734
- const allStructs = nonNullTypes.every((t: any) => AST.isObjects(t))
854
+ const allStructs = nonNullTypes.every(S.AST.isObjects)
735
855
 
736
856
  if (allStructs && nonNullTypes.length > 0) {
737
857
  // Extract discriminator values from each union member
@@ -739,16 +859,32 @@ const metadataFromAst = <_From, To>(
739
859
 
740
860
  // Store metadata for each union member by its tag value
741
861
  for (const memberType of nonNullTypes) {
742
- if (AST.isObjects(memberType)) {
862
+ if (S.AST.isObjects(memberType)) {
743
863
  // Find the discriminator field (usually _tag)
744
864
  const tagProp = memberType.propertySignatures.find(
745
- (p: any) => p.name.toString() === "_tag"
865
+ (p) => p.name.toString() === "_tag"
746
866
  )
747
867
 
748
868
  let tagValue: string | null = null
749
- if (tagProp && AST.isLiteral(tagProp.type)) {
750
- tagValue = tagProp.type.literal as string
869
+ // TODO: remove after manual _tag deprecation — unwrap legacy S.Struct({ _tag: S.Literal("X") }) pattern
870
+ const resolvedTagType = tagProp ? unwrapSingleLiteralUnion(tagProp.type) : null
871
+ if (resolvedTagType && S.AST.isLiteral(resolvedTagType)) {
872
+ tagValue = resolvedTagType.literal as string
751
873
  discriminatorValues.push(tagValue)
874
+ // Warn if the tag was wrapped in a single-element Union (legacy pattern)
875
+ if (
876
+ tagProp
877
+ && S.AST.isUnion(tagProp.type)
878
+ && isDevelopmentEnvironment()
879
+ && tagValue != null
880
+ && !legacyTagWarningEmittedFor.has(tagValue)
881
+ ) {
882
+ legacyTagWarningEmittedFor.add(tagValue)
883
+ console.warn(
884
+ `[OmegaForm] Union member with _tag "${tagValue}" uses S.Struct({ _tag: S.Literal("${tagValue}"), ... }). `
885
+ + `Please migrate to S.TaggedStruct("${tagValue}", { ... }) for cleaner AST handling.`
886
+ )
887
+ }
752
888
  }
753
889
 
754
890
  // Create metadata for this member's properties
@@ -779,7 +915,7 @@ const metadataFromAst = <_From, To>(
779
915
  }
780
916
  }
781
917
 
782
- if (AST.isObjects(ast)) {
918
+ if (S.AST.isObjects(ast)) {
783
919
  const meta = createMeta<To>({
784
920
  propertySignatures: ast.propertySignatures
785
921
  })
@@ -808,16 +944,80 @@ const metadataFromAst = <_From, To>(
808
944
  return { meta: newMeta, defaultValues, unionMeta }
809
945
  }
810
946
 
947
+ /*
948
+ * Checks if an AST node is a S.Redacted Declaration without encoding.
949
+ * These need to be swapped to S.RedactedFromValue for form usage
950
+ * because S.Redacted expects Redacted objects, not plain strings.
951
+ */
952
+ const isRedactedWithoutEncoding = (ast: S.AST.AST): boolean =>
953
+ S.AST.isDeclaration(ast)
954
+ && (ast.annotations as any)?.typeConstructor?._tag === "effect/Redacted"
955
+ && !ast.encoding
956
+
957
+ /*
958
+ * Creates a form-compatible schema by replacing S.Redacted(X) with
959
+ * S.RedactedFromValue(X). S.Redacted is a Declaration that expects
960
+ * Redacted<A> on both encoded and type sides, so form inputs (which
961
+ * produce plain strings) fail validation. S.RedactedFromValue accepts
962
+ * plain values on the encoded side and wraps them in Redacted on decode.
963
+ */
964
+ export const toFormSchema = <From, To>(
965
+ schema: S.Codec<To, From, never>
966
+ ): S.Codec<To, From, never> => {
967
+ const ast = schema.ast
968
+ const objAst = S.AST.isObjects(ast)
969
+ ? ast
970
+ : S.AST.isDeclaration(ast)
971
+ ? S.AST.toEncoded(ast)
972
+ : null
973
+
974
+ if (!objAst || !("propertySignatures" in objAst)) return schema
975
+
976
+ let hasRedacted = false
977
+ const props: Record<string, S.Struct.Fields[string]> = {}
978
+
979
+ for (const p of objAst.propertySignatures) {
980
+ if (isRedactedWithoutEncoding(p.type)) {
981
+ hasRedacted = true
982
+ const innerSchema = S.make((p.type as S.AST.Declaration).typeParameters[0]!)
983
+ props[p.name as string] = S.RedactedFromValue(innerSchema)
984
+ } else if (S.AST.isUnion(p.type)) {
985
+ const types = p.type.types
986
+ const redactedType = types.find(isRedactedWithoutEncoding)
987
+ if (redactedType) {
988
+ hasRedacted = true
989
+ const innerSchema = S.make((redactedType as S.AST.Declaration).typeParameters[0]!)
990
+ const hasNull = types.some(S.AST.isNull)
991
+ const hasUndefined = types.some(S.AST.isUndefined)
992
+ const base = S.RedactedFromValue(innerSchema)
993
+ props[p.name as string] = hasNull && hasUndefined
994
+ ? S.NullishOr(base)
995
+ : hasNull
996
+ ? S.NullOr(base)
997
+ : hasUndefined
998
+ ? S.UndefinedOr(base)
999
+ : base
1000
+ } else {
1001
+ props[p.name as string] = S.make(p.type)
1002
+ }
1003
+ } else {
1004
+ props[p.name as string] = S.make(p.type)
1005
+ }
1006
+ }
1007
+
1008
+ return hasRedacted ? S.Struct(props) as unknown as S.Codec<To, From, never> : schema
1009
+ }
1010
+
811
1011
  export const duplicateSchema = <From, To>(
812
1012
  schema: S.Codec<To, From, never>
813
1013
  ) => {
814
1014
  return schema
815
1015
  }
816
1016
 
817
- export const generateMetaFromSchema = <_From, To>(
818
- schema: any // v4 Schema type is complex, use any for now
1017
+ export const generateMetaFromSchema = <From, To>(
1018
+ schema: S.Codec<To, From, never>
819
1019
  ): {
820
- schema: any
1020
+ schema: S.Codec<To, From, never>
821
1021
  meta: MetaRecord<To>
822
1022
  unionMeta: Record<string, MetaRecord<To>>
823
1023
  } => {
@@ -833,168 +1033,138 @@ export const generateInputStandardSchemaFromFieldMeta = (
833
1033
  if (!trans) {
834
1034
  trans = useIntl().trans
835
1035
  }
836
- let schema: S.Codec<any>
837
-
1036
+ let schema: any
838
1037
  switch (meta.type) {
839
- case "string": {
840
- schema = S.String
841
-
842
- // Apply format-specific schemas
843
- if (meta.format === "email") {
844
- // v4 doesn't have S.Email, use pattern validation
845
- schema = S.String.check(
846
- S.makeFilter(
847
- (s) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s) || trans("validation.email.invalid"),
848
- { title: "email format" }
849
- )
850
- )
851
- }
1038
+ case "string":
1039
+ schema = meta.format === "email"
1040
+ ? S.Email.annotate({
1041
+ message: trans("validation.email.invalid")
1042
+ })
1043
+ : S.String.annotate({
1044
+ message: trans("validation.empty")
1045
+ })
852
1046
 
853
- // Apply length validations
854
- if (meta.required || typeof meta.minLength === "number") {
855
- const minLen = meta.required ? Math.max(1, meta.minLength || 0) : (meta.minLength || 0)
856
- if (minLen > 0) {
857
- schema = schema.check(
858
- S.makeFilter(
859
- (s) => s.length >= minLen || trans("validation.string.minLength", { minLength: minLen }),
860
- { title: `minLength(${minLen})` }
861
- )
862
- )
863
- }
1047
+ if (meta.required) {
1048
+ schema = schema.check(S.isMinLength(1, {
1049
+ message: trans("validation.empty")
1050
+ }))
864
1051
  }
865
1052
 
866
1053
  if (typeof meta.maxLength === "number") {
867
- schema = schema.check(
868
- S.makeFilter(
869
- (s) => s.length <= meta.maxLength! || trans("validation.string.maxLength", { maxLength: meta.maxLength }),
870
- { title: `maxLength(${meta.maxLength})` }
871
- )
872
- )
1054
+ schema = schema.check(S.isMaxLength(meta.maxLength, {
1055
+ message: trans("validation.string.maxLength", {
1056
+ maxLength: meta.maxLength
1057
+ })
1058
+ }))
1059
+ }
1060
+ if (typeof meta.minLength === "number") {
1061
+ schema = schema.check(S.isMinLength(meta.minLength, {
1062
+ message: trans("validation.string.minLength", {
1063
+ minLength: meta.minLength
1064
+ })
1065
+ }))
873
1066
  }
874
1067
  break
875
- }
876
1068
 
877
- case "number": {
1069
+ case "number":
878
1070
  if (meta.refinement === "int") {
879
- schema = S.Int
1071
+ schema = S
1072
+ .Number
1073
+ .annotate({
1074
+ message: trans("validation.empty")
1075
+ })
1076
+ .check(S.isInt({
1077
+ message: trans("validation.integer.expected", { actualValue: "NaN" })
1078
+ }))
880
1079
  } else {
881
- schema = S.Number
1080
+ schema = S.Finite.annotate({
1081
+ message: trans("validation.number.expected", { actualValue: "NaN" })
1082
+ })
1083
+
1084
+ if (meta.required) {
1085
+ schema = schema.annotate({
1086
+ message: trans("validation.empty")
1087
+ })
1088
+ }
882
1089
  }
883
1090
 
884
- // Apply numeric validations
885
1091
  if (typeof meta.minimum === "number") {
886
- schema = schema.check(
887
- S.makeFilter(
888
- (n) =>
889
- n >= meta.minimum! || trans(
890
- meta.minimum === 0 ? "validation.number.positive" : "validation.number.min",
891
- { minimum: meta.minimum, isExclusive: false }
892
- ),
893
- { title: `>=${meta.minimum}` }
894
- )
895
- )
1092
+ schema = schema.check(S.isGreaterThanOrEqualTo(meta.minimum, {
1093
+ message: trans(meta.minimum === 0 ? "validation.number.positive" : "validation.number.min", {
1094
+ minimum: meta.minimum,
1095
+ isExclusive: true
1096
+ })
1097
+ }))
896
1098
  }
897
-
898
1099
  if (typeof meta.maximum === "number") {
899
- schema = schema.check(
900
- S.makeFilter(
901
- (n) =>
902
- n <= meta.maximum! || trans("validation.number.max", {
903
- maximum: meta.maximum,
904
- isExclusive: false
905
- }),
906
- { title: `<=${meta.maximum}` }
907
- )
908
- )
1100
+ schema = schema.check(S.isLessThanOrEqualTo(meta.maximum, {
1101
+ message: trans("validation.number.max", {
1102
+ maximum: meta.maximum,
1103
+ isExclusive: true
1104
+ })
1105
+ }))
909
1106
  }
910
-
911
1107
  if (typeof meta.exclusiveMinimum === "number") {
912
- schema = schema.check(
913
- S.makeFilter(
914
- (n) =>
915
- n > meta.exclusiveMinimum! || trans(
916
- meta.exclusiveMinimum === 0 ? "validation.number.positive" : "validation.number.min",
917
- { minimum: meta.exclusiveMinimum, isExclusive: true }
918
- ),
919
- { title: `>${meta.exclusiveMinimum}` }
920
- )
921
- )
1108
+ schema = schema.check(S.isGreaterThan(meta.exclusiveMinimum, {
1109
+ message: trans(meta.exclusiveMinimum === 0 ? "validation.number.positive" : "validation.number.min", {
1110
+ minimum: meta.exclusiveMinimum,
1111
+ isExclusive: false
1112
+ })
1113
+ }))
922
1114
  }
923
-
924
1115
  if (typeof meta.exclusiveMaximum === "number") {
925
- schema = schema.check(
926
- S.makeFilter(
927
- (n) =>
928
- n < meta.exclusiveMaximum! || trans("validation.number.max", {
929
- maximum: meta.exclusiveMaximum,
930
- isExclusive: true
931
- }),
932
- { title: `<${meta.exclusiveMaximum}` }
933
- )
934
- )
1116
+ schema = schema.check(S.isLessThan(meta.exclusiveMaximum, {
1117
+ message: trans("validation.number.max", {
1118
+ maximum: meta.exclusiveMaximum,
1119
+ isExclusive: false
1120
+ })
1121
+ }))
935
1122
  }
936
1123
  break
937
- }
1124
+ case "select":
1125
+ schema = S.Literals(meta.members as [any, ...any[]]).annotate({
1126
+ message: trans("validation.not_a_valid", {
1127
+ type: "select",
1128
+ message: meta.members.join(", ")
1129
+ })
1130
+ })
938
1131
 
939
- case "select": {
940
- // Use Literal for select options
941
- if (meta.members.length === 0) {
942
- schema = S.Unknown
943
- } else if (meta.members.length === 1) {
944
- schema = S.Literal(meta.members[0])
945
- } else {
946
- // v4 Union accepts an array of schemas
947
- schema = S.Union(meta.members.map((m) => S.Literal(m)))
948
- }
949
1132
  break
950
- }
951
1133
 
952
- case "multiple": {
953
- schema = S.Array(S.String)
1134
+ case "multiple":
1135
+ schema = S.Array(S.String).annotate({
1136
+ message: trans("validation.not_a_valid", {
1137
+ type: "multiple",
1138
+ message: meta.members.join(", ")
1139
+ })
1140
+ })
954
1141
  break
955
- }
956
1142
 
957
- case "boolean": {
1143
+ case "boolean":
958
1144
  schema = S.Boolean
959
1145
  break
960
- }
961
1146
 
962
- case "unknown": {
1147
+ case "date":
1148
+ schema = S.Date
1149
+ break
1150
+
1151
+ case "unknown":
963
1152
  schema = S.Unknown
964
1153
  break
965
- }
966
1154
 
967
- default: {
968
- console.warn(`Unhandled field type: ${(meta as any).type}`)
1155
+ default:
1156
+ // For any unhandled types, use Unknown schema to prevent undefined errors
1157
+ console.warn(`Unhandled field type: ${meta}`)
969
1158
  schema = S.Unknown
970
1159
  break
971
- }
972
1160
  }
973
-
974
- // Wrap in union with null/undefined if not required
975
1161
  if (!meta.required) {
976
- // v4 Union takes an array of schemas
977
- schema = S.Union([schema, S.Null, S.Undefined])
1162
+ schema = S.NullishOr(schema)
978
1163
  }
979
-
980
- return S.toStandardSchemaV1(schema as any)
1164
+ const result = S.toStandardSchemaV1(schema as any)
1165
+ return result
981
1166
  }
982
1167
 
983
- // TODO: Fix v4 migration - nullableInput transformation needs proper type handling
984
- // export const nullableInput = <A>(
985
- // schema: S.Codec<A>,
986
- // defaultValue: () => A
987
- // ): S.Codec<A> =>
988
- // S.NullOr(schema).pipe(
989
- // S.decodeTo(
990
- // schema,
991
- // SchemaTransformation.transform({
992
- // decode: (input: A | null) => input ?? defaultValue(),
993
- // encode: (output: A) => output
994
- // })
995
- // )
996
- // )
997
-
998
1168
  export type OmegaAutoGenMeta<
999
1169
  From extends Record<PropertyKey, any>,
1000
1170
  To extends Record<PropertyKey, any>,
@@ -1037,18 +1207,8 @@ export function deepMerge(target: any, source: any) {
1037
1207
  return result
1038
1208
  }
1039
1209
 
1040
- // Type definitions for schemas with fields and members
1041
- type SchemaWithFields = {
1042
- fields: Record<string, S.Top>
1043
- }
1044
-
1045
1210
  type SchemaWithMembers = {
1046
- members: readonly S.Top[]
1047
- }
1048
-
1049
- // Type guards to check schema types
1050
- function hasFields(schema: any): schema is SchemaWithFields {
1051
- return schema && "fields" in schema && typeof schema.fields === "object"
1211
+ members: readonly S.Schema<any>[]
1052
1212
  }
1053
1213
 
1054
1214
  function hasMembers(schema: any): schema is SchemaWithMembers {
@@ -1057,10 +1217,15 @@ function hasMembers(schema: any): schema is SchemaWithMembers {
1057
1217
 
1058
1218
  // Internal implementation with WeakSet tracking
1059
1219
  export const defaultsValueFromSchema = (
1060
- schema: S.Codec<any>,
1220
+ schema: S.Schema<any>,
1061
1221
  record: Record<string, any> = {}
1062
1222
  ): any => {
1063
1223
  const ast = schema.ast
1224
+ const defaultValue = getDefaultFromAst(ast)
1225
+
1226
+ if (defaultValue !== undefined) {
1227
+ return defaultValue
1228
+ }
1064
1229
 
1065
1230
  if (isNullableOrUndefined(schema.ast) === "null") {
1066
1231
  return null
@@ -1069,106 +1234,79 @@ export const defaultsValueFromSchema = (
1069
1234
  return undefined
1070
1235
  }
1071
1236
 
1072
- // Handle v4 Objects AST structure
1073
- if (AST.isObjects(ast)) {
1074
- const result: Record<string, any> = { ...record }
1237
+ // Handle structs via AST (covers plain structs, transformed schemas like decodeTo, ExtendedClass, etc.)
1238
+ const objectsAst = S.AST.isObjects(ast)
1239
+ ? ast
1240
+ : S.AST.isDeclaration(ast)
1241
+ ? unwrapDeclaration(ast)
1242
+ : undefined
1243
+ if (objectsAst && S.AST.isObjects(objectsAst)) {
1244
+ const result: Record<string, any> = {}
1075
1245
 
1076
- for (const prop of ast.propertySignatures) {
1246
+ for (const prop of objectsAst.propertySignatures) {
1077
1247
  const key = prop.name.toString()
1078
1248
  const propType = prop.type
1079
1249
 
1080
- // Get the property schema from the original schema's fields if available
1081
- // This preserves schema wrappers like withDefaultConstructor
1082
- let propSchema: S.Codec<any>
1083
- if ((schema as any).fields && (schema as any).fields[key]) {
1084
- propSchema = (schema as any).fields[key]
1085
- } else {
1086
- propSchema = S.make(propType)
1250
+ const propDefault = getDefaultFromAst(propType)
1251
+ if (propDefault !== undefined) {
1252
+ result[key] = propDefault
1253
+ continue
1087
1254
  }
1088
1255
 
1089
- // Recursively process the property to get its defaults
1256
+ const propSchema = S.make(propType)
1090
1257
  const propValue = defaultsValueFromSchema(propSchema, record[key] || {})
1091
1258
 
1092
1259
  if (propValue !== undefined) {
1093
1260
  result[key] = propValue
1261
+ } else if (isNullableOrUndefined(propType) === "undefined") {
1262
+ result[key] = undefined
1094
1263
  }
1095
1264
  }
1096
1265
 
1097
- return result
1266
+ return { ...result, ...record }
1098
1267
  }
1099
1268
 
1100
- // v3 compatible fields extraction
1101
- if (hasFields(schema)) {
1102
- // Process fields and extract default values
1103
- const result: Record<string, any> = {}
1104
-
1105
- for (const [key, fieldSchema] of Object.entries(schema.fields)) {
1106
- // Check if this field has a defaultValue in its AST
1107
- const fieldAst = (fieldSchema as any)?.ast
1108
- if (fieldAst?.defaultValue) {
1109
- try {
1110
- result[key] = fieldAst.defaultValue()
1111
- continue
1112
- } catch {
1113
- // If defaultValue() throws, fall through to recursive processing
1269
+ // Handle unions via AST or schema-level .members
1270
+ const unionTypes = S.AST.isUnion(ast)
1271
+ ? ast.types
1272
+ : hasMembers(schema)
1273
+ ? schema.members.map((m) => m.ast)
1274
+ : undefined
1275
+ if (unionTypes) {
1276
+ const mergedFields: Record<string, { ast: S.AST.AST }> = {}
1277
+
1278
+ for (const memberAstRaw of unionTypes) {
1279
+ const memberAst = unwrapDeclaration(memberAstRaw)
1280
+ if (!S.AST.isObjects(memberAst)) continue
1281
+
1282
+ for (const prop of memberAst.propertySignatures) {
1283
+ const key = prop.name.toString()
1284
+ const fieldDefault = getDefaultFromAst(prop.type)
1285
+ const existingDefault = mergedFields[key] ? getDefaultFromAst(mergedFields[key]!.ast) : undefined
1286
+
1287
+ if (!mergedFields[key] || (fieldDefault !== undefined && existingDefault === undefined)) {
1288
+ mergedFields[key] = { ast: prop.type }
1114
1289
  }
1115
1290
  }
1116
-
1117
- // Recursively process the field
1118
- const fieldValue = defaultsValueFromSchema(fieldSchema as any, record[key] || {})
1119
- if (fieldValue !== undefined) {
1120
- result[key] = fieldValue
1121
- }
1122
1291
  }
1123
1292
 
1124
- return { ...result, ...record }
1125
- }
1126
-
1127
- // Check if schema has fields in from (for ExtendedClass and similar transformations)
1128
- if ((schema as any)?.from && hasFields((schema as any).from)) {
1129
- return defaultsValueFromSchema((schema as any).from, record)
1130
- }
1131
-
1132
- if (hasMembers(schema)) {
1133
- // Merge all member fields, giving precedence to fields with default values
1134
- const mergedMembers = schema.members.reduce((acc, member) => {
1135
- if (hasFields(member)) {
1136
- // Check each field and give precedence to ones with default values
1137
- Object.entries(member.fields).forEach(([key, fieldSchema]) => {
1138
- const fieldAst: any = fieldSchema.ast
1139
- const existingFieldAst: any = acc[key]?.ast
1140
-
1141
- // If field doesn't exist yet, or new field has default and existing doesn't, use new field
1142
- if (!acc[key] || (fieldAst?.defaultValue && !existingFieldAst?.defaultValue)) {
1143
- acc[key] = fieldSchema
1144
- }
1145
- // If both have defaults or neither have defaults, keep the first one (existing)
1146
- })
1147
- return acc
1148
- }
1149
- return acc
1150
- }, {} as Record<string, any>)
1293
+ if (Object.keys(mergedFields).length === 0) {
1294
+ return Object.keys(record).length > 0 ? record : undefined
1295
+ }
1151
1296
 
1152
- // Use reduce to properly accumulate the merged fields
1153
- return Object.entries(mergedMembers).reduce((acc, [key, value]) => {
1154
- acc[key] = defaultsValueFromSchema(value, record[key] || {})
1297
+ return Object.entries(mergedFields).reduce((acc, [key, { ast: propAst }]) => {
1298
+ acc[key] = defaultsValueFromSchema(S.make(propAst), record[key] || {})
1155
1299
  return acc
1156
1300
  }, record)
1157
1301
  }
1158
1302
 
1159
1303
  if (Object.keys(record).length === 0) {
1160
- // Check for constructor defaults in v4's context
1161
- if (ast.context?.defaultValue) {
1162
- // In v4, defaultValue is an Encoding type, not directly callable
1163
- // For now, skip complex default extraction
1164
- // TODO: properly extract default from encoding chain
1304
+ if (S.AST.isString(ast)) {
1305
+ return ""
1165
1306
  }
1166
- }
1167
1307
 
1168
- if (AST.isString(ast)) {
1169
- return ""
1170
- }
1171
- if (AST.isBoolean(ast)) {
1172
- return false
1308
+ if (S.AST.isBoolean(ast)) {
1309
+ return false
1310
+ }
1173
1311
  }
1174
1312
  }