/** * A Encoder is a type representing the ability to identify when a value is of type A at runtime * * @tsplus type Encoder * @tsplus derive nominal */ export interface Encoder { readonly encode: (a: A) => unknown } /** * @tsplus type EncoderOps */ export interface EncoderOps { (f: (a: A) => unknown): Encoder } /** * Creates a new Encoder */ export const Encoder: EncoderOps = (encode) => ({ encode }) // // Implicits // /** * Encoder for `true` * * @tsplus implicit */ export const _true: Encoder = Encoder((u) => u) /** * Encoder for `false` * * @tsplus implicit */ export const _false: Encoder = Encoder((u) => u) /** * Encoder for a boolean * * @tsplus implicit */ export const boolean: Encoder = Encoder((u) => u) /** * Encoder for a number * * @tsplus implicit */ export const number: Encoder = Encoder((u) => u) /** * Encoder for null * * @tsplus implicit */ export const _null: Encoder = Encoder((u) => u) /** * Encoder for a string * * @tsplus implicit */ export const string: Encoder = Encoder((u) => u) /** * Encoder for a Date * * @tsplus implicit */ export const date: Encoder = Encoder((u) => u.toISOString()) /** * Encoder for an object shaped like { _tag: string } * * @tsplus implicit */ export const taggedObject: Encoder<{ _tag: string }> = Derive() // // Derivation Rules // /** * @tsplus derive Encoder<_> 10 */ export function deriveNamed>( ...[base]: Check> extends Check.False ? [base: Encoder>] : never ): Encoder { // @ts-expect-error return base } /** * @tsplus derive Encoder<_> 10 */ export function deriveValidation>( ...[base]: Check> extends Check.True ? [base: Encoder>] : never ): Encoder { return Encoder((a) => base.encode(a as Brand.Unbranded)) } /** * @tsplus derive Encoder lazy */ export function deriveLazy(fn: (_: Encoder) => Encoder): Encoder { let cached: Encoder | undefined const encoder: Encoder = Encoder((a) => { if (!cached) { cached = fn(encoder) } return cached.encode(a) }) return encoder } type EitherStructural = { _tag: "Left"; left: E } | { _tag: "Right"; right: A } function deriveEitherInternal( left: Encoder, right: Encoder ): Encoder> { return Derive() } /** * @tsplus derive Encoder[Either]<_> 10 */ export function deriveEither>( ...[left, right]: [A] extends [Either] ? [left: Encoder<_E>, right: Encoder<_A>] : never ): Encoder { const structural = deriveEitherInternal(left, right) return Encoder((u) => structural.encode(u)) } type OptionStructural = { _tag: "None" } | { _tag: "Some"; value: A } function deriveOptionInternal(value: Encoder): Encoder> { return Derive() } /** * @tsplus derive Encoder[Maybe]<_> 10 */ export function deriveOption>( ...[value]: [A] extends [Maybe] ? [value: Encoder<_A>] : never ): Encoder { const structural = deriveOptionInternal(value) return Encoder((u) => structural.encode(u)) } /** * @tsplus derive Encoder[Array]<_> 10 */ export function deriveArray>( ...[element]: [A] extends [Array] ? Check>> extends Check.True ? [element: Encoder<_A>] : never : never ): Encoder { return Encoder((u) => u.map((a) => element.encode(a))) } /** * @tsplus derive Encoder[Chunk]<_> 10 */ export function deriveChunk>( ...[element]: [A] extends [Chunk] ? Check>> extends Check.True ? [element: Encoder<_A>] : never : never ): Encoder { return Encoder((u) => Array.from(u).map((a) => element.encode(a))) } /** * @tsplus derive Encoder[List]<_> 10 */ export function deriveList>( ...[element]: [A] extends [List] ? Check>> extends Check.True ? [element: Encoder<_A>] : never : never ): Encoder { return Encoder((u) => Array.from(u).map((a) => element.encode(a))) } /** * @tsplus derive Encoder[ImmutableArray]<_> 10 */ export function deriveImmutableArray>( ...[element]: [A] extends [ImmutableArray] ? Check>> extends Check.True ? [element: Encoder<_A>] : never : never ): Encoder { return Encoder((u) => u.array.map((a) => element.encode(a))) } /** * @tsplus derive Encoder[SortedSet]<_> 10 */ export function deriveSortedSet>( ...[element]: [A] extends [SortedSet] ? Check>> extends Check.True ? [element: Encoder<_A>] : never : never ): Encoder { return Encoder((u) => Array.from(u).map((a) => element.encode(a))) } /** * @tsplus derive Encoder<_> 10 */ export function deriveEmptyRecord( ..._: Check> extends Check.True ? [] : never ): Encoder { return Encoder((a) => a) } /** * @tsplus derive Encoder<_> 15 */ export function deriveDictionary>( ...[value]: Check> extends Check.True ? [value: Encoder] : never ): Encoder { return Encoder((u) => { const encoded = {} for (const k of Object.keys(u)) { encoded[k] = value.encode(u[k]) } return encoded }) } /** * @tsplus derive Encoder<_> 15 */ export function deriveRecord>( ...[value, requiredKeys]: Check> extends Check.True ? [ value: Encoder, requiredKeys: { [k in keyof A]: 0 } ] : never ): Encoder { return Encoder((u) => { const encoded = {} for (const k of Object.keys(requiredKeys)) { encoded[k] = value.encode(u[k]) } return encoded }) } /** * @tsplus derive Encoder<_> 20 */ export function deriveLiteral( ...[value]: Check & Check.Not>> extends Check.True ? [value: A] : never ): Encoder { return Encoder(() => value) } /** * @tsplus derive Encoder<_> 20 */ export function deriveStruct>( ...[requiredFields, optionalFields]: Check> extends Check.True ? [ ...[ requiredFields: { [k in TypeLevel.RequiredKeys]: Encoder } ], ...([TypeLevel.OptionalKeys] extends [never] ? [] : [ optionalFields: { [k in TypeLevel.OptionalKeys]: Encoder> } ]) ] : never ): Encoder { return Encoder((u) => { const encoded = {} for (const field of Object.keys(requiredFields)) { encoded[field] = (requiredFields[field] as Encoder).encode(u[field]) } if (optionalFields) { for (const field of Object.keys(optionalFields)) { if (field in u && typeof u[field] !== "undefined") { encoded[field] = (optionalFields[field] as Encoder).encode(u[field]) } } } return encoded }) } /** * @tsplus derive Encoder<_> 20 */ export function deriveTagged( ...[elements]: Check> extends Check.True ? [ elements: { [k in A["_tag"]]: Encoder> } ] : never ): Encoder { return Encoder((u) => elements[u._tag].encode(u)) } /** * @tsplus derive Encoder<|> 30 */ export function deriveUnion( ...elements: { [k in keyof A]: [Guard, Encoder] } ): Encoder { return Encoder((u) => { for (const element of elements) { if (element[0].is(u)) { return element[1].encode(u) } } }) } /** * @tsplus fluent Encoder encodeJSON */ export function encodeJSON(encoder: Encoder, a: A) { return JSON.stringify(encoder.encode(a)) }