@effect-app/vue-components 4.0.0-beta.4 → 4.0.0-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/reset.css +51 -0
- package/dist/types/components/OmegaForm/OmegaAutoGen.vue.d.ts +1 -1
- package/dist/types/components/OmegaForm/OmegaFormStuff.d.ts +22 -15
- package/dist/types/components/OmegaForm/useOmegaForm.d.ts +3 -5
- package/dist/types/utils/index.d.ts +3 -4
- package/dist/vue-components.es.js +8 -7
- package/dist/vue-components.es10.js +138 -135
- package/dist/vue-components.es11.js +2 -2
- package/dist/vue-components.es12.js +430 -344
- package/dist/vue-components.es16.js +4 -11
- package/dist/vue-components.es17.js +11 -723
- package/dist/vue-components.es18.js +52 -138
- package/dist/vue-components.es19.js +54 -4
- package/dist/vue-components.es2.js +15 -14
- package/dist/vue-components.es20.js +6 -11
- package/dist/vue-components.es21.js +6 -3
- package/dist/vue-components.es22.js +3 -24
- package/dist/vue-components.es23.js +3 -4
- package/dist/vue-components.es24.js +2 -8
- package/dist/vue-components.es25.js +2 -55
- package/dist/vue-components.es26.js +2 -69
- package/dist/vue-components.es27.js +2 -6
- package/dist/vue-components.es28.js +17 -6
- package/dist/vue-components.es29.js +11 -3
- package/dist/vue-components.es3.js +11 -10
- package/dist/vue-components.es30.js +192 -3
- package/dist/vue-components.es32.js +29 -2
- package/dist/vue-components.es33.js +4 -2
- package/dist/vue-components.es34.js +1 -1
- package/dist/vue-components.es35.js +2 -17
- package/dist/vue-components.es36.js +107 -188
- package/dist/vue-components.es38.js +7 -4
- package/dist/vue-components.es39.js +30 -36
- package/dist/vue-components.es41.js +4 -83
- package/dist/vue-components.es42.js +22 -51
- package/dist/vue-components.es43.js +5 -561
- package/dist/vue-components.es44.js +20 -40
- package/dist/vue-components.es45.js +29 -26
- package/dist/vue-components.es46.js +22 -5
- package/dist/vue-components.es47.js +12 -40
- package/dist/vue-components.es48.js +5 -314
- package/dist/vue-components.es49.js +16 -28
- package/dist/vue-components.es5.js +1 -1
- package/dist/vue-components.es50.js +9 -4
- package/dist/vue-components.es51.js +31 -24
- package/dist/vue-components.es52.js +46 -73
- package/dist/vue-components.es53.js +25 -98
- package/dist/vue-components.es54.js +11 -2
- package/dist/vue-components.es55.js +61 -314
- package/dist/vue-components.es56.js +55 -63
- package/dist/vue-components.es57.js +17 -2
- package/dist/vue-components.es58.js +33 -2
- package/dist/vue-components.es59.js +29 -111
- package/dist/{vue-components.es97.js → vue-components.es60.js} +1 -1
- package/dist/vue-components.es61.js +2 -7
- package/dist/vue-components.es62.js +43 -31
- package/dist/vue-components.es7.js +37 -37
- package/package.json +17 -13
- package/src/components/OmegaForm/OmegaAutoGen.vue +25 -30
- package/src/components/OmegaForm/OmegaErrorsInternal.vue +2 -3
- package/src/components/OmegaForm/OmegaFormStuff.ts +499 -326
- package/src/components/OmegaForm/OmegaInternalInput.vue +9 -5
- package/src/components/OmegaForm/useOmegaForm.ts +39 -36
- package/src/reset.css +51 -0
- package/src/utils/index.ts +4 -8
- package/dist/vue-components.es31.js +0 -4
- package/dist/vue-components.es40.js +0 -6
- package/dist/vue-components.es64.js +0 -103
- package/dist/vue-components.es65.js +0 -4
- package/dist/vue-components.es66.js +0 -23
- package/dist/vue-components.es67.js +0 -84
- package/dist/vue-components.es68.js +0 -14
- package/dist/vue-components.es69.js +0 -115
- package/dist/vue-components.es70.js +0 -5
- package/dist/vue-components.es71.js +0 -34
- package/dist/vue-components.es72.js +0 -4
- package/dist/vue-components.es73.js +0 -4
- package/dist/vue-components.es74.js +0 -17
- package/dist/vue-components.es75.js +0 -72
- package/dist/vue-components.es76.js +0 -25
- package/dist/vue-components.es77.js +0 -7
- package/dist/vue-components.es78.js +0 -23
- package/dist/vue-components.es79.js +0 -32
- package/dist/vue-components.es80.js +0 -24
- package/dist/vue-components.es81.js +0 -14
- package/dist/vue-components.es82.js +0 -7
- package/dist/vue-components.es83.js +0 -21
- package/dist/vue-components.es84.js +0 -11
- package/dist/vue-components.es85.js +0 -33
- package/dist/vue-components.es86.js +0 -50
- package/dist/vue-components.es87.js +0 -28
- package/dist/vue-components.es88.js +0 -17
- package/dist/vue-components.es89.js +0 -18
- package/dist/vue-components.es90.js +0 -10
- package/dist/vue-components.es91.js +0 -13
- package/dist/vue-components.es92.js +0 -67
- package/dist/vue-components.es93.js +0 -58
- package/dist/vue-components.es94.js +0 -19
- package/dist/vue-components.es95.js +0 -35
- package/dist/vue-components.es96.js +0 -31
- package/dist/vue-components.es98.js +0 -4
- package/dist/vue-components.es99.js +0 -46
- /package/dist/{vue-components.es100.js → vue-components.es63.js} +0 -0
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
import type
|
|
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
|
|
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
|
-
|
|
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
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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((_) =>
|
|
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
|
-
|
|
347
|
-
|
|
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
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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
|
-
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
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
|
+
}
|
|
774
|
+
|
|
775
|
+
const nonNullType = nonNullTypes[0]!
|
|
621
776
|
|
|
622
|
-
if (AST.isObjects(nonNullType)) {
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
865
|
+
(p) => p.name.toString() === "_tag"
|
|
746
866
|
)
|
|
747
867
|
|
|
748
868
|
let tagValue: string | null = null
|
|
749
|
-
|
|
750
|
-
|
|
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 = <
|
|
818
|
-
schema:
|
|
1017
|
+
export const generateMetaFromSchema = <From, To>(
|
|
1018
|
+
schema: S.Codec<To, From, never>
|
|
819
1019
|
): {
|
|
820
|
-
schema:
|
|
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:
|
|
837
|
-
|
|
1036
|
+
let schema: any
|
|
838
1037
|
switch (meta.type) {
|
|
839
|
-
case "string":
|
|
840
|
-
schema =
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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 "
|
|
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
|
-
|
|
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
|
-
|
|
977
|
-
schema = S.Union([schema, S.Null, S.Undefined])
|
|
1162
|
+
schema = S.NullishOr(schema)
|
|
978
1163
|
}
|
|
979
|
-
|
|
980
|
-
return
|
|
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>,
|
|
@@ -1039,11 +1209,11 @@ export function deepMerge(target: any, source: any) {
|
|
|
1039
1209
|
|
|
1040
1210
|
// Type definitions for schemas with fields and members
|
|
1041
1211
|
type SchemaWithFields = {
|
|
1042
|
-
fields: Record<string, S.
|
|
1212
|
+
fields: Record<string, S.Schema<any>>
|
|
1043
1213
|
}
|
|
1044
1214
|
|
|
1045
1215
|
type SchemaWithMembers = {
|
|
1046
|
-
members: readonly S.
|
|
1216
|
+
members: readonly S.Schema<any>[]
|
|
1047
1217
|
}
|
|
1048
1218
|
|
|
1049
1219
|
// Type guards to check schema types
|
|
@@ -1057,10 +1227,15 @@ function hasMembers(schema: any): schema is SchemaWithMembers {
|
|
|
1057
1227
|
|
|
1058
1228
|
// Internal implementation with WeakSet tracking
|
|
1059
1229
|
export const defaultsValueFromSchema = (
|
|
1060
|
-
schema: S.
|
|
1230
|
+
schema: S.Schema<any>,
|
|
1061
1231
|
record: Record<string, any> = {}
|
|
1062
1232
|
): any => {
|
|
1063
1233
|
const ast = schema.ast
|
|
1234
|
+
const defaultValue = getDefaultFromAst(ast)
|
|
1235
|
+
|
|
1236
|
+
if (defaultValue !== undefined) {
|
|
1237
|
+
return defaultValue
|
|
1238
|
+
}
|
|
1064
1239
|
|
|
1065
1240
|
if (isNullableOrUndefined(schema.ast) === "null") {
|
|
1066
1241
|
return null
|
|
@@ -1069,55 +1244,24 @@ export const defaultsValueFromSchema = (
|
|
|
1069
1244
|
return undefined
|
|
1070
1245
|
}
|
|
1071
1246
|
|
|
1072
|
-
//
|
|
1073
|
-
if (AST.isObjects(ast)) {
|
|
1074
|
-
const result: Record<string, any> = { ...record }
|
|
1075
|
-
|
|
1076
|
-
for (const prop of ast.propertySignatures) {
|
|
1077
|
-
const key = prop.name.toString()
|
|
1078
|
-
const propType = prop.type
|
|
1079
|
-
|
|
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)
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// Recursively process the property to get its defaults
|
|
1090
|
-
const propValue = defaultsValueFromSchema(propSchema, record[key] || {})
|
|
1091
|
-
|
|
1092
|
-
if (propValue !== undefined) {
|
|
1093
|
-
result[key] = propValue
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
|
-
return result
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
// v3 compatible fields extraction
|
|
1247
|
+
// Check if schema has fields directly
|
|
1101
1248
|
if (hasFields(schema)) {
|
|
1102
1249
|
// Process fields and extract default values
|
|
1103
1250
|
const result: Record<string, any> = {}
|
|
1104
1251
|
|
|
1105
1252
|
for (const [key, fieldSchema] of Object.entries(schema.fields)) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
result[key] = fieldAst.defaultValue()
|
|
1111
|
-
continue
|
|
1112
|
-
} catch {
|
|
1113
|
-
// If defaultValue() throws, fall through to recursive processing
|
|
1114
|
-
}
|
|
1253
|
+
const fieldDefault = getDefaultFromAst((fieldSchema as any)?.ast)
|
|
1254
|
+
if (fieldDefault !== undefined) {
|
|
1255
|
+
result[key] = fieldDefault
|
|
1256
|
+
continue
|
|
1115
1257
|
}
|
|
1116
1258
|
|
|
1117
1259
|
// Recursively process the field
|
|
1118
1260
|
const fieldValue = defaultsValueFromSchema(fieldSchema as any, record[key] || {})
|
|
1119
1261
|
if (fieldValue !== undefined) {
|
|
1120
1262
|
result[key] = fieldValue
|
|
1263
|
+
} else if (isNullableOrUndefined((fieldSchema as any).ast) === "undefined") {
|
|
1264
|
+
result[key] = undefined
|
|
1121
1265
|
}
|
|
1122
1266
|
}
|
|
1123
1267
|
|
|
@@ -1135,11 +1279,11 @@ export const defaultsValueFromSchema = (
|
|
|
1135
1279
|
if (hasFields(member)) {
|
|
1136
1280
|
// Check each field and give precedence to ones with default values
|
|
1137
1281
|
Object.entries(member.fields).forEach(([key, fieldSchema]) => {
|
|
1138
|
-
const
|
|
1139
|
-
const
|
|
1282
|
+
const fieldDefault = getDefaultFromAst(fieldSchema.ast)
|
|
1283
|
+
const existingDefault = acc[key] ? getDefaultFromAst(acc[key].ast) : undefined
|
|
1140
1284
|
|
|
1141
1285
|
// If field doesn't exist yet, or new field has default and existing doesn't, use new field
|
|
1142
|
-
if (!acc[key] || (
|
|
1286
|
+
if (!acc[key] || (fieldDefault !== undefined && existingDefault === undefined)) {
|
|
1143
1287
|
acc[key] = fieldSchema
|
|
1144
1288
|
}
|
|
1145
1289
|
// If both have defaults or neither have defaults, keep the first one (existing)
|
|
@@ -1149,6 +1293,10 @@ export const defaultsValueFromSchema = (
|
|
|
1149
1293
|
return acc
|
|
1150
1294
|
}, {} as Record<string, any>)
|
|
1151
1295
|
|
|
1296
|
+
if (Object.keys(mergedMembers).length === 0) {
|
|
1297
|
+
return Object.keys(record).length > 0 ? record : undefined
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1152
1300
|
// Use reduce to properly accumulate the merged fields
|
|
1153
1301
|
return Object.entries(mergedMembers).reduce((acc, [key, value]) => {
|
|
1154
1302
|
acc[key] = defaultsValueFromSchema(value, record[key] || {})
|
|
@@ -1157,18 +1305,43 @@ export const defaultsValueFromSchema = (
|
|
|
1157
1305
|
}
|
|
1158
1306
|
|
|
1159
1307
|
if (Object.keys(record).length === 0) {
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1308
|
+
if (S.AST.isObjects(ast)) {
|
|
1309
|
+
// Process TypeLiteral fields directly to build the result object
|
|
1310
|
+
const result: Record<string, any> = { ...record }
|
|
1311
|
+
|
|
1312
|
+
for (const prop of ast.propertySignatures) {
|
|
1313
|
+
const key = prop.name.toString()
|
|
1314
|
+
const propType = prop.type
|
|
1315
|
+
|
|
1316
|
+
const propDefault = getDefaultFromAst(propType)
|
|
1317
|
+
if (propDefault !== undefined) {
|
|
1318
|
+
result[key] = propDefault
|
|
1319
|
+
continue
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Create a schema from the property type and get its defaults
|
|
1323
|
+
const propSchema = S.make(propType)
|
|
1324
|
+
|
|
1325
|
+
// Recursively process the property - don't pas for prop processing
|
|
1326
|
+
// to allow proper unwrapping of nested structures
|
|
1327
|
+
const propValue = defaultsValueFromSchema(propSchema, record[key] || {})
|
|
1328
|
+
|
|
1329
|
+
if (propValue !== undefined) {
|
|
1330
|
+
result[key] = propValue
|
|
1331
|
+
} else if (isNullableOrUndefined(propType) === "undefined") {
|
|
1332
|
+
result[key] = undefined
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
return result
|
|
1165
1337
|
}
|
|
1166
|
-
}
|
|
1167
1338
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1339
|
+
if (S.AST.isString(ast)) {
|
|
1340
|
+
return ""
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
if (S.AST.isBoolean(ast)) {
|
|
1344
|
+
return false
|
|
1345
|
+
}
|
|
1173
1346
|
}
|
|
1174
1347
|
}
|