Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Combinator to help creating migrations between types #82

Open
sledorze opened this issue Feb 17, 2019 · 3 comments
Open

Combinator to help creating migrations between types #82

sledorze opened this issue Feb 17, 2019 · 3 comments

Comments

@sledorze
Copy link
Collaborator

Provides a mean to decode a (previous) type and encode a new one.
Two flavors; one for which the conversion is always possible (total); the other for which the conversion is partial.

The resulting Type is minimal but breaks some laws (namely it cannot decode what it has encoded.

Sketch:

const migrate = <A, B, I, O>(
  from: t.Decoder<I, A>,
  to: t.Type<B, O, I>,
  migration: (a: A) => B,
  name?: string
): t.Type<B, O, I> =>
  new t.Type(
    name === undefined ? `migrate(${from.name}, ${to.name})` : name,
    to.is,
    (i, context) => from.validate(i, context).map(migration),
    to.encode
  )

const migratePartial = <A, B, I, O>(
  from: t.Decoder<I, A>,
  to: t.Type<B, O, I>,
  migration: (a: A, context: t.Context) => Either<t.Errors, B>,
  name?: string
): t.Type<B, O, I> =>
  new t.Type(
    name === undefined ? `migratePartial(${from.name}, ${to.name})` : name,
    to.is,
    (i, context) => from.validate(i, context).chain(v => migration(v, context)),
    to.encode
  )

Another version (not copied here) handles also decoding the new Type.

I'm opening the discussion to debate what's the most useful for other users.

@sledorze
Copy link
Collaborator Author

We've used it in our codebase.
This is the most used version (and fixes the problem of previous version).

export const migrate = <A, B, I, O>(
  from: t.Decoder<I, A>,
  to: t.Type<B, O, I>,
  migrate: (a: A) => B,
  name?: string
): t.Type<B, O, I> =>
  new t.Type(
    name === undefined ? `migrate(${from.name}, ${to.name})` : name,
    to.is,
    (i, context) => to.validate(i, context).orElse(_ => from.validate(i, context).map(migrate)),
    to.encode
  )

@gcanti I'm pushing for its inclusion; ok with it?

@Schniz
Copy link

Schniz commented Jun 17, 2019

Hey @sledorze! I worked on a library that does kinda the same. maybe I could use the decoder internally instead of doing that myself. I wonder what do you think: https://github.com/Schniz/migratype

import {migratype, TypeOf, LevelsOf} from 'migratype';
import * as t from 'io-ts';

const UserType = migratype("User", t.string)
  .extend({
    type: t.type({ name: t.string }),
    migration: name => right({ name })
  })

type User = TypeOf<typeof UserType>; // { name: string }
type AllCombinationsOfUser = LevelsOf<typeof UserType>; // string | { name: string }

@sledorze
Copy link
Collaborator Author

sledorze commented Jun 17, 2019

maybe I could use the decoder internally instead of doing that myself

@Schniz
I like the builder approach atop a lower level implementation.
I think you should take into account all Type params (A, O, I) when defining your Migratable and stick to the decoders semantic.
For instance; I'm suspicious about the decode input type which defaults to T instead of I (which in turn defaults to unknown in io-ts).
You should see the type constraints propagate if trying to reimplement from those combinators.

Also this is the migratePartial:

export const migratePartial = <A, B, I, O>(
  from: t.Decoder<I, A>,
  to: t.Type<B, O, I>,
  migration: (a: A, context: t.Context) => Either<t.Errors, B>,
  name?: string
): t.Type<B, O, I> =>
  new t.Type(
    name === undefined ? `migratePartial(${from.name}, ${to.name})` : name,
    to.is,
    (i, context) =>
      to
        .validate(i, context)
        .orElse(_ => from.validate(i, context).chain(v => migration(v, context))),
    to.encode
  )

@gcanti pinging again, would you accept a PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants