Skip to content

Commit 5f5fcd9

Browse files
tim-smartgcanti
andauthoredMar 18, 2024··
Schema: make optional dual (#2353)
Co-authored-by: gcanti <giulio.canti@gmail.com>
1 parent a45a525 commit 5f5fcd9

File tree

5 files changed

+171
-79
lines changed

5 files changed

+171
-79
lines changed
 

‎.changeset/dry-experts-laugh.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
"@effect/schema": patch
3+
---
4+
5+
make `optional` dual:
6+
7+
```ts
8+
import * as S from "@effect/schema/Schema";
9+
10+
const schema = S.struct({
11+
a: S.string.pipe(S.optional()),
12+
});
13+
14+
// same as:
15+
const schema2 = S.struct({
16+
a: S.optional(S.string),
17+
});
18+
```

‎.changeset/tidy-rocks-live.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
Types: add `Has` helper

‎packages/effect/src/Types.ts

+16
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ export type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
8282
>() => T extends Y ? 1 : 2 ? true
8383
: false
8484

85+
/**
86+
* Determines if a record contains any of the given keys.
87+
*
88+
* @example
89+
* import * as Types from "effect/Types"
90+
*
91+
* type Res1 = Types.Has<{ a: number }, "a" | "b"> // true
92+
* type Res2 = Types.Has<{ c: number }, "a" | "b"> // false
93+
*
94+
* @since 2.0.0
95+
* @category models
96+
*/
97+
export type Has<A, Key extends string> = (Key extends infer K ? K extends keyof A ? true : never : never) extends never
98+
? false
99+
: true
100+
85101
/**
86102
* Merges two object where the keys of the left object take precedence in the case of a conflict.
87103
*

‎packages/schema/dtslint/Schema.ts

+62-4
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,14 @@ S.asSchema(S.struct({ a: S.optional(S.never, { exact: true }) }))
586586
// $ExpectType struct<{ a: PropertySignature<"?:", never, never, "?:", never, never>; }>
587587
S.struct({ a: S.optional(S.never, { exact: true }) })
588588

589+
// $ExpectType Schema<{ readonly a?: string; }, { readonly a?: string; }, never>
590+
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ exact: true })) }))
591+
592+
// $ExpectType struct<{ a: PropertySignature<"?:", string, never, "?:", string, never>; }>
593+
S.struct({ a: S.string.pipe(S.optional({ exact: true })) })
594+
589595
// ---------------------------------------------
590-
// optional
596+
// optional()
591597
// ---------------------------------------------
592598

593599
// $ExpectType Schema<{ readonly a: string; readonly b: number; readonly c?: boolean | undefined; }, { readonly a: string; readonly b: number; readonly c?: boolean | undefined; }, never>
@@ -608,6 +614,12 @@ S.asSchema(S.struct({ a: S.optional(S.never) }))
608614
// $ExpectType struct<{ a: PropertySignature<"?:", undefined, never, "?:", undefined, never>; }>
609615
S.struct({ a: S.optional(S.never) })
610616

617+
// $ExpectType Schema<{ readonly a?: string | undefined; }, { readonly a?: string | undefined; }, never>
618+
S.asSchema(S.struct({ a: S.string.pipe(S.optional()) }))
619+
620+
// $ExpectType struct<{ a: PropertySignature<"?:", string | undefined, never, "?:", string | undefined, never>; }>
621+
S.struct({ a: S.string.pipe(S.optional()) })
622+
611623
// ---------------------------------------------
612624
// optional { exact: true, default: () => A }
613625
// ---------------------------------------------
@@ -640,9 +652,15 @@ S.struct({
640652
c: S.optional(S.NumberFromString, { exact: true, default: () => 0 })
641653
})
642654

643-
// @ts-expect-error
655+
// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b", never>; }>
644656
S.struct({ a: S.optional(S.literal("a", "b"), { default: () => "a", exact: true }) })
645657

658+
// $ExpectType Schema<{ readonly a: "a" | "b"; }, { readonly a?: "a" | "b"; }, never>
659+
S.asSchema(S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a", exact: true })) }))
660+
661+
// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b", never>; }>
662+
S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a", exact: true })) })
663+
646664
// ---------------------------------------------
647665
// optional { default: () => A }
648666
// ---------------------------------------------
@@ -659,9 +677,15 @@ S.asSchema(S.struct({ a: S.string, b: S.number, c: S.optional(S.NumberFromString
659677
// $ExpectType struct<{ a: $string; b: $number; c: PropertySignature<":", number, never, "?:", string | undefined, never>; }>
660678
S.struct({ a: S.string, b: S.number, c: S.optional(S.NumberFromString, { default: () => 0 }) })
661679

662-
// @ts-expect-error
680+
// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b" | undefined, never>; }>
663681
S.struct({ a: S.optional(S.literal("a", "b"), { default: () => "a" }) })
664682

683+
// $ExpectType Schema<{ readonly a: "a" | "b"; }, { readonly a?: "a" | "b" | undefined; }, never>
684+
S.asSchema(S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a" as const })) }))
685+
686+
// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b" | undefined, never>; }>
687+
S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a" as const })) })
688+
665689
// ---------------------------------------------
666690
// optional { nullable: true, default: () => A }
667691
// ---------------------------------------------
@@ -678,9 +702,15 @@ S.asSchema(S.struct({ a: S.optional(S.NumberFromString, { exact: true, nullable:
678702
// $ExpectType struct<{ a: PropertySignature<":", number, never, "?:", string | null, never>; }>
679703
S.struct({ a: S.optional(S.NumberFromString, { exact: true, nullable: true, default: () => 0 }) })
680704

681-
// @ts-expect-error
705+
// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b" | null | undefined, never>; }>
682706
S.struct({ a: S.optional(S.literal("a", "b"), { default: () => "a", nullable: true }) })
683707

708+
// $ExpectType Schema<{ readonly a: "a" | "b"; }, { readonly a?: "a" | "b" | null | undefined; }, never>
709+
S.asSchema(S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a", nullable: true })) }))
710+
711+
// $ExpectType struct<{ a: PropertySignature<":", "a" | "b", never, "?:", "a" | "b" | null | undefined, never>; }>
712+
S.struct({ a: S.literal("a", "b").pipe(S.optional({ default: () => "a", nullable: true })) })
713+
684714
// ---------------------------------------------
685715
// optional { exact: true, as: "Option" }
686716
// ---------------------------------------------
@@ -705,6 +735,12 @@ S.struct({
705735
c: S.optional(S.NumberFromString, { exact: true, as: "Option" })
706736
})
707737

738+
// $ExpectType Schema<{ readonly a: Option<string>; }, { readonly a?: string; }, never>
739+
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ exact: true, as: "Option" })) }))
740+
741+
// $ExpectType struct<{ a: PropertySignature<":", Option<string>, never, "?:", string, never>; }>
742+
S.struct({ a: S.string.pipe(S.optional({ exact: true, as: "Option" })) })
743+
708744
// ---------------------------------------------
709745
// optional { as: "Option" }
710746
// ---------------------------------------------
@@ -721,6 +757,12 @@ S.asSchema(S.struct({ a: S.string, b: S.number, c: S.optional(S.NumberFromString
721757
// $ExpectType struct<{ a: $string; b: $number; c: PropertySignature<":", Option<number>, never, "?:", string | undefined, never>; }>
722758
S.struct({ a: S.string, b: S.number, c: S.optional(S.NumberFromString, { as: "Option" }) })
723759

760+
// $ExpectType Schema<{ readonly a: Option<string>; }, { readonly a?: string | undefined; }, never>
761+
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ as: "Option" })) }))
762+
763+
// $ExpectType struct<{ a: PropertySignature<":", Option<string>, never, "?:", string | undefined, never>; }>
764+
S.struct({ a: S.string.pipe(S.optional({ as: "Option" })) })
765+
724766
// ---------------------------------------------
725767
// optional { nullable: true, as: "Option" }
726768
// ---------------------------------------------
@@ -731,12 +773,28 @@ S.asSchema(S.struct({ a: S.optional(S.NumberFromString, { nullable: true, as: "O
731773
// $ExpectType struct<{ a: PropertySignature<":", Option<number>, never, "?:", string | null | undefined, never>; }>
732774
S.struct({ a: S.optional(S.NumberFromString, { nullable: true, as: "Option" }) })
733775

776+
// $ExpectType Schema<{ readonly a: Option<string>; }, { readonly a?: string | null | undefined; }, never>
777+
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ nullable: true, as: "Option" })) }))
778+
779+
// $ExpectType struct<{ a: PropertySignature<":", Option<string>, never, "?:", string | null | undefined, never>; }>
780+
S.struct({ a: S.string.pipe(S.optional({ nullable: true, as: "Option" })) })
781+
782+
// ---------------------------------------------
783+
// optional { exact: true, nullable: true, as: "Option" }
784+
// ---------------------------------------------
785+
734786
// $ExpectType Schema<{ readonly a: Option<number>; }, { readonly a?: string | null; }, never>
735787
S.asSchema(S.struct({ a: S.optional(S.NumberFromString, { exact: true, nullable: true, as: "Option" }) }))
736788

737789
// $ExpectType struct<{ a: PropertySignature<":", Option<number>, never, "?:", string | null, never>; }>
738790
S.struct({ a: S.optional(S.NumberFromString, { exact: true, nullable: true, as: "Option" }) })
739791

792+
// $ExpectType Schema<{ readonly a: Option<string>; }, { readonly a?: string | null; }, never>
793+
S.asSchema(S.struct({ a: S.string.pipe(S.optional({ exact: true, nullable: true, as: "Option" })) }))
794+
795+
// $ExpectType struct<{ a: PropertySignature<":", Option<string>, never, "?:", string | null, never>; }>
796+
S.struct({ a: S.string.pipe(S.optional({ exact: true, nullable: true, as: "Option" })) })
797+
740798
// ---------------------------------------------
741799
// pick
742800
// ---------------------------------------------

‎packages/schema/src/Schema.ts

+70-75
Original file line numberDiff line numberDiff line change
@@ -1571,85 +1571,80 @@ export const optionalToOptional = <FA, FI, FR, TA, TI, TR>(
15711571
* @since 1.0.0
15721572
*/
15731573
export const optional: {
1574-
<A, I, R>(
1575-
schema: Schema<A, I, R>,
1576-
options: {
1577-
readonly exact: true
1578-
readonly default: () => A
1579-
readonly nullable: true
1580-
}
1581-
): PropertySignature<":", A, never, "?:", I | null, R>
1582-
<A, I, R>(
1583-
schema: Schema<A, I, R>,
1584-
options: {
1585-
readonly exact: true
1574+
<
1575+
A,
1576+
const Options extends {
1577+
readonly exact?: true
1578+
readonly nullable?: true
1579+
} | {
15861580
readonly default: () => A
1587-
}
1588-
): PropertySignature<":", A, never, "?:", I, R>
1589-
<A, I, R>(
1590-
schema: Schema<A, I, R>,
1591-
options: {
1592-
readonly exact: true
1593-
readonly nullable: true
1594-
readonly as: "Option"
1595-
}
1596-
): PropertySignature<":", Option.Option<A>, never, "?:", I | null, R>
1597-
<A, I, R>(
1598-
schema: Schema<A, I, R>,
1599-
options: {
1600-
readonly exact: true
1581+
readonly exact?: true
1582+
readonly nullable?: true
1583+
} | {
16011584
readonly as: "Option"
1602-
}
1603-
): PropertySignature<":", Option.Option<A>, never, "?:", I, R>
1604-
<A, I, R>(
1605-
schema: Schema<A, I, R>,
1606-
options: {
1607-
readonly exact: true
1608-
readonly nullable: true
1609-
}
1610-
): PropertySignature<"?:", A, never, "?:", I | null, R>
1611-
<A, I, R>(
1612-
schema: Schema<A, I, R>,
1613-
options: {
1614-
readonly exact: true
1615-
}
1616-
): PropertySignature<"?:", A, never, "?:", I, R>
1617-
<A, I, R>(
1618-
schema: Schema<A, I, R>,
1619-
options: {
1585+
readonly exact?: true
1586+
readonly nullable?: true
1587+
} | undefined
1588+
>(
1589+
options?: Options
1590+
): <I, R>(schema: Schema<A, I, R>) => [undefined] extends [Options] ? PropertySignature<
1591+
"?:",
1592+
A | undefined,
1593+
never,
1594+
"?:",
1595+
I | undefined,
1596+
R
1597+
> :
1598+
PropertySignature<
1599+
Types.Has<Options, "as" | "default"> extends true ? ":" : "?:",
1600+
| (Types.Has<Options, "as"> extends true ? Option.Option<A> : A)
1601+
| (Types.Has<Options, "as" | "default" | "exact"> extends true ? never : undefined),
1602+
never,
1603+
"?:",
1604+
| I
1605+
| (Types.Has<Options, "nullable"> extends true ? null : never)
1606+
| (Types.Has<Options, "exact"> extends true ? never : undefined),
1607+
R
1608+
>
1609+
<
1610+
A,
1611+
I,
1612+
R,
1613+
const Options extends {
1614+
readonly exact?: true
1615+
readonly nullable?: true
1616+
} | {
16201617
readonly default: () => A
1621-
readonly nullable: true
1622-
}
1623-
): PropertySignature<":", A, never, "?:", I | null | undefined, R>
1624-
<A, I, R>(
1625-
schema: Schema<A, I, R>,
1626-
options: {
1627-
readonly nullable: true
1618+
readonly exact?: true
1619+
readonly nullable?: true
1620+
} | {
16281621
readonly as: "Option"
1629-
}
1630-
): PropertySignature<":", Option.Option<A>, never, "?:", I | null | undefined, R>
1631-
<A, I, R>(
1632-
schema: Schema<A, I, R>,
1633-
options: {
1634-
readonly as: "Option"
1635-
}
1636-
): PropertySignature<":", Option.Option<A>, never, "?:", I | undefined, R>
1637-
<A, I, R>(
1638-
schema: Schema<A, I, R>,
1639-
options: {
1640-
readonly default: () => A
1641-
}
1642-
): PropertySignature<":", A, never, "?:", I | undefined, R>
1643-
<A, I, R>(
1622+
readonly exact?: true
1623+
readonly nullable?: true
1624+
} | undefined
1625+
>(
16441626
schema: Schema<A, I, R>,
1645-
options: {
1646-
readonly nullable: true
1647-
}
1648-
): PropertySignature<"?:", A | undefined, never, "?:", I | null | undefined, R>
1649-
<A, I, R>(
1650-
schema: Schema<A, I, R>
1651-
): PropertySignature<"?:", A | undefined, never, "?:", I | undefined, R>
1652-
} = <A, I, R>(
1627+
options?: Options
1628+
): [undefined] extends [Options] ? PropertySignature<
1629+
"?:",
1630+
A | undefined,
1631+
never,
1632+
"?:",
1633+
I | undefined,
1634+
R
1635+
> :
1636+
PropertySignature<
1637+
Types.Has<Options, "as" | "default"> extends true ? ":" : "?:",
1638+
| (Types.Has<Options, "as"> extends true ? Option.Option<A> : A)
1639+
| (Types.Has<Options, "as" | "default" | "exact"> extends true ? never : undefined),
1640+
never,
1641+
"?:",
1642+
| I
1643+
| (Types.Has<Options, "nullable"> extends true ? null : never)
1644+
| (Types.Has<Options, "exact"> extends true ? never : undefined),
1645+
R
1646+
>
1647+
} = dual((args) => isSchema(args[0]), <A, I, R>(
16531648
schema: Schema<A, I, R>,
16541649
options?: {
16551650
readonly exact?: true
@@ -1756,7 +1751,7 @@ export const optional: {
17561751
}
17571752
}
17581753
}
1759-
}
1754+
})
17601755

17611756
/**
17621757
* @since 1.0.0

0 commit comments

Comments
 (0)
Please sign in to comment.