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

Improve typings #45

Merged
merged 14 commits into from
Jan 24, 2017
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
coverage
tsc-out
*.log
lib
.idea
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"lint": "eslint src/ test/",
"prepublish": "npm test && npm run build",
"pretest": "npm run lint",
"test": "cross-env NODE_ENV=test nyc mocha"
"test": "cross-env NODE_ENV=test nyc mocha",
"posttest": "tsc"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -43,7 +44,8 @@
"eslint-plugin-import": "^2.2.0",
"mocha": "^3.1.2",
"nyc": "^10.0.0",
"rimraf": "^2.5.4"
"rimraf": "^2.5.4",
"typescript": "^2.0.10"
},
"dependencies": {
"lodash.isplainobject": "^4.0.6",
Expand Down
23 changes: 16 additions & 7 deletions src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface FluxStandardAction {
export interface FluxStandardAction<Payload, Meta> {
/**
* The `type` of an action identifies to the consumer the nature of the action that has occurred.
* Two actions with the same `type` MUST be strictly equivalent (using `===`)
Expand All @@ -11,7 +11,7 @@ export interface FluxStandardAction {
* By convention, if `error` is `true`, the `payload` SHOULD be an error object.
* This is akin to rejecting a promise with an error object.
*/
payload?: any;
payload: Payload;
/**
* The optional `error` property MAY be set to true if the action represents an error.
* An action whose `error` is true is analogous to a rejected Promise.
Expand All @@ -23,20 +23,29 @@ export interface FluxStandardAction {
* The optional `meta` property MAY be any type of value.
* It is intended for any extra information that is not part of the payload.
*/
meta?: any
meta: Meta
}

export interface ErrorFluxStandardAction<CustomError extends Error, Meta> extends FluxStandardAction<CustomError, Meta> {
error: true
}

/**
* Alias for FluxStandardAction.
*/
export type FSA<Payload, Meta> = FluxStandardAction<Payload, Meta>;

/**
* Alias to FluxStandardAction for shorthand
* Alias for ErrorFluxStandardAction.
*/
export type FSA = FluxStandardAction;
export type ErrorFSA<CustomError extends Error, Meta> = ErrorFluxStandardAction<CustomError, Meta>;

/**
* Returns `true` if `action` is FSA compliant.
*/
export function isFSA(action: any): boolean;
export function isFSA<Payload, Meta>(action: any): action is FluxStandardAction<Payload, Meta>;

/**
* Returns `true` if `action` is FSA compliant error.
*/
export function isError(action: any): boolean;
export function isError<CustomError extends Error, Meta>(action: any): action is ErrorFluxStandardAction<CustomError, Meta>;
78 changes: 78 additions & 0 deletions test/typings-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { FluxStandardAction, isError, isFSA } from '../src';

interface CustomPayload {
a: number;
}

interface CustomMetadata {
b: string;
}

interface MyError extends Error {
someField: string;
}

function createCustomAction(payload: CustomPayload) {
return {
type: 'custom',
payload
};
}

function isCustomAction(action: FluxStandardAction<any, any>): action is FluxStandardAction<CustomPayload, any> {
return isFSA(action) && action.type === 'custom';
}

function isCustomAction2(action: FluxStandardAction<any, any>): action is FluxStandardAction<CustomPayload, CustomMetadata> {
return isFSA(action) && action.type === 'custom2';
}

function isCustomAction3(action: any): action is FluxStandardAction<void, string> {
return isFSA(action) && action.type === 'custom3';
}

function isCustomAction4(action: any): action is FluxStandardAction<{ message: string }, void> {
return true
}

let action2 = {}
if (isCustomAction4(action2)) {
// type guard infers payload will not be undefined
console.log(action2.payload.message)
}

function reducer(state, action) {
if (isFSA<CustomPayload, void>(action)) {
let a: number = action.payload.a;
}
else if (isFSA<CustomPayload, CustomMetadata>(action)) {
let a: number = action.payload.a;
let b: string = action.meta.b;
}
else if (isFSA<void, string>(action)) {
let meta: string = action.meta;
}
else if (isError(action)) {
let iserr: true = action.error; // iserr === true
let err: Error = action.payload;
}
else if (isError<MyError, void>(action)) {
let err: MyError = action.payload;
let someFieldValue: string = err.someField;
}
}

function reducer2(state, action) {
if (isCustomAction(action)) {
let a: number = action.payload.a;
}
else if (isCustomAction2(action)) {
let a: number = action.payload.a;
let b: string = action.meta.b;
}
else if (isCustomAction3(action)) {
let meta: string = action.meta;
}
}

let action = createCustomAction({ a: 123 });
10 changes: 10 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"outDir": "tsc-out",
"strictNullChecks": true,
"target": "es5"
},
"files": [
"test/typings-test.ts"
]
}