Last active
August 14, 2024 10:35
-
-
Save tim-smart/42ade9c1f210373ddbc757adcfc079a2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Schema } from "@effect/schema" | |
import * as Model from "./Model.js" | |
import { DateTime } from "effect" | |
export const GroupId = Schema.Number.pipe(Schema.brand("GroupId")) | |
export class Group extends Model.Class<Group>("Group")({ | |
id: Model.PrimaryKey(GroupId), | |
name: Schema.NonEmptyTrimmedString, | |
createdAt: Model.CreatedAt, | |
updatedAt: Model.UpdatedAt, | |
}) {} | |
const group = Group.make({ | |
id: GroupId.make(1), | |
name: "Group 1", | |
createdAt: DateTime.unsafeNow(), | |
updatedAt: DateTime.unsafeNow(), | |
}) | |
console.log(Schema.encodeSync(Group.json)(group)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as AST from "@effect/schema/AST" | |
import * as Schema from "@effect/schema/Schema" | |
import * as ParseResult from "@effect/schema/ParseResult" | |
import { dual } from "effect/Function" | |
import * as Data from "effect/Data" | |
/** | |
* @since 1.0.0 | |
* @category type ids | |
*/ | |
export const TypeId: unique symbol = Symbol.for("@effect/schema/VariantSchema") | |
/** | |
* @since 1.0.0 | |
* @category type ids | |
*/ | |
export type TypeId = typeof TypeId | |
/** | |
* @since 1.0.0 | |
* @category models | |
*/ | |
export interface Struct<in out A extends Field.Fields> { | |
readonly [TypeId]: A | |
} | |
/** | |
* @since 1.0.0 | |
* @category models | |
*/ | |
export declare namespace Struct { | |
/** | |
* @since 1.0.0 | |
* @category models | |
*/ | |
export type Fields = { | |
readonly [key: string]: | |
| Schema.Schema.All | |
| Schema.PropertySignature.All | |
| Field<any> | |
| Struct<any> | |
} | |
/** | |
* @since 1.0.0 | |
* @category models | |
*/ | |
export type FieldsWithKeys<K extends string> = { | |
readonly [key: string]: | |
| Schema.Schema.All | |
| Schema.PropertySignature.All | |
| Field<Field.ConfigWithKeys<K>> | |
| Struct<FieldsWithKeys<K>> | |
} | |
} | |
/** | |
* @since 1.0.0 | |
* @category type ids | |
*/ | |
export const FieldTypeId: unique symbol = Symbol.for( | |
"@effect/schema/VariantSchema/Field", | |
) | |
/** | |
* @since 1.0.0 | |
* @category type ids | |
*/ | |
export type FieldTypeId = typeof FieldTypeId | |
/** | |
* @since 1.0.0 | |
* @category models | |
*/ | |
export interface Field<in out A extends Field.Config> { | |
readonly [FieldTypeId]: FieldTypeId | |
readonly schemas: A | |
} | |
/** | |
* @since 1.0.0 | |
* @category models | |
*/ | |
export declare namespace Field { | |
/** | |
* @since 1.0.0 | |
* @category models | |
*/ | |
export type Config = Partial<{ | |
readonly [key: string]: Schema.Schema.All | Schema.PropertySignature.All | |
}> | |
/** | |
* @since 1.0.0 | |
* @category models | |
*/ | |
export type ConfigWithKeys<K extends string> = { | |
readonly [P in K]?: Schema.Schema.All | Schema.PropertySignature.All | |
} | |
/** | |
* @since 1.0.0 | |
* @category models | |
*/ | |
export type Fields = { | |
readonly [key: string]: | |
| Schema.Schema.All | |
| Schema.PropertySignature.All | |
| Field<any> | |
| Struct<any> | |
} | |
} | |
/** | |
* @since 1.0.0 | |
* @category constructors | |
*/ | |
export const Struct = <const A extends Field.Fields>(fields: A): Struct<A> => ({ | |
[TypeId]: fields, | |
}) | |
/** | |
* @since 1.0.0 | |
* @category constructors | |
*/ | |
export const Field = <const A extends Field.Config>(schemas: A): Field<A> => ({ | |
[FieldTypeId]: FieldTypeId, | |
schemas, | |
}) | |
/** | |
* @since 1.0.0 | |
* @category extractors | |
*/ | |
export type ExtractFields<V extends string, Fields extends Struct.Fields> = { | |
readonly [K in keyof Fields as [Fields[K]] extends [Field<infer Config>] | |
? V extends keyof Config | |
? K | |
: never | |
: K]: [Fields[K]] extends [Struct<infer _>] | |
? Extract<V, Fields[K]> | |
: [Fields[K]] extends [Field<infer Config>] | |
? [Config[V]] extends [Schema.Schema.All | Schema.PropertySignature.All] | |
? Config[V] | |
: never | |
: [Fields[K]] extends [Schema.Schema.All | Schema.PropertySignature.All] | |
? Fields[K] | |
: never | |
} | |
/** | |
* @since 1.0.0 | |
* @category extractors | |
*/ | |
export type Extract<V extends string, A extends Struct<any>> = [A] extends [ | |
Struct<infer Fields>, | |
] | |
? Schema.Struct<Schema.Simplify<ExtractFields<V, Fields>>> | |
: never | |
/** | |
* @since 1.0.0 | |
* @category extractors | |
*/ | |
export const extract: { | |
<V extends string>( | |
variant: V, | |
): <A extends Struct<any>>(self: A) => Extract<V, A> | |
<V extends string, A extends Struct<any>>(self: A, variant: V): Extract<V, A> | |
} = dual( | |
2, | |
<V extends string, A extends Struct<any>>( | |
self: A, | |
variant: V, | |
): Extract<V, A> => { | |
const fields: Record<string, any> = {} | |
for (const key of Object.keys(self[TypeId])) { | |
const value = self[TypeId][key] | |
if (Schema.TypeId in value) { | |
fields[key] = value | |
} else if (FieldTypeId in value) { | |
if (variant in value.schemas) { | |
fields[key] = value.schemas[variant] | |
} | |
} else { | |
fields[key] = extract(value, variant) | |
} | |
} | |
return Schema.Struct(fields) as any | |
}, | |
) | |
type RequiredKeys<T> = { | |
[K in keyof T]-?: {} extends Pick<T, K> ? never : K | |
}[keyof T] | |
/** | |
* @category models | |
* @since 1.0.0 | |
*/ | |
export interface Class< | |
Self, | |
Fields extends Struct.Fields, | |
SchemaFields extends Schema.Struct.Fields, | |
A, | |
I, | |
R, | |
C, | |
> extends Schema.Schema<Self, Schema.Simplify<I>, R>, | |
Struct<Fields> { | |
new ( | |
props: RequiredKeys<C> extends never | |
? void | Schema.Simplify<C> | |
: Schema.Simplify<C>, | |
options?: { | |
readonly disableValidation?: boolean | |
}, | |
): A | |
readonly ast: AST.Transformation | |
make<Args extends Array<any>, X>( | |
this: { new (...args: Args): X }, | |
...args: Args | |
): X | |
annotations( | |
annotations: Schema.Annotations.Schema<Self>, | |
): Schema.SchemaClass<Self, Schema.Simplify<I>, R> | |
readonly identifier: string | |
readonly fields: SchemaFields | |
} | |
type ClassFromFields< | |
Self, | |
Fields extends Struct.Fields, | |
SchemaFields extends Schema.Struct.Fields, | |
> = Class< | |
Self, | |
Fields, | |
SchemaFields, | |
Schema.Struct.Type<SchemaFields>, | |
Schema.Struct.Encoded<SchemaFields>, | |
Schema.Struct.Context<SchemaFields>, | |
Schema.Struct.Constructor<SchemaFields> | |
> | |
type MissingSelfGeneric<Params extends string = ""> = | |
`Missing \`Self\` generic - use \`class Self extends Class<Self>()(${Params}{ ... })\`` | |
const schemaVariance = { | |
/* c8 ignore next */ | |
_A: (_: any) => _, | |
/* c8 ignore next */ | |
_I: (_: any) => _, | |
/* c8 ignore next */ | |
_R: (_: never) => _, | |
} | |
/** | |
* @since 1.0.0 | |
* @category constructors | |
*/ | |
export const factory = < | |
const Variants extends ReadonlyArray<string>, | |
const Default extends Variants[number], | |
>(options: { | |
readonly variants: Variants | |
readonly defaultVariant: Default | |
}): { | |
readonly Struct: <const A extends Struct.FieldsWithKeys<Variants[number]>>( | |
fields: A, | |
) => Struct<A> | |
readonly Field: <const A extends Field.ConfigWithKeys<Variants[number]>>( | |
config: A & { readonly [K in Exclude<keyof A, Variants[number]>]: never }, | |
) => Field<A> | |
readonly Class: <Self = never>( | |
identifier: string, | |
) => <Fields extends Struct.Fields>( | |
fields: Fields, | |
annotations?: Schema.Annotations.Schema<Self>, | |
) => [Self] extends [never] | |
? MissingSelfGeneric | |
: ClassFromFields< | |
Self, | |
Fields, | |
Schema.Simplify<ExtractFields<Default, Fields>> | |
> & { | |
readonly [V in Variants[number]]: Extract<V, Struct<Fields>> | |
} | |
} => { | |
function Class<Self>(identifier: string) { | |
return function ( | |
fields: Struct.Fields, | |
annotations?: Schema.Annotations.Schema<Self>, | |
) { | |
const variantStruct = Struct(fields) | |
const schema = extract(variantStruct, options.defaultVariant) | |
const validate = ParseResult.validateSync(schema) | |
const klass = Schema.Class<any>(identifier)(schema.fields, annotations) | |
class Base extends Data.Class { | |
constructor( | |
props: any, | |
options?: { readonly disableValidation?: boolean }, | |
) { | |
if (options?.disableValidation !== true) { | |
props = validate(props) | |
} | |
super(props) | |
} | |
// implement Struct | |
static [TypeId] = fields | |
// implement Schema | |
static [Schema.TypeId] = schemaVariance | |
static ast = klass.ast | |
static make = klass.make | |
static fields = klass.fields | |
static identifier = klass.identifier | |
} | |
for (const variant of options.variants) { | |
Object.defineProperty(Base, variant, { | |
value: extract(variantStruct, variant), | |
}) | |
} | |
return Base | |
} | |
} | |
return { | |
Struct, | |
Field, | |
Class, | |
} as any | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as Variant from "./Variant.js" | |
import * as Schema from "@effect/schema/Schema" | |
import * as DateTime from "effect/DateTime" | |
import { LazyArg } from "effect/Function" | |
export const { Class, Struct, Field } = Variant.factory({ | |
variants: ["select", "insert", "update", "json", "jsonCreate", "jsonUpdate"], | |
defaultVariant: "select", | |
}) | |
export interface PrimaryKey<S extends Schema.Schema.All> | |
extends Variant.Field<{ | |
readonly select: S | |
readonly update: S | |
readonly json: S | |
}> {} | |
export const PrimaryKey = <S extends Schema.Schema.All>( | |
schema: S, | |
): PrimaryKey<S> => | |
Field({ | |
select: schema, | |
update: schema, | |
json: schema, | |
}) | |
export interface FieldNoInsert<S extends Schema.Schema.All> | |
extends Variant.Field<{ | |
readonly select: S | |
readonly update: S | |
readonly json: S | |
readonly jsonUpdate: S | |
}> {} | |
export const FieldNoInsert = <S extends Schema.Schema.All>( | |
schema: S, | |
): FieldNoInsert<S> => | |
Field({ | |
select: schema, | |
update: schema, | |
json: schema, | |
jsonUpdate: schema, | |
}) | |
export interface FieldNoJson<S extends Schema.Schema.All> | |
extends Variant.Field<{ | |
readonly select: S | |
readonly insert: S | |
readonly update: S | |
}> {} | |
export const FieldNoJson = <S extends Schema.Schema.All>( | |
schema: S, | |
): FieldNoJson<S> => | |
Field({ | |
select: schema, | |
insert: schema, | |
update: schema, | |
}) | |
export interface DateTimeFromDate | |
extends Schema.transform< | |
typeof Schema.ValidDateFromSelf, | |
typeof Schema.DateTimeUtcFromSelf | |
> {} | |
export const DateTimeFromDate: DateTimeFromDate = Schema.transform( | |
Schema.ValidDateFromSelf, | |
Schema.DateTimeUtcFromSelf, | |
{ | |
decode: DateTime.unsafeFromDate, | |
encode: DateTime.toDateUtc, | |
}, | |
) | |
const DateTimeWithNow = DateTimeFromDate.pipe( | |
Schema.optionalWith({ default: DateTime.unsafeNow }), | |
) | |
export interface CreatedAt | |
extends Variant.Field<{ | |
readonly select: DateTimeFromDate | |
readonly insert: Schema.optionalWith< | |
DateTimeFromDate, | |
{ default: LazyArg<DateTime.Utc> } | |
> | |
readonly json: typeof Schema.DateTimeUtc | |
}> {} | |
export const CreatedAt: CreatedAt = Field({ | |
select: DateTimeFromDate, | |
insert: DateTimeWithNow, | |
json: Schema.DateTimeUtc, | |
}) | |
export interface UpdatedAt | |
extends Variant.Field<{ | |
readonly select: DateTimeFromDate | |
readonly insert: Schema.optionalWith< | |
DateTimeFromDate, | |
{ default: LazyArg<DateTime.Utc> } | |
> | |
readonly update: Schema.optionalWith< | |
DateTimeFromDate, | |
{ default: LazyArg<DateTime.Utc> } | |
> | |
readonly json: typeof Schema.DateTimeUtc | |
}> {} | |
export const UpdatedAt: UpdatedAt = Field({ | |
select: DateTimeFromDate, | |
insert: DateTimeWithNow, | |
update: DateTimeWithNow, | |
json: Schema.DateTimeUtc, | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment