diff --git a/package.json b/package.json index 0b496b348d..22afd1649c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,9 @@ "precompile": "rm -rf lib", "compile": "tsc --outDir lib", "compile:watch": "yarn compile -- --watch", - "lint": "eslint src --config eslintrc.json", + "lint": "yarn lint:js && yarn lint:ts", + "lint:js": "eslint src --config eslintrc.json", + "lint:ts": "tslint src/**/*.ts --config tslint.json", "prerelease": "git fetch --tags && yarn validate-dependencies && yarn validate-commits && yarn build", "release": "git add dist lib && standard-version -a", "test": "jest --config jest-config.js", @@ -44,6 +46,8 @@ }, "dependencies": { "@bigcommerce/request-sender": "git+ssh://git@github.com/bigcommerce/request-sender-js.git#0.1.0", + "@types/jest": "^21.1.10", + "@types/lodash": "^4.14.92", "bigpay-client": "git+ssh://git@github.com/bigcommerce-labs/bigpay-client-js.git#2.9.1", "form-poster": "git+ssh://git@github.com/bigcommerce-labs/form-poster-js.git#1.1.1", "lodash": "^4.17.4", @@ -60,6 +64,7 @@ "standard-version": "^4.2.0", "ts-jest": "^21.2.3", "ts-loader": "^3.2.0", + "tslint": "^5.9.1", "typescript": "^2.6.2", "typescript-eslint-parser": "^9.0.1", "uglifyjs-webpack-plugin": "^1.1.1", diff --git a/src/data-store/action.ts b/src/data-store/action.ts new file mode 100644 index 0000000000..3e4abf9632 --- /dev/null +++ b/src/data-store/action.ts @@ -0,0 +1,6 @@ +export default interface Action { + type: string; + error?: boolean; + meta?: TMeta; + payload?: TPayload; +} diff --git a/src/data-store/action.typedef.js b/src/data-store/action.typedef.js deleted file mode 100644 index 21fb0e80f8..0000000000 --- a/src/data-store/action.typedef.js +++ /dev/null @@ -1,7 +0,0 @@ -/** - * @typedef {Object} Action - * @property {string} type - * @property {Object} payload - * @property {Object} meta - * @property {?boolean} error - */ diff --git a/src/data-store/combine-reducers.js b/src/data-store/combine-reducers.js deleted file mode 100644 index 692fe73189..0000000000 --- a/src/data-store/combine-reducers.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @param {Object} reducers - * @return {Reducer} - */ -export default function combineReducers(reducers) { - return (state, action) => - Object.keys(reducers).reduce((result, key) => { - const reducer = reducers[key]; - const currentState = state ? state[key] : undefined; - const newState = reducer(currentState, action); - - if (currentState === newState && result) { - return result; - } - - return { ...result, [key]: newState }; - }, state); -} diff --git a/src/data-store/combine-reducers.spec.js b/src/data-store/combine-reducers.spec.ts similarity index 100% rename from src/data-store/combine-reducers.spec.js rename to src/data-store/combine-reducers.spec.ts diff --git a/src/data-store/combine-reducers.ts b/src/data-store/combine-reducers.ts new file mode 100644 index 0000000000..a708acdcea --- /dev/null +++ b/src/data-store/combine-reducers.ts @@ -0,0 +1,23 @@ +import Action from './action'; +import Reducer from './reducer'; + +export default function combineReducers( + reducers: ReducerMap +): Reducer { + return (state, action) => + Object.keys(reducers).reduce((result: TState, key) => { + const reducer = reducers[key as keyof TState]; + const currentState = state ? state[key as keyof TState] : undefined; + const newState = reducer(currentState, action); + + if (currentState === newState && result) { + return result; + } + + return Object.assign({}, result, { [key]: newState }); + }, state); +} + +export type ReducerMap = { + [Key in keyof TState]: Reducer; +}; diff --git a/src/data-store/compose-reducers.js b/src/data-store/compose-reducers.js deleted file mode 100644 index d5d1c6a144..0000000000 --- a/src/data-store/compose-reducers.js +++ /dev/null @@ -1,12 +0,0 @@ -import { curryRight, flowRight } from 'lodash'; - -/** - * @param {...Reducer} reducers - * @return {Reducer} - */ -export default function composeReducers(...reducers) { - return (state, action) => - flowRight(...reducers.map(reducer => - curryRight(reducer)(action) - ))(state); -} diff --git a/src/data-store/compose-reducers.spec.js b/src/data-store/compose-reducers.spec.ts similarity index 91% rename from src/data-store/compose-reducers.spec.js rename to src/data-store/compose-reducers.spec.ts index fbbe085d31..a908a6143c 100644 --- a/src/data-store/compose-reducers.spec.js +++ b/src/data-store/compose-reducers.spec.ts @@ -1,7 +1,7 @@ import composeReducers from './compose-reducers'; describe('composeReducers()', () => { - const fooReducer = (state, action) => { + const fooReducer = (state = '', action) => { switch (action.type) { case 'FOO': return 'foo'; @@ -14,7 +14,7 @@ describe('composeReducers()', () => { } }; - const barReducer = (state, action) => { + const barReducer = (state = '', action) => { switch (action.type) { case 'BAR': return 'bar'; diff --git a/src/data-store/compose-reducers.ts b/src/data-store/compose-reducers.ts new file mode 100644 index 0000000000..1c717edcac --- /dev/null +++ b/src/data-store/compose-reducers.ts @@ -0,0 +1,13 @@ +import { curryRight, flowRight } from 'lodash'; +import Action from './action'; +import Reducer from './reducer'; + +export default function composeReducers( + ...reducers: Array, TAction>> +): Reducer { + return (state, action) => + flowRight.apply( + null, + reducers.map(reducer => curryRight(reducer)(action)) + )(state); +} diff --git a/src/data-store/create-action.js b/src/data-store/create-action.js deleted file mode 100644 index d9f5fe9bd9..0000000000 --- a/src/data-store/create-action.js +++ /dev/null @@ -1,19 +0,0 @@ -import { omitBy } from 'lodash'; - -/** - * @param {string} type - * @param {Object} [payload] - * @param {Object} [meta] - * @return {Action} - */ -export default function createAction(type, payload, meta) { - if (typeof type !== 'string') { - throw new Error('`type` must be a string'); - } - - return omitBy({ - type, - payload, - meta, - }, value => value === undefined); -} diff --git a/src/data-store/create-action.spec.js b/src/data-store/create-action.spec.ts similarity index 81% rename from src/data-store/create-action.spec.js rename to src/data-store/create-action.spec.ts index e16d2ad017..0a9c726481 100644 --- a/src/data-store/create-action.spec.js +++ b/src/data-store/create-action.spec.ts @@ -15,7 +15,7 @@ describe('createAction()', () => { expect(action).toEqual({ type: 'ACTION' }); }); - it('throws an error if `type` is not provided', () => { - expect(() => createAction()).toThrow(); + it('throws an error if `type` is not provided or empty', () => { + expect(() => createAction('')).toThrow(); }); }); diff --git a/src/data-store/create-action.ts b/src/data-store/create-action.ts new file mode 100644 index 0000000000..a82793564e --- /dev/null +++ b/src/data-store/create-action.ts @@ -0,0 +1,14 @@ +import { omitBy } from 'lodash'; +import Action from './action'; + +export default function createAction( + type: string, + payload?: TPayload, + meta?: TMeta +): Action { + if (typeof type !== 'string' || type === '') { + throw new Error('`type` must be a string'); + } + + return { type, ...omitBy({ payload, meta }, value => value === undefined) }; +} diff --git a/src/data-store/create-data-store.js b/src/data-store/create-data-store.js deleted file mode 100644 index 8e65715a21..0000000000 --- a/src/data-store/create-data-store.js +++ /dev/null @@ -1,16 +0,0 @@ -import combineReducers from './combine-reducers'; -import DataStore from './data-store'; - -/** - * @param {Reducer|Object} reducer - * @param {Object} [initialState] - * @param {Object} [options] - * @return {DataStore} - */ -export default function createDataStore(reducer, initialState, options) { - if (typeof reducer === 'function') { - return new DataStore(reducer, initialState, options); - } - - return new DataStore(combineReducers(reducer), initialState, options); -} diff --git a/src/data-store/create-data-store.ts b/src/data-store/create-data-store.ts new file mode 100644 index 0000000000..6d16d73f5a --- /dev/null +++ b/src/data-store/create-data-store.ts @@ -0,0 +1,16 @@ +import Action from './action'; +import combineReducers, { ReducerMap } from './combine-reducers'; +import DataStore, { DataStoreOptions } from './data-store'; +import Reducer from './reducer'; + +export default function createDataStore( + reducer: Reducer, TAction> | ReducerMap, TAction>, + initialState: Partial, + options: DataStoreOptions +): DataStore { + if (typeof reducer === 'function') { + return new DataStore(reducer, initialState, options); + } + + return new DataStore(combineReducers(reducer), initialState, options); +} diff --git a/src/data-store/create-error-action.js b/src/data-store/create-error-action.js deleted file mode 100644 index 8c01bcafbf..0000000000 --- a/src/data-store/create-error-action.js +++ /dev/null @@ -1,14 +0,0 @@ -import createAction from './create-action'; - -/** - * @param {string} type - * @param {Object} [payload] - * @param {Object} [meta] - * @return {Action} - */ -export default function createErrorAction(type, payload, meta) { - return { - ...createAction(type, payload, meta), - error: true, - }; -} diff --git a/src/data-store/create-error-action.spec.js b/src/data-store/create-error-action.spec.ts similarity index 82% rename from src/data-store/create-error-action.spec.js rename to src/data-store/create-error-action.spec.ts index 7ac20ef412..c9515b51aa 100644 --- a/src/data-store/create-error-action.spec.js +++ b/src/data-store/create-error-action.spec.ts @@ -15,7 +15,7 @@ describe('createErrorAction()', () => { expect(action).toEqual({ type: 'ACTION', error: true }); }); - it('throws an error if `type` is not provided', () => { - expect(() => createErrorAction()).toThrow(); + it('throws an error if `type` is not provided or empty', () => { + expect(() => createErrorAction('')).toThrow(); }); }); diff --git a/src/data-store/create-error-action.ts b/src/data-store/create-error-action.ts new file mode 100644 index 0000000000..3f65b366d6 --- /dev/null +++ b/src/data-store/create-error-action.ts @@ -0,0 +1,13 @@ +import Action from './action'; +import createAction from './create-action'; + +export default function createErrorAction( + type: string, + payload?: TPayload, + meta?: TMeta +): Action { + return { + ...createAction(type, payload, meta), + error: true, + }; +} diff --git a/src/data-store/data-store.spec.js b/src/data-store/data-store.spec.ts similarity index 97% rename from src/data-store/data-store.spec.js rename to src/data-store/data-store.spec.ts index f6b3249422..37e68c7311 100644 --- a/src/data-store/data-store.spec.js +++ b/src/data-store/data-store.spec.ts @@ -1,4 +1,5 @@ import { Observable } from 'rxjs'; +import Action from './action'; import DataStore from './data-store'; describe('DataStore', () => { @@ -31,7 +32,7 @@ describe('DataStore', () => { it('dispatches error actions and rejects with payload', async () => { const store = new DataStore(state => state); - const action = { error: true, payload: 'foobar' }; + const action = { type: 'FOOBAR', error: true, payload: 'foobar' }; try { await store.dispatch(action); @@ -52,7 +53,7 @@ describe('DataStore', () => { expect(await store.dispatch(Observable.of( { type: 'APPEND', payload: 'foo' }, { type: 'APPEND', payload: 'bar' }, - { type: 'APPEND', payload: '!!!' }, + { type: 'APPEND', payload: '!!!' } ))).toEqual({ message: 'foobar!!!' }); }); @@ -156,8 +157,8 @@ describe('DataStore', () => { const store = new DataStore(reducer); reducer.mockClear(); - store.dispatch({}); - store.dispatch({ payload: 'foobar' }); + store.dispatch({ type: '' }); + store.dispatch({ type: '', payload: 'foobar' }); expect(reducer).not.toHaveBeenCalled(); }); @@ -167,7 +168,7 @@ describe('DataStore', () => { const store = new DataStore(reducer); reducer.mockClear(); - store.dispatch(Observable.of({}, { payload: 'foobar' })); + store.dispatch(Observable.of({ type: '', payload: 'foo' }, { type: '', payload: 'bar' })); expect(reducer).not.toHaveBeenCalled(); }); @@ -290,7 +291,7 @@ describe('DataStore', () => { default: return state; } - }); + }, { foo: '', bar: '' }); const subscriber = jest.fn(); store.subscribe(subscriber, (state) => state.foo); @@ -323,7 +324,7 @@ describe('DataStore', () => { default: return state; } - }); + }, { foo: '', bar: '', foobar: '' }); const subscriber = jest.fn(); store.subscribe( diff --git a/src/data-store/data-store.js b/src/data-store/data-store.ts similarity index 56% rename from src/data-store/data-store.js rename to src/data-store/data-store.ts index 6cf2aaf582..7f2f25d4e3 100644 --- a/src/data-store/data-store.js +++ b/src/data-store/data-store.ts @@ -2,9 +2,13 @@ import { isEqual } from 'lodash'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; +import Action from './action'; import deepFreeze from './deep-freeze'; +import DispatchableDataStore, { DispatchOptions } from './dispatchable-data-store'; import noopActionTransformer from './noop-action-transformer'; import noopStateTransformer from './noop-state-transformer'; +import ReadableDataStore, { Filter, Subscriber, Unsubscriber } from './readable-data-store'; +import Reducer from './reducer'; import 'rxjs/add/observable/merge'; import 'rxjs/add/observable/of'; import 'rxjs/add/observable/throw'; @@ -17,52 +21,45 @@ import 'rxjs/add/operator/map'; import 'rxjs/add/operator/mergeMap'; import 'rxjs/add/operator/scan'; -/** - * @implements {ReadableDataStore} - * @implements {DispatchableDataStore} - */ -export default class DataStore { - /** - * @param {Reducer} reducer - * @param {State} [initialState={}] - * @param {Object} [options={}] - * @param {boolean} [options.shouldWarnMutation=true] - * @param {function(state: State): TransformedState} [options.stateTransformer=noopStateTransformer] - * @param {function(action: Observable>): Observable>} [options.actionTransformer=noopActionTransformer] - * @return {void} - * @template State, TransformedState - */ - constructor(reducer, initialState = {}, options = {}) { +export default class DataStore implements ReadableDataStore, DispatchableDataStore { + private _options: DataStoreOptions; + private _notification$: Subject; + private _dispatchers: { [key: string]: Dispatcher }; + private _dispatchQueue$: Subject>; + private _state$: BehaviorSubject; + + constructor( + reducer: Reducer, TAction>, + initialState: Partial = {}, + options?: DataStoreOptions + ) { this._options = { shouldWarnMutation: true, stateTransformer: noopStateTransformer, actionTransformer: noopActionTransformer, ...options, }; - this._state$ = new BehaviorSubject(initialState); + this._state$ = new BehaviorSubject(this._options.stateTransformer(initialState)); this._notification$ = new Subject(); this._dispatchers = {}; - this._dispatchQueue$ = new Subject() - .mergeMap((dispatcher$) => dispatcher$.concatMap((action$) => action$)) - .filter((action) => action.type); + this._dispatchQueue$ = new Subject(); this._dispatchQueue$ + .mergeMap((dispatcher$) => dispatcher$.concatMap((action$) => action$)) + .filter((action) => !!action.type) .scan((state, action) => reducer(state, action), initialState) .distinctUntilChanged(isEqual) .map((state) => this._options.shouldWarnMutation === false ? state : deepFreeze(state)) .map((state) => this._options.stateTransformer(state)) .subscribe(this._state$); - this.dispatch({ type: 'INIT' }); + this.dispatch({ type: 'INIT' } as TAction); } - /** - * @param {Action|Observable>} action - * @param {Object} [options] - * @return {Promise} - * @template T - */ - dispatch(action, options) { + dispatch( + action: TDispatchAction | Observable, + options?: DispatchOptions + ): Promise { if (action instanceof Observable) { return this._dispatchObservableAction(action, options); } @@ -70,27 +67,19 @@ export default class DataStore { return this._dispatchAction(action); } - /** - * @return {TransformedState} - */ - getState() { + getState(): TTransformedState { return this._state$.getValue(); } - /** - * @return {void} - */ - notifyState() { + notifyState(): void { this._notification$.next(this.getState()); } - /** - * @param {function(state: TransformedState): void} subscriber - * @param {...function(state: TransformedState): any} [filters] - * @return {function(): void} - */ - subscribe(subscriber, ...filters) { - let state$ = this._state$; + subscribe( + subscriber: Subscriber, + ...filters: Array> + ): Unsubscriber { + let state$: Observable = this._state$; if (filters.length > 0) { state$ = state$.distinctUntilChanged((stateA, stateB) => @@ -106,27 +95,23 @@ export default class DataStore { return () => subscriptions.forEach((subscription) => subscription.unsubscribe()); } - /** - * @private - * @param {Action} action - * @return {Promise} - * @template T - */ - _dispatchAction(action) { - return this._dispatchObservableAction(action.error ? Observable.throw(action) : Observable.of(action)); + private _dispatchAction( + action: TDispatchAction + ): Promise { + return this._dispatchObservableAction( + action.error ? + Observable.throw(action) : + Observable.of(action) + ); } - /** - * @private - * @param {Observable>} action$ - * @param {Object} [options] - * @return {Promise} - * @template T - */ - _dispatchObservableAction(action$, options = {}) { + private _dispatchObservableAction( + action$: Observable, + options: DispatchOptions = {} + ): Promise { return new Promise((resolve, reject) => { - let action; - let error; + let action: TDispatchAction; + let error: any; this._getDispatcher(options.queueId).next( this._options.actionTransformer(action$) @@ -153,12 +138,7 @@ export default class DataStore { }); } - /** - * @private - * @param {string} [queueId='default'] - * @return {Subject>} - */ - _getDispatcher(queueId = 'default') { + private _getDispatcher(queueId: string = 'default'): Dispatcher { if (!this._dispatchers[queueId]) { this._dispatchers[queueId] = new Subject(); @@ -168,3 +148,11 @@ export default class DataStore { return this._dispatchers[queueId]; } } + +export interface DataStoreOptions { + shouldWarnMutation?: boolean; + actionTransformer?: (action: Observable) => Observable; + stateTransformer?: (state: Partial) => TTransformedState; +} + +type Dispatcher = Subject>; diff --git a/src/data-store/deep-freeze.js b/src/data-store/deep-freeze.js deleted file mode 100644 index 92a43a6250..0000000000 --- a/src/data-store/deep-freeze.js +++ /dev/null @@ -1,20 +0,0 @@ -import { isPlainObject } from 'lodash'; - -/** - * @param {any} object - * @return {any} - */ -export default function deepFreeze(object) { - if (Object.isFrozen(object) || (!Array.isArray(object) && !isPlainObject(object))) { - return object; - } - - return Object.freeze( - Object.getOwnPropertyNames(object) - .reduce((result, key) => { - result[key] = deepFreeze(object[key]); - - return result; - }, Array.isArray(object) ? [] : {}) - ); -} diff --git a/src/data-store/deep-freeze.spec.js b/src/data-store/deep-freeze.spec.ts similarity index 68% rename from src/data-store/deep-freeze.spec.js rename to src/data-store/deep-freeze.spec.ts index 0a28f7dc09..197561fe6e 100644 --- a/src/data-store/deep-freeze.spec.js +++ b/src/data-store/deep-freeze.spec.ts @@ -2,7 +2,8 @@ import deepFreeze from './deep-freeze'; describe('deepFreeze()', () => { it('throws error if mutating object', () => { - const object = deepFreeze({ message: 'Foobar' }); + // Cast as `any` to bypass `Readonly` constraint. + const object = deepFreeze({ message: 'Foobar' }) as any; expect(() => { object.message = 'Hello'; }).toThrow(); expect(() => { object.newMessage = 'Hello'; }).toThrow(); @@ -16,7 +17,8 @@ describe('deepFreeze()', () => { }); it('throws error if mutating array', () => { - const array = deepFreeze(['Foobar']); + // Cast as `any` to bypass `ReadonlyArray` constraint. + const array = deepFreeze(['Foobar']) as any; expect(() => { array[0] = 'Hello'; }).toThrow(); expect(() => { array.push('Hello'); }).toThrow(); @@ -36,4 +38,17 @@ describe('deepFreeze()', () => { expect(() => { object.child.message = 'Hello'; }).toThrow(); expect(() => { collection[0].message = 'Hello'; }).toThrow(); }); + + it('does not freeze primitive values', () => { + const value = 'Foobar'; + + expect(deepFreeze(value)).toBe(value); + }); + + it('does not freeze complex types', () => { + class Foobar {} + const object = new Foobar(); + + expect(deepFreeze(object)).toBe(object); + }); }); diff --git a/src/data-store/deep-freeze.ts b/src/data-store/deep-freeze.ts new file mode 100644 index 0000000000..c5527e18ac --- /dev/null +++ b/src/data-store/deep-freeze.ts @@ -0,0 +1,20 @@ +import { isPlainObject } from 'lodash'; + +export default function deepFreeze(object: T[]): ReadonlyArray; +export default function deepFreeze(object: T): Readonly; +export default function deepFreeze(object: T): T; +export default function deepFreeze(object: T[] | T): ReadonlyArray | Readonly | T { + if (Object.isFrozen(object) || (!Array.isArray(object) && !isPlainObject(object))) { + return object; + } + + if (Array.isArray(object)) { + return Object.freeze(object.map((value) => deepFreeze(value))); + } + + return Object.freeze(Object.getOwnPropertyNames(object).reduce((result, key) => { + result[key as keyof T] = deepFreeze(object[key as keyof T]); + + return result; + }, {} as T)); +} diff --git a/src/data-store/dispatchable-data-store.ts b/src/data-store/dispatchable-data-store.ts new file mode 100644 index 0000000000..31239f5131 --- /dev/null +++ b/src/data-store/dispatchable-data-store.ts @@ -0,0 +1,14 @@ +import { Observable } from 'rxjs/Observable'; +import Action from './action'; +import ReadableDataStore from './readable-data-store'; + +export default interface DispatchableDataStore extends ReadableDataStore { + dispatch: ( + action: TDispatchAction | Observable, + options?: DispatchOptions + ) => Promise; +} + +export interface DispatchOptions { + queueId?: string; +} diff --git a/src/data-store/dispatchable-data-store.typedef.js b/src/data-store/dispatchable-data-store.typedef.js deleted file mode 100644 index 9ad4c92697..0000000000 --- a/src/data-store/dispatchable-data-store.typedef.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * @interface DispatchableDataStore - * @template TransformedState - */ - -/** - * @function - * @name DispatchableDataStore#dispatch - * @param {Action} action - * @return {Promise} - * @template T - */ diff --git a/src/data-store/index.js b/src/data-store/index.ts similarity index 100% rename from src/data-store/index.js rename to src/data-store/index.ts diff --git a/src/data-store/noop-action-transformer.js b/src/data-store/noop-action-transformer.js deleted file mode 100644 index c2605a68ff..0000000000 --- a/src/data-store/noop-action-transformer.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @param {Observable>} action - * @return {Observable>} - * @template T - */ -export default function noopActionTransformer(action) { - return action; -} diff --git a/src/data-store/noop-action-transformer.ts b/src/data-store/noop-action-transformer.ts new file mode 100644 index 0000000000..aae2be4017 --- /dev/null +++ b/src/data-store/noop-action-transformer.ts @@ -0,0 +1,8 @@ +import { Observable } from 'rxjs/Observable'; +import Action from './action'; + +export default function noopActionTransformer( + action: Observable +): Observable { + return action as any as Observable; +} diff --git a/src/data-store/noop-state-transformer.js b/src/data-store/noop-state-transformer.js deleted file mode 100644 index 1f1847f8fc..0000000000 --- a/src/data-store/noop-state-transformer.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @param {T} state - * @return {T} - * @template T - */ -export default function noopStateTransformer(state) { - return state; -} diff --git a/src/data-store/noop-state-transformer.ts b/src/data-store/noop-state-transformer.ts new file mode 100644 index 0000000000..474e12f8aa --- /dev/null +++ b/src/data-store/noop-state-transformer.ts @@ -0,0 +1,5 @@ +export default function noopStateTransformer( + state: TState +): TTransformedState { + return state as any as TTransformedState; +} diff --git a/src/data-store/readable-data-store.ts b/src/data-store/readable-data-store.ts new file mode 100644 index 0000000000..16d585332f --- /dev/null +++ b/src/data-store/readable-data-store.ts @@ -0,0 +1,15 @@ +import Action from './action'; + +export default interface ReadableDataStore { + getState(): TTransformedState; + subscribe( + subscriber: Subscriber, + ...filters: Array> + ): Unsubscriber; +} + +export type Filter = (state: TState) => any; + +export type Subscriber = (state: TState) => void; + +export type Unsubscriber = () => void; diff --git a/src/data-store/readable-data-store.typedef.js b/src/data-store/readable-data-store.typedef.js deleted file mode 100644 index 072523f29b..0000000000 --- a/src/data-store/readable-data-store.typedef.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * @interface ReadableDataStore - * @template TransformedState - */ - -/** - * @function - * @name ReadableDataStore#getState - * @returns {TransformedState} - */ - -/** - * @function - * @name ReadableDataStore#subscribe - * @param {function(state: TransformedState): void} subscriber - * @param {...function(state: TransformedState): any} [filters] - * @return {function(): void} - */ diff --git a/src/data-store/reducer.ts b/src/data-store/reducer.ts new file mode 100644 index 0000000000..36f6cfb418 --- /dev/null +++ b/src/data-store/reducer.ts @@ -0,0 +1,5 @@ +import Action from './action'; + +type Reducer = (state: TState, action: TAction) => TState; + +export default Reducer; diff --git a/src/data-store/reducer.typedef.js b/src/data-store/reducer.typedef.js deleted file mode 100644 index 1c260ef0b1..0000000000 --- a/src/data-store/reducer.typedef.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @callback Reducer - * @param {T} state - * @param {Action} action - * @returns {T} - */ diff --git a/tsconfig.json b/tsconfig.json index 96a1c1d240..135cb9a78b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,12 @@ "experimentalDecorators": true, "forceConsistentCasingInFileNames": true, "importHelpers": true, + "lib": [ + "dom", + "dom.iterable", + "es6", + "scripthost", + ], "moduleResolution": "node", "removeComments": true, "skipLibCheck": true, diff --git a/tslint.json b/tslint.json new file mode 100644 index 0000000000..3822098852 --- /dev/null +++ b/tslint.json @@ -0,0 +1,27 @@ +{ + "extends": "tslint:recommended", + "rules": { + "arrow-parens": false, + "grouped-imports": false, + "interface-name": [true, "never-prefix"], + "max-line-length": false, + "member-access": [true, "no-public"], + "no-empty": false, + "no-shadowed-variable": false, + "object-literal-sort-keys": false, + "ordered-imports": false, + "quotemark": [true, "single"], + "trailing-comma": [ + true, + { + "multiline": { + "arrays": "always", + "functions": "never", + "objects": "always", + "typeLiterals": "always" + } + } + ], + "variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore"] + } +} diff --git a/yarn.lock b/yarn.lock index e5b4cab0af..807ba00b50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11,6 +11,14 @@ query-string "^5.0.0" tslib "^1.8.0" +"@types/jest@^21.1.10": + version "21.1.10" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-21.1.10.tgz#dcacb5217ddf997a090cc822bba219b4b2fd7984" + +"@types/lodash@^4.14.92": + version "4.14.92" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.92.tgz#6e3cb0b71a1e12180a47a42a744e856c3ae99a57" + JSONStream@^1.0.4: version "1.3.1" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a" @@ -294,7 +302,7 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.26.0: +babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: @@ -626,7 +634,7 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -builtin-modules@^1.0.0: +builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -806,6 +814,10 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" +commander@^2.12.1: + version "2.13.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" + commander@^2.9.0, commander@~2.12.1: version "2.12.2" resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" @@ -3839,7 +3851,7 @@ resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.7: +resolve@^1.1.7, resolve@^1.3.2: version "1.5.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" dependencies: @@ -4438,6 +4450,33 @@ tslib@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.0.tgz#dc604ebad64bcbf696d613da6c954aa0e7ea1eb6" +tslib@^1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac" + +tslint@^5.9.1: + version "5.9.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae" + dependencies: + babel-code-frame "^6.22.0" + builtin-modules "^1.1.1" + chalk "^2.3.0" + commander "^2.12.1" + diff "^3.2.0" + glob "^7.1.1" + js-yaml "^3.7.0" + minimatch "^3.0.4" + resolve "^1.3.2" + semver "^5.3.0" + tslib "^1.8.0" + tsutils "^2.12.1" + +tsutils@^2.12.1: + version "2.19.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.19.0.tgz#170a267c1df5ae046e902568ca3444a1a4dfcb3a" + dependencies: + tslib "^1.8.1" + tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"