Skip to content

Latest commit

 

History

History
227 lines (172 loc) · 9.83 KB

MIGRATING-v2.md

File metadata and controls

227 lines (172 loc) · 9.83 KB

Migrating to v2

Decoders v2 has been rewritten to be simpler, smaller, and more efficient. Conceptually, it still works the same way as v1, but some of the APIs have been rewritten in a backward incompatible manner. As such, you may have to make some code changes to migrate to v2.

Please go over this checklist to perform the migration. If you run into issues that aren't somehow covered by this migration guide, please open an issue for this, and I'm happy to help 🙏 !

🏁 Checklist

  1. Install $ npm install decoders
  2. If applicable, uninstall debrief: $ npm uninstall debrief
  3. If applicable, uninstall lemons: $ npm uninstall lemons
  4. Stop "calling" decoders, see instructions.
  5. Rewrite patterns imports for decoders that have been renamed, see instructions.
  6. Rewrite all uses of guard(), see instructions.
  7. If applicable, see migration instructions below for how to rewrite imports from those libraries.
  8. Flow users only: Rewrite use of $DecoderType, see instructions.

Stop "calling" decoders

Decoders in 1.x where functions. In 2.x they're more like classes.

// ❌ 1.x
const result = mydecoder(externalData);

// ✅ 2.x
const result = mydecoder.decode(externalData);
//                       ^^^^^^

Rename some decoders

Some decoders have been renamed. See the table below. You can do a simple "search & replace" command for these:

Replace this v1 pattern... ...with this v2 API Notes
map(mydecoder, ...) mydecoder.transform(...) migration instructions
compose(mydecoder, predicate(...)) mydecoder.refine(...) migration instructions
describe(mydecoder, ...) mydecoder.describe(...)
either, either3, ..., either9 either
tuple1, tuple2, ... tuple6 tuple
dispatch taggedUnion
url(...) httpsUrl / url (signature has changed) migration instructions

map() is now .transform()

The map() decoder has been renamed to the .transform decoder method:

// ❌ 1.x
import { string } from 'decoders';
const uppercase = map(string, (s) => s.toUpperCase());

// ✅ 2.x
const uppercase = string.transform((s) => s.toUpperCase());

compose() + predicate() is now .refine()

The compose() and predicate() decoders from v1 where mostly used in tandem and used to add additional checks to existing decoders. Predicates have been moved to the .refine() decoder method:

// ❌ 1.x
import { compose, number, predicate } from 'decoders';
const odd = compose(
  number,
  predicate((n) => n % 2 !== 0, 'Must be odd'),
);

// ✅ 2.x
const odd = number.refine((n) => n % 2 !== 0, 'Must be odd');

Guards are no longer a thing

The concept of "guards" has been removed entirely. The following APIs have been removed:

  • The function guard()
  • The type Guard<T>
  • The helper type GuardType<T>

Instead, all decoders now have a .verify() method which does the exact same thing.

// ❌ 1.x
import { guard, Guard } from 'decoders';

const verify: Guard<Whatever> = guard(whatever);
const value: Whatever = verify(externalData);

// ✅ 2.x
const value: Whatever = whatever.verify(externalData);

Signature of url has changed

The signature of the old url decoder has changed. Compare old vs new.

  1. url() is no longer a function taking a list of schemes.
  2. url now returns a URL instance, no longer a string.
  3. There's no direct way for decoding custom schemas anymore, but you can reimplement the old decoder yourself. This is also a great way to enforce other rules, like specific domains, etc.
Replace this v1 pattern... ...with this v2 API
url() httpsUrl
url([]) url
url(['git']) Define manually (example)

Rewriting imports from lemons or debrief

This section only applies if you used the lemons or debrief library directly to build custom decoders. If not, just ignore this section.

Decoders used to depend on external libraries lemons and debrief, but this is no longer the case in v2. You no longer have to create Ok/Err results manually when defining your own decoders, nor deal with annotating inputs manually.

Take this example decoder, which defines a Buffer decoder:

// ❌ 1.x
import { Err, Ok } from 'lemons/Result';
import { annotate } from 'debrief';
import { Decoder } from 'decoders';

export const buffer: Decoder<Buffer> = (blob) =>
  Buffer.isBuffer(blob) ? Ok(blob) : Err(annotate(blob, 'Must be Buffer'));

In 2.x the ok and err helpers get passed to you by .define(), so you no longer have to import them yourself. Also, notice how you no longer have to manually annotate the blob in simple cases like this. You can simply return a string err message directly.

// ✅ 2.x
import { define } from 'decoders';

export const buffer: Decoder<Buffer> = define((blob, ok, err) =>
  Buffer.isBuffer(blob) ? ok(blob) : err('Must be Buffer'),
);

If you are accessing Result instances directly

The Result<T> type is no longer a class, but a very simple data structure. This plays well with TypeScript as well as helps to tree-shake many unused methods from your bundle.

As such, methods previously available on Result instances no longer exist. Direct access of the ok, value, and error properties are now favored.

Suggested changes:

import { err, ok } from 'decoders';
Replace this v1 pattern... ...with this v2 API
result.andThen(f) result.ok ? f(result.value) : result
result.dispatch(f, g) result.ok ? f(result.value) : g(result.error)
result.errValue() result.error
result.expect() removed
result.isErr() !result.ok
result.isOk() result.ok
result.map(f) result.ok ? ok(f(result.value)) : result
result.mapError(g) result.ok ? result : err(g(result.error))
result.toString() removed
result.unwrap() removed (see below)
result.value() ?? xxx decoder.value(...) ?? xxx
result.value() || xxx decoder.value(...) || xxx
result.withDefault(xxx) decoder.value(...) ?? xxx

If you're using result.unwrap() it's probably because you're using it like so:

// ❌ 1.x
const result = mydecoder(externalData);
result.unwrap(); // Might throw

You will likely no longer need it, now that the decoder method .verify() exists.

// ✅ 2.x
mydecoder.verify(externalData); // Might throw

Rewrite use of $DecoderType

Flow users only! The helper type $DecoderType has now been simplified. The $-sign has been removed from the name, and you no longer have to $Call<> it.

For TypeScript users no changes are needed.

const mydecoder = array(whatever);

// ❌
type X = $Call<$DecoderType, typeof mydecoder>; // Array<Whatever>

// ✅
type X = DecoderType<typeof mydecoder>; // Array<Whatever>