Flow typing of Meteor Collections - some progress


#1

Not sure if this should go in Help or Announce, but anyway. We’re using Flow very extensively in our app, and I’ve been thinking about how we could extend this to Meteor Collections. I made a first attempt, and surprisingly it’s not bad. The key is a MongoT type (should probably be called CollectionT):

export type CursorT<T> = {
  fetch: () => T[],
  map: T => void,
  forEach: T => void,
  observe: Object => void,
  observeChanges: Object => void
};

type UpdateQueryT<T> = {
  $set?: $Shape<T>,
  $inc?: { [key: $Keys<T>]: number },
  $unset?: { [key: $Keys<T>]: any }
};

export type MongoT<T> = {
  find: (
    string | $Shape<T> | { [$Keys<T>]: { $in: any } },
    ?Object
  ) => CursorT<T>,
  findOne: (string | $Shape<T>, ?Object) => ?T,
  update: (string | $Shape<T>, UpdateQueryT<T>) => void,
  insert: (T, ?(T) => void) => string
};

You’ll notice that we’re not typing all the different options or settings. On the one hand, I’ve focused on the stuff we actually use, on the other hand, strongly typing update queries would be great, but very complex.

Still, if we create a type for a collection, and pass it through, we get this: In /api/activities.js I have this code

export type ActivityDbT = {|
  _id: string,
  data: Object,
  title?: string,
  groupingKey?: string,
  plane?: number,
  startTime: number,
  length: number,
  activityType: string,
  actualStartingTime?: Date,
  actualClosingTime?: Date,
  parentId?: string
|};

export const Activities: MongoT<ActivityDbT> = new Mongo.Collection('activities');

and that’s all! Now, when I import Activities in other files (as long as they also have Flow turned on), the result of doing a findOne is automatically typed! Trying to insert a new entry that has missing keys or extraneous keys is automatically detected.

(This immediately found tons and tons of Flow errors - places where I assume a key is there, but there is no guarantee of it etc. Partially I might need to change my code, but partially I might also need to update my type definition, which is a very healthy process - and we now have much better documentation for new contributors what they can expect to always or sometimes find in these collections).

Would love some help to write a Flow type definition for the update function, that says that if you do $set: {name: ‘stian’}, ‘stian’ has to be a string, because the type of name in ActivityDbT is string… I think this is possible, but it’s beyond my skills right now.


#2

@houshuang

Awesome!

I experimented with it a little and i have an addition:



export type CursorT<T> = {
  fetch: () => T[],
  map: T => void,
  forEach: T => void,
  observe: Object => void,
  observeChanges: Object => void
};

type UpdateQueryT<T> = {
  $set?: $Shape<T>,
  $inc?: { [key: $Keys<T>]: number },
  $unset?: { [key: $Keys<T>]: string }
};

type SpecialSelectorsT<T> = {
  [key: $Keys<T>]: {
    $ne?: ?$Values<T>,
    $exists?: boolean,
    $in?: Array<any>
  }
};

type SelectorT<T> = string | $Shape<T & SpecialSelectorsT<T>>;

export type MongoT<T> = {
  find: (SelectorT<T>, ?Object) => CursorT<T>,
  findOne: (SelectorT<T>, ?Object) => ?T,
  update: (SelectorT<T>, UpdateQueryT<T>) => void,
  insert: (T, ?(T) => void) => string,
  attachSchema: any => any
};

not sure if its 100% correct, but i now can do mixed selectors! (with $ne, $in, etc.). Will update it, if i tested it more


#3

Hi there, @houshuang! I was also using Flow heavily in few projects, and I’ve already cover far more API in radekmie/meteor-flow-types. If you have some questions or ideas - ping me or send a PR - we’ll all make use of it.

Also, with these types and Flow >=0.72 you can do it this way:

type DocType = {
  _id: string;
  num: number;
};

const docs = new Mongo.Collection<DocType>('');
const example = docs.find({}).map(doc => doc.num.toFixed()); // string[] correctly