├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .prettierrc ├── .size-limit.js ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── book.json ├── code-of-conduct.md ├── configs └── tsconfig.base.json ├── docs ├── CNAME ├── README.md ├── SUMMARY.md ├── core │ ├── createApp.md │ ├── createEffect.md │ ├── createEvent.md │ ├── createReducer.md │ ├── epics.md │ └── modules.md ├── guides │ ├── cli.md │ ├── interop.md │ ├── lifecycle.md │ ├── react.md │ └── ssr.md └── modules │ ├── formbase.md │ ├── loaders.md │ ├── persist.md │ ├── select.md │ └── validate.md ├── examples ├── form-async-validation │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── apps │ │ │ └── asyncForm.js │ │ ├── components │ │ │ ├── App.js │ │ │ ├── RenderCount.js │ │ │ ├── Spinner.js │ │ │ └── Styles.js │ │ ├── index.js │ │ ├── modules │ │ │ └── formSubmit.js │ │ └── utils │ │ │ └── wait.js │ └── yarn.lock └── todo-mvc │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── apps │ │ ├── todoApp.js │ │ └── todoSelectors.js │ ├── components │ │ ├── App.js │ │ ├── Footer.js │ │ ├── Header.js │ │ ├── List.js │ │ ├── ListItem.js │ │ ├── OverallFooter.js │ │ └── TodoApp.js │ ├── index.js │ └── modules │ │ ├── handlers.js │ │ └── todo.js │ └── yarn.lock ├── greenkeeper.json ├── lerna.json ├── package.json ├── packages ├── stapp-cli-tools │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── index-check.js │ │ ├── index-install.js │ │ ├── index-update.js │ │ ├── index.js │ │ ├── stapp-packages.json │ │ ├── tasks-install │ │ └── parseInput.js │ │ ├── tasks-update │ │ └── getPackages.js │ │ ├── tasks │ │ ├── checkExistingPackages.js │ │ ├── collectPeerDependencies.js │ │ ├── fetchInitialMeta.js │ │ └── installDependencies.js │ │ └── utils │ │ ├── filterSkipped.js │ │ ├── has.js │ │ ├── installationLog.js │ │ └── removeCaret.js ├── stapp-formbase │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── constants.ts │ │ ├── events.ts │ │ ├── formBase.h.ts │ │ ├── formBase.spec.ts │ │ ├── formBase.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── reducers.ts │ │ └── selectors.ts │ ├── tsconfig.json │ └── tslint.json ├── stapp-loaders │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── loaders.h.ts │ │ ├── loaders.spec.ts │ │ └── loaders.ts │ ├── tsconfig.json │ └── tslint.json ├── stapp-persist │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── constants.ts │ │ ├── global.d.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── persist.h.ts │ │ ├── persist.spec.ts │ │ ├── persist.ts │ │ └── toAsync.ts │ ├── tsconfig.json │ └── tslint.json ├── stapp-react-hooks │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── helpers │ │ │ └── constants.ts │ │ ├── index.ts │ │ ├── models │ │ │ └── global.d.ts │ │ └── use │ │ │ ├── use.spec.tsx │ │ │ ├── useApi.ts │ │ │ ├── useField.ts │ │ │ ├── useForm.ts │ │ │ └── useStapp.ts │ ├── tsconfig.json │ └── tslint.json ├── stapp-react │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── binded │ │ │ ├── createApi.ts │ │ │ ├── createComponents.spec.tsx │ │ │ ├── createComponents.ts │ │ │ ├── createConsume.ts │ │ │ ├── createConsumer.spec.tsx │ │ │ ├── createConsumer.ts │ │ │ ├── createField.ts │ │ │ ├── createForm.spec.tsx │ │ │ └── createForm.ts │ │ ├── context │ │ │ ├── Api.ts │ │ │ ├── Consumer.ts │ │ │ ├── Context.spec.tsx │ │ │ ├── Field.ts │ │ │ ├── Form.ts │ │ │ ├── Provider.ts │ │ │ └── consume.ts │ │ ├── helpers │ │ │ ├── StappSubscription.ts │ │ │ ├── constants.ts │ │ │ ├── getDisplayName.ts │ │ │ ├── propTypes.ts │ │ │ ├── renderComponent.spec.tsx │ │ │ ├── renderComponent.ts │ │ │ ├── simpleMemoize.spec.ts │ │ │ ├── simpleMemoize.ts │ │ │ └── testApp.ts │ │ ├── index.ts │ │ ├── models │ │ │ ├── ConsumeHoc.ts │ │ │ ├── Form.ts │ │ │ ├── Props.ts │ │ │ └── global.d.ts │ │ └── shared │ │ │ └── StappContext.ts │ ├── testSetup.ts │ ├── tsconfig.json │ └── tslint.json ├── stapp-rxjs │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── observableConfig.spec.ts │ │ └── observableConfig.ts │ ├── tsconfig.json │ └── tslint.json ├── stapp-select │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── select.h.ts │ │ ├── select.spec.ts │ │ └── select.ts │ ├── tsconfig.json │ └── tslint.json ├── stapp-validate │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── constants.ts │ │ ├── events.ts │ │ ├── helpers.ts │ │ ├── index.ts │ │ ├── reducers.ts │ │ ├── selectors.ts │ │ ├── validate.h.ts │ │ ├── validate.spec.ts │ │ └── validate.ts │ ├── tsconfig.json │ └── tslint.json ├── stapp │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── core │ │ │ ├── createApp │ │ │ │ ├── bindApi.ts │ │ │ │ ├── createApp.h.ts │ │ │ │ ├── createApp.spec.ts │ │ │ │ ├── createApp.ts │ │ │ │ ├── getReadyPromise.ts │ │ │ │ ├── getRootReducer.ts │ │ │ │ ├── getStore.ts │ │ │ │ └── setObservableConfig.ts │ │ │ ├── createEffect │ │ │ │ ├── createEffect.h.ts │ │ │ │ ├── createEffect.spec.ts │ │ │ │ └── createEffect.ts │ │ │ ├── createEpic │ │ │ │ ├── createEpic.spec.ts │ │ │ │ └── createEpic.ts │ │ │ ├── createEvent │ │ │ │ ├── createEvent.h.ts │ │ │ │ ├── createEvent.spec.ts │ │ │ │ └── createEvent.ts │ │ │ └── createReducer │ │ │ │ ├── createReducer.h.ts │ │ │ │ ├── createReducer.spec.ts │ │ │ │ └── createReducer.ts │ │ ├── events │ │ │ ├── dangerous.ts │ │ │ └── lifecycle.ts │ │ ├── helpers │ │ │ ├── combineEpics │ │ │ │ ├── combineEpics.spec.ts │ │ │ │ └── combineEpics.ts │ │ │ ├── combineReducers │ │ │ │ ├── combineReducers.spec.ts │ │ │ │ └── combineReducers.ts │ │ │ ├── constants.ts │ │ │ ├── controlledPromise │ │ │ │ ├── controlledPromise.spec.ts │ │ │ │ └── controlledPromise.ts │ │ │ ├── getEventType │ │ │ │ ├── getEventType.spec.ts │ │ │ │ └── getEventType.ts │ │ │ ├── has │ │ │ │ ├── has.spec.ts │ │ │ │ └── has.ts │ │ │ ├── identity │ │ │ │ ├── identity.spec.ts │ │ │ │ └── identity.ts │ │ │ ├── is │ │ │ │ ├── isArray │ │ │ │ │ ├── isArray.spec.ts │ │ │ │ │ └── isArray.ts │ │ │ │ ├── isEvent │ │ │ │ │ ├── isEvent.spec.ts │ │ │ │ │ └── isEvent.ts │ │ │ │ ├── isModule │ │ │ │ │ └── isModule.ts │ │ │ │ ├── isPromise │ │ │ │ │ └── isPromise.ts │ │ │ │ └── isSubscribable │ │ │ │ │ └── isSubscribable.ts │ │ │ ├── logError │ │ │ │ └── logError.ts │ │ │ ├── omit │ │ │ │ ├── omit.spec.ts │ │ │ │ └── omit.ts │ │ │ ├── pick │ │ │ │ ├── pick.spec.ts │ │ │ │ └── pick.ts │ │ │ ├── select │ │ │ │ ├── select.spec.ts │ │ │ │ └── select.ts │ │ │ ├── testHelpers │ │ │ │ ├── collectData │ │ │ │ │ └── collectData.ts │ │ │ │ ├── getInitialState │ │ │ │ │ ├── getInitialState.spec.ts │ │ │ │ │ └── getInitialState.ts │ │ │ │ ├── loggerModule │ │ │ │ │ └── loggerModule.ts │ │ │ │ └── wait │ │ │ │ │ └── wait.ts │ │ │ └── uniqueId │ │ │ │ ├── uniqify.ts │ │ │ │ ├── uniqueId.spec.ts │ │ │ │ └── uniqueId.ts │ │ ├── stapp.ts │ │ └── types │ │ │ └── omit.ts │ ├── tsconfig.json │ └── tslint.json └── template │ ├── .babelrc │ ├── .npmignore │ ├── LICENSE │ ├── README-TEMPLATE.md │ ├── README.md │ ├── jest.config.json │ ├── package-lock.json │ ├── package.json │ ├── src │ ├── index.spec.ts │ └── index.ts │ ├── tsconfig.json │ └── tslint.json └── scripts ├── getPackages.js └── update.js /.editorconfig: -------------------------------------------------------------------------------- 1 | #root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | max_line_length = 100 10 | indent_size = 2 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | 21 | 22 | **Desktop (please complete the following information):** 23 | - OS: [e.g. iOS] 24 | - Browser [e.g. chrome, safari] 25 | - Version [e.g. 22] 26 | 27 | **Smartphone (please complete the following information):** 28 | - Device: [e.g. iPhone6] 29 | - OS: [e.g. iOS8.1] 30 | - Browser [e.g. stock browser, safari] 31 | - Version [e.g. 22] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Coverage 15 | lib-cov 16 | coverage 17 | .nyc_output 18 | 19 | # Dependency directories 20 | node_modules/ 21 | jspm_packages/ 22 | 23 | # IDE 24 | .DS_Store 25 | .vscode 26 | .idea 27 | 28 | # Compiled sources 29 | lib 30 | docs/_book 31 | _book 32 | .tempdocs 33 | compiled 34 | 35 | # Cache 36 | .awcache 37 | .rpt2_cache 38 | .npm 39 | 40 | # Private data 41 | .npmrc 42 | .coveralls.yml 43 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "proseWrap": "never", 11 | "parser": "typescript", 12 | "arrowParens": "always" 13 | } 14 | -------------------------------------------------------------------------------- /.size-limit.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | name: 'stapp core', 4 | path: './packages/stapp/lib/stapp.js' 5 | }, 6 | { 7 | name: 'stapp-formbase', 8 | path: './packages/stapp-formbase/lib/index.js' 9 | }, 10 | { 11 | name: 'stapp-loaders', 12 | path: './packages/stapp-loaders/lib/index.js' 13 | }, 14 | { 15 | name: 'stapp-validate', 16 | path: './packages/stapp-validate/lib/index.js' 17 | }, 18 | { 19 | name: 'stapp-persist', 20 | path: './packages/stapp-persist/lib/index.js' 21 | }, 22 | { 23 | name: 'stapp-react', 24 | path: './packages/stapp-react/lib/index.js' 25 | }, 26 | { 27 | name: 'stapp-react-hooks', 28 | path: './packages/stapp-react/lib/index.js' 29 | }, 30 | { 31 | name: 'stapp-select', 32 | path: './packages/stapp-select/lib/index.js' 33 | }, 34 | { 35 | name: 'Total', 36 | path: [ 37 | './packages/stapp/lib/stapp.js', 38 | './packages/stapp-formbase/lib/index.js', 39 | './packages/stapp-loaders/lib/index.js', 40 | './packages/stapp-validate/lib/index.js', 41 | './packages/stapp-persist/lib/index.js', 42 | './packages/stapp-react/lib/index.js', 43 | './packages/stapp-react-hooks/lib/index.js', 44 | './packages/stapp-select/lib/index.js' 45 | ] 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | if: tag IS blank 2 | 3 | cache: 4 | yarn: true 5 | directories: 6 | - node_modules 7 | 8 | language: node_js 9 | node_js: 10 | - '10' 11 | - '8' 12 | - '6' 13 | 14 | script: 15 | - npm run build 16 | - npm run test:ci 17 | 18 | install: 19 | - npm install 20 | - lerna bootstrap 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | By contributing to Stapp, you agree to abide by the [code of conduct](https://github.com/TinkoffCreditSystems/stapp/blob/master/code-of-conduct.md). 4 | 5 | ## Instructions 6 | 7 | These steps will guide you through contributing to this project: 8 | 9 | - Fork the repo. 10 | - Checkout the `develop` branch. 11 | - Install dependencies with `npm run bootstrap` command. 12 | 13 | ```bash 14 | git clone https://github.com/TinkoffCreditSystems/stapp 15 | cd stapp 16 | npm install 17 | ``` 18 | - Create a new branch from the `develop` branch. 19 | - Make your changes. Make sure the commands `npm run build` and `npm run test` are passing. 20 | 21 | ## Updating README 22 | * `README.md` in the root is copied automatically from the `./docs/` directory, so if you plan to make changes, make them in the `./docs/README.md` file. 23 | 24 | ## Committing 25 | 26 | Please, commit changes with `npm run cz`. This command will run commitizen, which will protect you against commit message pain. You may also install commitizen globally and commit with `git cz`. 27 | 28 | You'll be asked about the changes scope. Please, select among the following: 29 | 30 | * `core` - any feature and tests changes related to the core `stapp` package 31 | * `validate` - same for `stapp-validate` package 32 | * `loaders` - same for `stapp-loaders` package 33 | * `formbase`- same for `stapp-formbase` package 34 | * `persist` - same for `stapp-persist` package 35 | * `react` - same for `stapp-react` package 36 | * `rxjs` - same for `stapp-rxjs` package 37 | * `select` - same for `stapp-select` package 38 | * `root` - changes related to the root of this project 39 | * use `all` or leave empty when your changes are related to every single package 40 | 41 | Finally send a GitHub Pull Request with a clear list of what you've done (read more [about pull requests](https://help.github.com/articles/about-pull-requests/)). Make sure all of your commits are atomic (one feature per commit). 42 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "./docs", 3 | "plugins": [ 4 | "-sharing", 5 | "my-toolbar" 6 | ], 7 | "pluginsConfig": { 8 | "my-toolbar": { 9 | "buttons": 10 | [ 11 | { 12 | "label": "GitHub", 13 | "text": " ", 14 | "icon": "fa fa-github", 15 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 16 | }, 17 | { 18 | "label": "Share link on Facebook", 19 | "text": " ", 20 | "icon": "fa fa-facebook", 21 | "url": "http://www.facebook.com/sharer/sharer.php?s=100&p[url]={{url}}" 22 | }, 23 | { 24 | "label": "Share link on Twitter", 25 | "text": " ", 26 | "icon": "fa fa-twitter", 27 | "url": "http://twitter.com/share?text=Stapp:%20modular%20state%20and%20side-effects%20manager&url=https://stapp.js.org/&hashtags=stapp" 28 | }, 29 | { 30 | "label": "Edit this page on github", 31 | "text":"Edit this page", 32 | "icon": "fa fa-pencil-square-o", 33 | "position" : "left", 34 | "url": "https://github.com/TinkoffCreditSystems/stapp/blob/master/docs/{{filepath_lang}}" 35 | } 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /configs/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "node", 4 | "target": "es5", 5 | "module": "commonjs", 6 | "lib": ["es2015", "es2016", "es2017", "dom"], 7 | "strict": true, 8 | "strictFunctionTypes": false, 9 | "sourceMap": true, 10 | "declaration": true, 11 | "stripInternal": true, 12 | "allowSyntheticDefaultImports": true, 13 | "esModuleInterop": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "skipLibCheck": true, 17 | "importHelpers": false, 18 | "jsx": "react" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | stapp.js.org 2 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | ### Stapp 3 | * [Introduction](README.md) 4 | * [Installation](README.md#installation) 5 | * [Motivation](README.md#motivation) 6 | * [Core concepts](README.md#stapp-core-concepts) 7 | 8 | ### Core 9 | * [createApp](/core/createApp.md) 10 | * [createEvent](/core/createEvent.md) 11 | * [createEffect](/core/createEffect.md) 12 | * [createReducer](/core/createReducer.md) 13 | * [Modules](/core/modules.md) 14 | * [Epics](/core/epics.md) 15 | 16 | ### Guides 17 | * [React](/guides/react.md) 18 | * [SSR](/guides/ssr.md) 19 | * [RxJS and other reactive libs](/guides/interop.md) 20 | * [Application lifecycle](/guides/lifecycle.md) 21 | * [Stapp CLI](/guides/cli.md) 22 | 23 | ### Modules 24 | * [persist](/modules/persist.md) 25 | * [loaders](/modules/loaders.md) 26 | * [formbase](/modules/formbase.md) 27 | * [validate](/modules/validate.md) 28 | * [select](/modules/select.md) 29 | 30 | ### Links 31 | * [Issues](https://github.com/TinkoffCreditSystems/stapp/issues) 32 | * [Changelog](https://github.com/TinkoffCreditSystems/stapp/releases) 33 | -------------------------------------------------------------------------------- /docs/guides/lifecycle.md: -------------------------------------------------------------------------------- 1 | # Application lifecycle 2 | 3 | 4 | 5 | 6 | 7 | - [`init`](#init) 8 | - [`ready`](#ready) 9 | - [`disconnect`](#disconnect) 10 | 11 | 12 | 13 | Stapp applications have two major events in their lifecycle. 14 | 15 | ## `init` 16 | The initiation process starts when `createApp` is called. The completion of the process is indicated by the `initEvent`. 17 | 18 | ## `ready` 19 | `readyEvent` is emitted after the `ready` promise resolves. See [SSR section](/guides/SSR.html) for more info. 20 | 21 | ## `disconnect` 22 | The disconnection process starts when `Stapp.disconnect` is called. It starts with the `disconnectEvent`. 23 | After that the application disables all dispatch functions and unsubscribes from epics. 24 | Note, that currently the application can't be restarted after the disconnection process completes. 25 | -------------------------------------------------------------------------------- /examples/form-async-validation/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /examples/form-async-validation/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | package-lock.json 23 | -------------------------------------------------------------------------------- /examples/form-async-validation/README.md: -------------------------------------------------------------------------------- 1 | # Stapp • Form with async validation 2 | Various form validations made with Stapp. 3 | 4 | See live at the [codesandbox.io](https://codesandbox.io/s/github/TinkoffCreditSystems/stapp/tree/master/examples/form-async-validation). 5 | 6 | SStapp is an application state-management tool based on redux and RxJS with significantly reduced 7 | boilerplate. Main goal of Stapp is to provide an easy way to create simple, robust and reusable applications. 8 | 9 | Stapp comprises all the best practices and provides instruments to write understandable and predicable code. 10 | 11 | ## Resources 12 | 13 | - [Website](https://github.com/TinkoffCreditSystems/stapp) 14 | - [Documentation](https://tinkoffcreditsystems.github.io/stapp/) 15 | 16 | ### Support 17 | 18 | - [Github issues](https://github.com/TinkoffCreditSystems/stapp/issues) 19 | 20 | ## Implementation 21 | 22 | The example is intentionally made overcomplicated to show some best practices (overcomplication is not a best practice itself). 23 | 24 | ## Credit 25 | 26 | Created by [Dmitry Korolev](https://github.com/dmitry-korolev) 27 | -------------------------------------------------------------------------------- /examples/form-async-validation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "form-async-validation", 3 | "version": "2.7.0-0", 4 | "private": true, 5 | "dependencies": { 6 | "fbjs": "^1.0.0", 7 | "light-observable": "^2.13.1", 8 | "prop-types": "^15.7.2", 9 | "react": "^16.8.6", 10 | "react-dom": "^16.8.6", 11 | "react-scripts": "^2.1.8", 12 | "redux": "^4.0.1", 13 | "reselect": "^4.0.0", 14 | "rxjs": "^6.4.0", 15 | "stapp": "^2.6.0", 16 | "stapp-formbase": "^2.6.0", 17 | "stapp-loaders": "^2.6.0", 18 | "stapp-persist": "^2.6.0", 19 | "stapp-react": "^2.6.0", 20 | "stapp-validate": "^2.6.0", 21 | "styled-components": "^4.2.0" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build" 26 | }, 27 | "browserslist": [ 28 | ">0.2%", 29 | "not dead", 30 | "not ie <= 11", 31 | "not op_mini all" 32 | ], 33 | "devDependencies": { 34 | "dotenv": "^8.0.0", 35 | "eslint": "^5.16.0", 36 | "node-pre-gyp": "^0.13.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/form-async-validation/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinkoff/stapp/1d7001fbac032f73c95d4a1807fe87329decc099/examples/form-async-validation/public/favicon.ico -------------------------------------------------------------------------------- /examples/form-async-validation/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/form-async-validation/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/form-async-validation/src/apps/asyncForm.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'stapp' 2 | import { createComponents } from 'stapp-react' 3 | import { formBase } from 'stapp-formbase' 4 | import { formSubmit } from '../modules/formSubmit' 5 | import { loaders } from 'stapp-loaders' 6 | import { persist, toAsync } from 'stapp-persist' 7 | import { validate } from 'stapp-validate' 8 | import { wait } from '../utils/wait' 9 | 10 | const required = (value, fieldName) => { 11 | console.log(fieldName, value) 12 | if (!value || value.length === 0) return 'Required!' 13 | } 14 | 15 | const checkAge = age => { 16 | if (age < 18) return 'Age should be greater than 18!' 17 | } 18 | 19 | const validateName = async (username) => { 20 | await wait(1500) 21 | return ['John', 'Paul', 'George', 'Ringo'].indexOf(username) >= 0 ? { 22 | username: 'Name already taken!' 23 | } : null 24 | } 25 | 26 | const asyncForm = createApp({ 27 | name: 'asyncForm', 28 | modules: [ 29 | persist({ 30 | key: 'asyncForm', 31 | storage: toAsync(localStorage), 32 | blackList: ['validating', 'loaders'] 33 | }), 34 | formBase(), 35 | loaders, 36 | validate({ 37 | rules: { 38 | age: (age, fieldName) => required(age, fieldName) || checkAge(age, fieldName), 39 | name: required, 40 | username: (username, fieldName) => { 41 | const error = required(username, fieldName) 42 | 43 | if (error) { 44 | return { username: error } 45 | } 46 | 47 | return validateName(username) 48 | } 49 | } 50 | }), 51 | formSubmit 52 | ] 53 | }) 54 | 55 | export const { Consumer, Form, Field } = createComponents(asyncForm) 56 | -------------------------------------------------------------------------------- /examples/form-async-validation/src/components/RenderCount.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Counter = styled.span` 5 | display: inline-block; 6 | border-radius: 50%; 7 | height: 32px; 8 | width: 32px; 9 | line-height: 32px; 10 | text-align: center; 11 | background: #ccc; 12 | ` 13 | 14 | export class RenderCount extends Component { 15 | count = 0 16 | 17 | render () { 18 | return { ++this.count } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/form-async-validation/src/components/Spinner.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from "styled-components" 2 | 3 | const rotation = keyframes` 4 | from { 5 | -webkit-transform: rotate(0deg); 6 | } 7 | to { 8 | -webkit-transform: rotate(359deg); 9 | } 10 | ` 11 | 12 | export const Spinner = styled.div` 13 | height: 12px; 14 | width: 12px; 15 | margin-left: 5px; 16 | position: absolute; 17 | right: 0; 18 | top: 0; 19 | animation: ${rotation} 0.6s infinite linear; 20 | border: 6px solid rgba(0, 174, 239, 0.15); 21 | border-top-color: rgba(0, 174, 239, 0.8); 22 | border-radius: 100%; 23 | ` 24 | -------------------------------------------------------------------------------- /examples/form-async-validation/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { App } from './components/App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | 7 | -------------------------------------------------------------------------------- /examples/form-async-validation/src/modules/formSubmit.js: -------------------------------------------------------------------------------- 1 | import { createEffect, combineEpics } from 'stapp' 2 | import { loaderStart, loaderEnd } from 'stapp-loaders' 3 | import { setSubmitting, submit, isValidSelector, isReadySelector } from 'stapp-formbase' 4 | import { map, filter, switchMap, switchMapTo } from 'light-observable/operators' 5 | import { of } from 'light-observable/observable' 6 | import { wait } from '../utils/wait' 7 | 8 | const isValid = isValidSelector() 9 | const isReady = isReadySelector() 10 | 11 | const asyncSubmit = createEffect('Submit', async values => { 12 | await wait(400) 13 | 14 | window.alert(JSON.stringify(values, 0, 2)); 15 | }) 16 | 17 | const submitEpic = submit.epic((submit$, state$, { getState }) => submit$.pipe( 18 | map(() => getState()), 19 | filter(state => isReady(state) & isValid(state)), 20 | map(state => state.values), 21 | switchMap(asyncSubmit) 22 | )) 23 | 24 | const onSubmitStartEpic = asyncSubmit.start.epic(start$ => start$.pipe( 25 | switchMapTo(of( 26 | loaderStart('submit'), 27 | setSubmitting(true) 28 | )) 29 | )) 30 | 31 | const onSubmitEndEpic = asyncSubmit.complete.epic(complete$ => complete$.pipe( 32 | switchMapTo(of( 33 | loaderEnd('submit'), 34 | setSubmitting(false) 35 | )) 36 | )) 37 | 38 | export const formSubmit = { 39 | name: 'formSubmit', 40 | epic: combineEpics([ 41 | submitEpic, 42 | onSubmitStartEpic, 43 | onSubmitEndEpic 44 | ]) 45 | } 46 | -------------------------------------------------------------------------------- /examples/form-async-validation/src/utils/wait.js: -------------------------------------------------------------------------------- 1 | export const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) 2 | -------------------------------------------------------------------------------- /examples/todo-mvc/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | -------------------------------------------------------------------------------- /examples/todo-mvc/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | package-lock.json 23 | -------------------------------------------------------------------------------- /examples/todo-mvc/README.md: -------------------------------------------------------------------------------- 1 | # Stapp • [TodoMVC](http://todomvc.com) 2 | Classic TodoMVC example made with Stapp. 3 | See live at the [codesandbox.io](https://codesandbox.io/s/github/TinkoffCreditSystems/stapp/tree/master/examples/todo-mvc). 4 | 5 | Stapp is an application state-management tool based on redux and RxJS with significantly reduced 6 | boilerplate. Main goal of Stapp is to provide an easy way to create simple, robust and reusable applications. 7 | 8 | Stapp comprises all the best practices and provides instruments to write understandable and predicable code. 9 | 10 | ## Resources 11 | 12 | - [Website](https://github.com/TinkoffCreditSystems/stapp) 13 | - [Documentation](https://tinkoffcreditsystems.github.io/stapp/) 14 | 15 | ### Support 16 | 17 | - [Github issues](https://github.com/TinkoffCreditSystems/stapp/issues) 18 | 19 | ## Implementation 20 | 21 | The example is intentionally made overcomplicated to show some best practices (overcomplication is not a best practice itself). 22 | 23 | ## Credit 24 | 25 | Created by [Dmitry Korolev](https://github.com/dmitry-korolev) 26 | -------------------------------------------------------------------------------- /examples/todo-mvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-mvc", 3 | "version": "2.7.0-0", 4 | "private": true, 5 | "dependencies": { 6 | "light-observable": "^2.13.1", 7 | "react": "^16.8.6", 8 | "react-dom": "^16.8.6", 9 | "react-scripts": "^2.1.8", 10 | "redux": "^4.0.1", 11 | "reselect": "^4.0.0", 12 | "rxjs": "^6.4.0", 13 | "stapp": "^2.6.0", 14 | "stapp-formbase": "^2.6.0", 15 | "stapp-persist": "^2.6.0", 16 | "stapp-react": "^2.6.0", 17 | "todomvc-app-css": "^2.2.0", 18 | "todomvc-common": "^1.0.5" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build" 23 | }, 24 | "browserslist": [ 25 | ">0.2%", 26 | "not dead", 27 | "not ie <= 11", 28 | "not op_mini all" 29 | ], 30 | "devDependencies": { 31 | "dotenv": "^8.0.0", 32 | "eslint": "^5.16.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/todo-mvc/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Tinkoff/stapp/1d7001fbac032f73c95d4a1807fe87329decc099/examples/todo-mvc/public/favicon.ico -------------------------------------------------------------------------------- /examples/todo-mvc/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Template • TodoMVC 7 | 8 | 9 |
10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/todo-mvc/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/apps/todoApp.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'stapp' 2 | import { createComponents } from 'stapp-react' 3 | import { persist, toAsync } from 'stapp-persist' 4 | import { handlers } from '../modules/handlers' 5 | import { todoModule } from '../modules/todo' 6 | 7 | const todoApp = createApp({ 8 | name: 'todo', 9 | modules: [ 10 | todoModule, 11 | persist({ 12 | key: 'todo', 13 | storage: toAsync(localStorage) 14 | }), 15 | handlers 16 | ] 17 | }) 18 | 19 | export const { Consumer, consume } = createComponents(todoApp) 20 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/apps/todoSelectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from "reselect" 2 | 3 | export const idsSelector = createSelector( 4 | state => state.todos, 5 | (todos) => todos.map(todo => todo.id) 6 | ) 7 | export const showToggleSelector = (state) => state.todos.length > 0 8 | export const everythingToggled = (state) => state.todos.every(todo => todo.completed) 9 | export const countUncompleted = (state) => state.todos.filter(todo => !todo.completed).length 10 | export const toggleSelector = createSelector( 11 | showToggleSelector, 12 | everythingToggled, 13 | (showToggle, isToggled) => ({ showToggle, isToggled }) 14 | ) 15 | 16 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react/index' 2 | import { OverallFooter } from './OverallFooter' 3 | import { TodoApp } from './TodoApp' 4 | 5 | class App extends Component { 6 | render() { 7 | return ( 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | } 15 | 16 | export default App 17 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react/index' 2 | import { Consumer } from '../apps/todoApp' 3 | import { countUncompleted } from '../apps/todoSelectors' 4 | 5 | const mapper = (state) => ({ 6 | count: state.todos.length, 7 | uncompleted: countUncompleted(state) 8 | }) 9 | 10 | export const Footer = () => { 11 | return 12 | { 13 | ({ count, uncompleted }, { handleClearClick }) => { 14 | const itemWord = uncompleted === 1 ? 'item' : 'items' 15 | 16 | if (count === 0) { 17 | return null 18 | } 19 | return
20 | { uncompleted } { itemWord } left 21 | {/* */} 22 | {/*
    */} 23 | {/*
  • */} 24 | {/*All*/} 25 | {/*
  • */} 26 | {/*
  • */} 27 | {/*Active*/} 28 | {/*
  • */} 29 | {/*
  • */} 30 | {/*Completed*/} 31 | {/*
  • */} 32 | {/*
*/} 33 | {/* */} 34 | 35 |
36 | } 37 | } 38 |
39 | } 40 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react/index' 2 | import { Consumer } from '../apps/todoApp' 3 | 4 | const mapper = (_, api) => ({ 5 | handleSubmit: (event) => { 6 | if (event.which === 13) { 7 | api.handleSubmit(event) 8 | event.target.value = '' 9 | } 10 | } 11 | }) 12 | 13 | export const Header = () =>
14 |

todos

15 | 16 | { 17 | ({ handleSubmit }) => 18 | } 19 | 20 |
21 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/components/List.js: -------------------------------------------------------------------------------- 1 | import React from 'react/index' 2 | import { Consumer } from '../apps/todoApp' 3 | import { ListItem } from './ListItem' 4 | import { idsSelector, toggleSelector } from '../apps/todoSelectors' 5 | 6 | export const List = () => { 7 | return
8 | 9 | { 10 | ({ showToggle, isToggled }, { handleToggleClick }) => showToggle ? 11 | 12 | 13 | 14 | : 15 | null 16 | } 17 | 18 | 19 | 29 |
30 | } 31 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/components/ListItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Consumer } from '../apps/todoApp' 3 | 4 | export const ListItem = ({ todoId }) => { 5 | const mapper = (state, api) => ({ 6 | todo: state.todos.find(todo => todo.id === todoId), 7 | handleDblClick: () => { 8 | return api.handleDblClick(todoId) 9 | }, 10 | handleDelete: () => api.handleDeleteClick(todoId), 11 | handleCheckboxClick: () => api.handleCheckboxClick(todoId), 12 | handleEdit: (event) => { 13 | if (event.which === 13) { 14 | api.handleEdit(event, todoId) 15 | } 16 | } 17 | }) 18 | 19 | return 20 | { 21 | ({ 22 | todo, 23 | handleDblClick, 24 | handleEdit, 25 | handleDelete, 26 | handleCheckboxClick 27 | }) => { 28 | const classNames = [] 29 | todo.completed && classNames.push('completed') 30 | todo.isEditing && classNames.push('editing') 31 | 32 | return
  • 33 |
    34 | 35 | 36 |
    38 | 39 |
  • 40 | } 41 | } 42 |
    43 | } 44 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/components/OverallFooter.js: -------------------------------------------------------------------------------- 1 | import React from 'react/index' 2 | 3 | export const OverallFooter = () => { 4 | return 10 | } 11 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/components/TodoApp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { List } from './List' 4 | import { Header } from './Header' 5 | import { Footer } from './Footer' 6 | 7 | export const TodoApp = () => { 8 | return
    9 |
    10 | 11 |
    12 |
    13 | } 14 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import 'todomvc-common/base.css' 4 | import 'todomvc-app-css/index.css' 5 | import 'todomvc-common/base.js' 6 | 7 | // Components 8 | import App from './components/App' 9 | 10 | ReactDOM.render(, document.querySelector('.root')) 11 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/modules/handlers.js: -------------------------------------------------------------------------------- 1 | import { createEvent, combineEpics } from 'stapp' 2 | import { map } from 'light-observable/operators' 3 | import { events } from './todo' 4 | 5 | const handleSubmit = createEvent('Handle todo submit', event => event.target.value.trim()) 6 | const submitEpic = handleSubmit.epic((event$) => event$.pipe( 7 | map(({ payload }) => events.addTodo(payload)) 8 | )) 9 | 10 | const handleEdit = createEvent('Handle todo edit', (event, id) => ({ 11 | text: event.target.value.trim(), 12 | id 13 | })) 14 | const editEpic = handleEdit.epic((event$) => event$.pipe( 15 | map(({ payload }) => events.editTodo(payload)) 16 | )) 17 | 18 | export const handlers = ({ 19 | name: 'handlers', 20 | events: { 21 | handleSubmit, 22 | handleEdit, 23 | handleDblClick: events.toggleEditing, 24 | handleDeleteClick: events.deleteTodo, 25 | handleCheckboxClick: events.toggleComplete, 26 | handleClearClick: events.clearCompleted, 27 | handleToggleClick: () => events.toggleAll() 28 | }, 29 | epic: combineEpics([ 30 | submitEpic, 31 | editEpic 32 | ]) 33 | }) 34 | -------------------------------------------------------------------------------- /examples/todo-mvc/src/modules/todo.js: -------------------------------------------------------------------------------- 1 | import { createReducer } from 'stapp' 2 | import { everythingToggled } from '../apps/todoSelectors' 3 | 4 | const todosReducer = createReducer([]) 5 | 6 | // This events could be passed to store directly 7 | // but that would be sort of messy 8 | // Instead this events should be imported and used by other modules. 9 | export const events = todosReducer.createEvents({ 10 | addTodo: (todos, newTodo) => todos.concat({id: +(new Date()), completed: false, isEditing: false, text: newTodo }), 11 | deleteTodo: (todos, id) => todos.filter(todo => todo.id !== id), 12 | editTodo: (todos, payload) => todos.map(todo => todo.id === payload.id ? {...todo, text: payload.text, isEditing: false } : todo), 13 | toggleComplete: (todos, id) => todos.map(todo => todo.id === id ? {...todo, completed: !todo.completed } : todo), 14 | toggleEditing: (todos, id) => todos.map(todo => todo.id === id ? {...todo, isEditing: !todo.isEditing } : todo), 15 | toggleAll: (todos) => { 16 | const nextState = everythingToggled({ todos }) 17 | 18 | return todos.map(todo => ({ ...todo, completed: !nextState })) 19 | }, 20 | clearCompleted: (todos) => todos.filter(todo => !todo.completed) 21 | }) 22 | 23 | events.addTodo() 24 | 25 | export const todoModule = ({ 26 | name: 'todos', 27 | reducers: { 28 | todos: todosReducer 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /greenkeeper.json: -------------------------------------------------------------------------------- 1 | { 2 | "groups": { 3 | "default": { 4 | "packages": [ 5 | "examples/form-async-validation/package.json", 6 | "examples/todo-mvc/package.json", 7 | "package.json", 8 | "packages/stapp-formbase/package.json", 9 | "packages/stapp-loaders/package.json", 10 | "packages/stapp-persist/package.json", 11 | "packages/stapp-react/package.json", 12 | "packages/stapp-rxjs/package.json", 13 | "packages/stapp-validate/package.json", 14 | "packages/stapp/package.json" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.11.0", 3 | "packages": [ 4 | "packages/*", 5 | "examples/*" 6 | ], 7 | "version": "2.7.0-0" 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp", 3 | "private": true, 4 | "engines": { 5 | "node": ">=8.0.0" 6 | }, 7 | "license": "Apache-2.0", 8 | "scripts": { 9 | "bootstrap": "lerna bootstrap", 10 | "build": "lerna run build", 11 | "test": "lerna run test && npm run test:size", 12 | "test:size": "size-limit", 13 | "test:ci": "lerna run test:ci && npm run test:size", 14 | "docs": "rimraf _book && npm run docs:toc && npm run docs:move && npm run docs:book", 15 | "docs:toc": "doctoc docs/README.md docs/guides/. docs/core/. docs/modules/. --notitle --github", 16 | "docs:move": "cp docs/README.md ./", 17 | "docs:book": "gitbook install && gitbook build", 18 | "docs:publish": "npm run docs && gh-pages -d _book", 19 | "update-all-select": "lernaupdate", 20 | "update-all": "node scripts/update.js && npm run bootstrap", 21 | "pretest": "npm run build", 22 | "precommit": "lint-staged", 23 | "cz": "git-cz", 24 | "prepush": "npm run test", 25 | "preversion": "npm run build" 26 | }, 27 | "lint-staged": { 28 | "packages/**/*.{ts,tsx}": [ 29 | "prettier --write --config .prettierrc", 30 | "git add" 31 | ], 32 | "./README.md": [ 33 | "doctoc --notitle --github --maxlevel 3", 34 | "git add" 35 | ] 36 | }, 37 | "devDependencies": { 38 | "cash-cp": "^0.2.0", 39 | "cash-mv": "^0.2.0", 40 | "commitizen": "^3.0.4", 41 | "coveralls": "^3.0.2", 42 | "doctoc": "^1.3.1", 43 | "gh-pages": "^2.0.1", 44 | "gitbook-cli": "^2.3.2", 45 | "glob": "^7.1.3", 46 | "husky": "^2.0.0", 47 | "lerna": "^3.4.3", 48 | "lerna-update-wizard": "^0.12.0", 49 | "lint-staged": "^8.0.5", 50 | "npm-check-updates": "^3.1.4", 51 | "lint-staged": "^9.0.0", 52 | "npm-check-updates": "^2.14.3", 53 | "p-series": "^2.1.0", 54 | "prettier": "^1.15.2", 55 | "rimraf": "^2.6.2", 56 | "size-limit": "^1.0.1", 57 | "webpack": "^4.26.0" 58 | }, 59 | "config": { 60 | "commitizen": { 61 | "path": "./node_modules/cz-conventional-changelog" 62 | } 63 | }, 64 | "dependencies": { 65 | "gitbook-plugin-edit-link": "^2.0.2", 66 | "gitbook-plugin-my-toolbar": "^0.6.5" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | testSetup.ts 42 | examples/ 43 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/README.md: -------------------------------------------------------------------------------- 1 | # Stapp CLI Tools 2 | 3 | CLI for [Stapp](https://github.com/TinkoffCreditSystems/stapp). 4 | 5 | See [documentation](https://stapp.js.org/guides/cli.html) for more info. 6 | 7 | ## Installation 8 | ```bash 9 | npm install -g stapp-cli-tools 10 | ``` 11 | 12 | ## License 13 | 14 | ``` 15 | Copyright 2019 Tinkoff Bank 16 | 17 | Licensed under the Apache License, Version 2.0 (the "License"); 18 | you may not use this file except in compliance with the License. 19 | You may obtain a copy of the License at 20 | 21 | http://www.apache.org/licenses/LICENSE-2.0 22 | 23 | Unless required by applicable law or agreed to in writing, software 24 | distributed under the License is distributed on an "AS IS" BASIS, 25 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | See the License for the specific language governing permissions and 27 | limitations under the License. 28 | ``` 29 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp-cli-tools", 3 | "version": "2.7.0-0", 4 | "description": "Command line utilities for Stapp", 5 | "keywords": [ 6 | "stapp", 7 | "cli", 8 | "utility", 9 | "tool", 10 | "generator" 11 | ], 12 | "main": "lib/index.js", 13 | "typings": "lib/index.d.ts", 14 | "files": [ 15 | "src" 16 | ], 17 | "author": "Dmitry Korolev (https://korolev.dk)", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 21 | }, 22 | "homepage": "https://stapp.js.org/", 23 | "bugs": "https://github.com/TinkoffCreditSystems/stapp/issues?q=is:issue+label:stapp-cli-tools", 24 | "engines": { 25 | "node": ">=8.0.0" 26 | }, 27 | "license": "Apache-2.0", 28 | "scripts": {}, 29 | "bin": { 30 | "stapp": "./src/index.js", 31 | "stapp-check": "./src/index-check.js", 32 | "stapp-install": "./src/index-install.js", 33 | "stapp-update": "./src/index-update.js" 34 | }, 35 | "dependencies": { 36 | "chalk": "^2.4.2", 37 | "commander": "^2.19.0", 38 | "dedent": "^0.7.0", 39 | "execa": "^1.0.0", 40 | "inquirer": "^6.2.2", 41 | "listr": "^0.14.3", 42 | "log-symbols": "^2.2.0", 43 | "node-notifier": "^5.4.0", 44 | "p-min-delay": "^3.0.0", 45 | "package-json": "^6.3.0", 46 | "parse-package-name": "^0.1.0", 47 | "rxjs": "^6.4.0", 48 | "semver": "^6.0.0", 49 | "string-similarity": "^3.0.0", 50 | "through": "^2.3.8" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const program = require('commander') 5 | const { version } = require('../package') 6 | 7 | program 8 | .version(version) 9 | .description('Stapp command line tools') 10 | .command('install [name]', 'Installs one or more stapp packages and related dependencies.').alias('i') 11 | .command('check', 'Checks existing stapp packages.').alias('c') 12 | .command('update', 'Updates existing stapp packages.').alias('u') 13 | .parse(process.argv) 14 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/stapp-packages.json: -------------------------------------------------------------------------------- 1 | { 2 | "stapp": "stapp", 3 | "stapp-cli-tools": "stapp-cli-tools", 4 | "cli-tools": "stapp-cli-tools", 5 | "stapp-formbase": "stapp-formbase", 6 | "formbase": "stapp-formbase", 7 | "stapp-loaders": "stapp-loaders", 8 | "loaders": "stapp-loaders", 9 | "stapp-persist": "stapp-persist", 10 | "persist": "stapp-persist", 11 | "stapp-react": "stapp-react", 12 | "react": "stapp-react", 13 | "stapp-react-hooks": "stapp-react-hooks", 14 | "react-hooks": "stapp-react-hooks", 15 | "stapp-rxjs": "stapp-rxjs", 16 | "rxjs": "stapp-rxjs", 17 | "stapp-select": "stapp-select", 18 | "select": "stapp-select", 19 | "stapp-validate": "stapp-validate", 20 | "validate": "stapp-validate" 21 | } 22 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/tasks-update/getPackages.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util') 2 | const readFile = promisify(require('fs').readFile) 3 | 4 | // Utils 5 | const validPackages = require('../stapp-packages') 6 | 7 | const getPackages = async (context) => { 8 | const dependencies = Object.entries(JSON.parse(await readFile('./package.json', 'utf-8')).dependencies || {}) 9 | 10 | dependencies.forEach(([name]) => { 11 | if (name === 'stapp-rxjs') { 12 | context.dependencies.set(name, { 13 | name, 14 | skip: 'Package "stapp-rxjs" is deprecated since 2.6.0.' 15 | }) 16 | 17 | return 18 | } 19 | 20 | if (name === validPackages[name]) { 21 | context.dependencies.set(name, { 22 | name, 23 | version: context.next ? 'next' : 'latest' 24 | }) 25 | } 26 | }) 27 | 28 | if (context.dependencies.size === 0) { 29 | throw new Error('Failed to find any stapp package') 30 | } 31 | } 32 | 33 | module.exports = getPackages 34 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/tasks/checkExistingPackages.js: -------------------------------------------------------------------------------- 1 | const { promisify } = require('util') 2 | const readFile = promisify(require('fs').readFile) 3 | 4 | // Utils 5 | const has = require('../utils/has') 6 | const removeCaret = require('../utils/removeCaret') 7 | 8 | const checkExistingPackages = async (context) => { 9 | const { dependencies } = JSON.parse(await readFile('./package.json', 'utf-8')) 10 | 11 | context.dependencies.forEach(({ name, version, skip }) => { 12 | if (skip) { 13 | return 14 | } 15 | 16 | if (!has(dependencies, name)) { 17 | return 18 | } 19 | 20 | const currentVersion = removeCaret(dependencies[name]) 21 | 22 | context.dependencies.set(name, { 23 | name, 24 | version, 25 | currentVersion, 26 | skip: (currentVersion !== version || context.reinstall) ? null : `Package ${name}@${version} already installed and is up to date` 27 | }) 28 | }) 29 | } 30 | 31 | module.exports = checkExistingPackages 32 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/tasks/collectPeerDependencies.js: -------------------------------------------------------------------------------- 1 | const semver = require('semver') 2 | const dedent = require('dedent') 3 | const packageJson = require('package-json') 4 | 5 | const collectPeerDependencies = async (context) => { 6 | if (!context.peer) { 7 | return 8 | } 9 | 10 | const { dependencies } = context 11 | 12 | const promises = [] 13 | 14 | dependencies.forEach(({ name, version, skip, peerDependencies }) => { 15 | if (skip) { 16 | return 17 | } 18 | 19 | if (!peerDependencies) { 20 | return 21 | } 22 | 23 | peerDependencies.forEach(([ peerName, peerVersion ]) => { 24 | promises.push(async () => { 25 | if (!dependencies.has(peerName)) { 26 | const meta = await packageJson(peerName, { version: peerVersion }) 27 | 28 | dependencies.set(peerName, { 29 | name: peerName, 30 | version: meta.version 31 | }) 32 | } else { 33 | const otherVersion = dependencies.get(peerName).version 34 | 35 | if (!semver.satisfies(otherVersion, peerVersion)) { 36 | throw new Error(dedent` 37 | Package "${name}@${version}" requires a peer dependency of ${peerName}@${peerVersion}, 38 | but there is another version of this dependency in the dependency tree (${peerName}@${otherVersion}), 39 | which doesn't satisfy the provided range. 40 | 41 | Check versions of the requested packages, or skip them to install the latest version of each package. 42 | `) 43 | } 44 | } 45 | }) 46 | }) 47 | }) 48 | 49 | return Promise.all(promises.map(p => p())) 50 | } 51 | 52 | 53 | module.exports = collectPeerDependencies 54 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/tasks/fetchInitialMeta.js: -------------------------------------------------------------------------------- 1 | const packageJson = require('package-json') 2 | 3 | const fetchInitialMeta = async (context) => { 4 | const promises = [] 5 | 6 | context.dependencies.forEach(({ name, version, skip }) => { 7 | if (skip) { 8 | return 9 | } 10 | 11 | promises.push(async () => { 12 | const meta = await packageJson(name, { version }) 13 | 14 | context.dependencies.set(name, { 15 | name, 16 | version: meta.version, 17 | peerDependencies: meta.peerDependencies ? Object.entries(meta.peerDependencies) : null 18 | }) 19 | }) 20 | }) 21 | 22 | return Promise.all(promises.map(p => p())) 23 | } 24 | 25 | module.exports = fetchInitialMeta 26 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/tasks/installDependencies.js: -------------------------------------------------------------------------------- 1 | const execa = require('execa') 2 | const { Observable } = require('rxjs') 3 | 4 | module.exports = (context) => { 5 | const tasks = ['install'] 6 | const deps = [] 7 | 8 | context.dependencies.forEach(({ name, version, skip }) => { 9 | if (skip) { 10 | return 11 | } 12 | 13 | deps.push(`${name}@${version}`) 14 | }) 15 | 16 | if (!context.save) { 17 | tasks.push('--no-save') 18 | } 19 | 20 | if (context.saveExact) { 21 | tasks.push('--save-exact') 22 | } 23 | 24 | return new Observable((observer) => { 25 | observer.next(`${deps.join(', ')}`) 26 | 27 | execa('npm', [...tasks, ...deps]) 28 | .then(() => { 29 | observer.next() 30 | observer.complete() 31 | }) 32 | .catch((error) => observer.error(error)) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/utils/filterSkipped.js: -------------------------------------------------------------------------------- 1 | module.exports = (packages) => { 2 | const notSkipped = new Map() 3 | const skipped = new Map() 4 | 5 | packages.forEach(p => { 6 | p.skip ? skipped.set(p.name, p) : notSkipped.set(p.name, p) 7 | }) 8 | 9 | return [notSkipped, skipped] 10 | } 11 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/utils/has.js: -------------------------------------------------------------------------------- 1 | module.exports = (o, name) => Object.prototype.hasOwnProperty.call(o, name) 2 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/utils/installationLog.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk') 2 | const { success, info, warning } = require('log-symbols') 3 | const filterSkipped = require('./filterSkipped') 4 | 5 | const installationLog = ({ dependencies, peer, save }) => { 6 | const [installed, skipped] = filterSkipped(dependencies) 7 | 8 | console.log() 9 | 10 | if (installed.size) { 11 | console.log(chalk.green('Successfully installed and/or updated packages:')) 12 | for (let [name, { version }] of installed) { 13 | console.log(' ', success, `${name}@${version}`) 14 | } 15 | } else { 16 | console.log(info, chalk.green('Nothing was installed nor updated.')) 17 | } 18 | 19 | if (skipped.size) { 20 | console.log() 21 | console.log('Installation of some packages was skipped, see details below.') 22 | for (let [name, { version, skip }] of skipped) { 23 | console.log(' ', info, `${name}@${version}: `, chalk.gray(skip)) 24 | } 25 | } 26 | 27 | console.log() 28 | 29 | if (!peer) { 30 | console.log(warning, chalk.yellow('Peer dependencies check was skipped.')) 31 | } 32 | 33 | if (!save) { 34 | console.log(warning, chalk.yellow('Installed packages were not saved to package.json.')) 35 | } 36 | } 37 | 38 | module.exports = installationLog 39 | -------------------------------------------------------------------------------- /packages/stapp-cli-tools/src/utils/removeCaret.js: -------------------------------------------------------------------------------- 1 | module.exports = v => v.startsWith('^') ? v.slice(1) : v 2 | -------------------------------------------------------------------------------- /packages/stapp-formbase/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/stapp-formbase/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/stapp-formbase/README.md: -------------------------------------------------------------------------------- 1 | # Stapp Formbase 2 | 3 | Base form module for [Stapp](https://github.com/TinkoffCreditSystems/stapp). 4 | 5 | See [documentation](https://stapp.js.org/modules/formbase.html) for more info. 6 | 7 | ## Installation 8 | ```bash 9 | npm install stapp-formbase stapp reselect 10 | # OR using stapp-cli-tools 11 | stapp install stapp-formbase 12 | ``` 13 | 14 | ### Peer dependencies 15 | * **stapp**: >= 2.6 16 | * **reselect**: >= 4 17 | 18 | ## License 19 | 20 | ``` 21 | Copyright 2019 Tinkoff Bank 22 | 23 | Licensed under the Apache License, Version 2.0 (the "License"); 24 | you may not use this file except in compliance with the License. 25 | You may obtain a copy of the License at 26 | 27 | http://www.apache.org/licenses/LICENSE-2.0 28 | 29 | Unless required by applicable law or agreed to in writing, software 30 | distributed under the License is distributed on an "AS IS" BASIS, 31 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | See the License for the specific language governing permissions and 33 | limitations under the License. 34 | ``` 35 | -------------------------------------------------------------------------------- /packages/stapp-formbase/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "collectCoverage": true, 29 | "coverageReporters": [ 30 | "json", 31 | "lcov" 32 | ], 33 | "globals": { 34 | "process.env.NODE_ENV": "test", 35 | "ts-jest": { 36 | "diagnostics": false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/stapp-formbase/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp-formbase", 3 | "version": "2.7.0-0", 4 | "description": "Base form module for stapp - modular state manager", 5 | "keywords": [ 6 | "state-management", 7 | "state", 8 | "reactive", 9 | "microservice", 10 | "form", 11 | "epics", 12 | "redux" 13 | ], 14 | "main": "lib/index.js", 15 | "typings": "lib/index.d.ts", 16 | "files": [ 17 | "lib" 18 | ], 19 | "author": "Dmitry Korolev (https://korolev.dk)", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 23 | }, 24 | "homepage": "https://stapp.js.org/modules/formbase.html", 25 | "bugs": "https://github.com/TinkoffCreditSystems/stapp/issues?q=is:issue+label:stapp-formbase", 26 | "engines": { 27 | "node": ">=8.0.0" 28 | }, 29 | "license": "Apache-2.0", 30 | "scripts": { 31 | "build": "npm run build:module", 32 | "build:module": "tsc", 33 | "prebuild": "rimraf lib", 34 | "test": "npm run test:lint && npm run test:jest", 35 | "test:lint": "tslint -t verbose './src/**/*.ts' './src/**/*.tsx' -p ./tsconfig.json -c ./tslint.json", 36 | "test:jest": "jest --config ./jest.config.json", 37 | "test:ci": "npm run test:lint && jest --config ./jest.config.json --runInBand --coverage --coverageReporters=text-lcov" 38 | }, 39 | "peerDependencies": { 40 | "reselect": ">=4", 41 | "stapp": ">=2.6" 42 | }, 43 | "devDependencies": { 44 | "@types/jest": "^24.0.11", 45 | "@types/node": "^12.0.0", 46 | "babel-jest": "^24.6.0", 47 | "babel-preset-env": "^1.7.0", 48 | "babel-preset-stage-0": "^6.24.1", 49 | "jest": "^24.6.0", 50 | "reselect": "^4.0.0", 51 | "stapp": "^2.7.0-0", 52 | "ts-jest": "^24.0.1", 53 | "ts-node": "^8.0.3", 54 | "tslib": "^1.9.3", 55 | "tslint": "^5.15.0", 56 | "tslint-config-prettier": "^1.18.0", 57 | "tslint-config-standard": "^8.0.1", 58 | "tslint-eslint-rules": "^5.4.0", 59 | "tslint-react": "^4.0.0", 60 | "typescript": "3.3.4000" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/stapp-formbase/src/constants.ts: -------------------------------------------------------------------------------- 1 | import { APP_KEY } from 'stapp/lib/helpers/constants' 2 | 3 | /** 4 | * @hidden 5 | */ 6 | export const FORM_BASE = `${APP_KEY}/formBase` 7 | -------------------------------------------------------------------------------- /packages/stapp-formbase/src/events.ts: -------------------------------------------------------------------------------- 1 | import { createEvent } from 'stapp' 2 | import { FORM_BASE } from './constants' 3 | 4 | // tslint:disable-next-line 5 | import { EmptyEventCreator, EventCreator0, EventCreator1 } from 'stapp' 6 | 7 | const u = () => undefined 8 | 9 | /** 10 | * Used to set values for fields 11 | */ 12 | export const setValue = createEvent<{ [K: string]: any }>( 13 | `${FORM_BASE}: Set field value` 14 | ) 15 | 16 | /** 17 | * Used to set meta for fields 18 | */ 19 | export const setMeta = createEvent<{ [K: string]: any }>( 20 | `${FORM_BASE}: Set field meta` 21 | ) 22 | 23 | /** 24 | * Used to set errors for fields 25 | */ 26 | export const setError = createEvent<{ [K: string]: any }>( 27 | `${FORM_BASE}: Set field error` 28 | ) 29 | 30 | /** 31 | * Used to set field as touched 32 | */ 33 | export const setTouched = createEvent<{ [K: string]: void | boolean }>( 34 | `${FORM_BASE}: Set field as touched` 35 | ) 36 | 37 | /** 38 | * Set field as active 39 | */ 40 | export const setActive = createEvent( 41 | `${FORM_BASE}: Set field as active` 42 | ) 43 | 44 | /** 45 | * Used to set readiness state 46 | */ 47 | export const setReady = createEvent<{ [K: string]: boolean }>( 48 | `${FORM_BASE}: Set readiness` 49 | ) 50 | 51 | /** 52 | * Used to clear fields data 53 | */ 54 | export const clearFields = createEvent(`${FORM_BASE}: clear fields`) 55 | 56 | /** 57 | * Used to clear fields data 58 | */ 59 | export const pickFields = createEvent(`${FORM_BASE}: pick fields`) 60 | 61 | /** 62 | * Used to reset form state 63 | */ 64 | export const resetForm = createEvent(`${FORM_BASE}: Reset form state`, u) 65 | 66 | /** 67 | * Used to indicate form submission 68 | */ 69 | export const submit = createEvent(`${FORM_BASE}: Submit`, u) 70 | 71 | export const setSubmitting = createEvent( 72 | `${FORM_BASE}: Set submitting` 73 | ) 74 | -------------------------------------------------------------------------------- /packages/stapp-formbase/src/formBase.h.ts: -------------------------------------------------------------------------------- 1 | export type FormBaseState = { 2 | values: Values 3 | meta: { [K in keyof Values]: any } 4 | errors: { [K in keyof Values]: any } 5 | touched: { [K in keyof Values]: boolean } 6 | dirty: { [K in keyof Values]: boolean } 7 | active: keyof Values | null 8 | ready: { [K: string]: boolean } 9 | pristine: boolean 10 | submitting: boolean 11 | } 12 | 13 | export type FormBaseConfig = { 14 | initialValues?: FormValues 15 | } 16 | -------------------------------------------------------------------------------- /packages/stapp-formbase/src/formBase.ts: -------------------------------------------------------------------------------- 1 | import { FORM_BASE } from './constants' 2 | import { 3 | clearFields, 4 | pickFields, 5 | resetForm, 6 | setActive, 7 | setError, 8 | setMeta, 9 | setReady, 10 | setSubmitting, 11 | setTouched, 12 | setValue, 13 | submit 14 | } from './events' 15 | import { FormBaseConfig, FormBaseState } from './formBase.h' 16 | import { createFormBaseReducers } from './reducers' 17 | 18 | // Models 19 | import { Module } from 'stapp' 20 | 21 | /** 22 | * Base form module 23 | * @typeparam FormValues Application state shape 24 | * @typeparam ReadyKeys List of fields used for readiness reducer 25 | */ 26 | export const formBase = < 27 | FormValues extends { [K: string]: any }, 28 | ErrorType = any 29 | >( 30 | config: FormBaseConfig = {} 31 | ): Module< 32 | { 33 | formBase: { 34 | clearFields: (fields: Array) => void 35 | pickFields: (fields: Array) => void 36 | resetForm: () => void 37 | setActive: (field: keyof FormValues) => void 38 | setError: (errors: { [K in keyof FormValues]?: ErrorType }) => void 39 | setReady: (ready: { [K: string]: boolean }) => void 40 | setSubmitting: (isSubmitting: boolean) => void 41 | setTouched: (touched: { [K in keyof FormValues]?: boolean }) => void 42 | setValue: (values: Partial) => void 43 | setMeta: (meta: Partial) => void 44 | submit: () => void 45 | } 46 | }, 47 | FormBaseState 48 | > => ({ 49 | name: FORM_BASE, 50 | state: createFormBaseReducers(config.initialValues || {}), 51 | api: { 52 | formBase: { 53 | clearFields, 54 | pickFields, 55 | resetForm, 56 | setActive, 57 | setError, 58 | setReady, 59 | setSubmitting, 60 | setTouched, 61 | setValue, 62 | setMeta, 63 | submit 64 | } 65 | }, 66 | useGlobalObservableConfig: false 67 | }) 68 | -------------------------------------------------------------------------------- /packages/stapp-formbase/src/helpers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @private 3 | */ 4 | export const mergeIfChanged = (o: T, n: Partial) => { 5 | const keys = Object.keys(n) as K[] 6 | const changedFields = keys.filter((field) => o[field] !== n[field]) 7 | 8 | return changedFields.length !== 0 ? Object.assign({}, o, n) : o 9 | } 10 | 11 | /** 12 | * @private 13 | */ 14 | export const replace =

    (_: any, payload: P) => payload 15 | 16 | /** 17 | * @private 18 | */ 19 | export const mapObject = ( 20 | fn: (element: T, key: string, index: number) => R, 21 | obj: O 22 | ): { [K in keyof O]: R } => { 23 | return Object.keys(obj).reduce( 24 | (result, key, index) => { 25 | result[key] = fn(obj[key], key, index) 26 | return result 27 | }, 28 | {} as any 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /packages/stapp-formbase/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | resetForm, 3 | setActive, 4 | setError, 5 | setMeta, 6 | setReady, 7 | setSubmitting, 8 | setTouched, 9 | setValue, 10 | clearFields, 11 | pickFields, 12 | submit 13 | } from './events' 14 | export { formBase } from './formBase' 15 | export { FORM_BASE } from './constants' 16 | export { 17 | isValidSelector, 18 | isReadySelector, 19 | isDirtySelector, 20 | isPristineSelector, 21 | fieldSelector, 22 | formSelector 23 | } from './selectors' 24 | 25 | // typings 26 | export { FormBaseState, FormBaseConfig } from './formBase.h' 27 | -------------------------------------------------------------------------------- /packages/stapp-formbase/src/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector, createStructuredSelector } from 'reselect' 2 | 3 | // Models 4 | import { FormBaseState } from './formBase.h' 5 | 6 | export const isValidSelector = () => { 7 | return createSelector( 8 | (state: State) => state.errors, 9 | (errors) => 10 | errors ? Object.keys(errors).every((key) => !errors[key]) : true 11 | ) 12 | } 13 | 14 | export const isReadySelector = () => { 15 | return createSelector( 16 | (state: State) => state.ready, 17 | (ready) => (ready ? Object.keys(ready).every((key) => !!ready[key]) : true) 18 | ) 19 | } 20 | 21 | export const isDirtySelector = () => { 22 | return createSelector( 23 | (state: State) => state.dirty, 24 | (dirty) => (dirty ? Object.keys(dirty).some((key) => !!dirty[key]) : false) 25 | ) 26 | } 27 | 28 | export const isPristineSelector = () => < 29 | State extends Pick 30 | >( 31 | state: State 32 | ) => state.pristine 33 | 34 | const noop = () => undefined 35 | 36 | export const fieldSelector = ( 37 | name: string, 38 | extraSelector: ((state: State) => Extra) = noop as any 39 | ) => 40 | createStructuredSelector< 41 | State, 42 | { 43 | value: any 44 | meta: any 45 | error: any 46 | dirty: boolean 47 | touched: boolean 48 | active: boolean 49 | extra: Extra 50 | } 51 | >({ 52 | value: (state: State) => (state.values ? state.values[name] : undefined), 53 | meta: (state: State) => (state.meta ? state.meta[name] : undefined), 54 | error: (state: State) => (state.errors ? state.errors[name] : undefined), 55 | dirty: (state: State) => (state.dirty ? !!state.dirty[name] : false), 56 | touched: (state: State) => (state.touched ? !!state.touched[name] : false), 57 | active: (state: State) => state.active === name, 58 | extra: extraSelector 59 | }) 60 | 61 | export const formSelector = () => 62 | createStructuredSelector({ 63 | submitting: (state: State) => 64 | !!state.submitting, 65 | valid: isValidSelector(), 66 | ready: isReadySelector(), 67 | dirty: isDirtySelector(), 68 | pristine: isPristineSelector() 69 | }) 70 | -------------------------------------------------------------------------------- /packages/stapp-formbase/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp-formbase/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-react", 8 | "tslint-config-standard", 9 | "tslint-config-prettier" 10 | ], 11 | "exclude": [ 12 | "./node_modules/**/*" 13 | ], 14 | "rules": { 15 | "prefer-for-of": false, 16 | "object-literal-sort-keys": false, 17 | "no-unused-variable": true, 18 | "interface-name": false, 19 | "max-line-length": [false], 20 | "interface-over-type-literal": false, 21 | "object-literal-key-quotes": false, 22 | "prefer-object-spread": false, 23 | "callable-types": false, 24 | 25 | "no-submodule-imports": false, 26 | "no-default-export": true, 27 | "no-magic-numbers": false, 28 | "promise-function-async": false, 29 | "member-access": [true, "no-public"], 30 | "no-implicit-dependencies": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/stapp-loaders/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/stapp-loaders/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/stapp-loaders/README.md: -------------------------------------------------------------------------------- 1 | # Stapp Loaders 2 | 3 | Loaders module for [Stapp](https://github.com/TinkoffCreditSystems/stapp). 4 | 5 | See [documentation](https://stapp.js.org/modules/loaders.html) for more info. 6 | 7 | ## Installation 8 | ```bash 9 | npm install stapp-loaders stapp reselect 10 | # OR using stapp-cli-tools 11 | stapp install stapp-loaders 12 | ``` 13 | 14 | ### Peer dependencies 15 | * **stapp**: >= 2.6 16 | * **reselect**: >= 4 17 | 18 | ## License 19 | 20 | ``` 21 | Copyright 2019 Tinkoff Bank 22 | 23 | Licensed under the Apache License, Version 2.0 (the "License"); 24 | you may not use this file except in compliance with the License. 25 | You may obtain a copy of the License at 26 | 27 | http://www.apache.org/licenses/LICENSE-2.0 28 | 29 | Unless required by applicable law or agreed to in writing, software 30 | distributed under the License is distributed on an "AS IS" BASIS, 31 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | See the License for the specific language governing permissions and 33 | limitations under the License. 34 | ``` 35 | -------------------------------------------------------------------------------- /packages/stapp-loaders/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "collectCoverage": true, 29 | "coverageReporters": [ 30 | "json", 31 | "lcov" 32 | ], 33 | "globals": { 34 | "process.env.NODE_ENV": "test", 35 | "ts-jest": { 36 | "diagnostics": false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/stapp-loaders/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp-loaders", 3 | "version": "2.7.0-0", 4 | "description": "Loaders module for stapp - modular state manager", 5 | "keywords": [ 6 | "state-management", 7 | "state", 8 | "reactive", 9 | "microservice", 10 | "epics", 11 | "redux" 12 | ], 13 | "main": "lib/index.js", 14 | "typings": "lib/index.d.ts", 15 | "files": [ 16 | "lib" 17 | ], 18 | "author": "Dmitry Korolev (https://korolev.dk)", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 22 | }, 23 | "homepage": "https://stapp.js.org/modules/loaders.html", 24 | "bugs": "https://github.com/TinkoffCreditSystems/stapp/issues?q=is:issue+label:stapp-loaders", 25 | "engines": { 26 | "node": ">=8.0.0" 27 | }, 28 | "license": "Apache-2.0", 29 | "scripts": { 30 | "build": "npm run build:module", 31 | "build:module": "tsc", 32 | "prebuild": "rimraf lib", 33 | "test": "npm run test:lint && npm run test:jest", 34 | "test:lint": "tslint -t verbose './src/**/*.ts' './src/**/*.tsx' -p ./tsconfig.json -c ./tslint.json", 35 | "test:jest": "jest --config ./jest.config.json", 36 | "test:ci": "npm run test:lint && jest --config ./jest.config.json --runInBand --coverage --coverageReporters=text-lcov" 37 | }, 38 | "peerDependencies": { 39 | "reselect": ">=4", 40 | "stapp": ">=2.6" 41 | }, 42 | "devDependencies": { 43 | "@types/jest": "^24.0.11", 44 | "@types/node": "^12.0.0", 45 | "babel-jest": "^24.6.0", 46 | "babel-preset-env": "^1.7.0", 47 | "babel-preset-stage-0": "^6.24.1", 48 | "jest": "^24.6.0", 49 | "reselect": ">=4.0.0", 50 | "stapp": "^2.7.0-0", 51 | "ts-jest": "^24.0.1", 52 | "ts-node": "^8.0.3", 53 | "tslib": "^1.9.3", 54 | "tslint": "^5.15.0", 55 | "tslint-config-prettier": "^1.18.0", 56 | "tslint-config-standard": "^8.0.1", 57 | "tslint-eslint-rules": "^5.4.0", 58 | "typescript": "3.3.4000" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/stapp-loaders/src/constants.ts: -------------------------------------------------------------------------------- 1 | import { APP_KEY } from 'stapp/lib/helpers/constants' 2 | 3 | /** 4 | * @hidden 5 | */ 6 | export const LOADERS = `${APP_KEY}/loaders` 7 | -------------------------------------------------------------------------------- /packages/stapp-loaders/src/index.ts: -------------------------------------------------------------------------------- 1 | export { loaderStart, loaderEnd, isLoadingSelector, loaders } from './loaders' 2 | export { LOADERS } from './constants' 3 | -------------------------------------------------------------------------------- /packages/stapp-loaders/src/loaders.h.ts: -------------------------------------------------------------------------------- 1 | export type LoadersState = { 2 | [K: string]: boolean 3 | } 4 | -------------------------------------------------------------------------------- /packages/stapp-loaders/src/loaders.spec.ts: -------------------------------------------------------------------------------- 1 | import { getInitialState } from 'stapp/lib/helpers/testHelpers/getInitialState/getInitialState' 2 | import { isLoadingSelector, loaderEnd, loaders, loaderStart } from './loaders' 3 | 4 | test('loaders module', () => { 5 | const loadersReducer = loaders().state!.loaders 6 | const initialState = getInitialState(loadersReducer) 7 | const selector = isLoadingSelector() 8 | 9 | const nextState1 = loadersReducer(initialState, loaderStart('test')) 10 | expect(nextState1).toEqual({ 11 | test: true 12 | }) 13 | expect(selector({ loaders: nextState1 })).toEqual(true) 14 | expect(loadersReducer(nextState1, loaderStart('test'))).toBe(nextState1) 15 | 16 | const nextState2 = loadersReducer(nextState1, loaderEnd('test')) 17 | expect(nextState2).toEqual({ 18 | test: false 19 | }) 20 | expect(selector({ loaders: nextState2 })).toEqual(false) 21 | expect(loadersReducer(nextState2, loaderEnd('test'))).toBe(nextState2) 22 | }) 23 | 24 | test('safe selector', () => { 25 | const selector = isLoadingSelector() 26 | 27 | expect(selector({})).toBe(false) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/stapp-loaders/src/loaders.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | import { createEvent, createReducer } from 'stapp' 3 | import { LOADERS } from './constants' 4 | 5 | // Models 6 | // tslint:disable-next-line 7 | import { Module, EventCreator1 } from 'stapp' 8 | import { LoadersState } from './loaders.h' 9 | // tslint:disable-next-line 10 | import { OutputSelector } from 'reselect' 11 | 12 | export const loaderStart = createEvent(`${LOADERS}: Loading start`) 13 | export const loaderEnd = createEvent(`${LOADERS}: Loading end`) 14 | 15 | const loadersReducer = createReducer({}) 16 | .on(loaderStart, (loadersState, name) => 17 | loadersState[name] 18 | ? loadersState 19 | : { 20 | ...loadersState, 21 | [name]: true 22 | } 23 | ) 24 | .on(loaderEnd, (loadersState, name) => 25 | !loadersState[name] 26 | ? loadersState 27 | : { 28 | ...loadersState, 29 | [name]: false 30 | } 31 | ) 32 | 33 | export const isLoadingSelector = () => 34 | createSelector( 35 | (state: State) => 36 | state.loaders || {}, 37 | (loadersState) => 38 | Object.keys(loadersState).filter((name) => loadersState[name]).length > 0 39 | ) 40 | 41 | export const loaders = (): Module<{}, { loaders: LoadersState }> => ({ 42 | name: LOADERS, 43 | state: { 44 | loaders: loadersReducer 45 | } 46 | }) 47 | -------------------------------------------------------------------------------- /packages/stapp-loaders/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp-loaders/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-config-standard", 8 | "tslint-config-prettier" 9 | ], 10 | "exclude": [ 11 | "./node_modules/**/*" 12 | ], 13 | "rules": { 14 | "prefer-for-of": false, 15 | "object-literal-sort-keys": false, 16 | "no-unused-variable": true, 17 | "interface-name": false, 18 | "max-line-length": [false], 19 | "interface-over-type-literal": false, 20 | "object-literal-key-quotes": false, 21 | "prefer-object-spread": false, 22 | "callable-types": false, 23 | 24 | "no-submodule-imports": false, 25 | "no-default-export": true, 26 | "no-magic-numbers": false, 27 | "promise-function-async": false, 28 | "member-access": [true, "no-public"], 29 | "no-implicit-dependencies": false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/stapp-persist/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/stapp-persist/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/stapp-persist/README.md: -------------------------------------------------------------------------------- 1 | # Stapp Persist 2 | 3 | Persistence module for [Stapp](https://github.com/TinkoffCreditSystems/stapp). 4 | 5 | See [documentation](https://stapp.js.org/modules/persist.html) for more info. 6 | 7 | ## Installation 8 | ```bash 9 | npm install stapp-persist stapp rxjs 10 | # OR using stapp-cli-tools 11 | stapp install stapp-persist 12 | ``` 13 | 14 | ### Peer dependencies 15 | * **stapp**: >= 2.6 16 | * **rxjs**: >= 6 17 | 18 | ## License 19 | 20 | ``` 21 | Copyright 2019 Tinkoff Bank 22 | 23 | Licensed under the Apache License, Version 2.0 (the "License"); 24 | you may not use this file except in compliance with the License. 25 | You may obtain a copy of the License at 26 | 27 | http://www.apache.org/licenses/LICENSE-2.0 28 | 29 | Unless required by applicable law or agreed to in writing, software 30 | distributed under the License is distributed on an "AS IS" BASIS, 31 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | See the License for the specific language governing permissions and 33 | limitations under the License. 34 | ``` 35 | -------------------------------------------------------------------------------- /packages/stapp-persist/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "collectCoverage": true, 29 | "coverageReporters": [ 30 | "json", 31 | "lcov" 32 | ], 33 | "globals": { 34 | "process.env.NODE_ENV": "test", 35 | "ts-jest": { 36 | "diagnostics": false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/stapp-persist/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp-persist", 3 | "version": "2.7.0-0", 4 | "description": "Persistence module for stapp - module state manager", 5 | "keywords": [ 6 | "state-management", 7 | "state", 8 | "reactive", 9 | "microservice", 10 | "epics", 11 | "persistence", 12 | "localStorage", 13 | "redux" 14 | ], 15 | "main": "lib/index.js", 16 | "typings": "lib/index.d.ts", 17 | "files": [ 18 | "lib" 19 | ], 20 | "author": "Dmitry Korolev (https://korolev.dk)", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 24 | }, 25 | "homepage": "https://stapp.js.org/modules/persist.html", 26 | "bugs": "https://github.com/TinkoffCreditSystems/stapp/issues?q=is:issue+label:stapp-persist", 27 | "engines": { 28 | "node": ">=8.0.0" 29 | }, 30 | "license": "Apache-2.0", 31 | "scripts": { 32 | "build": "npm run build:module", 33 | "build:module": "tsc", 34 | "prebuild": "rimraf lib", 35 | "test": "npm run test:lint && npm run test:jest", 36 | "test:lint": "tslint -t verbose './src/**/*.ts' './src/**/*.tsx' -p ./tsconfig.json -c ./tslint.json", 37 | "test:jest": "jest --config ./jest.config.json", 38 | "test:ci": "npm run test:lint && jest --config ./jest.config.json --runInBand --coverage --coverageReporters=text-lcov" 39 | }, 40 | "peerDependencies": { 41 | "rxjs": ">=6", 42 | "stapp": ">=2.5" 43 | }, 44 | "dependencies": { 45 | "fbjs": ">=1" 46 | }, 47 | "devDependencies": { 48 | "@types/jest": "^24.0.11", 49 | "@types/node": "^12.0.0", 50 | "babel-jest": "^24.6.0", 51 | "babel-preset-env": "^1.7.0", 52 | "babel-preset-stage-0": "^6.24.1", 53 | "jest": "^24.6.0", 54 | "rxjs": ">= 6", 55 | "stapp": "^2.7.0-0", 56 | "ts-jest": "^24.0.1", 57 | "ts-node": "^8.0.3", 58 | "tslib": "^1.9.3", 59 | "tslint": "^5.15.0", 60 | "tslint-config-prettier": "^1.18.0", 61 | "tslint-config-standard": "^8.0.1", 62 | "tslint-eslint-rules": "^5.4.0", 63 | "tslint-react": "^4.0.0", 64 | "typescript": "3.3.4000" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/stapp-persist/src/constants.ts: -------------------------------------------------------------------------------- 1 | import { APP_KEY } from 'stapp/lib/helpers/constants' 2 | 3 | export const PERSIST = `${APP_KEY}/persist` 4 | 5 | /** 6 | * @private 7 | */ 8 | export const DEFAULT_TIMEOUT = 5000 9 | -------------------------------------------------------------------------------- /packages/stapp-persist/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'fbjs/lib/shallowEqual' { 2 | export default function(a: any, b: any): boolean 3 | } 4 | -------------------------------------------------------------------------------- /packages/stapp-persist/src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { omit } from 'stapp/lib/helpers/omit/omit' 2 | import { pick as corePick } from 'stapp/lib/helpers/pick/pick' 3 | 4 | // Models 5 | import { Transform } from './persist.h' 6 | 7 | /** 8 | * @private 9 | */ 10 | export const defaultSerialize = (data: any) => JSON.stringify(data) 11 | 12 | /** 13 | * @private 14 | */ 15 | export const defaultDeserialize = (data: string) => JSON.parse(data) 16 | 17 | /** 18 | * @private 19 | */ 20 | export const pick = (blackList?: string[], whiteList?: string[]) => ( 21 | data: any 22 | ): any => { 23 | if (whiteList) { 24 | return corePick(data, whiteList) 25 | } 26 | 27 | if (blackList) { 28 | return omit(data, blackList) 29 | } 30 | 31 | return data 32 | } 33 | 34 | /** 35 | * @private 36 | */ 37 | export const mapTransform = (transforms: Transform[]) => (data: any) => { 38 | return Object.keys(data).reduce( 39 | (result, key) => { 40 | result[key] = transforms.reduce((subState, transform) => { 41 | return transform.in(subState, key, data) 42 | }, data[key]) 43 | 44 | return result 45 | }, 46 | {} as any 47 | ) 48 | } 49 | 50 | /** 51 | * @private 52 | */ 53 | export const mapTransformRight = (transforms: Transform[]) => (data: any) => { 54 | return Object.keys(data).reduce( 55 | (result, key) => { 56 | result[key] = transforms.reduceRight((subState, transform) => { 57 | return transform.out(subState, key, data) 58 | }, data[key]) 59 | 60 | return result 61 | }, 62 | {} as any 63 | ) 64 | } 65 | 66 | /** 67 | * @private 68 | */ 69 | export const defaultMerge = (restoredState: any, originalState: any) => 70 | Object.assign({}, originalState, restoredState) 71 | -------------------------------------------------------------------------------- /packages/stapp-persist/src/index.ts: -------------------------------------------------------------------------------- 1 | export { persist } from './persist' 2 | export { toAsync } from './toAsync' 3 | export { PERSIST } from './constants' 4 | export { AsyncStorage, PersistConfig, Transform } from './persist.h' 5 | -------------------------------------------------------------------------------- /packages/stapp-persist/src/persist.h.ts: -------------------------------------------------------------------------------- 1 | export type AsyncStorage = { 2 | getItem(key: string): Promise 3 | removeItem(key: string): Promise 4 | setItem(key: string, data: string): Promise 5 | } 6 | 7 | /** 8 | * Persist module config 9 | * @typeparam State Application state shape 10 | */ 11 | export type PersistConfig = { 12 | key: string 13 | storage: AsyncStorage 14 | whiteList?: string[] 15 | blackList?: string[] 16 | transforms?: Transform[] 17 | throttle?: number 18 | stateReconciler?: ((restoredState: State, originalState: State) => State) | false 19 | serialize?: boolean 20 | timeout?: number 21 | } 22 | 23 | export type Transform = { 24 | in: (subState: any, key?: string, state?: any) => any 25 | out: (subState: any, key?: string, state?: any) => any 26 | config?: PersistConfig 27 | } 28 | -------------------------------------------------------------------------------- /packages/stapp-persist/src/toAsync.ts: -------------------------------------------------------------------------------- 1 | import { AsyncStorage } from './persist.h' 2 | 3 | /** 4 | * Converts synchronous storage into asynchronous 5 | * @param storage any localStorage or sessionStorage compatible object 6 | * @returns {AsyncStorage} 7 | */ 8 | export const toAsync = (storage: { 9 | getItem: (key: string) => any 10 | setItem: (key: string, data: string) => any 11 | removeItem: (key: string) => void 12 | }): AsyncStorage => ({ 13 | getItem(key) { 14 | return Promise.resolve(storage.getItem(key)) 15 | }, 16 | setItem(key, data) { 17 | return Promise.resolve(storage.setItem(key, data)) 18 | }, 19 | removeItem(key) { 20 | return Promise.resolve(storage.removeItem(key)) 21 | } 22 | }) 23 | -------------------------------------------------------------------------------- /packages/stapp-persist/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp-persist/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-react", 8 | "tslint-config-standard", 9 | "tslint-config-prettier" 10 | ], 11 | "exclude": [ 12 | "./node_modules/**/*" 13 | ], 14 | "rules": { 15 | "prefer-for-of": false, 16 | "object-literal-sort-keys": false, 17 | "no-unused-variable": true, 18 | "interface-name": false, 19 | "max-line-length": [false], 20 | "interface-over-type-literal": false, 21 | "object-literal-key-quotes": false, 22 | "prefer-object-spread": false, 23 | "callable-types": false, 24 | 25 | "no-submodule-imports": false, 26 | "no-default-export": true, 27 | "no-magic-numbers": false, 28 | "promise-function-async": false, 29 | "member-access": [true, "no-public"], 30 | "no-implicit-dependencies": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/stapp-react-hooks/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/README.md: -------------------------------------------------------------------------------- 1 | # Stapp React Hooks 2 | 3 | React hooks for [Stapp](https://github.com/TinkoffCreditSystems/stapp) here. 4 | 5 | See [documentation](https://stapp.js.org/guides/react.html) for more info. 6 | 7 | ## Installation 8 | ```bash 9 | npm install stapp-react-hooks stapp react stapp-formbase 10 | # OR using stapp-cli-tools 11 | stapp install stapp-cli-tools 12 | ``` 13 | 14 | ### Peer dependencies 15 | * **react**: >= 16.8 16 | * **stapp**: >= 2.6 17 | * **stapp-formbase**: >= 2.6 18 | 19 | ## License 20 | 21 | ``` 22 | Copyright 2019 Tinkoff Bank 23 | 24 | Licensed under the Apache License, Version 2.0 (the "License"); 25 | you may not use this file except in compliance with the License. 26 | You may obtain a copy of the License at 27 | 28 | http://www.apache.org/licenses/LICENSE-2.0 29 | 30 | Unless required by applicable law or agreed to in writing, software 31 | distributed under the License is distributed on an "AS IS" BASIS, 32 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33 | See the License for the specific language governing permissions and 34 | limitations under the License. 35 | ``` 36 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "collectCoverage": true, 29 | "coverageReporters": [ 30 | "json", 31 | "lcov" 32 | ], 33 | "globals": { 34 | "process.env.NODE_ENV": "test" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp-react-hooks", 3 | "version": "2.7.0-0", 4 | "description": "React hook-based bindings for stapp - modular state manager", 5 | "keywords": [ 6 | "state-management", 7 | "stapp", 8 | "state", 9 | "reactive", 10 | "microservice", 11 | "epics", 12 | "redux", 13 | "react", 14 | "hooks" 15 | ], 16 | "main": "lib/index.js", 17 | "typings": "lib/index.d.ts", 18 | "files": [ 19 | "lib" 20 | ], 21 | "author": "Dmitry Korolev (https://korolev.dk)", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 25 | }, 26 | "homepage": "https://stapp.js.org/modules/persist.html", 27 | "bugs": "https://github.com/TinkoffCreditSystems/stapp/issues?q=is:issue+label:stapp-persist", 28 | "engines": { 29 | "node": ">=8.0.0" 30 | }, 31 | "license": "Apache-2.0", 32 | "scripts": { 33 | "build": "npm run build:module", 34 | "build:module": "tsc", 35 | "prebuild": "rimraf lib", 36 | "update-all": "lernaupdate", 37 | "test": "npm run test:lint && npm run test:jest", 38 | "test:lint": "tslint -t verbose './src/**/*.ts' './src/**/*.tsx' -p ./tsconfig.json -c ./tslint.json", 39 | "test:jest": "jest --config ./jest.config.json", 40 | "test:ci": "npm run test:lint && jest --config ./jest.config.json --runInBand --coverage --coverageReporters=text-lcov" 41 | }, 42 | "peerDependencies": { 43 | "react": ">=16.8", 44 | "stapp": ">=2.6", 45 | "stapp-formbase": ">=2.6" 46 | }, 47 | "devDependencies": { 48 | "@types/jest": "^24.0.11", 49 | "@types/node": "^11.13.0", 50 | "@types/react": "^16.8.10", 51 | "babel-jest": "^24.6.0", 52 | "babel-preset-env": "^1.7.0", 53 | "babel-preset-stage-0": "^6.24.1", 54 | "jest": "^24.6.0", 55 | "react": "^16.8.6", 56 | "react-dom": "^16.8.6", 57 | "react-testing-library": "^6.0.4", 58 | "stapp": "^2.7.0-0", 59 | "stapp-formbase": "^2.7.0-0", 60 | "ts-jest": "^24.0.1", 61 | "ts-node": "^8.0.3", 62 | "tslib": "^1.9.3", 63 | "tslint": "^5.15.0", 64 | "tslint-config-prettier": "^1.18.0", 65 | "tslint-config-standard": "^8.0.1", 66 | "tslint-eslint-rules": "^5.4.0", 67 | "tslint-react": "^4.0.0", 68 | "typescript": "3.3.4000" 69 | }, 70 | "dependencies": { 71 | "fbjs": "^1.0.0", 72 | "stapp-react": "^2.7.0-0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/src/helpers/constants.ts: -------------------------------------------------------------------------------- 1 | import { APP_KEY } from 'stapp/lib/helpers/constants' 2 | 3 | export const STAPP_REACT_HOOKS = `${APP_KEY}/react-hooks` 4 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/src/index.ts: -------------------------------------------------------------------------------- 1 | export { useApi } from './use/useApi' 2 | export { useStapp } from './use/useStapp' 3 | export { useForm } from './use/useForm' 4 | export { useField } from './use/useField' 5 | export { Provider } from 'stapp-react/lib/context/Provider' 6 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/src/models/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'fbjs/lib/shallowEqual' { 2 | export default function shallowEqual(objectA: any, objectB: any): boolean 3 | } 4 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/src/use/useApi.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { Stapp, StappApi } from 'stapp' 3 | import { StappContext } from 'stapp-react/lib/shared/StappContext' 4 | import { STAPP_REACT_HOOKS } from '../helpers/constants' 5 | 6 | export const useApi = >(): StappApi => { 7 | const app = useContext(StappContext) 8 | 9 | /* istanbul ignore next */ 10 | if (!app) { 11 | throw new Error(`${STAPP_REACT_HOOKS} error: Provider missing!`) 12 | } 13 | 14 | return app.api 15 | } 16 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/src/use/useField.ts: -------------------------------------------------------------------------------- 1 | import { SyntheticEvent } from 'react' 2 | import { Stapp, StappApi, StappState } from 'stapp' 3 | import { fieldSelector } from 'stapp-formbase' 4 | import { FieldApi } from 'stapp-react' 5 | import { useStapp } from './useStapp' 6 | 7 | export const useField = , Extra = any>( 8 | name: string, 9 | extraSelector?: (state: StappState) => Extra 10 | ): [FieldApi, StappApi, App] => { 11 | const [fieldState, api, app] = useStapp(fieldSelector(name, extraSelector)) 12 | 13 | const onChange = (event: SyntheticEvent) => { 14 | api.formBase.setValue({ 15 | [name]: event.currentTarget.value 16 | }) 17 | } 18 | 19 | const onBlur = () => { 20 | api.formBase.setActive(null) 21 | api.formBase.setTouched({ [name]: true }) 22 | } 23 | 24 | const onFocus = () => { 25 | api.formBase.setActive(name) 26 | } 27 | 28 | return [ 29 | { 30 | input: { 31 | name, 32 | value: fieldState.value || '', 33 | onChange, 34 | onBlur, 35 | onFocus 36 | }, 37 | meta: { 38 | error: fieldState.error, 39 | touched: fieldState.touched, 40 | active: fieldState.active, 41 | dirty: fieldState.dirty 42 | }, 43 | extra: fieldState.extra 44 | }, 45 | api, 46 | app 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/src/use/useForm.ts: -------------------------------------------------------------------------------- 1 | import { SyntheticEvent } from 'react' 2 | import { Stapp, StappApi } from 'stapp' 3 | import { formSelector } from 'stapp-formbase' 4 | import { FormApi } from 'stapp-react' 5 | import { useStapp } from './useStapp' 6 | 7 | export const useForm = >(): [ 8 | FormApi, 9 | StappApi, 10 | App 11 | ] => { 12 | const [formData, api, app] = useStapp(formSelector()) 13 | 14 | const handleSubmit = (syntheticEvent: SyntheticEvent) => { 15 | if ( 16 | syntheticEvent && 17 | // tslint:disable-next-line strict-type-predicates 18 | typeof syntheticEvent.preventDefault === 'function' 19 | ) { 20 | syntheticEvent.preventDefault() 21 | } 22 | 23 | api.formBase.submit() 24 | } 25 | 26 | return [ 27 | { 28 | handleSubmit, 29 | handleReset: api.formBase.resetForm, 30 | submitting: formData.submitting, 31 | valid: formData.valid, 32 | ready: formData.ready, 33 | dirty: formData.dirty, 34 | pristine: formData.pristine 35 | }, 36 | api, 37 | app 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/src/use/useStapp.ts: -------------------------------------------------------------------------------- 1 | import shallowEqual from 'fbjs/lib/shallowEqual' 2 | import { useContext, useEffect, useState } from 'react' 3 | import { Stapp } from 'stapp' 4 | import { StappContext } from 'stapp-react/lib/shared/StappContext' 5 | import { StappApi, StappState } from 'stapp/lib/core/createApp/createApp.h' 6 | import { identity } from 'stapp/lib/helpers/identity/identity' 7 | import { STAPP_REACT_HOOKS } from '../helpers/constants' 8 | 9 | type Selector, Result> = ( 10 | state: StappState, 11 | api: StappApi, 12 | app: T 13 | ) => Result 14 | 15 | export const useStapp = , Result = StappState>( 16 | selector: Selector = identity as any 17 | ): [Result, StappApi, T] => { 18 | const app = useContext(StappContext as any) 19 | 20 | /* istanbul ignore next */ 21 | if (!app) { 22 | throw new Error(`${STAPP_REACT_HOOKS} error: Provider missing!`) 23 | } 24 | 25 | const [state, setState] = useState(selector(app.getState(), app.api, app)) 26 | 27 | useEffect( 28 | () => { 29 | const subscription = app.subscribe((_nextState) => { 30 | const nextState = selector(_nextState, app.api, app) 31 | 32 | if (!shallowEqual(state, nextState)) { 33 | setState(nextState) 34 | } 35 | }) 36 | 37 | return () => subscription.unsubscribe() 38 | }, 39 | [app] 40 | ) 41 | 42 | return [state, app.api, app] 43 | } 44 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp-react-hooks/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-react", 8 | "tslint-config-standard", 9 | "tslint-config-prettier" 10 | ], 11 | "exclude": [ 12 | "./node_modules/**/*" 13 | ], 14 | "rules": { 15 | "prefer-for-of": false, 16 | "object-literal-sort-keys": false, 17 | "no-unused-variable": true, 18 | "interface-name": false, 19 | "max-line-length": [false], 20 | "interface-over-type-literal": false, 21 | "object-literal-key-quotes": false, 22 | "prefer-object-spread": false, 23 | "callable-types": false, 24 | 25 | "no-submodule-imports": false, 26 | "no-default-export": true, 27 | "no-magic-numbers": false, 28 | "promise-function-async": false, 29 | "member-access": [true, "no-public"], 30 | "no-implicit-dependencies": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/stapp-react/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/stapp-react/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/stapp-react/README.md: -------------------------------------------------------------------------------- 1 | # Stapp React 2 | 3 | React bindings for [Stapp](https://github.com/TinkoffCreditSystems/stapp). 4 | 5 | See [documentation](https://stapp.js.org/guides/react.html) for more info. 6 | 7 | ## Installation 8 | ```bash 9 | npm install stapp-react stapp stapp-formbase react rxjs 10 | # OR using stapp-cli-tools 11 | stapp install stapp-react 12 | ``` 13 | 14 | ### Peer dependencies 15 | * **stapp**: >= 2.6 16 | * **stapp-formbase**: >= 2.6 17 | * **react**: >= 16 18 | * **rxjs**: >= 6 19 | 20 | ## License 21 | 22 | ``` 23 | Copyright 2019 Tinkoff Bank 24 | 25 | Licensed under the Apache License, Version 2.0 (the "License"); 26 | you may not use this file except in compliance with the License. 27 | You may obtain a copy of the License at 28 | 29 | http://www.apache.org/licenses/LICENSE-2.0 30 | 31 | Unless required by applicable law or agreed to in writing, software 32 | distributed under the License is distributed on an "AS IS" BASIS, 33 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | See the License for the specific language governing permissions and 35 | limitations under the License. 36 | ``` 37 | -------------------------------------------------------------------------------- /packages/stapp-react/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "setupFiles": [ 29 | "/testSetup.ts" 30 | ], 31 | "collectCoverage": true, 32 | "coverageReporters": [ 33 | "json", 34 | "lcov" 35 | ], 36 | "globals": { 37 | "process.env.NODE_ENV": "test", 38 | "ts-jest": { 39 | "diagnostics": false 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/stapp-react/src/binded/createApi.ts: -------------------------------------------------------------------------------- 1 | import { FunctionComponent } from 'react' 2 | import { renderPropType } from '../helpers/propTypes' 3 | import { renderComponent } from '../helpers/renderComponent' 4 | 5 | // Models 6 | import { Stapp } from 'stapp' 7 | import { ApiProps } from '../models/Props' 8 | 9 | export const createApi = ( 10 | app: Stapp, 11 | name: string = 'Stapp' 12 | ): FunctionComponent> => { 13 | const Api = (props: ApiProps) => { 14 | return renderComponent({ 15 | name: 'Api', 16 | renderProps: props, 17 | renderArgs: [app.api, app], 18 | componentProps: { api: app.api, app } 19 | }) 20 | } 21 | ;(Api as FunctionComponent).displayName = `${name}.Api` 22 | ;(Api as FunctionComponent).propTypes = { 23 | render: renderPropType, 24 | children: renderPropType, 25 | component: renderPropType 26 | } 27 | 28 | return Api 29 | } 30 | -------------------------------------------------------------------------------- /packages/stapp-react/src/binded/createComponents.ts: -------------------------------------------------------------------------------- 1 | import { createConsume } from './createConsume' 2 | import { createConsumer } from './createConsumer' 3 | import { createField } from './createField' 4 | import { createForm } from './createForm' 5 | 6 | // Models 7 | import { FunctionComponent } from 'react' 8 | import { Stapp } from 'stapp' 9 | import { FormBaseState } from 'stapp-formbase' 10 | import { ConsumeHoc } from '../models/ConsumeHoc' 11 | import { FormApi } from '../models/Form' 12 | import { ApiProps, FieldProps, RenderProps } from '../models/Props' 13 | import { createApi } from './createApi' 14 | 15 | export const createComponents = ( 16 | app: Stapp 17 | ) => { 18 | const Consumer = createConsumer(app, app.name) 19 | let consume: ConsumeHoc 20 | let Api: FunctionComponent> 21 | let Form: FunctionComponent> 22 | let Field: FunctionComponent> 23 | 24 | return { 25 | Consumer, 26 | 27 | get Api() { 28 | return Api || (Api = createApi(app, app.name)) 29 | }, 30 | 31 | get consume() { 32 | return consume || (consume = createConsume(Consumer, app.name)) 33 | }, 34 | 35 | get Form() { 36 | return Form || (Form = createForm(Consumer, app.name)) 37 | }, 38 | 39 | get Field() { 40 | return Field || (Field = createField(Consumer, app.name)) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/stapp-react/src/binded/createConsume.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentClass, ComponentType, createElement } from 'react' 2 | import { identity } from 'stapp/lib/helpers/identity/identity' 3 | import { getDisplayName } from '../helpers/getDisplayName' 4 | 5 | // Models 6 | import { Stapp } from 'stapp' 7 | import { ConsumeHoc } from '../models/ConsumeHoc' 8 | import { ConsumerProps } from '../models/Props' 9 | 10 | /** 11 | * Creates higher order component, that passes state and api from a Stapp application 12 | * to a wrapped component 13 | */ 14 | export const createConsume = ( 15 | Consumer: ComponentClass>, 16 | name: string = 'Stapp' 17 | ): ConsumeHoc => { 18 | return (( 19 | map: (state: State, api: Api, props: any) => Result = identity as any 20 | ) => (WrappedComponent: ComponentType>) => { 21 | return class Consume extends Component { 22 | static displayName = `${name}.Consume(${getDisplayName( 23 | WrappedComponent 24 | )})` 25 | 26 | mapState = (state: State, api: Api) => map(state, api, this.props) 27 | 28 | render() { 29 | return createElement(Consumer, { 30 | map: this.mapState, 31 | render: (result: Result, api: Api, app: Stapp) => { 32 | return createElement( 33 | WrappedComponent as any, 34 | Object.assign({}, this.props, { api, app }, result) 35 | ) 36 | } 37 | }) 38 | } 39 | } 40 | }) as any 41 | } 42 | 43 | /** 44 | * @hidden 45 | */ 46 | export const createInject = createConsume 47 | -------------------------------------------------------------------------------- /packages/stapp-react/src/binded/createConsumer.ts: -------------------------------------------------------------------------------- 1 | import { Component, ComponentClass, createElement } from 'react' 2 | import { consumerPropTypes } from '../helpers/propTypes' 3 | import { StappSubscription } from '../helpers/StappSubscription' 4 | 5 | // Models 6 | import { Stapp } from 'stapp' 7 | import { ConsumerProps } from '../models/Props' 8 | 9 | export const createConsumer = ( 10 | app: Stapp, 11 | name: string = 'Stapp' 12 | ): ComponentClass> => { 13 | return class Consumer extends Component> { 14 | static displayName = `${name}.Consumer` 15 | static propTypes: any = consumerPropTypes 16 | 17 | render() { 18 | return createElement( 19 | StappSubscription, 20 | Object.assign( 21 | { 22 | app 23 | }, 24 | this.props as any 25 | ) 26 | ) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/stapp-react/src/binded/createField.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line no-unused-variable 2 | import React, { 3 | ComponentClass, 4 | createElement, 5 | FunctionComponent, 6 | SyntheticEvent 7 | } from 'react' 8 | import { fieldSelector, FormBaseState } from 'stapp-formbase' 9 | 10 | // Models 11 | // tslint:disable-next-line no-unused-variable 12 | import { Stapp } from 'stapp' 13 | import { renderComponent } from '../helpers/renderComponent' 14 | import { ConsumerProps, FieldProps } from '../models/Props' 15 | 16 | /** 17 | * Creates react form helper. 18 | * See examples in the examples folder. 19 | */ 20 | export const createField = ( 21 | Consumer: ComponentClass>, 22 | appName: string = 'Stapp' 23 | ): FunctionComponent> => { 24 | const Field = (props: FieldProps) => { 25 | const selector = fieldSelector(props.name, props.extraSelector) 26 | 27 | const handleChange = (api: any) => ( 28 | event: SyntheticEvent 29 | ) => { 30 | api.formBase.setValue({ 31 | [props.name]: event.currentTarget.value 32 | }) 33 | } 34 | 35 | const handleBlur = (api: any, active: boolean) => () => { 36 | active && api.formBase.setActive(null) 37 | api.formBase.setTouched({ [props.name]: true }) 38 | } 39 | 40 | const handleFocus = (api: any) => () => { 41 | api.formBase.setActive(props.name) 42 | } 43 | 44 | return createElement(Consumer, { 45 | map: selector, 46 | children: ( 47 | fieldState: ReturnType, 48 | api: any, 49 | app: Stapp 50 | ) => { 51 | const result = { 52 | input: { 53 | name: props.name, 54 | value: fieldState.value || '', 55 | onChange: handleChange(api), 56 | onBlur: handleBlur(api, fieldState.active), 57 | onFocus: handleFocus(api) 58 | }, 59 | meta: { 60 | error: fieldState.error, 61 | touched: fieldState.touched, 62 | active: fieldState.active, 63 | dirty: fieldState.dirty 64 | }, 65 | extra: fieldState.extra 66 | } 67 | 68 | return renderComponent({ 69 | name: 'Field', 70 | renderProps: props, 71 | renderArgs: [result, api, app], 72 | componentProps: Object.assign({ api, app }, result) 73 | }) 74 | } 75 | }) 76 | } 77 | ;(Field as FunctionComponent).displayName = `${appName}.Field` 78 | 79 | return Field 80 | } 81 | -------------------------------------------------------------------------------- /packages/stapp-react/src/binded/createForm.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line no-unused-variable 2 | import React, { 3 | ComponentClass, 4 | createElement, 5 | FunctionComponent, 6 | SyntheticEvent 7 | } from 'react' 8 | import { formSelector } from 'stapp-formbase' 9 | import { renderComponent } from '../helpers/renderComponent' 10 | import { simpleMemoize } from '../helpers/simpleMemoize' 11 | 12 | // Models 13 | import { Stapp } from 'stapp' 14 | import { FormApi } from '../models/Form' 15 | import { ConsumerProps, RenderProps } from '../models/Props' 16 | 17 | /** 18 | * Creates react form helpers 19 | */ 20 | export const createForm = ( 21 | Consumer: ComponentClass>, 22 | name: string = 'Stapp' 23 | ): FunctionComponent> => { 24 | const formDataSelector = formSelector() 25 | 26 | const handle = simpleMemoize( 27 | (fn: () => void) => (syntheticEvent: SyntheticEvent) => { 28 | // preventDefault might not exist in some environments (React Native e.g.) 29 | /* istanbul ignore next */ 30 | if ( 31 | syntheticEvent && 32 | // tslint:disable-next-line strict-type-predicates 33 | typeof syntheticEvent.preventDefault === 'function' 34 | ) { 35 | syntheticEvent.preventDefault() 36 | } 37 | 38 | fn() 39 | } 40 | ) 41 | 42 | const Form = (props: RenderProps) => { 43 | return createElement(Consumer, { 44 | map: formDataSelector, 45 | render: ( 46 | formData: ReturnType, 47 | api: any, 48 | app: Stapp 49 | ) => { 50 | const result = { 51 | handleSubmit: handle(api.formBase.submit), 52 | handleReset: handle(api.formBase.resetForm), 53 | submitting: formData.submitting, 54 | valid: formData.valid, 55 | ready: formData.ready, 56 | dirty: formData.dirty, 57 | pristine: formData.pristine 58 | } 59 | 60 | return renderComponent({ 61 | name: 'Form', 62 | renderProps: props, 63 | renderArgs: [result, api, app], 64 | componentProps: Object.assign({ api, app }, result) 65 | }) 66 | } 67 | }) 68 | } 69 | ;(Form as FunctionComponent).displayName = `${name}.Form` 70 | 71 | return Form 72 | } 73 | -------------------------------------------------------------------------------- /packages/stapp-react/src/context/Api.ts: -------------------------------------------------------------------------------- 1 | import { createElement, FunctionComponent } from 'react' 2 | import { STAPP_REACT } from '../helpers/constants' 3 | import { renderPropType } from '../helpers/propTypes' 4 | import { renderComponent } from '../helpers/renderComponent' 5 | 6 | // Models 7 | import { Stapp } from 'stapp' 8 | import { ApiProps } from '../models/Props' 9 | import { StappContext } from '../shared/StappContext' 10 | 11 | export const Api = (props: ApiProps) => { 12 | return createElement(StappContext.Consumer, { 13 | children(app: Stapp) { 14 | /* istanbul ignore next */ 15 | if (!app) { 16 | throw new Error(`${STAPP_REACT} error: Provider missing!`) 17 | } 18 | 19 | return renderComponent({ 20 | name: 'Api', 21 | renderProps: props, 22 | renderArgs: [app.api, app], 23 | componentProps: { api: app.api, app } 24 | }) 25 | } 26 | }) 27 | } 28 | ;(Api as FunctionComponent).displayName = 'Stapp.Provider' 29 | ;(Api as FunctionComponent).propTypes = { 30 | render: renderPropType, 31 | children: renderPropType, 32 | component: renderPropType 33 | } 34 | -------------------------------------------------------------------------------- /packages/stapp-react/src/context/Consumer.ts: -------------------------------------------------------------------------------- 1 | import { Component, createElement } from 'react' 2 | import { Stapp } from 'stapp' 3 | import { STAPP_REACT } from '../helpers/constants' 4 | import { consumerPropTypes } from '../helpers/propTypes' 5 | import { StappSubscription } from '../helpers/StappSubscription' 6 | 7 | // Models 8 | import { ConsumerProps } from '../models/Props' 9 | import { StappContext } from '../shared/StappContext' 10 | 11 | export class Consumer extends Component< 12 | ConsumerProps 13 | > { 14 | static displayName = 'Stapp.Consumer' 15 | static propTypes: any = consumerPropTypes 16 | 17 | render() { 18 | return createElement(StappContext.Consumer, { 19 | children: (app: Stapp) => { 20 | /* istanbul ignore next */ 21 | if (!app) { 22 | throw new Error(`${STAPP_REACT} error: Provider missing!`) 23 | } 24 | 25 | return createElement( 26 | StappSubscription, 27 | Object.assign( 28 | { 29 | app 30 | }, 31 | this.props as any 32 | ) 33 | ) 34 | } 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/stapp-react/src/context/Field.ts: -------------------------------------------------------------------------------- 1 | import { createField } from '../binded/createField' 2 | import { Consumer } from './Consumer' 3 | 4 | export const Field = createField(Consumer) 5 | -------------------------------------------------------------------------------- /packages/stapp-react/src/context/Form.ts: -------------------------------------------------------------------------------- 1 | import { createForm } from '../binded/createForm' 2 | import { Consumer } from './Consumer' 3 | 4 | export const Form = createForm(Consumer) 5 | -------------------------------------------------------------------------------- /packages/stapp-react/src/context/Provider.ts: -------------------------------------------------------------------------------- 1 | import { createElement, FunctionComponent, ReactNode } from 'react' 2 | import { Stapp } from 'stapp' 3 | import { StappContext } from '../shared/StappContext' 4 | 5 | export const Provider = ({ 6 | app, 7 | children 8 | }: { 9 | app: Stapp 10 | children?: ReactNode 11 | }) => 12 | createElement(StappContext.Provider, { 13 | value: app, 14 | children 15 | }) 16 | ;(Provider as FunctionComponent).displayName = 'Stapp.Provider' 17 | -------------------------------------------------------------------------------- /packages/stapp-react/src/context/consume.ts: -------------------------------------------------------------------------------- 1 | import { createConsume } from '../binded/createConsume' 2 | import { Consumer } from './Consumer' 3 | 4 | export const consume = createConsume(Consumer) 5 | export const inject = consume 6 | -------------------------------------------------------------------------------- /packages/stapp-react/src/helpers/constants.ts: -------------------------------------------------------------------------------- 1 | import { APP_KEY } from 'stapp/lib/helpers/constants' 2 | 3 | export const STAPP_REACT = `${APP_KEY}/React` 4 | -------------------------------------------------------------------------------- /packages/stapp-react/src/helpers/getDisplayName.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line no-unused-variable // Needed for declarations 2 | import React, { ComponentType } from 'react' 3 | 4 | /** 5 | * @private 6 | */ 7 | export const getDisplayName = (Component: ComponentType): string => 8 | Component.displayName || Component.name || 'Unknown' 9 | -------------------------------------------------------------------------------- /packages/stapp-react/src/helpers/propTypes.ts: -------------------------------------------------------------------------------- 1 | import pt from 'prop-types' 2 | 3 | /** 4 | * @private 5 | */ 6 | export const renderPropType = pt.func 7 | 8 | /** 9 | * @private 10 | */ 11 | export const selectorType = pt.func 12 | 13 | /** 14 | * @private 15 | */ 16 | export const consumerPropTypes = { 17 | render: renderPropType, 18 | children: renderPropType, 19 | component: renderPropType, 20 | map: pt.func 21 | } 22 | -------------------------------------------------------------------------------- /packages/stapp-react/src/helpers/renderComponent.spec.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Stapp } from 'stapp' 3 | import { renderComponent } from './renderComponent' 4 | 5 | describe('(helpers) renderComponent', () => { 6 | it('should call provided render function', () => { 7 | const render = jest.fn() 8 | const testA = {} 9 | const testB = {} 10 | const testC = {} 11 | 12 | renderComponent({ 13 | name: 'test', 14 | renderProps: { 15 | render 16 | }, 17 | renderArgs: [testA, testB, testC], 18 | componentProps: Object.assign({ api: testB, app: testC }, testA) 19 | }) 20 | 21 | expect(render).toBeCalledWith(testA, testB, testC) 22 | }) 23 | 24 | it('should call provided render function', () => { 25 | const children = jest.fn() 26 | const testA = {} 27 | const testB = {} 28 | const testC = {} 29 | 30 | renderComponent({ 31 | name: 'test', 32 | renderProps: { 33 | children 34 | }, 35 | renderArgs: [testA, testB, testC], 36 | componentProps: Object.assign({ api: testB, app: testC }, testA) 37 | }) 38 | 39 | expect(children).toBeCalledWith(testA, testB, testC) 40 | }) 41 | 42 | it('should render provided component with provided props', () => { 43 | const testA = { testA: 123 } 44 | const testB = { testB: 321 } 45 | const testC = {} 46 | 47 | const Component = (props: any) => { 48 | expect(props.testA).toBe(123) 49 | expect(props.api).toBe(testB) 50 | expect(props.app).toBe(testC) 51 | 52 | expect(props).toEqual({ 53 | testA: 123, 54 | testB: 321 55 | }) 56 | return

    57 | } 58 | 59 | renderComponent({ 60 | name: 'test', 61 | renderProps: { component: Component }, 62 | renderArgs: [testA, testB, testC], 63 | componentProps: Object.assign({ api: testB, app: testC }, testA) 64 | }) 65 | }) 66 | 67 | it('should throw if no render prop was provided', () => { 68 | expect(() => 69 | renderComponent({ 70 | name: 'test', 71 | renderProps: {}, 72 | renderArgs: [{}, {}, {}], 73 | componentProps: { app: {}, api: {} } 74 | }) 75 | ).toThrow() 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /packages/stapp-react/src/helpers/renderComponent.ts: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react' 2 | import { RenderProps } from '../models/Props' 3 | import { STAPP_REACT } from './constants' 4 | 5 | export const renderComponent = (parameters: { 6 | name: string 7 | renderProps: RenderProps 8 | renderArgs: any[] 9 | componentProps: any 10 | }): ReactElement | null => { 11 | const { 12 | name, 13 | renderProps: { render, children, component }, 14 | renderArgs, 15 | componentProps 16 | } = parameters 17 | 18 | if (component) { 19 | return React.createElement(component, componentProps) 20 | } 21 | 22 | if (render) { 23 | return render.apply(null, renderArgs) 24 | } 25 | 26 | if (typeof children === 'function') { 27 | return children.apply(null, renderArgs) 28 | } 29 | 30 | throw new Error( 31 | `${STAPP_REACT} error: Must specify either a render prop, a render function as children, or a component prop to ${name}` 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /packages/stapp-react/src/helpers/simpleMemoize.spec.ts: -------------------------------------------------------------------------------- 1 | import { simpleMemoize } from './simpleMemoize' 2 | 3 | describe('simpleMemoize', () => { 4 | it('should memoize provided function', () => { 5 | const rand = () => Math.random() 6 | const memoizedRand = simpleMemoize(rand) 7 | 8 | expect(memoizedRand(1)).toEqual(memoizedRand(1)) 9 | }) 10 | 11 | it('should rememoize if argument changes', () => { 12 | const rand = () => Math.random() 13 | const memoizedRand = simpleMemoize(rand) 14 | 15 | expect(memoizedRand(1)).not.toEqual(memoizedRand(2)) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /packages/stapp-react/src/helpers/simpleMemoize.ts: -------------------------------------------------------------------------------- 1 | export const simpleMemoize = (fn: (arg: A) => R): ((arg: A) => R) => { 2 | let lastResult: R | null = null 3 | let lastArg: A | null = null 4 | 5 | return (arg: A) => { 6 | if (arg === lastArg) { 7 | return lastResult as R 8 | } 9 | 10 | lastArg = arg 11 | 12 | return (lastResult = fn(arg)) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/stapp-react/src/helpers/testApp.ts: -------------------------------------------------------------------------------- 1 | import { createApp, createEvent, createReducer } from 'stapp' 2 | import { formBase } from 'stapp-formbase' 3 | import { loggerModule } from 'stapp/lib/helpers/testHelpers/loggerModule/loggerModule' 4 | 5 | const inc = createEvent() 6 | const dec = createEvent() 7 | const testModule = { 8 | name: 'test', 9 | state: { 10 | counter: createReducer(0) 11 | .on(inc, (s) => s + 1) 12 | .on(dec, (s) => s - 1) 13 | }, 14 | api: { 15 | inc, 16 | dec 17 | } 18 | } 19 | 20 | /** 21 | * @private 22 | */ 23 | export const getApp = () => 24 | createApp({ 25 | name: 'test', 26 | modules: [ 27 | loggerModule({ pattern: undefined }), 28 | formBase<{ test1: string }>({ 29 | initialValues: { 30 | test1: '1' 31 | } 32 | }), 33 | testModule 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /packages/stapp-react/src/index.ts: -------------------------------------------------------------------------------- 1 | // Binded 2 | export { createApi } from './binded/createApi' 3 | export { createComponents } from './binded/createComponents' 4 | export { createConsume, createInject } from './binded/createConsume' 5 | export { createConsumer } from './binded/createConsumer' 6 | export { createField } from './binded/createField' 7 | export { createForm } from './binded/createForm' 8 | 9 | // Context 10 | export { Api } from './context/Api' 11 | export { consume, inject } from './context/consume' 12 | export { Consumer } from './context/Consumer' 13 | export { Field } from './context/Field' 14 | export { Form } from './context/Form' 15 | export { Provider } from './context/Provider' 16 | 17 | // Generic 18 | export { StappSubscription } from './helpers/StappSubscription' 19 | 20 | // Types 21 | export { ConsumerProps, RenderProps, FieldProps } from './models/Props' 22 | export { FieldApi, FormApi } from './models/Form' 23 | -------------------------------------------------------------------------------- /packages/stapp-react/src/models/ConsumeHoc.ts: -------------------------------------------------------------------------------- 1 | import { ComponentClass, ComponentType } from 'react' 2 | import { Omit } from 'stapp/lib/types/omit' 3 | 4 | /** 5 | * @hidden 6 | */ 7 | export type ConsumeHoc = { 8 | // No mapState and no mapApi 9 | (): < 10 | ConsumerProps extends Partial, 11 | ResultProps extends Omit 12 | >( 13 | component: ComponentType 14 | ) => ComponentClass 15 | 16 | // MapState using only state 17 | ( 18 | map: (state: State, api: Api, props?: any) => SelectedState 19 | ): < 20 | ConsumerProps extends Partial, 21 | ResultProps = Omit 22 | >( 23 | component: ComponentType 24 | ) => ComponentClass 25 | } 26 | -------------------------------------------------------------------------------- /packages/stapp-react/src/models/Form.ts: -------------------------------------------------------------------------------- 1 | import { SyntheticEvent } from 'react' 2 | 3 | export type FormApi = { 4 | handleSubmit: (event?: SyntheticEvent) => void 5 | handleReset: (event?: SyntheticEvent) => void 6 | submitting: boolean 7 | valid: boolean 8 | ready: boolean 9 | dirty: boolean 10 | pristine: boolean 11 | } 12 | 13 | export type FieldApi = { 14 | input: { 15 | name: string 16 | value: string 17 | onChange: (event: SyntheticEvent) => void 18 | onBlur: (event?: SyntheticEvent) => void 19 | onFocus: (event?: SyntheticEvent) => void 20 | } 21 | meta: { 22 | error: any 23 | touched: boolean 24 | active: boolean 25 | dirty: boolean 26 | } 27 | extra: any 28 | } 29 | -------------------------------------------------------------------------------- /packages/stapp-react/src/models/Props.ts: -------------------------------------------------------------------------------- 1 | import { ElementType, ReactElement } from 'react' 2 | import { Stapp } from 'stapp' 3 | import { FormBaseState } from 'stapp-formbase' 4 | import { FieldApi } from './Form' 5 | 6 | export type RenderProps = { 7 | children?: ( 8 | state: S, 9 | api: A, 10 | app: Stapp 11 | ) => ReactElement | null 12 | render?: (state: S, api: A, app: Stapp) => ReactElement | null 13 | component?: ElementType }> 14 | } 15 | 16 | export type ConsumerProps = { 17 | map?: (state: State, api: Api) => Result 18 | } & RenderProps 19 | 20 | export type ApiProps = { 21 | children?: (api: Api, app: Stapp) => ReactElement | null 22 | render?: (api: Api, app: Stapp) => ReactElement | null 23 | component?: ElementType<{ api: Api; app: Stapp }> 24 | } 25 | 26 | export type FieldProps = RenderProps & { 27 | name: string 28 | extraSelector?: (state: State) => any 29 | } 30 | -------------------------------------------------------------------------------- /packages/stapp-react/src/models/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'fbjs/lib/invariant' { 2 | export default function invariant(condition: any, message: string): void 3 | } 4 | 5 | declare module 'fbjs/lib/shallowEqual' { 6 | export default function shallowEqual(objectA: any, objectB: any): boolean 7 | } 8 | -------------------------------------------------------------------------------- /packages/stapp-react/src/shared/StappContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | import { Stapp } from '../../../stapp/lib/core/createApp/createApp.h' 3 | 4 | export const StappContext = createContext | null>(null) 5 | -------------------------------------------------------------------------------- /packages/stapp-react/testSetup.ts: -------------------------------------------------------------------------------- 1 | // setup file 2 | import { configure } from 'enzyme' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | 5 | configure({ adapter: new Adapter() }) 6 | -------------------------------------------------------------------------------- /packages/stapp-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp-react/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-react", 8 | "tslint-config-standard", 9 | "tslint-config-prettier" 10 | ], 11 | "exclude": [ 12 | "./node_modules/**/*" 13 | ], 14 | "rules": { 15 | "prefer-for-of": false, 16 | "object-literal-sort-keys": false, 17 | "no-unused-variable": true, 18 | "interface-name": false, 19 | "max-line-length": [false], 20 | "interface-over-type-literal": false, 21 | "object-literal-key-quotes": false, 22 | "prefer-object-spread": false, 23 | "callable-types": false, 24 | 25 | "no-submodule-imports": false, 26 | "no-default-export": true, 27 | "no-magic-numbers": false, 28 | "promise-function-async": false, 29 | "member-access": [true, "no-public"], 30 | "no-implicit-dependencies": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/stapp-rxjs/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/stapp-rxjs/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/stapp-rxjs/README.md: -------------------------------------------------------------------------------- 1 | # This package is DEPRECATED since 2.6.0 2 | 3 | # Stapp RxJS 4 | 5 | RxJS config for [Stapp](https://github.com/TinkoffCreditSystems/stapp). 6 | 7 | See [documentation](https://stapp.js.org/guides/interop.html) for more info. 8 | 9 | ## Installation 10 | ```bash 11 | npm install stapp-rxjs rxjs stapp 12 | # OR using stapp-cli-tools 13 | stapp install stapp-rxjs 14 | ``` 15 | 16 | ### Peer dependencies 17 | * **stapp**: >= 2.6 18 | * **rxjs**: >= 6 19 | 20 | ## License 21 | 22 | ``` 23 | Copyright 2019 Tinkoff Bank 24 | 25 | Licensed under the Apache License, Version 2.0 (the "License"); 26 | you may not use this file except in compliance with the License. 27 | You may obtain a copy of the License at 28 | 29 | http://www.apache.org/licenses/LICENSE-2.0 30 | 31 | Unless required by applicable law or agreed to in writing, software 32 | distributed under the License is distributed on an "AS IS" BASIS, 33 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | See the License for the specific language governing permissions and 35 | limitations under the License. 36 | ``` 37 | -------------------------------------------------------------------------------- /packages/stapp-rxjs/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "collectCoverage": true, 29 | "coverageReporters": [ 30 | "json", 31 | "lcov" 32 | ], 33 | "globals": { 34 | "process.env.NODE_ENV": "test", 35 | "ts-jest": { 36 | "diagnostics": false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/stapp-rxjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp-rxjs", 3 | "version": "2.7.0-0", 4 | "description": "RxJS tools for Stapp - modular state manager", 5 | "keywords": [ 6 | "state-management", 7 | "state", 8 | "reactive", 9 | "microservice", 10 | "epics", 11 | "rxjs" 12 | ], 13 | "main": "lib/index.js", 14 | "typings": "lib/index.d.ts", 15 | "files": [ 16 | "lib" 17 | ], 18 | "author": "Dmitry Korolev (https://korolev.dk)", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 22 | }, 23 | "homepage": "https://stapp.js.org/guides/interop.html", 24 | "bugs": "https://github.com/TinkoffCreditSystems/stapp/issues?q=is:issue+label:stapp-rxjs", 25 | "engines": { 26 | "node": ">=8.0.0" 27 | }, 28 | "license": "Apache-2.0", 29 | "scripts": { 30 | "build": "npm run build:module", 31 | "build:module": "tsc", 32 | "prebuild": "rimraf lib", 33 | "test": "npm run test:lint && npm run test:jest", 34 | "test:lint": "tslint -t verbose './src/**/*.ts' './src/**/*.tsx' -p ./tsconfig.json -c ./tslint.json", 35 | "test:jest": "jest --config ./jest.config.json", 36 | "test:ci": "npm run test:lint && jest --config ./jest.config.json --runInBand --coverage --coverageReporters=text-lcov" 37 | }, 38 | "peerDependencies": { 39 | "rxjs": ">=6", 40 | "stapp": ">=2.6" 41 | }, 42 | "devDependencies": { 43 | "@types/jest": "^24.0.11", 44 | "@types/node": "^12.0.0", 45 | "babel-jest": "^24.6.0", 46 | "babel-preset-env": "^1.7.0", 47 | "babel-preset-stage-0": "^6.24.1", 48 | "jest": "^24.6.0", 49 | "rxjs": "^6.4.0", 50 | "stapp": "^2.7.0-0", 51 | "ts-jest": "^24.0.1", 52 | "ts-node": "^8.0.3", 53 | "tslib": "^1.9.3", 54 | "tslint": "^5.15.0", 55 | "tslint-config-prettier": "^1.18.0", 56 | "tslint-config-standard": "^8.0.1", 57 | "tslint-eslint-rules": "^5.4.0", 58 | "typescript": "3.3.4000" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/stapp-rxjs/src/index.ts: -------------------------------------------------------------------------------- 1 | export { observableConfig } from './observableConfig' 2 | -------------------------------------------------------------------------------- /packages/stapp-rxjs/src/observableConfig.spec.ts: -------------------------------------------------------------------------------- 1 | import { observableConfig } from './observableConfig' 2 | 3 | describe('observableConfig', function() { 4 | it('should do nothing', () => { 5 | const o = {} as any 6 | expect(observableConfig.fromESObservable(o)).toBe(o) 7 | expect(observableConfig.toESObservable(o)).toBe(o) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/stapp-rxjs/src/observableConfig.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs' 2 | import { ObservableConfig } from 'stapp' 3 | import { identity } from 'stapp/lib/helpers/identity/identity' 4 | 5 | export const observableConfig: ObservableConfig> = { 6 | fromESObservable: identity as any, 7 | toESObservable: identity as any 8 | } 9 | -------------------------------------------------------------------------------- /packages/stapp-rxjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp-rxjs/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-config-standard", 8 | "tslint-config-prettier" 9 | ], 10 | "exclude": [ 11 | "./node_modules/**/*" 12 | ], 13 | "rules": { 14 | "prefer-for-of": false, 15 | "object-literal-sort-keys": false, 16 | "no-unused-variable": true, 17 | "interface-name": false, 18 | "max-line-length": [false], 19 | "interface-over-type-literal": false, 20 | "object-literal-key-quotes": false, 21 | "prefer-object-spread": false, 22 | "callable-types": false, 23 | 24 | "no-submodule-imports": false, 25 | "no-default-export": true, 26 | "no-magic-numbers": false, 27 | "promise-function-async": false, 28 | "member-access": [true, "no-public"], 29 | "no-implicit-dependencies": false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/stapp-select/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/stapp-select/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/stapp-select/README.md: -------------------------------------------------------------------------------- 1 | # Stapp Select 2 | 3 | Provides a way to pick some data from app state when some events was dispatched and dispatch some events in response. 4 | 5 | Part of [Stapp](https://github.com/TinkoffCreditSystems/stapp). 6 | 7 | See [documentation](https://stapp.js.org/modules/select.html) for more info. 8 | 9 | ## Installation 10 | ```bash 11 | npm install stapp-select stapp rxjs 12 | # OR using stapp-cli-tools 13 | stapp install stapp-select 14 | ``` 15 | 16 | ### Peer dependencies 17 | * **stapp**: >= 2.6 18 | * **rxjs**: >= 6 19 | 20 | ## License 21 | 22 | ``` 23 | Copyright 2019 Tinkoff Bank 24 | 25 | Licensed under the Apache License, Version 2.0 (the "License"); 26 | you may not use this file except in compliance with the License. 27 | You may obtain a copy of the License at 28 | 29 | http://www.apache.org/licenses/LICENSE-2.0 30 | 31 | Unless required by applicable law or agreed to in writing, software 32 | distributed under the License is distributed on an "AS IS" BASIS, 33 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | See the License for the specific language governing permissions and 35 | limitations under the License. 36 | ``` 37 | -------------------------------------------------------------------------------- /packages/stapp-select/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "collectCoverage": true, 29 | "coverageReporters": [ 30 | "json", 31 | "lcov" 32 | ], 33 | "globals": { 34 | "process.env.NODE_ENV": "test", 35 | "ts-jest": { 36 | "diagnostics": false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/stapp-select/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp-select", 3 | "version": "2.7.0-0", 4 | "description": "Select module for stapp - modular state manager", 5 | "keywords": [ 6 | "state-management", 7 | "state", 8 | "reactive", 9 | "microservice", 10 | "epics", 11 | "redux" 12 | ], 13 | "main": "lib/index.js", 14 | "typings": "lib/index.d.ts", 15 | "files": [ 16 | "lib" 17 | ], 18 | "author": "Eugenie Dasaev ", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 22 | }, 23 | "homepage": "https://stapp.js.org/modules/select.html", 24 | "bugs": "https://github.com/TinkoffCreditSystems/stapp/issues?q=is:issue+label:stapp-select", 25 | "engines": { 26 | "node": ">=8.0.0" 27 | }, 28 | "license": "Apache-2.0", 29 | "scripts": { 30 | "build": "npm run build:module", 31 | "build:module": "tsc", 32 | "prebuild": "rimraf lib", 33 | "update-all": "lernaupdate", 34 | "test": "npm run test:lint && npm run test:jest", 35 | "test:lint": "tslint -t verbose './src/**/*.ts' './src/**/*.tsx' -p ./tsconfig.json -c ./tslint.json", 36 | "test:jest": "jest --config ./jest.config.json", 37 | "test:ci": "npm run test:lint && jest --config ./jest.config.json --runInBand --coverage --coverageReporters=text-lcov" 38 | }, 39 | "peerDependencies": { 40 | "rxjs": ">=6", 41 | "stapp": ">=2.6" 42 | }, 43 | "devDependencies": { 44 | "@types/jest": "^24.0.11", 45 | "@types/node": "^11.13.0", 46 | "babel-jest": "^24.6.0", 47 | "babel-preset-env": "^1.7.0", 48 | "babel-preset-stage-0": "^6.24.1", 49 | "jest": "^24.6.0", 50 | "rxjs": ">= 6", 51 | "stapp": "^2.7.0-0", 52 | "ts-jest": "^24.0.1", 53 | "ts-node": "^8.0.3", 54 | "tslib": "^1.9.3", 55 | "tslint": "^5.15.0", 56 | "tslint-config-prettier": "^1.18.0", 57 | "tslint-config-standard": "^8.0.1", 58 | "tslint-eslint-rules": "^5.4.0", 59 | "tslint-react": "^4.0.0", 60 | "typescript": "3.3.4000" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/stapp-select/src/constants.ts: -------------------------------------------------------------------------------- 1 | import { APP_KEY } from 'stapp/lib/helpers/constants' 2 | 3 | /** 4 | * @hidden 5 | */ 6 | export const SELECT = `${APP_KEY}/select` 7 | -------------------------------------------------------------------------------- /packages/stapp-select/src/index.ts: -------------------------------------------------------------------------------- 1 | export { select } from './select' 2 | export { SELECT } from './constants' 3 | -------------------------------------------------------------------------------- /packages/stapp-select/src/select.h.ts: -------------------------------------------------------------------------------- 1 | import { AnyEventCreator, EventCreator1 } from 'stapp' 2 | 3 | export type SelectConfig = { 4 | name: Name 5 | selector: (state: State) => Result 6 | reactOn: Array 7 | reactWith?: Array> 8 | } 9 | -------------------------------------------------------------------------------- /packages/stapp-select/src/select.ts: -------------------------------------------------------------------------------- 1 | import { from } from 'rxjs' 2 | import { filter, map, mergeMap } from 'rxjs/operators' 3 | import { 4 | createEvent, 5 | createReducer, 6 | Epic, 7 | initEvent, 8 | Module, 9 | select as selectEvents 10 | } from 'stapp' 11 | import { SELECT } from './constants' 12 | 13 | // Models 14 | import { SelectConfig } from './select.h' 15 | 16 | export const select = ({ 17 | name, 18 | selector, 19 | reactOn, 20 | reactWith = [] 21 | }: SelectConfig): Module< 22 | {}, 23 | { [K in Name]: Result | null } 24 | > => { 25 | const selected = createEvent(`${SELECT}/${name}: State was selected`) 26 | const selectReducer = createReducer(null).on( 27 | selected, 28 | (state, selectResult) => selectResult || state 29 | ) 30 | 31 | const eventFilter = selectEvents(reactOn.concat(initEvent)) 32 | const events = [selected].concat(reactWith) 33 | 34 | const reactEpic: Epic = (event$, _, { getState }) => { 35 | return event$.pipe( 36 | filter(eventFilter), 37 | map(() => selector(getState())), 38 | mergeMap((result) => 39 | from(events.map((eventCreator) => eventCreator(result))) 40 | ) 41 | ) 42 | } 43 | 44 | return { 45 | name: `${SELECT}/${name}`, 46 | state: { 47 | [name]: selectReducer 48 | } as any, 49 | epic: reactEpic, 50 | useGlobalObservableConfig: false 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/stapp-select/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp-select/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-config-standard", 8 | "tslint-config-prettier" 9 | ], 10 | "exclude": [ 11 | "./node_modules/**/*" 12 | ], 13 | "rules": { 14 | "prefer-for-of": false, 15 | "object-literal-sort-keys": false, 16 | "no-unused-variable": true, 17 | "interface-name": false, 18 | "max-line-length": [false], 19 | "interface-over-type-literal": false, 20 | "object-literal-key-quotes": false, 21 | "prefer-object-spread": false, 22 | "callable-types": false, 23 | 24 | "no-submodule-imports": false, 25 | "no-default-export": true, 26 | "no-magic-numbers": false, 27 | "promise-function-async": false, 28 | "member-access": [true, "no-public"], 29 | "no-implicit-dependencies": false 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/stapp-validate/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/stapp-validate/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/stapp-validate/README.md: -------------------------------------------------------------------------------- 1 | # Stapp Validate 2 | 3 | Validation module for [Stapp](https://github.com/TinkoffCreditSystems/stapp). 4 | 5 | See [documentation](https://stapp.js.org/modules/validate.html) for more info. 6 | 7 | ## Installation 8 | ```bash 9 | npm install stapp-`validate` stapp stapp-formbase rxjs reselect 10 | # OR using stapp-cli-tools 11 | stapp install stapp-validate 12 | ``` 13 | 14 | ### Peer dependencies 15 | * **stapp**: >= 2.6 16 | * **stapp-formbase**: >= 2.6 17 | * **reselect**: >= 4 18 | * **rxjs**: >= 6 19 | 20 | ## License 21 | 22 | ``` 23 | Copyright 2019 Tinkoff Bank 24 | 25 | Licensed under the Apache License, Version 2.0 (the "License"); 26 | you may not use this file except in compliance with the License. 27 | You may obtain a copy of the License at 28 | 29 | http://www.apache.org/licenses/LICENSE-2.0 30 | 31 | Unless required by applicable law or agreed to in writing, software 32 | distributed under the License is distributed on an "AS IS" BASIS, 33 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34 | See the License for the specific language governing permissions and 35 | limitations under the License. 36 | ``` 37 | -------------------------------------------------------------------------------- /packages/stapp-validate/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "collectCoverage": true, 29 | "coverageReporters": [ 30 | "json", 31 | "lcov" 32 | ], 33 | "globals": { 34 | "process.env.NODE_ENV": "test", 35 | "ts-jest": { 36 | "diagnostics": false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/stapp-validate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp-validate", 3 | "version": "2.7.0-0", 4 | "description": "Form validation module for stapp - modular state manager", 5 | "keywords": [ 6 | "state-management", 7 | "state", 8 | "reactive", 9 | "microservice", 10 | "form", 11 | "validation", 12 | "epics", 13 | "redux" 14 | ], 15 | "main": "lib/index.js", 16 | "typings": "lib/index.d.ts", 17 | "files": [ 18 | "lib" 19 | ], 20 | "author": "Dmitry Korolev (https://korolev.dk)", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 24 | }, 25 | "homepage": "https://stapp.js.org/modules/validate.html", 26 | "bugs": "https://github.com/TinkoffCreditSystems/stapp/issues?q=is:issue+label:stapp-validate", 27 | "engines": { 28 | "node": ">=8.0.0" 29 | }, 30 | "license": "Apache-2.0", 31 | "scripts": { 32 | "build": "npm run build:module", 33 | "build:module": "tsc", 34 | "prebuild": "rimraf lib", 35 | "test": "npm run test:lint && npm run test:jest", 36 | "test:lint": "tslint -t verbose './src/**/*.ts' './src/**/*.tsx' -p ./tsconfig.json -c ./tslint.json", 37 | "test:jest": "jest --config ./jest.config.json", 38 | "test:ci": "npm run test:lint && jest --config ./jest.config.json --runInBand --coverage --coverageReporters=text-lcov" 39 | }, 40 | "peerDependencies": { 41 | "reselect": ">=4", 42 | "rxjs": ">=6", 43 | "stapp": ">=2.6", 44 | "stapp-formbase": ">=2.6" 45 | }, 46 | "devDependencies": { 47 | "@types/jest": "^24.0.11", 48 | "@types/node": "^12.0.0", 49 | "babel-jest": "^24.6.0", 50 | "babel-preset-env": "^1.7.0", 51 | "babel-preset-stage-0": "^6.24.1", 52 | "jest": "^24.6.0", 53 | "request": "^2.88.0", 54 | "reselect": "^4.0.0", 55 | "rxjs": ">= 6", 56 | "stapp": "^2.7.0-0", 57 | "stapp-formbase": "^2.7.0-0", 58 | "ts-jest": "^24.0.1", 59 | "ts-node": "^8.0.3", 60 | "tslib": "^1.9.3", 61 | "tslint": "^5.15.0", 62 | "tslint-config-prettier": "^1.18.0", 63 | "tslint-config-standard": "^8.0.1", 64 | "tslint-eslint-rules": "^5.4.0", 65 | "tslint-react": "^4.0.0", 66 | "typescript": "3.3.4000" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/stapp-validate/src/constants.ts: -------------------------------------------------------------------------------- 1 | import { APP_KEY } from 'stapp/lib/helpers/constants' 2 | 3 | /** 4 | * @hidden 5 | */ 6 | export const VALIDATE = `${APP_KEY}/Validate` 7 | -------------------------------------------------------------------------------- /packages/stapp-validate/src/events.ts: -------------------------------------------------------------------------------- 1 | import { createEvent } from 'stapp' 2 | import { VALIDATE } from './constants' 3 | 4 | /** 5 | * @private 6 | */ 7 | export const asyncValidationStart = createEvent( 8 | `${VALIDATE}: async validation started` 9 | ) 10 | 11 | /** 12 | * @private 13 | */ 14 | export const asyncValidationEnd = createEvent( 15 | `${VALIDATE}: async validation finished` 16 | ) 17 | 18 | /** 19 | * Start revalidating all fields 20 | */ 21 | export const revalidate = createEvent( 22 | `${VALIDATE}: force validation` 23 | ) 24 | -------------------------------------------------------------------------------- /packages/stapp-validate/src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { concat, from, Observable, of } from 'rxjs' 2 | import { catchError, switchMap } from 'rxjs/operators' 3 | import { FormBaseState, setError, setReady } from 'stapp-formbase' 4 | import { isPromise } from 'stapp/lib/helpers/is/isPromise/isPromise' 5 | import { asyncValidationEnd, asyncValidationStart } from './events' 6 | 7 | // Models 8 | import { Event } from 'stapp' 9 | import { ValidationFlags, ValidationRule } from './validate.h' 10 | 11 | /** 12 | * @private 13 | */ 14 | export const normalizeResult = ( 15 | fieldName: string, 16 | result: any 17 | ): Observable> => { 18 | // if it's a promise, unwrap 19 | if (isPromise(result)) { 20 | return from(result).pipe( 21 | switchMap((promiseResult) => normalizeResult(fieldName, promiseResult)), 22 | catchError((error) => of(setError({ [fieldName]: error }))) 23 | ) 24 | } 25 | 26 | // if it's a plain object, return 27 | if (result != null && typeof result === 'object') { 28 | return of(setError(result)) 29 | } 30 | 31 | // in all other cases wrap to an object 32 | return of( 33 | setError({ 34 | [fieldName]: result 35 | }) 36 | ) 37 | } 38 | 39 | /** 40 | * @private 41 | */ 42 | export const runValidation = ( 43 | state: State, 44 | fieldName: string, 45 | rule: ValidationRule, 46 | flags: ValidationFlags 47 | ): Observable> => { 48 | let result: any 49 | 50 | try { 51 | result = rule(state.values[fieldName], fieldName, state, flags) 52 | } catch (error) { 53 | result = error 54 | } 55 | 56 | const syncMode = !isPromise(result) 57 | const result$ = normalizeResult(fieldName, result) 58 | 59 | return syncMode 60 | ? result$ 61 | : concat( 62 | of(asyncValidationStart(fieldName), setReady({ [fieldName]: false })), 63 | result$, 64 | of(asyncValidationEnd(fieldName), setReady({ [fieldName]: true })) 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /packages/stapp-validate/src/index.ts: -------------------------------------------------------------------------------- 1 | export { revalidate } from './events' 2 | export { validate } from './validate' 3 | export { VALIDATE } from './constants' 4 | export { isValidatingSelector } from './selectors' 5 | export { 6 | ValidateConfig, 7 | ValidationFlags, 8 | ValidationRule, 9 | ValidationRules 10 | } from './validate.h' 11 | -------------------------------------------------------------------------------- /packages/stapp-validate/src/reducers.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from 'stapp' 2 | import { resetForm } from 'stapp-formbase' 3 | import { asyncValidationEnd, asyncValidationStart } from './events' 4 | 5 | /** 6 | * @private 7 | */ 8 | export const validateReducer = createReducer<{ [K: string]: boolean }>({}) 9 | .on( 10 | asyncValidationStart, 11 | /* istanbul ignore next */ 12 | (validating, name) => 13 | validating[name] 14 | ? validating 15 | : { 16 | ...validating, 17 | [name]: true 18 | } 19 | ) 20 | .on( 21 | asyncValidationEnd, 22 | /* istanbul ignore next */ 23 | (validating, name) => 24 | !validating[name] 25 | ? validating 26 | : { 27 | ...validating, 28 | [name]: false 29 | } 30 | ) 31 | .reset(resetForm) 32 | -------------------------------------------------------------------------------- /packages/stapp-validate/src/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector, Selector } from 'reselect' 2 | 3 | // Models 4 | import { ValidationState } from './validate.h' 5 | 6 | export const isValidatingSelector: < 7 | State extends ValidationState 8 | >() => Selector = () => { 9 | return createSelector( 10 | (state: State) => state.validating, 11 | (validating) => 12 | validating 13 | ? Object.keys(validating).some((key) => validating[key]) 14 | : false 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/stapp-validate/src/validate.h.ts: -------------------------------------------------------------------------------- 1 | export type ValidationFlags = { 2 | onInit?: boolean 3 | onChange?: boolean 4 | onRevalidate?: boolean 5 | } 6 | 7 | export type ValidationRule = ( 8 | value: string | void, 9 | fieldName: string, 10 | state: State, 11 | flags: ValidationFlags 12 | ) => any 13 | 14 | export type ValidationRules = { 15 | [K: string]: ValidationRule 16 | } 17 | 18 | export type ValidationState = { 19 | validating: { [K: string]: boolean } 20 | } 21 | 22 | export type ValidateConfig = { 23 | rules: ((state: State) => ValidationRules) | ValidationRules 24 | moduleName?: string 25 | validateOnInit?: boolean 26 | setTouchedOnSubmit?: boolean 27 | } 28 | -------------------------------------------------------------------------------- /packages/stapp-validate/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp-validate/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-react", 8 | "tslint-config-standard", 9 | "tslint-config-prettier" 10 | ], 11 | "exclude": [ 12 | "./node_modules/**/*" 13 | ], 14 | "rules": { 15 | "prefer-for-of": false, 16 | "object-literal-sort-keys": false, 17 | "no-unused-variable": true, 18 | "interface-name": false, 19 | "max-line-length": [false], 20 | "interface-over-type-literal": false, 21 | "object-literal-key-quotes": false, 22 | "prefer-object-spread": false, 23 | "callable-types": false, 24 | 25 | "no-submodule-imports": false, 26 | "no-default-export": true, 27 | "no-magic-numbers": false, 28 | "promise-function-async": false, 29 | "member-access": [true, "no-public"], 30 | "no-implicit-dependencies": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/stapp/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/stapp/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/stapp/README.md: -------------------------------------------------------------------------------- 1 | # Stapp 2 | 3 | Core part of [Stapp](https://github.com/TinkoffCreditSystems/stapp). 4 | 5 | See [documentation](https://stapp.js.org/) for more info. 6 | 7 | ## Installation 8 | ```bash 9 | npm install stapp redux rxjs 10 | # OR using stapp-cli-tools 11 | stapp install stapp 12 | ``` 13 | 14 | ### Peer dependencies 15 | * **rxjs**: >= 6 16 | * **redux**: >= 4 17 | 18 | ## License 19 | 20 | ``` 21 | Copyright 2019 Tinkoff Bank 22 | 23 | Licensed under the Apache License, Version 2.0 (the "License"); 24 | you may not use this file except in compliance with the License. 25 | You may obtain a copy of the License at 26 | 27 | http://www.apache.org/licenses/LICENSE-2.0 28 | 29 | Unless required by applicable law or agreed to in writing, software 30 | distributed under the License is distributed on an "AS IS" BASIS, 31 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32 | See the License for the specific language governing permissions and 33 | limitations under the License. 34 | ``` 35 | -------------------------------------------------------------------------------- /packages/stapp/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "ts-jest", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "collectCoverage": true, 29 | "coverageReporters": [ 30 | "json", 31 | "lcov" 32 | ], 33 | "globals": { 34 | "process.env.NODE_ENV": "test", 35 | "ts-jest": { 36 | "diagnostics": false 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/stapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stapp", 3 | "version": "2.7.0-0", 4 | "description": "Modular state manager for microapps", 5 | "keywords": [ 6 | "state-management", 7 | "state", 8 | "reactive", 9 | "microservice", 10 | "epics", 11 | "redux" 12 | ], 13 | "main": "lib/stapp.js", 14 | "typings": "lib/stapp.d.ts", 15 | "files": [ 16 | "lib" 17 | ], 18 | "author": "Dmitry Korolev (https://korolev.dk)", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 22 | }, 23 | "homepage": "https://stapp.js.org/", 24 | "bugs": "https://github.com/TinkoffCreditSystems/stapp/issues?q=is:issue+label:stapp-core", 25 | "engines": { 26 | "node": ">=8.0.0" 27 | }, 28 | "license": "Apache-2.0", 29 | "scripts": { 30 | "build": "npm run build:module", 31 | "build:module": "tsc", 32 | "prebuild": "rimraf lib", 33 | "test": "npm run test:lint && npm run test:jest", 34 | "test:lint": "tslint -t verbose './src/**/*.ts' './src/**/*.tsx' -p ./tsconfig.json -c ./tslint.json", 35 | "test:jest": "jest --config ./jest.config.json", 36 | "test:ci": "npm run test:lint && jest --config ./jest.config.json --runInBand --coverage --coverageReporters=text-lcov" 37 | }, 38 | "peerDependencies": { 39 | "redux": ">=4", 40 | "rxjs": ">=6" 41 | }, 42 | "dependencies": { 43 | "symbol-observable": "^1.2.0" 44 | }, 45 | "devDependencies": { 46 | "@types/jest": "^24.0.11", 47 | "@types/node": "^12.0.0", 48 | "babel-jest": "^24.6.0", 49 | "babel-preset-env": "^1.7.0", 50 | "babel-preset-stage-0": "^6.24.1", 51 | "flux-standard-action": "^2.0.4", 52 | "jest": "^24.6.0", 53 | "redux": ">= 4", 54 | "rxjs": ">= 6", 55 | "ts-jest": "^24.0.1", 56 | "ts-node": "^8.0.3", 57 | "tslib": "^1.9.3", 58 | "tslint": "^5.15.0", 59 | "tslint-config-prettier": "^1.18.0", 60 | "tslint-config-standard": "^8.0.1", 61 | "tslint-eslint-rules": "^5.4.0", 62 | "tslint-react": "^4.0.0", 63 | "typescript": "3.3.4000" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/stapp/src/core/createApp/bindApi.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from './createApp.h' 2 | 3 | /** 4 | * @private 5 | */ 6 | const bindFunction = ( 7 | actionCreator: (...args: any[]) => any, 8 | dispatch: Dispatch 9 | ) => { 10 | return (...args: any[]) => dispatch(actionCreator(...args)) 11 | } 12 | 13 | /** 14 | * @private 15 | * @internal 16 | */ 17 | export function bindApi(api: T, dispatch: Dispatch): T { 18 | const keys = Object.keys(api) 19 | const result: any = {} 20 | 21 | for (let i = 0; i < keys.length; i++) { 22 | const key = keys[i] 23 | const element = (api as any)[key] 24 | 25 | if (typeof element === 'function') { 26 | result[key] = bindFunction(element, dispatch) 27 | } 28 | 29 | if (typeof element === 'object' && element !== null) { 30 | result[key] = bindApi(element, dispatch) 31 | } 32 | } 33 | return result as T 34 | } 35 | -------------------------------------------------------------------------------- /packages/stapp/src/core/createApp/getReadyPromise.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable no-use-before-declare 2 | import { Observable, Subscription } from 'rxjs' 3 | import { initEvent } from '../../events/lifecycle' 4 | import { controlledPromise } from '../../helpers/controlledPromise/controlledPromise' 5 | import { getEventType } from '../../helpers/getEventType/getEventType' 6 | import { AnyEventCreator, Event } from '../createEvent/createEvent.h' 7 | import { WaitFor } from './createApp.h' 8 | 9 | const isEvent = (ev: any): ev is AnyEventCreator | string => { 10 | return typeof ev === 'string' || typeof ev === 'function' 11 | } 12 | 13 | export const getReadyPromise = ( 14 | event$: Observable>, 15 | getState: () => Partial, 16 | waitFor: WaitFor 17 | ): Promise> => { 18 | const [promise, resolve] = controlledPromise>() 19 | const toWait = new Set( 20 | waitFor.reduce( 21 | (result, event) => { 22 | if (isEvent(event)) { 23 | result.push(getEventType(event)) 24 | return result 25 | } 26 | 27 | if (event.condition && !event.condition()) { 28 | return result 29 | } 30 | 31 | const type = getEventType(event.event) 32 | 33 | if (event.timeout) { 34 | setTimeout(() => { 35 | removeEvent(type) 36 | }, event.timeout) 37 | } 38 | 39 | result.push(type) 40 | return result 41 | }, 42 | [initEvent.getType()] 43 | ) 44 | ) 45 | const removeEvent = (type: string) => { 46 | // We shouldn't get here due to the unsubscribing, just in case 47 | /* istanbul ignore next */ 48 | if (ready) { 49 | return 50 | } 51 | 52 | toWait.delete(type) 53 | 54 | if (toWait.size === 0) { 55 | ready = true 56 | resolve(getState()) 57 | subscription.unsubscribe() 58 | } 59 | } 60 | 61 | let ready = false 62 | const subscription = event$.subscribe({ 63 | next(event) { 64 | removeEvent(event.type) 65 | } 66 | }) 67 | 68 | return promise 69 | } 70 | -------------------------------------------------------------------------------- /packages/stapp/src/core/createApp/getRootReducer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | dangerouslyReplaceState, 3 | dangerouslyResetState 4 | } from '../../events/dangerous' 5 | import { combineReducers } from '../../helpers/combineReducers/combineReducers' 6 | import { ReducerFunction, ReducersMap } from '../createReducer/createReducer.h' 7 | 8 | /** 9 | * @private 10 | */ 11 | const dangerouslyReplaceStateType = dangerouslyReplaceState.getType() 12 | 13 | /** 14 | * @private 15 | */ 16 | const dangerouslyResetStateType = dangerouslyResetState.getType() 17 | 18 | /** 19 | * Creates root reducer with some superpowers. 20 | * And remember, Pete, great power comes with great responsibility. 21 | * @private 22 | * @internal 23 | */ 24 | export function getRootReducer( 25 | reducers: ReducersMap, 26 | initialState: Partial 27 | ): ReducerFunction { 28 | const rootReducer = combineReducers(reducers) 29 | 30 | return (oldState: any, event) => { 31 | let state = oldState 32 | 33 | if (event.type === dangerouslyReplaceStateType) { 34 | state = event.payload 35 | } 36 | 37 | if (event.type === dangerouslyResetStateType) { 38 | state = initialState 39 | } 40 | 41 | return rootReducer(state, event) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/stapp/src/core/createApp/setObservableConfig.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs' 2 | import { identity } from '../../helpers/identity/identity' 3 | import { Module, ObservableConfig } from './createApp.h' 4 | 5 | let globalObservableConfig: ObservableConfig = {} 6 | 7 | export const setObservableConfig = (config: ObservableConfig) => { 8 | globalObservableConfig = config 9 | } 10 | 11 | export const getConfig = ({ 12 | useGlobalObservableConfig = true, 13 | observableConfig 14 | }: Module): { 15 | fromESObservable: (innerStream: Observable) => any 16 | toESObservable: (outerStream: any) => Observable 17 | } => { 18 | const config = observableConfig 19 | ? observableConfig 20 | : useGlobalObservableConfig 21 | ? globalObservableConfig 22 | : {} 23 | 24 | return { 25 | fromESObservable: config.fromESObservable || identity, 26 | toESObservable: config.toESObservable || identity 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/stapp/src/core/createEffect/createEffect.h.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs' 2 | import { 3 | EmptyEventCreator, 4 | Event, 5 | EventCreator1 6 | } from '../createEvent/createEvent.h' 7 | 8 | export type EffectCreator = { 9 | (payload: Payload): Observable> 10 | start: EventCreator1 11 | success: EventCreator1 12 | fail: EventCreator1 13 | complete: EmptyEventCreator 14 | use( 15 | effectFn: (payload: Payload) => Promise | Result 16 | ): EffectCreator 17 | getType(): string 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp/src/core/createEffect/createEffect.ts: -------------------------------------------------------------------------------- 1 | import { concat, EMPTY, from, of } from 'rxjs' 2 | import { APP_KEY } from '../../helpers/constants' 3 | import { isPromise } from '../../helpers/is/isPromise/isPromise' 4 | import { createEvent } from '../createEvent/createEvent' 5 | 6 | // Models 7 | import { EffectCreator } from './createEffect.h' 8 | 9 | /** 10 | * @private 11 | */ 12 | const run = ( 13 | params: Payload, 14 | effect: (payload: Payload) => Promise | Result 15 | ): Promise => { 16 | let response: any 17 | try { 18 | response = effect(params) 19 | } catch (err) { 20 | return Promise.reject(err) 21 | } 22 | 23 | if (isPromise(response)) { 24 | return response 25 | } 26 | 27 | return Promise.resolve(response) 28 | } 29 | 30 | /** 31 | * @private 32 | */ 33 | const T = () => true 34 | 35 | /** 36 | * Creates an effect creator. Effect is a stream, that uses provided function, and emits start, success, error and complete types. 37 | * 38 | * @param description 39 | * @param effect Side effect performing function, should return a Promise. 40 | * @param condition Function, that defines if an effect should run. Must return boolean. T by default. E.g. can be used to separate server-side effects. 41 | * @returns 42 | */ 43 | export const createEffect = ( 44 | description: string, 45 | effect?: (payload: Payload) => Promise | Result, 46 | condition: (payload?: Payload) => boolean = T 47 | ): EffectCreator => { 48 | const success = createEvent(`${description}: SUCCESS`) 49 | const fail = createEvent(`${description}: FAIL`) 50 | const start = createEvent(`${description}: START`) 51 | const complete = createEvent(`${description}: COMPLETE`) 52 | 53 | let _effect = effect 54 | 55 | const runEffect: any = (payload: Payload) => { 56 | if (!_effect) { 57 | throw new Error(`${APP_KEY} error: Effect is not provided!`) 58 | } 59 | 60 | if (!condition(payload)) { 61 | return EMPTY 62 | } 63 | 64 | return concat( 65 | of(start(payload)), 66 | from( 67 | run(payload, _effect) 68 | .then((result) => success(result)) 69 | .catch((error) => fail(error)) 70 | ), 71 | of(complete()) 72 | ) 73 | } 74 | 75 | return Object.assign(runEffect, { 76 | start, 77 | success, 78 | fail, 79 | complete, 80 | getType: () => start.getType(), 81 | use(fn: (payload: Payload) => Promise | Result) { 82 | _effect = fn 83 | return runEffect 84 | } 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /packages/stapp/src/core/createEpic/createEpic.spec.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY, of } from 'rxjs' 2 | import { identity } from '../../helpers/identity/identity' 3 | import { collectData } from '../../helpers/testHelpers/collectData/collectData' 4 | import { createEvent } from '../createEvent/createEvent' 5 | import { createEpic } from './createEpic' 6 | 7 | describe('createEpic', () => { 8 | it('should create a filtered epic', async () => { 9 | const a = createEvent() 10 | const b = createEvent() 11 | const aEpic = createEpic(a, (event$) => event$) 12 | const events$ = of(a(), b()) 13 | const result = await collectData( 14 | aEpic(events$, EMPTY, { 15 | getState: () => ({}), 16 | dispatch: (x: any) => x, 17 | fromESObservable: identity, 18 | toESObservable: identity 19 | } as any) 20 | ) 21 | 22 | expect(result).toEqual([a()]) 23 | }) 24 | 25 | it('should create a filtered epic from an array of eventCreators', async () => { 26 | const a = createEvent() 27 | const b = createEvent() 28 | const c = createEvent() 29 | const aEpic = createEpic([a, b], (event$) => event$) 30 | const events$ = of(a(), b(), c()) 31 | const result = await collectData( 32 | aEpic(events$, EMPTY, { 33 | getState: () => ({}), 34 | dispatch: (x: any) => x, 35 | fromESObservable: identity, 36 | toESObservable: identity 37 | } as any) 38 | ) 39 | 40 | expect(result).toEqual([a(), b()]) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /packages/stapp/src/core/createEpic/createEpic.ts: -------------------------------------------------------------------------------- 1 | import { from } from 'rxjs' 2 | import { filter } from 'rxjs/operators' 3 | import { select } from '../../helpers/select/select' 4 | import { Epic } from '../createApp/createApp.h' 5 | import { AnyEventCreator } from '../createEvent/createEvent.h' 6 | 7 | export const createEpic = ( 8 | events: 9 | | AnyEventCreator 10 | | string 11 | | Array, 12 | fn: Epic 13 | ): Epic => { 14 | const selector = select(events) 15 | 16 | return (events$, state$, staticApi) => { 17 | const filtered$ = staticApi.fromESObservable( 18 | from(staticApi.toESObservable(events$)).pipe(filter(selector)) 19 | ) 20 | 21 | return fn(filtered$, state$, staticApi) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/stapp/src/events/dangerous.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This events can be used in epics. But should not. 3 | */ 4 | import { createEvent } from '../core/createEvent/createEvent' 5 | import { APP_KEY } from '../helpers/constants' 6 | 7 | // Models 8 | import { EmptyEventCreator, EventCreator1 } from '../core/createEvent/createEvent.h' 9 | 10 | /** 11 | * Can be used to replace state completely 12 | */ 13 | export const dangerouslyReplaceState: EventCreator1 = createEvent( 14 | `${APP_KEY}: Replace state` 15 | ) 16 | 17 | /** 18 | * Can be used to reinitialize state 19 | */ 20 | export const dangerouslyResetState: EmptyEventCreator = createEvent(`${APP_KEY}: Reset state`) 21 | -------------------------------------------------------------------------------- /packages/stapp/src/events/lifecycle.ts: -------------------------------------------------------------------------------- 1 | import { createEvent } from '../core/createEvent/createEvent' 2 | import { APP_KEY } from '../helpers/constants' 3 | 4 | // Model 5 | import { EmptyEventCreator } from '../core/createEvent/createEvent.h' 6 | 7 | /** 8 | * Indicates about completing initialization 9 | * @private 10 | */ 11 | export const initEvent: EmptyEventCreator = createEvent( 12 | `${APP_KEY}: Initialization complete` 13 | ) 14 | 15 | /** 16 | * Indicates ready event 17 | * @private 18 | */ 19 | export const readyEvent: EmptyEventCreator = createEvent(`${APP_KEY}: Ready`) 20 | 21 | /** 22 | * @deprecated 23 | */ 24 | export const initDone = initEvent 25 | 26 | /** 27 | * Indicates about completing initialization 28 | * @private 29 | */ 30 | export const disconnectEvent: EmptyEventCreator = createEvent( 31 | `${APP_KEY}: Disconnect` 32 | ) 33 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/combineEpics/combineEpics.spec.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY, of } from 'rxjs' 2 | import { collectData } from '../testHelpers/collectData/collectData' 3 | import { combineEpics } from './combineEpics' 4 | 5 | // Models 6 | 7 | describe('combineEpics', () => { 8 | it('should return empty epic if no epics provided', async () => { 9 | // @ts-ignore 10 | const epic = combineEpics([]) 11 | const data = await collectData(epic()) 12 | 13 | expect(data.length).toBe(0) 14 | }) 15 | 16 | it('should return first epic, if only one provided', () => { 17 | const epic = () => EMPTY 18 | expect(combineEpics([epic])).toBe(epic) 19 | }) 20 | 21 | it('should combine epics', async () => { 22 | const epicA = () => of(1) 23 | const epicB = () => of(2) 24 | const epicC = () => of(3, 4, 5) 25 | const epic: any = combineEpics([epicA, epicB, epicC]) 26 | 27 | const result = await collectData(epic()) 28 | 29 | expect(result).toEqual([1, 2, 3, 4, 5]) 30 | }) 31 | 32 | it('should accept epics, that do not return stream', async () => { 33 | const epicA = () => undefined 34 | const epicB = () => of(1) 35 | 36 | const epic: any = combineEpics([epicA, epicB]) 37 | 38 | const result = await collectData(epic()) 39 | 40 | expect(result).toEqual([1]) 41 | }) 42 | 43 | it('should pass arguments to epics', async () => { 44 | const epicA = (event$: any, state$: any) => { 45 | event$('epicA: event$') 46 | state$('epicA: state$') 47 | return of(1) 48 | } 49 | const epicB = (event$: any, state$: any) => { 50 | event$('epicB: event$') 51 | state$('epicB: state$') 52 | return of(1) 53 | } 54 | const combined = combineEpics([epicA, epicB]) 55 | const e = jest.fn() 56 | const s = jest.fn() 57 | const staticApi = { 58 | getState: jest.fn(), 59 | dispatch: jest.fn() 60 | } 61 | 62 | await collectData(combined(e as any, s as any, staticApi as any)) 63 | 64 | expect(e.mock.calls).toEqual([['epicA: event$'], ['epicB: event$']]) 65 | expect(s.mock.calls).toEqual([['epicA: state$'], ['epicB: state$']]) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/combineEpics/combineEpics.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY, merge, Observable } from 'rxjs' 2 | 3 | // Models 4 | import { Epic } from '../../core/createApp/createApp.h' 5 | 6 | export function combineEpics(epics: [Epic]): Epic 7 | export function combineEpics(epics: [Epic, Epic]): Epic 8 | export function combineEpics( 9 | epics: [Epic, Epic, Epic] 10 | ): Epic 11 | export function combineEpics( 12 | epics: [Epic, Epic, Epic, Epic] 13 | ): Epic 14 | export function combineEpics( 15 | epics: [Epic, Epic, Epic, Epic, Epic] 16 | ): Epic 17 | export function combineEpics(epics: Array>): Epic { 18 | if (!epics.length) { 19 | return () => EMPTY 20 | } 21 | 22 | if (epics.length === 1) { 23 | return epics[0] 24 | } 25 | 26 | return (event$, state$, staticApi): Observable => { 27 | const streams = epics.map( 28 | (epic) => epic(event$, state$, staticApi) || EMPTY 29 | ) 30 | return merge(...streams) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/combineReducers/combineReducers.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers as reduxCombine } from 'redux' 2 | import { 3 | ReducerFunction, 4 | ReducersMap 5 | } from '../../core/createReducer/createReducer.h' 6 | 7 | export const combineReducers = ( 8 | reducers: ReducersMap 9 | ): ReducerFunction => { 10 | const keys = Object.keys(reducers) 11 | const result: any = {} 12 | 13 | for (let i = 0; i < keys.length; i++) { 14 | const key = keys[i] 15 | const element = (reducers as any)[key] 16 | 17 | if (typeof element === 'function') { 18 | result[key] = element 19 | } 20 | 21 | if (typeof element === 'object' && element !== null) { 22 | result[key] = combineReducers(element) 23 | } 24 | } 25 | 26 | return reduxCombine(result) 27 | } 28 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @private 3 | */ 4 | export const APP_KEY = '@@stapp' 5 | 6 | /** 7 | * @private 8 | */ 9 | export const SOURCE = `${APP_KEY}/source` 10 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/controlledPromise/controlledPromise.spec.ts: -------------------------------------------------------------------------------- 1 | import { controlledPromise } from './controlledPromise' 2 | 3 | describe('controlledPromise', () => { 4 | it('should return a new Promise and resolve callback', () => { 5 | const [promise, resolve] = controlledPromise() 6 | 7 | expect(promise).toBeInstanceOf(Promise) 8 | expect(typeof resolve).toBe('function') 9 | }) 10 | 11 | it('should resolve when resolve callback is called', (done) => { 12 | expect.assertions(1) 13 | 14 | const [promise, resolve] = controlledPromise() 15 | const payload = {} 16 | 17 | promise.then((x) => { 18 | expect(x).toBe(payload) 19 | done() 20 | }) 21 | 22 | resolve(payload) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/controlledPromise/controlledPromise.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @private 3 | */ 4 | export const controlledPromise = (): [Promise, (value?: T) => void] => { 5 | // Promise callback is called synchronously, so it's ok 6 | let resolve: any 7 | const promise = new Promise((_resolve) => { 8 | resolve = (value?: T) => _resolve(value) 9 | }) 10 | 11 | return [promise, resolve] 12 | } 13 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/getEventType/getEventType.spec.ts: -------------------------------------------------------------------------------- 1 | import { createEvent } from '../../core/createEvent/createEvent' 2 | import { getEventType } from './getEventType' 3 | 4 | describe('getEventType', () => { 5 | it('should get string representing event type from event creator', () => { 6 | const event = createEvent() 7 | expect(getEventType(event)).toEqual(event().type) 8 | }) 9 | 10 | it('should return string, if string passed', () => { 11 | const str = 'STRING' 12 | expect(getEventType(str)).toEqual(str) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/getEventType/getEventType.ts: -------------------------------------------------------------------------------- 1 | import { has } from '../has/has' 2 | 3 | // Models 4 | import { AnyEventCreator } from '../../core/createEvent/createEvent.h' 5 | 6 | /** 7 | * Gets type from an eventCreator 8 | * @private 9 | */ 10 | export function getEventType(eventCreator: AnyEventCreator | string): string { 11 | return has('getType', eventCreator) ? eventCreator.getType() : eventCreator 12 | } 13 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/has/has.spec.ts: -------------------------------------------------------------------------------- 1 | import { has } from './has' 2 | 3 | describe('has', () => { 4 | it('checks if passed object has property', () => { 5 | expect(has('a', null)).toBe(false) 6 | expect(has('a', undefined)).toBe(false) 7 | expect(has('a', 'test')).toBe(false) 8 | expect(has('a', { a: 5 })).toBe(true) 9 | expect(has('a', {})).toBe(false) 10 | expect(has('a', 'a')).toBe(false) 11 | }) 12 | 13 | it('ignores prototype', () => { 14 | class C { 15 | a() { 16 | return 17 | } 18 | } 19 | 20 | expect(has('a', new C())).toBe(false) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/has/has.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns whether or not an object has an own property with the specified name 3 | * 4 | * @param {String} prop The name of the property to check for. 5 | * @param {Object} obj The object to query. 6 | * @return {Boolean} Whether the property exists. 7 | * @private 8 | */ 9 | export const has =

    (prop: P, obj: any): obj is O => 10 | obj != null && Object.prototype.hasOwnProperty.call(obj, prop) 11 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/identity/identity.spec.ts: -------------------------------------------------------------------------------- 1 | import { identity } from './identity' 2 | 3 | describe('identity', () => { 4 | it('should return passed value', () => { 5 | const values = ['test', 1, false, {}, [], () => undefined] 6 | 7 | values.forEach((value) => { 8 | expect(identity(value)).toBe(value) 9 | }) 10 | }) 11 | 12 | it('should ignore any arguments except the first', () => { 13 | expect(identity(1, 2, 3)).toBe(1) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/identity/identity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns passed argument 3 | * 4 | * @return The input value 5 | * @private 6 | */ 7 | export const identity = (x: T, ...other: any[]) => x 8 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/is/isArray/isArray.spec.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from './isArray' 2 | 3 | it('should check if provided value is an array', () => { 4 | expect(isArray([])).toBe(true) 5 | expect(isArray(123)).toBe(false) 6 | expect(isArray(NaN)).toBe(false) 7 | expect(isArray('100')).toBe(false) 8 | expect(isArray('test')).toBe(false) 9 | expect(isArray(() => undefined)).toBe(false) 10 | }) 11 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/is/isArray/isArray.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Checks if provided argument is an array 3 | * @param a 4 | * @returns 5 | * @private 6 | */ 7 | export const isArray = (a: any): a is T[] => 8 | Object.prototype.toString.call(a) === '[object Array]' 9 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/is/isEvent/isEvent.spec.ts: -------------------------------------------------------------------------------- 1 | import { createEvent } from '../../../core/createEvent/createEvent' 2 | import { isEvent } from './isEvent' 3 | 4 | describe('isEvent', () => { 5 | const type = 'TYPE' 6 | 7 | it('requires a type', () => { 8 | expect(isEvent({ type })).toBe(true) 9 | expect(isEvent()).toBe(false) 10 | expect(isEvent({})).toBe(false) 11 | expect(isEvent({ type: undefined })).toBe(false) 12 | }) 13 | 14 | it('only accepts plain objects', () => { 15 | const event: any = () => undefined 16 | event.type = type 17 | expect(isEvent(event)).toBe(false) 18 | }) 19 | 20 | it('only returns true if type is a string', () => { 21 | expect(isEvent({ type: true })).toBe(false) 22 | expect(isEvent({ type: 123 })).toBe(false) 23 | }) 24 | 25 | it('works with createEvent', () => { 26 | expect(isEvent(createEvent()())).toBe(true) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/is/isEvent/isEvent.ts: -------------------------------------------------------------------------------- 1 | import { has } from '../../has/has' 2 | 3 | // Models 4 | import { Event } from '../../../core/createEvent/createEvent.h' 5 | 6 | /** 7 | * Checks if provided argument is a valid Redux action / event 8 | * @param arg any value 9 | * @private 10 | */ 11 | export const isEvent =

    (arg?: any): arg is Event => { 12 | return arg != null && typeof arg === 'object' && has('type', arg) && typeof arg.type === 'string' 13 | } 14 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/is/isModule/isModule.ts: -------------------------------------------------------------------------------- 1 | import { AnyModule, Module } from '../../../core/createApp/createApp.h' 2 | 3 | /** 4 | * @private 5 | */ 6 | export const isModule = ( 7 | module: AnyModule, Partial, State, any> 8 | ): module is Module, Partial, State> => { 9 | return typeof module !== 'function' 10 | } 11 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/is/isPromise/isPromise.ts: -------------------------------------------------------------------------------- 1 | export const isPromise = (maybePromise: any): maybePromise is Promise => 2 | typeof maybePromise === 'object' && 3 | maybePromise != null && 4 | typeof maybePromise.then === 'function' 5 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/is/isSubscribable/isSubscribable.ts: -------------------------------------------------------------------------------- 1 | import { Subscribable } from 'rxjs' 2 | 3 | export const isSubscribable = (value: any): value is Subscribable => { 4 | return Boolean( 5 | value && value.subscribe && typeof value.subscribe === 'function' 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/logError/logError.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export const logError = (nameSpace: string, error: any) => { 3 | /* istanbul ignore next */ 4 | if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { 5 | // tslint:disable-next-line no-console 6 | console.error(`${nameSpace} error:`, error) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/omit/omit.spec.ts: -------------------------------------------------------------------------------- 1 | import { omit } from './omit' 2 | 3 | describe('omit', () => { 4 | it('should omit props', () => { 5 | expect(omit({ a: 1, b: 2 }, ['a'])).toEqual({ b: 2 }) 6 | expect(omit({ a: 2 }, ['a', 'c', 'd'] as any)).toEqual({}) 7 | expect(omit({ a: 3 }, [])).toEqual({ a: 3 }) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/omit/omit.ts: -------------------------------------------------------------------------------- 1 | import { has } from '../has/has' 2 | 3 | // Models 4 | import { Omit } from '../../types/omit' 5 | 6 | export const omit = (obj: T, props: K[]): Omit => { 7 | const result: { [K: string]: any } = {} 8 | 9 | for (const key in obj) { 10 | if (has(key, obj) && props.indexOf(key as any) < 0) { 11 | result[key] = obj[key] 12 | } 13 | } 14 | 15 | return result as Omit 16 | } 17 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/pick/pick.spec.ts: -------------------------------------------------------------------------------- 1 | import { pick } from './pick' 2 | 3 | describe('pick', () => { 4 | it('return new object with picked properties', () => { 5 | expect(pick({ a: 3, b: 2, c: 1 }, ['a', 'b'])).toEqual({ a: 3, b: 2 }) 6 | expect(pick({}, ['a', 'b'] as any)).toEqual({}) 7 | expect(pick({ a: 1, b: 2 }, ['a', 'b', 'c'] as any)).toEqual({ a: 1, b: 2 }) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/pick/pick.ts: -------------------------------------------------------------------------------- 1 | import { has } from '../has/has' 2 | 3 | export const pick = (obj: T, props: K[]): Pick => { 4 | const result: { [K: string]: any } = {} 5 | 6 | for (const key of props) { 7 | if (has(key as string, obj)) { 8 | result[key as string] = obj[key] 9 | } 10 | } 11 | 12 | return result as Pick 13 | } 14 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/select/select.spec.ts: -------------------------------------------------------------------------------- 1 | import { createEvent } from '../../core/createEvent/createEvent' 2 | import { select, selectArray } from './select' 3 | 4 | describe('select', () => { 5 | it('should return a filter function', () => { 6 | const a = createEvent() 7 | const b = createEvent() 8 | const c = createEvent() 9 | const values = [a(), b(), c(), a(), b(), c()] 10 | const filterFn = select(a) 11 | 12 | expect(values.filter(filterFn)).toEqual([a(), a()]) 13 | }) 14 | 15 | it('should filter stream by type', () => { 16 | const a = createEvent() 17 | const b = createEvent() 18 | const c = createEvent() 19 | const values = [a(), b(), c(), a(), b(), c()] 20 | const filterFn = select(a.getType()) 21 | 22 | expect(values.filter(filterFn)).toEqual([a(), a()]) 23 | }) 24 | 25 | it('should filter stream by event creators', () => { 26 | const a = createEvent() 27 | const b = createEvent() 28 | const c = createEvent() 29 | const values = [a(), b(), c(), a(), b(), c()] 30 | const filterFn = select([a, b]) 31 | 32 | expect(values.filter(filterFn)).toEqual([a(), b(), a(), b()]) 33 | }) 34 | 35 | it('should filter stream by types', () => { 36 | const a = createEvent() 37 | const b = createEvent() 38 | const c = createEvent() 39 | const values = [a(), b(), c(), a(), b(), c()] 40 | const filterFn = select([a.getType(), b.getType()]) 41 | 42 | expect(values.filter(filterFn)).toEqual([a(), b(), a(), b()]) 43 | }) 44 | }) 45 | 46 | describe('selectArray', () => { 47 | it('should filter stream by event creators', () => { 48 | const a = createEvent() 49 | const b = createEvent() 50 | const c = createEvent() 51 | const values = [a(), b(), c(), a(), b(), c()] 52 | const filterFn = selectArray([a, b]) 53 | 54 | expect(values.filter(filterFn)).toEqual([a(), b(), a(), b()]) 55 | }) 56 | 57 | it('should filter stream by types', () => { 58 | const a = createEvent() 59 | const b = createEvent() 60 | const c = createEvent() 61 | const values = [a(), b(), c(), a(), b(), c()] 62 | const filterFn = selectArray([a.getType(), b.getType()]) 63 | 64 | expect(values.filter(filterFn)).toEqual([a(), b(), a(), b()]) 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/select/select.ts: -------------------------------------------------------------------------------- 1 | import { AnyEventCreator, Event } from '../../core/createEvent/createEvent.h' 2 | import { getEventType } from '../getEventType/getEventType' 3 | import { isArray } from '../is/isArray/isArray' 4 | 5 | export function select( 6 | eventCreator: AnyEventCreator | string | Array 7 | ) { 8 | if (isArray(eventCreator)) { 9 | return selectArray(eventCreator) // tslint:disable-line deprecation 10 | } 11 | 12 | const type = getEventType(eventCreator) 13 | return (event: Event) => type === event.type 14 | } 15 | 16 | /** 17 | * @deprecated 18 | * @param eventCreators 19 | */ 20 | export function selectArray(eventCreators: Array) { 21 | const types = eventCreators.map((eventCreator) => getEventType(eventCreator)) 22 | 23 | return (event: Event) => types.indexOf(event.type) >= 0 24 | } 25 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/testHelpers/collectData/collectData.ts: -------------------------------------------------------------------------------- 1 | import { EMPTY, Subscribable } from 'rxjs' 2 | import { Thunk } from '../../../core/createApp/createApp.h' 3 | 4 | /** 5 | * @private 6 | */ 7 | export async function collectData( 8 | stream: Subscribable = EMPTY 9 | ): Promise { 10 | const result: T[] = [] 11 | 12 | return new Promise((res, rej) => 13 | stream.subscribe({ 14 | next: (x: T) => result.push(x), 15 | complete: res, 16 | error: rej 17 | }) 18 | ).then(() => result) 19 | } 20 | 21 | /** 22 | * @private 23 | */ 24 | export async function collectThunkData(thunk: Thunk, state?: any) { 25 | const dispatch = jest.fn() 26 | await thunk(() => state, dispatch) 27 | 28 | return dispatch.mock.calls.map((call) => call[0]) 29 | } 30 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/testHelpers/getInitialState/getInitialState.spec.ts: -------------------------------------------------------------------------------- 1 | import { createReducer } from '../../../core/createReducer/createReducer' 2 | import { getInitialState } from './getInitialState' 3 | 4 | describe('getInitialState', () => { 5 | it('should return initial state from reducer', () => { 6 | const initialState = { test: 123 } 7 | const reducer = createReducer(initialState) 8 | 9 | expect(getInitialState(reducer)).toBe(initialState) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/testHelpers/getInitialState/getInitialState.ts: -------------------------------------------------------------------------------- 1 | import { Reducer } from 'redux' 2 | 3 | /** 4 | * @private 5 | */ 6 | export function getInitialState(reducer: Reducer) { 7 | // @ts-ignore 8 | return reducer(undefined, { type: '@@init' }) 9 | } 10 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/testHelpers/loggerModule/loggerModule.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '../../../core/createApp/createApp.h' 2 | import { Event } from '../../../core/createEvent/createEvent.h' 3 | import { APP_KEY } from '../../constants' 4 | 5 | /** 6 | * @private 7 | */ 8 | export const loggerModule = ({ 9 | pattern = new RegExp(`^${APP_KEY}`) 10 | }: { 11 | pattern?: RegExp | null 12 | }): Module<{}, { eventLog: Array> }> => ({ 13 | name: 'logger', 14 | reducers: { 15 | eventLog: (state: Array> = [], event: Event) => { 16 | if (event.type.startsWith('@@redux')) { 17 | return state 18 | } 19 | 20 | if (pattern && pattern.test(event.type)) { 21 | return state 22 | } 23 | 24 | return state.concat(event) 25 | } 26 | } 27 | }) 28 | 29 | export const logger = ({ 30 | ignoreRedux = true, 31 | ignoreInternal = true, 32 | ignorePattern 33 | }: { 34 | ignoreInternal?: boolean 35 | ignoreRedux?: boolean 36 | ignorePattern?: RegExp 37 | }): Module< 38 | {}, 39 | { 40 | logger: { 41 | events: Array> 42 | allEvents: Array> 43 | last: Event 44 | } 45 | } 46 | > => ({ 47 | name: 'logger', 48 | state: { 49 | logger: { 50 | events: (state: Array> = [], event: Event) => { 51 | if (ignoreRedux && event.type.startsWith('@@redux')) { 52 | return state 53 | } 54 | 55 | if (ignoreInternal && event.type.startsWith(APP_KEY)) { 56 | return state 57 | } 58 | 59 | if (ignorePattern && ignorePattern.test(event.type)) { 60 | return state 61 | } 62 | 63 | return state.concat(event) 64 | }, 65 | allEvents: ( 66 | state: Array> = [], 67 | event: Event 68 | ) => { 69 | return state.concat(event) 70 | }, 71 | last: (_: any, event: Event) => event 72 | } 73 | } 74 | }) 75 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/testHelpers/wait/wait.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @private 3 | */ 4 | export function wait(n: number) { 5 | return new Promise((resolve) => setTimeout(resolve, n)) 6 | } 7 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/uniqueId/uniqify.ts: -------------------------------------------------------------------------------- 1 | import { uniqueId } from './uniqueId' 2 | 3 | const uniqueSet = new Set() 4 | 5 | export const uniqify = (id?: string) => { 6 | if (!id) { 7 | return `[${uniqueId()}]` 8 | } 9 | 10 | if (uniqueSet.has(id)) { 11 | return `${id} [${uniqueId()}]` 12 | } 13 | 14 | uniqueSet.add(id) 15 | return id 16 | } 17 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/uniqueId/uniqueId.spec.ts: -------------------------------------------------------------------------------- 1 | import { uniqify } from './uniqify' 2 | import { uniqueId } from './uniqueId' 3 | 4 | describe('uniqueId', () => { 5 | it('should generate unique id', () => { 6 | const array = Array(100) 7 | .fill(0) 8 | .map(uniqueId) 9 | const set = new Set(array) 10 | 11 | expect(array.length).toEqual(set.size) 12 | }) 13 | 14 | it('should uniqify ids', () => { 15 | expect(uniqify('unique_test')).toEqual('unique_test') 16 | expect(uniqify('unique_test').length >= 7).toBe(true) 17 | expect(uniqify().length >= 3).toBe(true) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/stapp/src/helpers/uniqueId/uniqueId.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @private 3 | */ 4 | let i = 0 5 | 6 | /** 7 | * Generates a unique ID. 8 | * 9 | * @returns {number} Returns the unique ID. 10 | * @private 11 | */ 12 | export const uniqueId = () => ++i 13 | -------------------------------------------------------------------------------- /packages/stapp/src/stapp.ts: -------------------------------------------------------------------------------- 1 | // Core utils 2 | export { createApp } from './core/createApp/createApp' 3 | export { createEvent } from './core/createEvent/createEvent' 4 | export { createEffect } from './core/createEffect/createEffect' 5 | export { createReducer } from './core/createReducer/createReducer' 6 | 7 | // Epic utils 8 | export { setObservableConfig } from './core/createApp/setObservableConfig' 9 | export { select, selectArray } from './helpers/select/select' 10 | export { getEventType } from './helpers/getEventType/getEventType' 11 | export { combineEpics } from './helpers/combineEpics/combineEpics' 12 | 13 | // Events 14 | export { 15 | dangerouslyReplaceState, 16 | dangerouslyResetState 17 | } from './events/dangerous' 18 | export { 19 | disconnectEvent, 20 | initEvent, 21 | initDone, 22 | readyEvent 23 | } from './events/lifecycle' 24 | 25 | // Typings 26 | export { 27 | Dispatch, 28 | Epic, 29 | EventEpic, 30 | Module, 31 | ModuleFactory, 32 | ObservableConfig, 33 | Stapp, 34 | Thunk, 35 | WaitFor, 36 | StappApi, 37 | StappState 38 | } from './core/createApp/createApp.h' 39 | export { EffectCreator } from './core/createEffect/createEffect.h' 40 | export { 41 | AnyEventCreator, 42 | BaseEventCreator, 43 | EmptyEventCreator, 44 | Event, 45 | EventCreator0, 46 | EventCreator1, 47 | EventCreator2, 48 | EventCreator3, 49 | EventCreators, 50 | PayloadTransformer0, 51 | PayloadTransformer1, 52 | PayloadTransformer3 53 | } from './core/createEvent/createEvent.h' 54 | export { 55 | EventHandler, 56 | EventHandlers, 57 | Reducer 58 | } from './core/createReducer/createReducer.h' 59 | -------------------------------------------------------------------------------- /packages/stapp/src/types/omit.ts: -------------------------------------------------------------------------------- 1 | export type Omit = Pick> 2 | -------------------------------------------------------------------------------- /packages/stapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/stapp/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-react", 8 | "tslint-config-standard", 9 | "tslint-config-prettier" 10 | ], 11 | "exclude": [ 12 | "./node_modules/**/*" 13 | ], 14 | "rules": { 15 | "prefer-for-of": false, 16 | "object-literal-sort-keys": false, 17 | "no-unused-variable": true, 18 | "interface-name": false, 19 | "max-line-length": [false], 20 | "interface-over-type-literal": false, 21 | "object-literal-key-quotes": false, 22 | "prefer-object-spread": false, 23 | "callable-types": false, 24 | 25 | "no-submodule-imports": false, 26 | "no-default-export": true, 27 | "no-magic-numbers": false, 28 | "promise-function-async": false, 29 | "member-access": [true, "no-public"], 30 | "no-implicit-dependencies": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/template/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": true 6 | } 7 | }] 8 | ] 9 | } -------------------------------------------------------------------------------- /packages/template/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | *.pid.lock 12 | 13 | # Coverage 14 | lib-cov 15 | coverage 16 | .nyc_output 17 | 18 | # Dependency directories 19 | node_modules/ 20 | jspm_packages/ 21 | 22 | # IDE 23 | .DS_Store 24 | .vscode 25 | .idea 26 | 27 | # Compiled sources 28 | docs 29 | _book 30 | compiled 31 | 32 | # Cache 33 | .awcache 34 | .rpt2_cache 35 | .npm 36 | 37 | # Private data 38 | .npmrc 39 | 40 | # Other 41 | tsconfig.json 42 | testSetup.ts 43 | src/ 44 | examples/ 45 | -------------------------------------------------------------------------------- /packages/template/README-TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Package name 2 | 3 | Description. Please, mention [Stapp](https://github.com/TinkoffCreditSystems/stapp) here. 4 | 5 | Add link to the docs, e.g.: [documentation](https://stapp.js.org). 6 | 7 | ## Installation 8 | ```bash 9 | npm install package-name stapp 10 | # OR using stapp-cli-tools 11 | stapp install package-name 12 | ``` 13 | 14 | ### Peer dependencies 15 | * **stapp**: >= 2.6 16 | 17 | ## License 18 | 19 | ``` 20 | Copyright 2019 Tinkoff Bank 21 | 22 | Licensed under the Apache License, Version 2.0 (the "License"); 23 | you may not use this file except in compliance with the License. 24 | You may obtain a copy of the License at 25 | 26 | http://www.apache.org/licenses/LICENSE-2.0 27 | 28 | Unless required by applicable law or agreed to in writing, software 29 | distributed under the License is distributed on an "AS IS" BASIS, 30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31 | See the License for the specific language governing permissions and 32 | limitations under the License. 33 | ``` 34 | -------------------------------------------------------------------------------- /packages/template/README.md: -------------------------------------------------------------------------------- 1 | # Package template 2 | 3 | Part of [Stapp](https://github.com/TinkoffCreditSystems/stapp). 4 | 5 | ## Usage 6 | 1. Copy this folder with desired name 7 | 2. Update package.json: 8 | 1. Add name 9 | 2. Add description 10 | 3. Add keywords 11 | 4. Remove `private` field 12 | 3. README: 13 | 1. Delete this file 14 | 2. Rename README-TEMPLATE.md to README.md 15 | 3. Update readme 16 | 4. Update docs in the `/docs` directory. 17 | -------------------------------------------------------------------------------- /packages/template/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "testURL": "http://localhost", 3 | "transform": { 4 | "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js", 5 | "^.+\\.jsx?$": "/node_modules/babel-jest" 6 | }, 7 | "modulePaths": [ 8 | "" 9 | ], 10 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "coveragePathIgnorePatterns": [ 17 | "/node_modules/", 18 | "/test|spec/" 19 | ], 20 | "coverageThreshold": { 21 | "global": { 22 | "branches": 100, 23 | "functions": 100, 24 | "lines": 100, 25 | "statements": 100 26 | } 27 | }, 28 | "collectCoverage": true, 29 | "coverageReporters": [ 30 | "json", 31 | "lcov" 32 | ], 33 | "globals": { 34 | "process.env.NODE_ENV": "test" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "name-here", 3 | "version": "2.7.0-0", 4 | "description": "DESCRIPTION HERE", 5 | "private": true, 6 | "keywords": [ 7 | "keyword a", 8 | "keyword b" 9 | ], 10 | "main": "lib/index.js", 11 | "typings": "lib/index.d.ts", 12 | "files": [ 13 | "lib" 14 | ], 15 | "author": "Firstname Lastname (optional address)", 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/TinkoffCreditSystems/stapp/" 19 | }, 20 | "engines": { 21 | "node": ">=8.0.0" 22 | }, 23 | "license": "Apache-2.0", 24 | "scripts": { 25 | "build": "npm run build:module", 26 | "build:module": "tsc", 27 | "prebuild": "rimraf lib", 28 | "update-all": "lernaupdate", 29 | "test": "npm run test:lint && npm run test:jest", 30 | "test:lint": "tslint -t verbose './src/**/*.ts' './src/**/*.tsx' -p ./tsconfig.json -c ./tslint.json", 31 | "test:jest": "jest --config ./jest.config.json", 32 | "test:ci": "npm run test:lint && jest --config ./jest.config.json --runInBand --coverage --coverageReporters=text-lcov" 33 | }, 34 | "peerDependencies": { 35 | "stapp": ">=2.5.0" 36 | }, 37 | "devDependencies": { 38 | "@types/jest": "^24.0.11", 39 | "@types/node": "^11.13.0", 40 | "babel-jest": "^24.6.0", 41 | "babel-preset-env": "^1.7.0", 42 | "babel-preset-stage-0": "^6.24.1", 43 | "jest": "^24.6.0", 44 | "stapp": "^2.7.0-0", 45 | "ts-jest": "^24.0.1", 46 | "ts-node": "^8.0.3", 47 | "tslib": "^1.9.3", 48 | "tslint": "^5.15.0", 49 | "tslint-config-prettier": "^1.18.0", 50 | "tslint-config-standard": "^8.0.1", 51 | "tslint-eslint-rules": "^5.4.0", 52 | "tslint-react": "^4.0.0", 53 | "typescript": "3.3.4000" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/template/src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { hello } from './' 2 | 3 | describe('template', () => { 4 | it('should work', () => { 5 | expect(hello('World')).toBe('hello, World!') 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /packages/template/src/index.ts: -------------------------------------------------------------------------------- 1 | export const hello = (name: string) => `hello, ${name}!` 2 | -------------------------------------------------------------------------------- /packages/template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../configs/tsconfig.base", 3 | "compilerOptions": { 4 | "declarationDir": "./lib", 5 | "outDir": "./lib", 6 | "typeRoots": [ 7 | "./node_modules/@types", 8 | "./src/models/global.d.ts" 9 | ] 10 | }, 11 | "exclude": [ 12 | "**/*.spec.ts", 13 | "**/*.spec.tsx" 14 | ], 15 | "include": [ 16 | "./src" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/template/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "./node_modules/tslint-eslint-rules/dist/rules" 4 | ], 5 | "extends": [ 6 | "tslint:latest", 7 | "tslint-react", 8 | "tslint-config-standard", 9 | "tslint-config-prettier" 10 | ], 11 | "exclude": [ 12 | "./node_modules/**/*" 13 | ], 14 | "rules": { 15 | "prefer-for-of": false, 16 | "object-literal-sort-keys": false, 17 | "no-unused-variable": true, 18 | "interface-name": false, 19 | "max-line-length": [false], 20 | "interface-over-type-literal": false, 21 | "object-literal-key-quotes": false, 22 | "prefer-object-spread": false, 23 | "callable-types": false, 24 | 25 | "no-submodule-imports": false, 26 | "no-default-export": true, 27 | "no-magic-numbers": false, 28 | "promise-function-async": false, 29 | "member-access": [true, "no-public"], 30 | "no-implicit-dependencies": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /scripts/getPackages.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const glob = require("glob") 4 | 5 | const readJSONFile = file => JSON.parse(fs.readFileSync(file)) 6 | 7 | const flatMap = (arr, fn) => { 8 | return arr.reduce((r, n) => { 9 | return r.concat(fn(n)) 10 | }, []) 11 | } 12 | 13 | module.exports = () => { 14 | const lernaConfig = readJSONFile(path.join(process.cwd(), 'lerna.json')) 15 | const { packages } = lernaConfig 16 | 17 | return flatMap(packages, p => glob.sync(`${p}/package.json`)) 18 | .map(packagePath => { 19 | const packageJson = readJSONFile(packagePath) 20 | 21 | return { 22 | name: packageJson.name, 23 | version: packageJson.version, 24 | private: packageJson.private, 25 | packagePath, 26 | dirPath: path.dirname(packagePath) 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /scripts/update.js: -------------------------------------------------------------------------------- 1 | const ncu = require('npm-check-updates') 2 | const pSeries = require('p-series'); 3 | const getPackages = require('./getPackages') 4 | 5 | const packages = getPackages() 6 | const tasks = packages.map(p => () => ncu.run({ 7 | packageFile: p.packagePath, 8 | upgrade: true, 9 | upgradeAll: true 10 | }).then(() => console.log(p.name, 'updated'))) 11 | 12 | 13 | pSeries(tasks).then(() => console.log('All done!')) 14 | --------------------------------------------------------------------------------