Skip to content

Repository files navigation

Codeco

Lightweight TypeScript-first encoding and decoding of complex objects.

Idea

A value of type Codec<A, O, I> (called "codec") is the runtime representation of the static type A.

A codec can:

  • decode inputs of type I,
  • encode values to type O,
  • be used as a type predicate.
exportabstractclassCodec<A,O=A,I=unknown>{protectedconstructor(readonlyname: string){}abstractis(input: unknown): input is A;abstractencode(value: A): O;abstractdecode(input: I): Either<Error,A>;}

As an example, here is a codec for integer encoded as a string:

// Represents integer `number`, the first type parameter.// If we encode a known number, it will turn into `string` (the second type parameter).// If we want to receive a number, the codec can accept `string` as input to parse (the third type parameter).// To decode `unknown` input do something like `string.pipe(numberAsString)`.classIntAsStringCodecextendsCodec<number,string,string>{constructor(){super(`IntAsString`);}// Similar to `instanceof`.is(input: unknown): input is number{returntypeofinput==="number";}decode(input: string,context: Context): Validation<number>{constsupposedlyInt=parseInt(input,10);// If an integerif(supposedlyInt.toString()===input){// Return value// Beware: do not return plain value, wrap it in `context.success`returncontext.success(supposedlyInt);}else{// If anything is wrong, signal failure by returning `context.failure`.// Whatever happens, **do not throw an error**.returncontext.failure(`Not an integer`);}}// Encode known value to string output.encode(value: number): string{returnvalue.toString();}}constintAsString=newIntAsStringCodec();

In most cases though, creating codecs this way is an overkill. Codec combinators provided by the library are enough for 90% of use cases.

The Either type represents a value of one of two possible types (a disjoint union):

  • Left meaning success,
  • Right meaning failure.
typeEither<TError,TValue>=|{readonly_tag: "Left";readonlyleft: TError;}|{readonly_tag: "Right";readonlyright: TValue;};

You could check a result of validation using isValid or isError helpers:

import{string,refinement,validate,isError}from"codeco";constlongString=refinement(string,(s)=>s.length>=100);constvalidation=validate(longString,"short input");if(isError(validation)){console.log("Validation errorr",validation.left);}constvalid=validation.right;// Here goes proper long string

Implemented types

DescriptionTypeScriptcodec
nullnullcs.null or cs.nullCodec
undefinedundefinedcs.undefined
voidvoidcs.void
stringstringcs.string
numbernumbercs.number
booleanbooleancs.boolean
BigIntbigintcs.bigint
unknownunknowncs.unknown
literal's'cs.literal('s')
array of unknownArray<unknown>cs.unknownArray
dictionary of unknownRecord<string, unknown>cs.unknownDictionary
array of typeArray<A>cs.array(A)
anyanycs.any
nevernevercs.never
dictionaryRecord<string, A>cs.dictionary(A)
record of typeRecord<K, A>cs.record(K, A)
partialPartial<{ name: string }>cs.partial({ name: cs.string })
readonlyReadonly<A>cs.readonly(A)
type aliastype T = { name: A }cs.type({ name: A })
tuple[A, B]cs.tuple([ A, B ])
unionA | Bcs.union([ A, B ])
intersectionA & Bcs.intersection([ A, B ])
keyofkeyof Mcs.keyof(M) (only supports string keys)
recursive typescs.recursive(name, definition)
exact typescs.exact(type) (no unknown extra properties)
strictcs.strict({ name: A }) (an alias of cs.exact(cs.type({ name: A })))
sparsecs.sparse({ name: A }) similar to cs.intersect(cs.type(), cs.partial()
replacementcs.replacement(A, altInput)
optionalA | undefinedcs.optional(A)

Linear parsing

In addition to structural encoding/decoding, we provide linear parsing functions in form of Parser Combinators available from 'codeco/linear':

import*asPfrom"codeco/linear";import{getOrThrow}from"codeco";constline=P.seq(P.literal("My name is "),P.match(/\w+/));constname=P.map(line,(parsed)=>parsed[1]);// `map` combinatorconstinput=newP.StringTape("My name is Marvin");// Prepare input for consumptionconstdecodedName=getOrThrow(P.parseAll(input));// Would throw if input does not conform to expected format

Provided combinators:

  • literal("string-value") - literal value
  • map(combinator, mapFn) - map return value of combinator to something else,
  • mapFold(combinator, mapFn) - map return value of combinator to something else as Either, so optionally indicating failure,
  • match(regexp) - like literal, but matches a RegExp,
  • seq(combinatorA, combinatorB, ...) - match combinators and return array of their results,
  • join(combinators) - match combinators and their results as a single string,
  • joinSeq(combinators) - shortcut for join(seq(combinatros)),
  • option(combinator, value) - try matching combinator, return value if the combinator does not match,
  • choice(combinatorA, combinatorB, ...) - match any of the passed combinators,
  • sepBy(combinator, separator, min = 1, max = Infinity) - match sequence of 1 or more combinators separated by separator, like A, A + A, A + A + A, etc.
  • many(combinator, min = 1, max = Infinity) - array of combinators of length [min, max),
  • parseAll(combinator) - make sure all the input is consumed.

About

Minimalistic yet feature-rich IO decoding and encoding

Resources

Stars

2 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages