Skip to content

Commit

Permalink
Add VariantSchema.fieldEvolve api (#3478)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart authored Aug 19, 2024
1 parent 2cb6ebb commit da52556
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 49 deletions.
5 changes: 5 additions & 0 deletions .changeset/wise-dodos-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/experimental": patch
---

Add VariantSchema.fieldEvolve api
128 changes: 118 additions & 10 deletions packages/experimental/src/VariantSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { Brand } from "effect/Brand"
import type * as Effect from "effect/Effect"
import { constUndefined, dual } from "effect/Function"
import * as Option from "effect/Option"
import { type Pipeable, pipeArguments } from "effect/Pipeable"
import * as Struct_ from "effect/Struct"

/**
* @since 1.0.0
Expand All @@ -25,7 +27,7 @@ export type TypeId = typeof TypeId
* @since 1.0.0
* @category models
*/
export interface Struct<in out A extends Field.Fields> {
export interface Struct<in out A extends Field.Fields> extends Pipeable {
readonly [TypeId]: A
}

Expand Down Expand Up @@ -77,7 +79,7 @@ export type FieldTypeId = typeof FieldTypeId
* @since 1.0.0
* @category models
*/
export interface Field<in out A extends Field.Config> {
export interface Field<in out A extends Field.Config> extends Pipeable {
readonly [FieldTypeId]: FieldTypeId
readonly schemas: A
}
Expand All @@ -87,6 +89,12 @@ export interface Field<in out A extends Field.Config> {
* @category models
*/
export declare namespace Field {
/**
* @since 1.0.0
* @category models
*/
type ValueAny = Schema.Schema.All | Schema.PropertySignature.All

/**
* @since 1.0.0
* @category models
Expand Down Expand Up @@ -116,22 +124,73 @@ export declare namespace Field {
}
}

const StructProto = {
pipe() {
return pipeArguments(this, arguments)
}
}

/**
* @since 1.0.0
* @category constructors
*/
export const Struct = <const A extends Field.Fields>(fields: A): Struct<A> => ({
[TypeId]: fields
})
export const Struct = <const A extends Field.Fields>(fields: A): Struct<A> => {
const self = Object.create(StructProto)
self[TypeId] = fields
return self
}

const FieldProto = {
[FieldTypeId]: FieldTypeId,
pipe() {
return pipeArguments(this, arguments)
}
}

/**
* @since 1.0.0
* @category constructors
*/
export const Field = <const A extends Field.Config>(schemas: A): Field<A> => ({
[FieldTypeId]: FieldTypeId,
schemas
})
export const Field = <const A extends Field.Config>(schemas: A): Field<A> => {
const self = Object.create(FieldProto)
self.schemas = schemas
return self
}

/**
* @since 1.0.0
* @category constructors
*/
export const fieldEvolve: {
<
Self extends Field<any>,
Mapping extends {
readonly [K in keyof Self["schemas"]]?: (variant: Self["schemas"][K]) => Field.ValueAny
}
>(f: Mapping): (self: Self) => Field<
{
readonly [K in keyof Self["schemas"]]: K extends keyof Mapping
? Mapping[K] extends (arg: any) => any ? ReturnType<Mapping[K]> : Self["schemas"][K]
: Self["schemas"][K]
}
>
<
Self extends Field<any>,
Mapping extends {
readonly [K in keyof Self["schemas"]]?: (variant: Self["schemas"][K]) => Field.ValueAny
}
>(self: Self, f: Mapping): Field<
{
readonly [K in keyof Self["schemas"]]: K extends keyof Mapping
? Mapping[K] extends (arg: any) => any ? ReturnType<Mapping[K]> : Self["schemas"][K]
: Self["schemas"][K]
}
>
} = dual(
2,
(self: Field<any>, f: Record<string, (schema: Field.ValueAny) => Field.ValueAny>): Field<any> =>
Field(Struct_.evolve(self.schemas, f))
)

/**
* @since 1.0.0
Expand Down Expand Up @@ -281,6 +340,42 @@ export const make = <
) => <S extends Schema.Schema.All | Schema.PropertySignature.All>(
schema: S
) => Field<{ readonly [K in Exclude<Variants[number], Keys[number]>]: S }>
readonly fieldEvolve: {
<
Self extends Field<any> | Field.ValueAny,
Mapping extends (Self extends Field<infer S> ? { readonly [K in keyof S]?: (variant: S[K]) => Field.ValueAny }
: { readonly [K in Variants[number]]?: (variant: Self) => Field.ValueAny })
>(f: Mapping): (self: Self) => Field<
Self extends Field<infer S> ? {
readonly [K in keyof S]: K extends keyof Mapping
? Mapping[K] extends (arg: any) => any ? ReturnType<Mapping[K]> : S[K]
: S[K]
} :
{
readonly [K in Variants[number]]: K extends keyof Mapping
? Mapping[K] extends (arg: any) => any ? ReturnType<Mapping[K]> : Self
: Self
}
>
<
Self extends Field<any> | Field.ValueAny,
Mapping extends (Self extends Field<infer S> ? {
readonly [K in keyof S]?: (variant: S[K]) => Field.ValueAny
}
: { readonly [K in Variants[number]]?: (variant: Self) => Field.ValueAny })
>(self: Self, f: Mapping): Field<
Self extends Field<infer S> ? {
readonly [K in keyof S]: K extends keyof Mapping
? Mapping[K] extends (arg: any) => any ? ReturnType<Mapping[K]> : S[K]
: S[K]
} :
{
readonly [K in Variants[number]]: K extends keyof Mapping
? Mapping[K] extends (arg: any) => any ? ReturnType<Mapping[K]> : Self
: Self
}
>
}
readonly Class: <Self = never>(
identifier: string
) => <Fields extends Struct.Fields>(
Expand Down Expand Up @@ -335,12 +430,25 @@ export const make = <
return Field(obj)
}
}
const fieldEvolveVariants = dual(
2,
(
self: Field<any> | Schema.Schema.All | Schema.PropertySignature.All,
f: Record<string, (schema: Field.ValueAny) => Field.ValueAny>
): Field<any> => {
const field = FieldTypeId in self ? self : Field(Object.fromEntries(
options.variants.map((variant) => [variant, self])
))
return fieldEvolve(field, f)
}
)
return {
Struct,
Field,
FieldOnly,
FieldExcept,
Class
Class,
fieldEvolve: fieldEvolveVariants
} as any
}

Expand Down
80 changes: 41 additions & 39 deletions packages/sql/src/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import type { Brand } from "effect/Brand"
import * as DateTime from "effect/DateTime"
import * as Effect from "effect/Effect"
import * as Option from "effect/Option"
import * as Record from "effect/Record"

const {
Class,
Field,
FieldExcept,
FieldOnly,
Struct
Struct,
fieldEvolve
} = VariantSchema.make({
variants: ["select", "insert", "update", "json", "jsonCreate", "jsonUpdate"],
defaultVariant: "select"
Expand Down Expand Up @@ -81,6 +81,11 @@ export {
* @category fields
*/
Field,
/**
* @since 1.0.0
* @category fields
*/
fieldEvolve,
/**
* @since 1.0.0
* @category fields
Expand Down Expand Up @@ -208,48 +213,45 @@ export const Sensitive = <S extends Schema.Schema.All | Schema.PropertySignature
* @since 1.0.0
* @category optional
*/
export const FieldOption = <Field extends VariantSchema.Field<any> | Schema.Schema.Any>(
export interface FieldOption<S extends Schema.Schema.Any> extends
VariantSchema.Field<{
readonly select: Schema.OptionFromNullOr<S>
readonly insert: Schema.OptionFromNullOr<S>
readonly update: Schema.OptionFromNullOr<S>
readonly json: Schema.optionalWith<S, { as: "Option" }>
readonly jsonCreate: Schema.optionalWith<S, { as: "Option"; nullable: true }>
readonly jsonUpdate: Schema.optionalWith<S, { as: "Option"; nullable: true }>
}>
{}

/**
* Convert a field to one that is optional for all variants.
*
* For the database variants, it will accept `null`able values.
* For the JSON variants, it will also accept missing keys.
*
* @since 1.0.0
* @category optional
*/
export const FieldOption: <Field extends VariantSchema.Field<any> | Schema.Schema.Any>(
self: Field
): VariantSchema.Field<
Field extends Schema.Schema.Any ? {
readonly select: Schema.OptionFromNullOr<Field>
readonly insert: Schema.OptionFromNullOr<Field>
readonly update: Schema.OptionFromNullOr<Field>
readonly json: Schema.optionalWith<Field, { as: "Option" }>
readonly jsonCreate: Schema.optionalWith<Field, { as: "Option"; nullable: true }>
readonly jsonUpdate: Schema.optionalWith<Field, { as: "Option"; nullable: true }>
}
: Field extends VariantSchema.Field<infer S> ? {
) => Field extends Schema.Schema.Any ? FieldOption<Field>
: Field extends VariantSchema.Field<infer S> ? VariantSchema.Field<
{
readonly [K in keyof S]: S[K] extends Schema.Schema.Any
? K extends VariantsDatabase ? Schema.OptionFromNullOr<S[K]> :
Schema.optionalWith<S[K], { as: "Option"; nullable: true }>
: never
} :
{}
> => {
if (Schema.isSchema(self)) {
return Field({
select: Schema.OptionFromNullOr(self),
insert: Schema.OptionFromNullOr(self),
update: Schema.OptionFromNullOr(self),
json: Schema.optionalWith(self, { as: "Option" }),
jsonCreate: Schema.optionalWith(self, { as: "Option", nullable: true }),
jsonUpdate: Schema.optionalWith(self, { as: "Option", nullable: true })
}) as any
}
return VariantSchema.Field(Record.map(self.schemas, (schema, variant) => {
switch (variant) {
case "select":
case "insert":
case "update":
return Schema.OptionFromNullOr(schema as any)
case "json":
return Schema.optionalWith(schema as any, { as: "Option" })
default:
return Schema.optionalWith(schema as any, { as: "Option", nullable: true })
}
}) as any)
}
}
> :
never = fieldEvolve({
select: Schema.OptionFromNullOr,
insert: Schema.OptionFromNullOr,
update: Schema.OptionFromNullOr,
json: Schema.optionalWith({ as: "Option" }),
jsonCreate: Schema.optionalWith({ as: "Option", nullable: true }),
jsonUpdate: Schema.optionalWith({ as: "Option", nullable: true })
}) as any

/**
* @since 1.0.0
Expand Down

0 comments on commit da52556

Please sign in to comment.