diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 000000000..41afc31ab --- /dev/null +++ b/.flowconfig @@ -0,0 +1,11 @@ +[ignore] +.*/node_modules + +[include] +test/flow + +[libs] +flow-typed/ + +[options] +suppress_comment= \\(.\\|\n\\)*\\$ExpectError diff --git a/.gitignore b/.gitignore index cc49bd097..1586009cb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ test/typescript-tests.js* dist/ .build/ yarn.lock +.idea diff --git a/flow-typed/mobx.js b/flow-typed/mobx.js new file mode 100644 index 000000000..83206f461 --- /dev/null +++ b/flow-typed/mobx.js @@ -0,0 +1,359 @@ +declare module 'mobx' { + declare type extras = { + allowStateChanges: (allowStateChanges: boolean, func: () => T) => T, + getAtom: (thing: any, property?: string) => IDepTreeNode, + getDebugName: (thing: any, property?: string) => string, + getDependencyTree: (thing: any, property?: string) => IDependencyTree, + getGlobalState: () => any; + getObserverTree: (thing: any, property?: string) => IObserverTree, + isComputingDerivation: () => boolean, + isSpyEnabled: () => boolean, + resetGlobalState: () => void, + shareGlobalState: () => void; + spyReport: (event: any) => boolean, + spyReportEnd: (change?: any) => void, + spyReportStart: (event: any) => void, + setReactionScheduler: (fn: (f: () => void) => void) => void + }; + + declare interface IInterceptable { + interceptors: IInterceptor[] | any, + intercept(handler: IInterceptor): Lambda + } + + declare type _ = { + getAdministration: (thing: any, property?: string) => any, + resetGlobalState: () => void + }; + + declare type ITransformer = (object: A) => B; + + declare type IInterceptor = (change: T) => T; + + declare type IMapEntry = [string, V]; + + declare type IMapEntries = IMapEntry[]; + + declare type isObservableMap = (x: any) => boolean; + + declare type ISimpleEventListener = { + (): void + }; + + declare interface IComputedValueOptions { + asStructure: boolean + } + + declare interface IDependencyTree { + name: string, + dependencies?: IDependencyTree[] + } + + declare interface IObserverTree { + name: string, + observers?: IObserverTree[] + } + + declare interface IAtom { + + } + + declare type IDerivationState = 'NOT_TRACKING' | 'UP_TO_DATE' | 'POSSIBLY_STALE' | 'STALE' + declare type PropertyDescriptor = any + + declare interface IComputedValue { + get(): T, + set(value: T): void, + observe( + listener: (newValue: T, oldValue: T) => void, fireImmediately?: boolean + ): Lambda + } + + declare interface IObservable { + diffValue: number, + lastAccessedBy: number, + lowestObserverState: IDerivationState, + isPendingUnobservation: boolean, + observers: IDerivation[], + observersIndexes: { + + }, + onBecomeUnobserved(): any + } + + declare interface IDepTreeNode { + name: string, + observing?: IObservable[] + } + + declare interface IDerivation { + name: string, + observing: IObservable[], + newObserving: ?IObservable[], + dependenciesState: IDerivationState, + runId: number, + unboundDepsCount: number, + ___mapid: string, + onBecomeStale(): any, + recoverFromError(): any + } + + declare interface IReactionPublic { + dispose: () => void + } + + declare interface IListenable { + changeListeners: any, + observe( + handler: (change: any, oldValue?: any) => void, fireImmediately?: boolean + ): Lambda + } + + declare interface IObservableArray extends Array { + spliceWithArray(index: number, deleteCount?: number, newItems?: T[]): T[], + observe( + listener: (changeData: IArrayChange | IArraySplice) => void, fireImmediately?: boolean + ): Lambda, + intercept(handler: IInterceptor | IArraySplice>): Lambda, + clear(): T[], + peek(): T[], + replace(newItems: T[]): T[], + find( + predicate: (item: T, index: number, array: Array) => boolean, thisArg?: any, fromIndex?: number + ): T | any, + remove(value: T): boolean + } + + declare interface IArrayChange { + type: 'update', + object: IObservableArray, + index: number, + newValue: T, + oldValue: T + } + + declare interface IArraySplice { + type: 'splice', + object: IObservableArray, + index: number, + added: T[], + addedCount: number, + removed: T[], + removedCount: number + } + + declare interface IArrayWillChange { + type: 'update', + object: IObservableArray, + index: number, + newValue: T + } + + declare interface IArrayWillSplice { + type: 'splice', + object: IObservableArray, + index: number, + added: T[], + removedCount: number + } + + declare interface IKeyValueMap { + [key: string]: V + } + + declare interface IMapChange { + object: ObservableMap, + type: 'update' | 'add' | 'delete', + name: string, + newValue?: any, + oldValue?: any + } + + declare interface IMapWillChange { + object: ObservableMap, + type: 'update' | 'add' | 'delete', + name: string, + newValue?: any + } + + declare interface IObservableObject {} + + declare interface IObjectChange { + name: string, + object: any, + type: 'update' | 'add', + oldValue?: any, + newValue: any + } + + declare interface IObjectWillChange { + object: any, + type: 'update' | 'add', + name: string, + newValue: any + } + + declare interface IValueWillChange { + object: any, + type: 'update', + newValue: T + } + + declare interface IObservableValue { + get(): T, + set(value: T): void, + intercept(handler: IInterceptor>): Lambda, + observe( + listener: (newValue: T, oldValue: T) => void, fireImmediately?: boolean + ): Lambda + } + + declare interface Iterator { + next(): { + done: boolean, + value?: T + } + } + + declare interface Lambda { + (): void, + name?: string + } + + declare class ObservableMap { + $mobx: {}; + name: string; + interceptors: any; + changeListeners: any; + constructor(initialData?: IMapEntries | IKeyValueMap, valueModeFunc?: Function): this; + has(key: string): boolean; + set(key: string, value: V): void; + delete(key: string): boolean; + get(key: string): V; + keys(): string[] & Iterator; + values(): V[] & Iterator; + entries(): IMapEntries & Iterator>; + forEach( + callback: (value: V, key: string, object: IKeyValueMap) => void, thisArg?: any + ): void; + merge(other: ObservableMap | IKeyValueMap): ObservableMap; + clear(): void; + size: number; + toJS(): IKeyValueMap; + toJs(): IKeyValueMap; + toJSON(): IKeyValueMap; + toString(): string; + observe(listener: (changes: IMapChange) => void, fireImmediately?: boolean): Lambda; + intercept(handler: IInterceptor>): Lambda + } + + declare function action(targetOrName: any, propertyKeyOrFuc?: any, descriptor?: PropertyDescriptor): any; + declare function runInAction(name: string, block: () => T, scope?: any): T; + declare function isAction(thing: any): boolean; + declare function autorun(nameOrFunction: string | (r: IReactionPublic) => void, viewOrScope?: any, scope?: any): any; + declare function when(predicate: () => boolean, effect: Lambda, scope?: any): any + declare function autorunAsync(func: (r: IReactionPublic) => void, delay?: number, scope?: any): any + declare function reaction( + expression: () => T, effect: (arg: T, r: IReactionPublic) => void, fireImmediately?: boolean, delay?: number, scope?: any + ): any + + declare function computed(target: any, key?: string, baseDescriptor?: PropertyDescriptor): any + declare function createTransformer( + transformer: ITransformer, onCleanup?: (resultObject: ?B | any, sourceObject?: A) => void + ): ITransformer + declare function expr(expr: () => T, scope?: any): T + declare function extendObservable(target: A, ...properties: B[]): A & B + + + declare function intercept(object: Object, property: string, handler: IInterceptor): Lambda; + + declare function isComputed(value: any, property?: string): boolean + + declare function isObservable(value: any, property?: string): boolean + + declare function observable(value: T): any + + declare function observe( + object: any, property: string, listener: (newValue: any, oldValue?: any) => void, fireImmediately?: boolean + ): Lambda + + declare function toJS(source: any, detectCycles: boolean, ___alreadySeen: [any, any][]): any + declare function toJSlegacy(source: any, detectCycles?: boolean, ___alreadySeen?: [any, any][]): any + declare function whyRun(thing?: any, prop?: string): string + declare function useStrict(strict: boolean): any + + declare function isStrictModeEnabled(): boolean + declare function untracked(action: () => T): T + + declare function spy(listener: (change: any) => void): Lambda + + declare function transaction(action: () => T, thisArg?: any, report?: boolean): T + + declare function asReference(value: T): T + declare function asStructure(value: T): T + declare function asFlat(value: T): T + declare function asMap(data: IKeyValueMap, modifierFunc?: Function): ObservableMap + declare function isObservableArray(thing: any): boolean + + declare function map( + initialValues?: IMapEntries | IKeyValueMap, valueModifier?: Function + ): ObservableMap + + declare function isObservableObject(thing: T): boolean + + declare function isArrayLike(x: any): boolean + + declare class BaseAtom { + name: string; + isPendingUnobservation: boolean; + observers: any[]; + observersIndexes: {}; + diffValue: number; + lastAccessedBy: number; + lowestObserverState: IDerivationState; + constructor(name?: string): this; + onBecomeUnobserved(): void; + reportObserved(): void; + reportChanged(): void; + toString(): string + } + + declare class Atom { + name: string; + onBecomeObservedHandler: () => void; + onBecomeUnobservedHandler: () => void; + isPendingUnobservation: boolean; + isBeingTracked: boolean; + constructor(name?: string, onBecomeObservedHandler?: () => void, onBecomeUnobservedHandler?: () => void): this; + reportObserved(): boolean; + onBecomeUnobserved(): void + } + + declare class Reaction { + name: string; + observing: any[]; + newObserving: any[]; + dependenciesState: IDerivationState; + diffValue: number; + runId: number; + unboundDepsCount: number; + ___mapid: string; + isDisposed: boolean; + _isScheduled: boolean; + _isTrackPending: boolean; + _isRunning: boolean; + constructor(name: string, onInvalidate: () => void): this; + onBecomeStale(): void; + schedule(): void; + isScheduled(): boolean; + runReaction(): void; + track(fn: () => void): void; + recoverFromError(): void; + dispose(): void; + getDisposer(): Lambda & { + $mosbservable: Reaction + }; + toString(): string; + whyRun(): string + } +} diff --git a/package.json b/package.json index 8d6edc3b5..46ea5abc4 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "typings": "lib/mobx.d.ts", "scripts": { "test": "npm run quick-build && npm run tape", - "full-test": "npm run small-build && npm run build-tests && npm run use-minified && npm run tape && npm run perf", + "full-test": "npm run small-build && npm run build-tests && npm run use-minified && npm run tape && npm run perf && npm run test-flow", "tape": "tape test/*.js | faucet", "perf": "npm run small-build && PERSIST=true time node --expose-gc test/perf/index.js", "_prepublish": "npm run small-build", @@ -16,7 +16,8 @@ "test-browser-chrome": "npm run small-build && ( browserify test/*.js | tape-run --browser chrome | faucet )", "test-browser-safari": "npm run small-build && ( browserify test/*.js -t [ babelify --presets [ es2015 ] ] | tape-run --browser safari | faucet )", "test-browser-firefox": "npm run small-build && ( browserify test/*.js | tape-run --browser firefox | faucet )", - "test-travis": "npm run small-build && npm run build-tests && tape test/*.js test/perf/index.js && tsc && istanbul cover tape test/*.js", + "test-travis": "npm run small-build && npm run build-tests && tape test/*.js test/perf/index.js && tsc && istanbul cover tape test/*.js && npm run test-flow", + "test-flow": "node_modules/.bin/flow check", "coverage": "npm run quick-build && npm run build-tests && istanbul cover tape test/*.js", "build-tests": "npm run build-typescript-tests && npm run build-babel-tests", "build-typescript-tests": "tsc -m commonjs -t es5 --experimentalDecorators --noImplicitAny --outDir test test/typescript-tests.ts", @@ -50,6 +51,7 @@ "browserify": "^12.0.1", "coveralls": "^2.11.4", "faucet": "0.0.1", + "flow-bin": "^0.36.0", "istanbul": "^0.3.21", "iterall": "^1.0.2", "lodash.intersection": "^3.2.0", @@ -73,4 +75,4 @@ "state management", "data flow" ] -} \ No newline at end of file +} diff --git a/test/flow/test.js b/test/flow/test.js new file mode 100644 index 000000000..82b921184 --- /dev/null +++ b/test/flow/test.js @@ -0,0 +1,28 @@ +//@flow + +import type { IObservableValue, IObservableArray, IComputedValue } from 'mobx'; +import mobx from 'mobx'; + + +const action = mobx.action(() => console.log(1)); +// $ExpectError +const isAction: string = mobx.isAction(action); + +const observableValue: IObservableValue = mobx.observable(1); +// $ExpectError +const initialValue: string = observableValue.get(); + + +const observableArray: IObservableArray = mobx.observable([1,2,3]); +// $ExpectError +const initialArray: Array = observableArray.peek(); + +const sum: IComputedValue = mobx.computed(() => { + return observableArray.reduce((a: number, b: number): number => { + return a + b; + }, 0); +}); + +const disposer = mobx.autorun(() => console.log(sum.get())); +disposer(); + diff --git a/test/perf/perf.txt b/test/perf/perf.txt index 2c3d56395..130cec343 100644 --- a/test/perf/perf.txt +++ b/test/perf/perf.txt @@ -1,51 +1,51 @@ -One observers many observes one - Started/Updated in 35/26 ms. -500 props observing sibling - Started/Updated in 3/5 ms. -Late dependency change - Updated in 59ms. +One observers many observes one - Started/Updated in 60/34 ms. +500 props observing sibling - Started/Updated in 3/11 ms. +Late dependency change - Updated in 135ms. Unused computables - Updated in 0 ms. -Unused observables - Updated in 9 ms. -Array reduce - Started/Updated in 30/25 ms. -Array loop - Started/Updated in 125/173 ms. -Order system batched: false tracked: true Started/Updated in 448/44 ms. -Order system batched: true tracked: true Started/Updated in 93/43 ms. -Order system batched: false tracked: false Started/Updated in 131/65 ms. -Order system batched: true tracked: false Started/Updated in 110/46 ms. +Unused observables - Updated in 25 ms. +Array reduce - Started/Updated in 57/44 ms. +Array loop - Started/Updated in 127/231 ms. +Order system batched: false tracked: true Started/Updated in 473/37 ms. +Order system batched: true tracked: true Started/Updated in 102/28 ms. +Order system batched: false tracked: false Started/Updated in 117/37 ms. +Order system batched: true tracked: false Started/Updated in 118/35 ms. -Create array - Created in 473ms. +Create array - Created in 552ms. -Create array (non-recursive) Created in 169ms. -Observable with many observers + dispose: 1240ms -expensive sort: created 5023 -expensive sort: updated 15817 -expensive sort: disposed641 -native plain sort: updated 1643 +Create array (non-recursive) Created in 297ms. +Observable with many observers + dispose: 768ms +expensive sort: created 2787 +expensive sort: updated 12518 +expensive sort: disposed561 +native plain sort: updated 1087 computed memoization 1ms -create folders 1ms. +create folders 0ms. create displayfolders 1ms. -create text 271ms. +create text 471ms. collapse folder 1ms. -uncollapse folder 0ms. -change name of folder 327ms. -search 47ms. -unsearch 228ms. +uncollapse folder 1ms. +change name of folder 498ms. +search 103ms. +unsearch 438ms. reactive folder tree [total] - 878ms. -create folders 28ms. + 1517ms. +create folders 21ms. create displayfolders 1ms. -create text 116ms. -collapse folder 3ms. -uncollapse folder 6ms. +create text 51ms. +collapse folder 2ms. +uncollapse folder 5ms. change name of folder 10ms. -search 29ms. -unsearch 20ms. +search 15ms. +unsearch 24ms. reactive folder tree [total] - 214ms. -create boxes 106ms. -mutations 380ms. -total 498ms. -create boxes 118ms. -mutations 945ms. -total 1175ms. + 131ms. +create boxes 89ms. +mutations 523ms. +total 625ms. +create boxes 90ms. +mutations 928ms. +total 1084ms. -Completed performance suite in 30.532 sec. \ No newline at end of file +Completed performance suite in 25.475 sec. \ No newline at end of file