this string has no description
0
maybefy.ts
1import Maybe, { just, nothing } from "true-myth/maybe";
2import * as v from "@atcute/lexicons/validations";
3
4type IsUnion<T, U = T> = T extends unknown ? ([U] extends [T] ? false : true) : never;
5
6type MaybeifyUnion<T> = T extends unknown ? (T extends object ? Maybeify<T> : T) : never;
7
8export type Maybeify<T> = {
9 [K in keyof T]-?: undefined extends T[K]
10 ? T[K] extends Array<infer U> | undefined
11 ? Maybe<Maybeify<U>[]>
12 : IsUnion<NonNullable<T[K]>> extends true
13 ? Maybe<MaybeifyUnion<NonNullable<T[K]>>>
14 : T[K] extends object | undefined
15 ? Maybe<Maybeify<NonNullable<T[K]>>>
16 : Maybe<NonNullable<T[K]>>
17 : T[K] extends Array<infer U>
18 ? Maybeify<U>[]
19 : IsUnion<T[K]> extends true
20 ? MaybeifyUnion<T[K]>
21 : T[K] extends object
22 ? Maybeify<T[K]>
23 : T[K];
24};
25
26type AnySchema = v.BaseSchema;
27
28function resolveMemberSchema(
29 member: v.ObjectSchema | v.RecordSchema<v.ObjectSchema, v.RecordKeySchema>,
30): v.ObjectSchema {
31 return (member as { type: string }).type === "record"
32 ? (member as v.RecordSchema<v.ObjectSchema, v.RecordKeySchema>).object
33 : (member as v.ObjectSchema);
34}
35
36function resolveLiteralFromShape(shape: Record<string, AnySchema>): string | undefined {
37 const typeSchema = shape["$type"] as AnySchema | undefined;
38 if (typeSchema === undefined) {
39 return undefined;
40 }
41 const inner =
42 (typeSchema as { type: string }).type === "optional"
43 ? (typeSchema as v.OptionalSchema<AnySchema, unknown>).wrapped
44 : typeSchema;
45 if ((inner as { type: string }).type === "literal") {
46 return (inner as v.LiteralSchema<string>).expected;
47 }
48 return undefined;
49}
50
51export function maybeify<TSchema extends AnySchema>(
52 schema: TSchema,
53 value: v.InferOutput<TSchema>,
54): Maybeify<v.InferOutput<TSchema>> {
55 const schemaType = (schema as { type: string }).type;
56
57 if (schemaType === "optional") {
58 const wrapped = (schema as unknown as v.OptionalSchema<AnySchema, unknown>).wrapped;
59 if (value === undefined) {
60 return nothing() as unknown as Maybeify<v.InferOutput<TSchema>>;
61 }
62 return just(maybeify(wrapped, value as never)) as unknown as Maybeify<v.InferOutput<TSchema>>;
63 }
64
65 if (schemaType === "record") {
66 const objectSchema = (schema as unknown as v.RecordSchema<v.ObjectSchema, v.RecordKeySchema>)
67 .object;
68 return maybeify(objectSchema, value as never) as unknown as Maybeify<v.InferOutput<TSchema>>;
69 }
70
71 if (schemaType === "object") {
72 const objectSchema = schema as unknown as v.ObjectSchema<Record<string, AnySchema>>;
73 const result: Record<string, unknown> = {};
74 for (const [key, propSchema] of Object.entries(objectSchema.shape)) {
75 const propValue = (value as Record<string, unknown>)[key];
76 result[key] = maybeify(propSchema as AnySchema, propValue as never);
77 }
78 return result as unknown as Maybeify<v.InferOutput<TSchema>>;
79 }
80
81 if (schemaType === "array") {
82 const arraySchema = schema as unknown as v.ArraySchema<AnySchema>;
83 return (value as unknown[]).map((item) =>
84 maybeify(arraySchema.item, item as never),
85 ) as unknown as Maybeify<v.InferOutput<TSchema>>;
86 }
87
88 if (schemaType === "variant") {
89 const variantSchema = schema as unknown as v.VariantSchema<any, any>;
90 const typeValue = (value as Record<string, unknown>)["$type"] as string | undefined;
91 if (typeValue !== undefined) {
92 for (const member of variantSchema.members) {
93 const memberSchema = resolveMemberSchema(member);
94 const expectedType = resolveLiteralFromShape(
95 memberSchema.shape as Record<string, AnySchema>,
96 );
97 if (expectedType === typeValue) {
98 return maybeify(memberSchema, value as never) as unknown as Maybeify<
99 v.InferOutput<TSchema>
100 >;
101 }
102 }
103 }
104 return value as unknown as Maybeify<v.InferOutput<TSchema>>;
105 }
106
107 return value as unknown as Maybeify<v.InferOutput<TSchema>>;
108}