FLOW THE

WAY YOU LIE

GENERAL FLOW

ANNOTATION

// @flow

function concat(a: string, b: string) {
  return a + b;
}

concat("A", "B"); // Works!
concat(1, 2); // Error!


function acceptsBoolean(value: boolean) {
  // ...
}

acceptsBoolean(true);  // Works!
acceptsBoolean(false); // Works!
acceptsBoolean("foo"); // Error!

PRIMITIVE

// @flow

function acceptsNumber(value: number) {
  // ...
}

acceptsNumber(42);       // Works!
acceptsNumber(3.14);     // Works!
acceptsNumber(NaN);      // Works!
acceptsNumber(Infinity); // Works!
acceptsNumber("foo");    // Error!

function acceptsString(value: string) {
  // ...
}

acceptsString("foo"); // Works!
acceptsString(false); // Error!

VARIABLE

// @flow

let foo: number = 1;

foo = 2;   // Works!
foo = "3"; // Error!

let bar = 42;

if (Math.random()) bar = true;
if (Math.random()) bar = "hello";

let isOneOf: number | boolean | string = bar; // Works!

OBJECT

// @flow

const obj1: { foo: boolean } = { foo: true };

const obj2: {
  foo: number,
  bar: boolean,
  baz: string,
} = {
  foo: 1,
  bar: true,
  baz: 'three',
};

const obj = {};

obj.prop = true;
obj.prop = "hello";

var val1: boolean = obj.prop; // Error!
var val2: string  = obj.prop; // Works!

ARRAY

// @flow

let arr1: Array<boolean> = [true, false, true];
let arr2: Array<string> = ["A", "B", "C"];
let arr3: Array<mixed> = [1, true, "three"]

let arr: number[] = [0, 1, 2, 3];

// ?Type[] === ?Array<T> !== Array<?T>

let arr1: ?number[] = null;   // Works!
let arr2: ?number[] = [1, 2]; // Works!
let arr3: ?number[] = [null]; // Error!

let arr1: (?number)[] = null;   // Error!
let arr2: (?number)[] = [1, 2]; // Works!
let arr3: (?number)[] = [null]; // Works!

FUNCTION

// @flow

function concat(a: string, b: string): string {
  return a + b;
}

concat("foo", "bar"); // Works!
concat(true, false);  // Error!

function method(): boolean {
  if (Math.random() > 0.5) {
    return true;
  }
} // Error!

function method(func: () => mixed) {
  // ...
}

ENUM

// @flow

// 'red', 'blue', and 'green' are valid colors.
type Color = 'red' | 'blue' | 'green';

type Chip = {
  // `color` can only be a valid color.
  color: Color,
};

// `createChip` only takes values of type `Color`.
function createChip(color: Color): Chip {
  return {
    color,
  };
}

FLOW GENERICS

BEHAVIOR

// @flow

function identity(value: string): string {
  return value;
}

function identity<T>(value: T): T {
  return value;
}

function identity(func: <T>(param: T) => T) {
  // ...
}

function identity<T>(value: T): T {
  value = "foo"; // Error!
  return value;  // Error!
}

type Item<T> = {
  foo: T,
  bar: T,
};

UKNOWN FLOW

ANY

// @flow

function add(one: any, two: any): number {
  return one + two;
}

add(1, 2);     // Works.
add("1", "2"); // Works.
add({}, []);   // Works.


function getNestedProperty(obj: any) {
  return obj.foo.bar.baz; // Runtime errors BUT will not be caught by Flow
}

getNestedProperty({});

function fn(obj: any) /* (:any) */ {
  let foo /* (:any) */ = obj.foo;
  let bar /* (:any) */ = foo * 2;
  return bar;
}

let bar /* (:any) */ = fn({ foo: 2 });
let baz /* (:any) */ = "baz:" + bar;

MIXED

// @flow

function stringify(value: mixed) {
  return "" + value; // Error!
}

stringify("foo");

// Figure out the type before any usage

function stringify(value: mixed) {
  if (typeof value === 'string') {
    return "" + value; // Works!
  } else {
    return "";
  }
}

stringify("foo");

FLOW TYPED

SYNTAX

// @flow

declare module 'aws-sdk/dynamodb/document_client' {
  import type { Request } from 'aws-sdk/request';

  declare type AttributeMap = { [key: string]: AttributeValue };
  declare type AttributeName = string;
  declare type AttributeValue = mixed;
  declare type ConditionExpression = string;
  declare type ConsistentRead = boolean;
  declare type ExpressionAttributeNameMap = { [key: string]: AttributeName };
  declare type ExpressionAttributeValueMap = { [key: string]: AttributeValue };
  declare type IndexName = string;
  declare type ItemList = AttributeMap[];
  declare type Key = { [key: string]: AttributeValue };
  declare type KeyExpression = string;
  declare type ReturnValue = 'NONE'|'ALL_OLD'|'UPDATED_OLD'|'ALL_NEW'|'UPDATED_NEW'|string;
  declare type TableName = string;
  declare type UpdateExpression = string;

  declare interface DocumentClient {
    constructor(): void;
    query(params: QueryInput, cb?: (err: Error, data: QueryOutput) => void): Request<QueryOutput>;
    update(params: UpdateItemInput, cb?: (err: Error, data: UpdateItemOutput) => void): Request<UpdateItemOutput>;
  }

  declare interface QueryInput {
    TableName: TableName;
    IndexName?: IndexName;
    Limit?: number;
    ConsistentRead?: ConsistentRead;
    KeyConditionExpression?: KeyExpression;
    ExpressionAttributeNames?: ExpressionAttributeNameMap;
    ExpressionAttributeValues?: ExpressionAttributeValueMap;
  }

  declare interface QueryOutput {
    Items?: ItemList;
    Count?: number;
    ScannedCount?: number;
    LastEvaluatedKey?: Key;
  }

  declare interface UpdateItemInput {
    TableName: TableName;
    Key: Key;
    ReturnValues?: ReturnValue;
    UpdateExpression?: UpdateExpression;
    ConditionExpression?: ConditionExpression;
    ExpressionAttributeNames?: ExpressionAttributeNameMap;
    ExpressionAttributeValues?: ExpressionAttributeValueMap;
  }

  declare interface UpdateItemOutput {
    Attributes?: AttributeMap;
  }
}

NAMESPASES

// @ts

declare namespace DynamoDB {
  export import DocumentClient = document_client;
}


// @flow

declare module 'aws-sdk/dynamodb/document_client' {
    declare interface DocumentClient { /* ... */ }
}

FLOW DOWN

JEST

// @flow

type Pojo<T> = { [string]: T };

type GenMockValue = (value?: mixed) => Function;
type GenMockFn = (fn: Function) => Function;
type GenMockVoidFn = () => void;

type MockJestFn = {
  mock: Pojo<*>,
  mockReturnValue: GenMockValue,
  mockReturnValueOnce: GenMockValue,
  mockImplementation: GenMockFn,
  mockImplementationOnce: GenMockFn,
  mockReset: GenMockVoidFn,
};

type MockedByJestFn<T> = T | MockJestFn;
type MockedByJestConstructor<T> = T | MockedObject;

export type MockedFn = MockedByJestFn<*>;
export type MockedObject = Pojo<MockedByJestFn<*>>;
export type MockedModule = MockedByJestConstructor<*>;
export type MockedCalls<T> = Array<Array<T>>;

JSON

// config.json

{
  "messages": {
    "errorPrefix": "[ERR]",
    "infoPrefix": "[INFO]",
    "okPrefix": "[OK]"
  }
}


// @flow

const {
  messages: {
    okPrefix,
    infoPrefix,
    errorPrefix,
  },
} = require('./config');

WINSTON

// @flow

export type WinstonModule = {
  createLogger: (options: { transports: Array<*> }) => WinstonInstance,
  transports: {
    Http: Function,
    Console: Function,
  },
};

export type WinstonInstance = {
  log: (config: WinstonLogConfig) => void,
};

export type WinstonLogConfig = {
  level: string,
  message: string,
  applicationId: string,
  correlationId: string,
};

UPDATE

MASTER FLOW

VOUCHERS

// @flow

export type ForbiddenTaskResult = {
  +statusCode: 403
};

export type RequestValidationResult<T> = {
  isValid: () => boolean,
  getError: () => string,
  getRequest: () => T,
};

export type CheckEligibilityResponse = {|
  campaignName: string,
  isEligible: boolean,
|};

export type SponsorshipVouchersService = {
  reserveVoucher: (memberId: string, campaignName: string, randomBase: number) => Promise<ReservedVoucher>,
  applyVoucher: (rawVoucherCode: string) => Promise<BusinessTaskResult<DynamoDbVoucherEntity>>,
  getReservedVoucher: (memberId: string, campaignName: string) => Promise<ReserveVoucherResponse | false>
};

TEST FLOW

INTEGRATION

// @flow

import type { AxiosPromise } from 'axios';

type Pojo<T> = { [string]: T };

export type TestRequest = {
  memberId?: string,
  campaignName?: string,
};

export type TestResponse = {
  status: string,
  data: Pojo<string>,
};

export interface ITestApi {
  checkEligibility: (data: TestRequest) => AxiosPromise<TestResponse>,
  reserveVoucher: (data: TestRequest) => AxiosPromise<TestResponse>,
  applyVoucher: (data: TestRequest) => AxiosPromise<TestResponse>,
}

FLOW TOOLS

CHECK

COVERAGE

GUIDELINES

// @flow

// Names
type foo = string;
type myFoo = string;
type JSONApiEntity = {};

// Functions
type HttpFn = () => Promise<Object>;
type DoProfilePressFn = (profile: Object) => void;

// Interfaces
interface Serializable {
  serialize(): string;
}

class Foo implements Serializable {
  serialize() {
    return '[Foo]';
  }
}

I FLOW YOU

Flow The Way You Lie

By Roman Stremedlovskyi

Flow The Way You Lie

Typed JavaScript in EPAM Systems

  • 1,235