dumbmatter dumbmatter - 2 months ago 6
Javascript Question

Annotating functions in Flow that operate on union types containing optional parameters

My apologies for the bad title, I don't know how to better describe this :)

I'm using Flow in an application based on an IndexedDB database with auto-incrementing IDs. So basically, I create some objects (with no

id
property), write them to the database (at which point they are given an
id
property by IndexedDB), and then read them back out (any object read from the DB is guaranteed to have a numeric
id
property).

I have some functions that operate on these objects. Sometimes they only operate on objects with IDs, sometimes they only operate on objects without IDs, and sometimes they operate on both. It's the latter case that is trickiest. Here's one attempt, using two different types for the objects before and after being written to the DB (so, without and with the
id
property, respectively):

/* @flow */

type BeforeDb = {prop: string};
type AfterDb = BeforeDb & {id: number};

var beforeDb: BeforeDb = {prop: 'hi'};
var afterDb: AfterDb = {id: 1, prop: 'hi'};

function a(obj: BeforeDb | AfterDb): BeforeDb | AfterDb {
if (typeof obj.id === 'number') {
console.log(obj.id * 2);
}
return obj;
}

function b(obj: AfterDb) {}

var x = a(afterDb);
b(x);


(demo link)

That produces an error on the last line, because it doesn't know that
x
is of type
AfterDb
, and I'm not sure how to convey that information appropriately.

Another idea would be to use bounded polymorphisms, except I don't believe this can create something like my
a
function above because it can't handle the fact that
id
is sometimes undefined. Like I want to do something like this:

function a<T: {id?: number}>(obj: T): T {
if (typeof obj.id === 'number') {
console.log(obj.id * 2);
}
return obj;
}


(demo link)

but that doesn't work. If I assigned a dummy value to
id
so it was always numeric (like -1 instead of undefined) then this would work, but then I'd have to be very careful about remembering to delete the
id
before the first write to the DB so the real
id
could be auto-generated, which would be pretty ugly.

After that, I'm pretty much out of good ideas. The one thing I got to work was to use just one type, like:

type Obj = {id?: number, prop: string};


and then explicitly check if the
id
property is there or not in every function that uses the
id
property. But that's annoying, because I have a bunch of functions that are only called with the output of IndexedDB, so I already know
id
is guaranteed to be there. I just don't know how to tell Flow that.

Any ideas?

Answer
function a<T: BeforeDb | AfterDb>(obj: T): T {
  if (typeof obj.id === 'number') {
    console.log(obj.id * 2);
  }
  return obj;
}