@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.
- 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 +21 -44
- package/dist/vue-components10.es.js +5 -0
- package/dist/vue-components11.es.js +13 -0
- package/dist/vue-components12.es.js +440 -0
- package/dist/vue-components13.es.js +4 -0
- package/dist/vue-components14.es.js +38 -0
- package/dist/vue-components15.es.js +27 -0
- package/dist/vue-components16.es.js +28 -0
- package/dist/vue-components17.es.js +7 -0
- package/dist/vue-components18.es.js +18 -0
- package/dist/vue-components19.es.js +36 -0
- package/dist/vue-components2.es.js +11 -0
- package/dist/vue-components20.es.js +18 -0
- package/dist/vue-components21.es.js +21 -0
- package/dist/vue-components22.es.js +30 -0
- package/dist/vue-components23.es.js +7 -0
- package/dist/vue-components24.es.js +9 -0
- package/dist/vue-components25.es.js +38 -0
- package/dist/vue-components26.es.js +25 -0
- package/dist/vue-components27.es.js +128 -0
- package/dist/vue-components28.es.js +24 -0
- package/dist/vue-components29.es.js +21 -0
- package/dist/vue-components3.es.js +54 -0
- package/dist/vue-components30.es.js +9 -0
- package/dist/vue-components31.es.js +19 -0
- package/dist/vue-components32.es.js +5 -0
- package/dist/vue-components33.es.js +29 -0
- package/dist/vue-components34.es.js +5 -0
- package/dist/vue-components35.es.js +29 -0
- package/dist/vue-components36.es.js +6 -0
- package/dist/vue-components37.es.js +18 -0
- package/dist/vue-components38.es.js +56 -0
- package/dist/vue-components39.es.js +5 -0
- package/dist/vue-components4.es.js +5 -0
- package/dist/vue-components40.es.js +44 -0
- package/dist/vue-components41.es.js +5 -0
- package/dist/vue-components42.es.js +84 -0
- package/dist/vue-components44.es.js +8 -0
- package/dist/vue-components45.es.js +7 -0
- package/dist/vue-components46.es.js +267 -0
- package/dist/vue-components48.es.js +6 -0
- package/dist/vue-components49.es.js +79 -0
- package/dist/vue-components5.es.js +24 -0
- package/dist/vue-components50.es.js +5 -0
- package/dist/vue-components51.es.js +66 -0
- package/dist/vue-components52.es.js +5 -0
- package/dist/vue-components53.es.js +24 -0
- package/dist/vue-components54.es.js +5 -0
- package/dist/vue-components55.es.js +59 -0
- package/dist/vue-components56.es.js +5 -0
- package/dist/vue-components57.es.js +12 -0
- package/dist/vue-components58.es.js +22 -0
- package/dist/vue-components6.es.js +13 -0
- package/dist/vue-components60.es.js +7 -0
- package/dist/vue-components61.es.js +235 -0
- package/dist/vue-components62.es.js +33 -0
- package/dist/vue-components63.es.js +8 -0
- package/dist/vue-components64.es.js +36 -0
- package/dist/vue-components7.es.js +28 -0
- package/dist/vue-components8.es.js +47 -0
- package/dist/vue-components9.es.js +5 -0
- package/package.json +29 -25
- package/src/components/OmegaForm/OmegaAutoGen.vue +25 -30
- package/src/components/OmegaForm/OmegaErrorsInternal.vue +2 -3
- package/src/components/OmegaForm/OmegaFormStuff.ts +495 -357
- package/src/components/OmegaForm/OmegaInternalInput.vue +9 -5
- package/src/components/OmegaForm/useOmegaForm.ts +57 -36
- package/src/reset.css +51 -0
- package/src/utils/index.ts +4 -8
- package/dist/vue-components.es10.js +0 -237
- package/dist/vue-components.es100.js +0 -4
- package/dist/vue-components.es11.js +0 -32
- package/dist/vue-components.es12.js +0 -439
- package/dist/vue-components.es13.js +0 -49
- package/dist/vue-components.es14.js +0 -4
- package/dist/vue-components.es15.js +0 -4
- package/dist/vue-components.es16.js +0 -725
- package/dist/vue-components.es17.js +0 -143
- package/dist/vue-components.es18.js +0 -6
- package/dist/vue-components.es19.js +0 -13
- package/dist/vue-components.es2.js +0 -30
- package/dist/vue-components.es20.js +0 -5
- package/dist/vue-components.es21.js +0 -26
- package/dist/vue-components.es22.js +0 -6
- package/dist/vue-components.es23.js +0 -10
- package/dist/vue-components.es24.js +0 -57
- package/dist/vue-components.es25.js +0 -71
- package/dist/vue-components.es26.js +0 -8
- package/dist/vue-components.es27.js +0 -8
- package/dist/vue-components.es28.js +0 -5
- package/dist/vue-components.es29.js +0 -5
- package/dist/vue-components.es3.js +0 -16
- package/dist/vue-components.es30.js +0 -4
- package/dist/vue-components.es31.js +0 -4
- package/dist/vue-components.es32.js +0 -4
- package/dist/vue-components.es33.js +0 -4
- package/dist/vue-components.es34.js +0 -19
- package/dist/vue-components.es35.js +0 -13
- package/dist/vue-components.es36.js +0 -40
- package/dist/vue-components.es37.js +0 -6
- package/dist/vue-components.es38.js +0 -85
- package/dist/vue-components.es39.js +0 -54
- package/dist/vue-components.es4.js +0 -52
- package/dist/vue-components.es40.js +0 -563
- package/dist/vue-components.es41.js +0 -43
- package/dist/vue-components.es42.js +0 -29
- package/dist/vue-components.es43.js +0 -7
- package/dist/vue-components.es44.js +0 -42
- package/dist/vue-components.es45.js +0 -316
- package/dist/vue-components.es46.js +0 -33
- package/dist/vue-components.es47.js +0 -6
- package/dist/vue-components.es48.js +0 -26
- package/dist/vue-components.es49.js +0 -77
- package/dist/vue-components.es5.js +0 -52
- package/dist/vue-components.es50.js +0 -101
- package/dist/vue-components.es51.js +0 -4
- package/dist/vue-components.es52.js +0 -320
- package/dist/vue-components.es53.js +0 -66
- package/dist/vue-components.es54.js +0 -4
- package/dist/vue-components.es55.js +0 -4
- package/dist/vue-components.es56.js +0 -113
- package/dist/vue-components.es58.js +0 -9
- package/dist/vue-components.es59.js +0 -34
- package/dist/vue-components.es6.js +0 -69
- package/dist/vue-components.es61.js +0 -194
- package/dist/vue-components.es63.js +0 -25
- package/dist/vue-components.es64.js +0 -7
- package/dist/vue-components.es65.js +0 -23
- package/dist/vue-components.es66.js +0 -32
- package/dist/vue-components.es67.js +0 -24
- package/dist/vue-components.es68.js +0 -14
- package/dist/vue-components.es69.js +0 -7
- package/dist/vue-components.es7.js +0 -83
- package/dist/vue-components.es70.js +0 -21
- package/dist/vue-components.es71.js +0 -11
- package/dist/vue-components.es72.js +0 -33
- package/dist/vue-components.es73.js +0 -50
- package/dist/vue-components.es74.js +0 -28
- package/dist/vue-components.es75.js +0 -103
- package/dist/vue-components.es76.js +0 -84
- package/dist/vue-components.es77.js +0 -17
- package/dist/vue-components.es78.js +0 -34
- package/dist/vue-components.es79.js +0 -23
- package/dist/vue-components.es8.js +0 -63
- package/dist/vue-components.es80.js +0 -14
- package/dist/vue-components.es81.js +0 -115
- package/dist/vue-components.es82.js +0 -5
- package/dist/vue-components.es83.js +0 -4
- package/dist/vue-components.es84.js +0 -4
- package/dist/vue-components.es85.js +0 -4
- package/dist/vue-components.es86.js +0 -17
- package/dist/vue-components.es87.js +0 -72
- package/dist/vue-components.es88.js +0 -18
- package/dist/vue-components.es89.js +0 -10
- package/dist/vue-components.es9.js +0 -21
- package/dist/vue-components.es90.js +0 -6
- 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.es97.js +0 -44
- package/dist/vue-components.es98.js +0 -4
- package/dist/vue-components.es99.js +0 -46
|
@@ -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
|
+
}
|
|
621
774
|
|
|
622
|
-
|
|
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
|
-
|
|
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>,
|
|
@@ -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.
|
|
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.
|
|
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
|
|
1073
|
-
|
|
1074
|
-
|
|
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
|
|
1246
|
+
for (const prop of objectsAst.propertySignatures) {
|
|
1077
1247
|
const key = prop.name.toString()
|
|
1078
1248
|
const propType = prop.type
|
|
1079
1249
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1153
|
-
|
|
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
|
-
|
|
1161
|
-
|
|
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
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
if (AST.isBoolean(ast)) {
|
|
1172
|
-
return false
|
|
1308
|
+
if (S.AST.isBoolean(ast)) {
|
|
1309
|
+
return false
|
|
1310
|
+
}
|
|
1173
1311
|
}
|
|
1174
1312
|
}
|