Add types for nuclear-js package (#40221)

* Add types from nuclear-js

* Fix linting
This commit is contained in:
Pat Lillis
2019-11-11 09:56:15 -08:00
committed by Nathan Shively-Sanders
parent 55eb6d3e07
commit aaed6775c2
5 changed files with 477 additions and 0 deletions
+263
View File
@@ -0,0 +1,263 @@
// Type definitions for nuclear-js 1.4
// Project: https://github.com/optimizely/nuclear-js
// Definitions by: Pat Lillis <https://github.com/patlillis>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
import * as _Immutable from 'immutable';
// Disable automatic exports.
export {};
// NuclearJS re-exports everything in ImmutableJS.
export import Immutable = _Immutable;
interface ReactorConfig {
/** If true it will log the entire app state for every dispatch. */
debug?: boolean;
}
// Getters have a really complex, recursive type that can't be represented
// in TypeScript, but at a high level they are all Arrays.
type Getter = any[];
interface ReactMixin {
getInitialState(): any;
componentDidMount(): void;
componentWillUnmount(): void;
}
interface Reactor {
prevReactorState: any;
reactorState: any;
observerState: any;
ReactMixin: ReactMixin;
/**
* Dispatches a message to all registered Stores.
*
* This process is done synchronously, all registered Stores are passed
* this message and all components are re-evaluated (efficiently). After
* a dispatch, a Reactor will emit the new state on the
* reactor.changeEmitter.
*/
dispatch(actionType: string, payload?: any): void;
/**
* Allows multiple dispatches within the `fn` function before notifying
* any observers.
*/
batch(fn: () => void): void;
/**
* Returns the immutable value for some KeyPath or Getter in the reactor
* state.
*
* Returns `undefined` if a keyPath doesn't have a value.
*/
evaluate(getter: Getter): any;
/**
* Returns a plain JS value for some KeyPath or Getter in the reactor
* state.
*
* Returns `undefined` if a keyPath doesn't have a value.
*/
evaluateToJS(getter: Getter): any;
/**
* Adds a change observer that is invoked whenever any part of the
* reactor state changes.
*/
observe(handler: () => void): () => void;
/**
* Adds a change observer that is invoked whenever any dependencies of
* the getter change.
*
* @returns An "unsubscribe" function
*/
observe(getter: Getter, handler: (value?: any) => void): () => void;
/**
* Removes the change observer for the getter.
*/
unobserve(getter: Getter, handler: (value?: any) => void): void;
/**
* Returns a plain JavaScript object representing the application state.
*
* By default this maps over all stores and returns `toJS(storeState)`.
*/
serialize(): any;
/**
* Takes a plain JavaScript object and merges into the reactor state,
* using `store.deserialize()`.
*
* This can be useful if you need to load data already on the page.
*/
loadState(state: any): void;
/**
* Registers stores.
*/
registerStores(stores: { [storeName: string]: Store<any> }): void;
/**
* Replace store implementation (handlers) without modifying the app
* state or calling `getInitialState`.
*
* Useful for hot reloading
*/
replaceStores(stores: { [storeName: string]: Store<any> }): void;
/**
* Resets the state of a reactor and returns it back to initial state.
*/
reset(): void;
}
export const Reactor: {
/**
* State is stored in NuclearJS Reactors. Reactors contain a `state` object
* which is an Immutable.Map
*
* The only way Reactors can change state is by reacting to messages. To
* update state, Reactor's dispatch messages to all registered stores, and
* the store returns it's new state based on the message
*/
new (config?: ReactorConfig): Reactor;
/**
* State is stored in NuclearJS Reactors. Reactors contain a `state` object
* which is an Immutable.Map
*
* The only way Reactors can change state is by reacting to messages. To
* update state, Reactor's dispatch messages to all registered stores, and
* the store returns it's new state based on the message
*/
(config?: ReactorConfig): Reactor;
};
interface Store<T> extends StoreLike<T> {
/**
* Takes a current reactor state, action type and payload, does the
* reaction, and returns the new state.
*/
handle(state: T, actionType: string, payload?: any): T;
/**
* Binds an action type to a handler.
*/
on(actionType: string, handler: (state: T, payload?: any) => T): void;
/**
* Pure function taking the current state of store and returning the new
* state after a NuclearJS reactor has been reset
*/
handleReset(this: Store<T>, state: T): T;
/**
* Serializes store state to plain JSON serializable JavaScript.
*/
serialize(this: Store<T>, state: T): any;
/**
* Deserializes plain JavaScript to store state.
*/
deserialize(this: Store<T>, state: any): T;
}
/**
* Stores are initialized like:
*
* ```
* new Store({
* initialize() { ... },
* getInitialState() { ... },
* })
* ```
*
* This type defines the functions for the object passed to the
* `new Store()` constructor. In additional, all of these functions are
* available on the base `Store` object itself.
*/
interface StoreLike<T> {
/**
* Gets the initial state for this type of store
*/
getInitialState(this: Store<T>): T;
/**
* Sets up message handlers via `this.on` and to set up the initial
* state.
*/
initialize(this: Store<T>): void;
/**
* Pure function taking the current state of store and returning the new
* state after a NuclearJS reactor has been reset
*/
handleReset?(this: Store<T>, state: T): T;
/**
* Serializes store state to plain JSON serializable JavaScript.
*/
serialize?(this: Store<T>, state: T): any;
/**
* Deserializes plain JavaScript to store state.
*/
deserialize?(this: Store<T>, state: any): T;
}
export const Store: {
/**
* A Store defines how a certain domain of the application should respond to
* actions taken on the whole system. They manage their own section of the
* entire app state and have no knowledge about the other parts of the
* application state.
*/
new <T = any>(config: StoreLike<T>): Store<T>;
/**
* A Store defines how a certain domain of the application should respond to
* actions taken on the whole system. They manage their own section of the
* entire app state and have no knowledge about the other parts of the
* application state.
*/
<T = any>(config: StoreLike<T>): Store<T>;
};
/**
* Checks if something is simply a keyPath and not a getter.
*/
export function isKeyPath(toTest: any): boolean;
/**
* Checks if something is a getter literal.
*
* For example, `['dep1', 'dep2', function(dep1, dep2) {...}]`.
*/
export function isGetter(toTest: any): boolean;
/**
* Converts an Immutable Sequence to JS object.
*
* Can be called on any type.
*/
export function toJS(arg: any): any;
/**
* Converts a JS object to an Immutable object, if it's already Immutable its a
* no-op.
*/
export function toImmutable(arg: any): any;
/**
* Returns true if the value is an ImmutableJS data structure.
*/
export function isImmutable(arg: any): boolean;
export function createReactMixin(reactor: Reactor): ReactMixin;
+181
View File
@@ -0,0 +1,181 @@
import {
Immutable,
Reactor,
Store,
isKeyPath,
isGetter,
toJS,
toImmutable,
isImmutable,
createReactMixin,
} from 'nuclear-js';
Immutable.Map({ a: 1 });
Immutable.fromJS([5]);
// Callable with or without `new`.
new Reactor();
Reactor();
new Reactor({ debug: true });
Reactor({ debug: undefined });
// Make sure that type checking succeeds with or without `new`.
const r1 = new Reactor();
const r2 = Reactor();
r1.dispatch('FETCH_ENTITY_SUCCESS');
r1.dispatch('FETCH_ENTITY_SUCCESS', { data: 5 });
r1.batch(() => null);
r1.evaluate(['keyPath']);
r1.evaluate([['keyPath'], (dep1: any) => 5]);
r1.evaluateToJS(['keyPath']);
r1.evaluateToJS([['keyPath'], (dep1: any) => 5]);
r1.observe(() => null)();
r1.observe(['getter'], (x: any) => null)();
r1.observe(['getter'], () => null)();
r1.unobserve(['getter'], (x: any) => null);
r1.unobserve(['getter'], () => null);
r1.serialize();
r1.loadState({});
r1.registerStores({});
r1.registerStores({
numberStore: new Store<number>({
getInitialState() {
return 5;
},
initialize() {},
}),
});
r1.replaceStores({});
r1.replaceStores({
numberStore: new Store<number>({
getInitialState() {
return 5;
},
initialize() {},
}),
});
r1.prevReactorState;
r1.reactorState;
r1.observerState;
r1.ReactMixin.componentDidMount();
r1.ReactMixin.componentWillUnmount();
r1.ReactMixin.getInitialState();
r2.reset();
r2.dispatch('FETCH_ENTITY_SUCCESS');
r2.dispatch('FETCH_ENTITY_SUCCESS', { data: 5 });
r2.batch(() => null);
r2.evaluate(['keyPath']);
r2.evaluate([['keyPath'], (dep1: any) => 5]);
r2.evaluateToJS(['keyPath']);
r2.evaluateToJS([['keyPath'], (dep1: any) => 5]);
r2.observe(() => null)();
r2.observe(['getter'], (x: any) => null)();
r2.observe(['getter'], () => null)();
r2.unobserve(['getter'], (x: any) => null);
r2.unobserve(['getter'], () => null);
r2.serialize();
r2.loadState({});
r2.registerStores({});
r2.registerStores({
numberStore: new Store<number>({
getInitialState() {
return 5;
},
initialize() {},
}),
});
r2.replaceStores({});
r2.replaceStores({
numberStore: new Store<number>({
getInitialState() {
return 5;
},
initialize() {},
}),
});
r2.reset();
r2.prevReactorState;
r2.reactorState;
r2.observerState;
r2.ReactMixin.componentDidMount();
r2.ReactMixin.componentWillUnmount();
r2.ReactMixin.getInitialState();
// Callable with or without `new`.
new Store({ getInitialState() {}, initialize() {} });
Store({ getInitialState() {}, initialize() {} });
new Store({ getInitialState() {}, initialize() {} });
Store({ getInitialState() {}, initialize() {} });
// Make sure that type checking succeeds with or without `new`.
const s1 = new Store<number>({
getInitialState() {
return 5;
},
initialize() {
this.on('FETCH_THING', (s: number, x: any) => 5);
},
});
const s2 = Store<string>({
getInitialState() {
return '';
},
initialize() {
this.on('FETCH_THING', (s: string) => '5');
},
handleReset(s: string) {
return '15';
},
});
s1.getInitialState();
s1.initialize();
s1.handleReset(5);
s1.serialize(15);
s1.deserialize({});
s1.handle(51, 'FETCH_THING', {});
s1.on('FETCH_THING', (x: number, y: any) => 15);
s2.getInitialState();
s2.initialize();
s2.handleReset('5');
s2.serialize('15');
s2.deserialize({});
s2.handle('51', 'FETCH_THING', {});
s2.on('FETCH_THING', (x: string, y: any) => '15');
isKeyPath({});
isKeyPath(['getter']);
isKeyPath('');
isKeyPath(5);
isGetter({});
isGetter(['getter']);
isGetter('');
isGetter(5);
toJS({});
toJS([]);
toJS('');
toJS(5);
toImmutable({});
toImmutable([]);
toImmutable('');
toImmutable(5);
isImmutable({});
isImmutable([]);
isImmutable('');
isImmutable(5);
createReactMixin(r1);
createReactMixin(r2);
// Test default export.
import Nuclear = require('nuclear-js');
Nuclear.Immutable.Map({ a: 1 });
Nuclear.Reactor({ debug: true });
Nuclear.Store({ getInitialState() {}, initialize() {} });
Nuclear.isKeyPath({});
Nuclear.isGetter({});
Nuclear.toJS({});
Nuclear.toImmutable({});
Nuclear.isImmutable({});
Nuclear.createReactMixin(r1);
+6
View File
@@ -0,0 +1,6 @@
{
"private": true,
"dependencies": {
"immutable": "^3.7.3"
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6"
],
"esModuleInterop": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"baseUrl": "../",
"typeRoots": [
"../"
],
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"files": [
"index.d.ts",
"nuclear-js-tests.ts"
]
}
+3
View File
@@ -0,0 +1,3 @@
{
"extends": "dtslint/dt.json"
}