├── .github ├── dependabot.yml └── workflows │ ├── check.yml │ └── release.yml ├── .gitignore ├── .idea ├── .gitignore ├── inspectionProfiles │ └── Project_Default.xml ├── jsLinters │ └── eslint.xml ├── modules.xml ├── prettier.xml ├── test-arch.iml └── vcs.xml ├── .vscode └── settings.json ├── README.md ├── examples ├── authentication │ ├── .babelrc │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.tsx │ │ ├── __tests__ │ │ │ └── counter.spec.ts │ │ ├── assets │ │ │ └── react.svg │ │ ├── components │ │ │ ├── button.tsx │ │ │ ├── container.tsx │ │ │ └── counter.tsx │ │ ├── cortex │ │ │ ├── _core.ts │ │ │ ├── dependencies │ │ │ │ └── _dependencies.ts │ │ │ ├── services │ │ │ │ ├── _services.ts │ │ │ │ └── counter.service.ts │ │ │ └── utils │ │ │ │ ├── hooks.ts │ │ │ │ ├── service.ts │ │ │ │ └── types.ts │ │ ├── input.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── yarn.lock ├── counter │ ├── .babelrc │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.tsx │ │ ├── __tests__ │ │ │ └── counter.spec.ts │ │ ├── assets │ │ │ └── react.svg │ │ ├── components │ │ │ ├── button.tsx │ │ │ ├── container.tsx │ │ │ └── counter.tsx │ │ ├── cortex │ │ │ ├── _core.ts │ │ │ ├── dependencies │ │ │ │ └── _dependencies.ts │ │ │ ├── services │ │ │ │ ├── _services.ts │ │ │ │ └── counter.service.ts │ │ │ └── utils │ │ │ │ ├── hooks.ts │ │ │ │ ├── service.ts │ │ │ │ └── types.ts │ │ ├── input.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── yarn.lock ├── dog-api │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── vite.svg │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── assets │ │ │ └── react.svg │ │ ├── cortex │ │ │ ├── _core.ts │ │ │ ├── dependencies │ │ │ │ ├── _dependencies.ts │ │ │ │ └── api │ │ │ │ │ ├── api.gateway.ts │ │ │ │ │ ├── axios.api.adapter.ts │ │ │ │ │ ├── fake.api.adapter.ts │ │ │ │ │ └── fetch.api.adapter.ts │ │ │ ├── services │ │ │ │ ├── _services.ts │ │ │ │ └── dog.service.ts │ │ │ └── utils │ │ │ │ ├── hooks.ts │ │ │ │ ├── service.ts │ │ │ │ └── types.ts │ │ ├── index.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── yarn.lock └── todo │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ ├── public │ └── vite.svg │ ├── src │ ├── App.css │ ├── App.tsx │ ├── Components │ │ ├── EditTodoForm.tsx │ │ ├── Todo.tsx │ │ ├── TodoForm.tsx │ │ └── TodoWrapper.tsx │ ├── cortex │ │ ├── _core.ts │ │ ├── dependencies │ │ │ └── _dependencies.ts │ │ ├── services │ │ │ ├── _services.ts │ │ │ └── todo │ │ │ │ ├── todo.service.spec.ts │ │ │ │ └── todo.service.ts │ │ └── utils │ │ │ ├── hooks.ts │ │ │ ├── service.ts │ │ │ └── types.ts │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── vite.config.ts │ └── yarn.lock ├── logo ├── favicon.ico ├── generate-logo.py ├── logo.png ├── logo_128.png ├── logo_16.png ├── logo_256.png ├── logo_48.png ├── logo_512.png └── original_logo.png ├── package.json ├── packages ├── .prettierrc ├── core │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__ │ │ ├── core.spec.ts │ │ └── services.spec.ts │ ├── assets │ │ ├── logo.png │ │ └── logo_512.png │ ├── jest.config.js │ ├── package.json │ ├── release.config.js │ ├── scripts │ │ ├── cli.js │ │ └── react │ │ │ └── cortex │ │ │ ├── _core.ts │ │ │ ├── core_services │ │ │ └── core_services.ts │ │ │ ├── dependencies │ │ │ └── _dependencies.ts │ │ │ ├── services │ │ │ ├── _services.ts │ │ │ └── counter.service.ts │ │ │ └── utils │ │ │ ├── hooks.ts │ │ │ ├── service.ts │ │ │ └── types.ts │ ├── src │ │ ├── base-service.ts │ │ ├── core-services │ │ │ ├── debugger │ │ │ │ └── create-debugger-service.ts │ │ │ └── persistence │ │ │ │ ├── create-persistence-service.spec.ts │ │ │ │ └── create-persistence-service.ts │ │ ├── create-cortex-factory.ts │ │ ├── index.ts │ │ ├── service-registry.ts │ │ └── types │ │ │ └── service-constructor.ts │ └── tsconfig.json ├── doc │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── blog │ │ ├── 2019-05-28-first-blog-post.md │ │ ├── 2019-05-29-long-blog-post.md │ │ ├── 2021-08-01-mdx-blog-post.mdx │ │ ├── 2021-08-26-welcome │ │ │ ├── docusaurus-plushie-banner.jpeg │ │ │ └── index.md │ │ └── authors.yml │ ├── docs │ │ ├── Concepts │ │ │ └── dependencies.mdx │ │ ├── React hooks │ │ │ ├── useAppSelector.mdx │ │ │ ├── useAppState.mdx │ │ │ ├── useLazyMethod.mdx │ │ │ ├── useMethod.mdx │ │ │ ├── useService.mdx │ │ │ └── useStore.mdx │ │ ├── Tutorials │ │ │ ├── api.mdx │ │ │ ├── counter.mdx │ │ │ └── todo-list.mdx │ │ ├── basic-example.mdx │ │ ├── debugger.mdx │ │ ├── intro.mdx │ │ ├── next.mdx │ │ ├── persistence.mdx │ │ ├── services.mdx │ │ ├── setup.mdx │ │ ├── store.mdx │ │ └── testing.mdx │ ├── docusaurus.config.js │ ├── package.json │ ├── sidebars.js │ ├── src │ │ ├── components │ │ │ └── HomepageFeatures │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.css │ │ ├── css │ │ │ └── custom.css │ │ └── pages │ │ │ ├── index.module.css │ │ │ ├── index.tsx │ │ │ └── markdown-page.md │ ├── static │ │ ├── .nojekyll │ │ └── img │ │ │ ├── adapters.png │ │ │ ├── cortex-scheme.png │ │ │ ├── docusaurus-social-card.jpg │ │ │ ├── docusaurus.png │ │ │ ├── favicon.ico │ │ │ ├── github-icon.svg │ │ │ ├── logo-heart.png │ │ │ ├── logo_256.png │ │ │ ├── logo_512.png │ │ │ ├── npm-icon.svg │ │ │ ├── schema-1.svg │ │ │ ├── undraw_docusaurus_mountain.svg │ │ │ ├── undraw_docusaurus_react.svg │ │ │ └── undraw_docusaurus_tree.svg │ └── tsconfig.json ├── example │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── App.css │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── cortex │ │ │ ├── _core.ts │ │ │ ├── dependencies │ │ │ │ └── _dependencies.ts │ │ │ ├── services │ │ │ │ ├── _services.ts │ │ │ │ └── counter.service.ts │ │ │ └── utils │ │ │ │ ├── hooks.ts │ │ │ │ ├── service.ts │ │ │ │ └── types.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── react-app-env.d.ts │ │ ├── reportWebVitals.ts │ │ └── setupTests.ts │ └── tsconfig.json └── react │ ├── .gitignore │ ├── CHANGELOG.md │ ├── __tests__ │ ├── hooks.spec.tsx │ └── provider.spec.tsx │ ├── jest.config.js │ ├── package.json │ ├── release.config.js │ ├── src │ ├── index.ts │ └── provider.tsx │ ├── tsconfig.json │ ├── yarn-error.log │ └── yarn.lock └── yarn.lock /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/packages/react' 5 | schedule: 6 | interval: 'weekly' 7 | commit-message: 8 | prefix: 'bump(react-cortex)' 9 | 10 | - package-ecosystem: 'npm' 11 | directory: '/packages/core' 12 | schedule: 13 | interval: 'weekly' 14 | commit-message: 15 | prefix: 'bump(cortex)' 16 | 17 | - package-ecosystem: 'npm' 18 | directory: '/packages/doc' 19 | schedule: 20 | interval: 'weekly' 21 | commit-message: 22 | prefix: 'bump(doc)' 23 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: check workflow 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | install-dependencies: 10 | name: Install Dependencies 11 | runs-on: ubuntu-latest 12 | outputs: 13 | cache-key: ${{ steps.cache-dependencies.outputs.cache-key }} 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: '20' 23 | 24 | - name: Get yarn.lock hash 25 | id: get-hash 26 | run: echo "YARN_LOCK_HASH=${{ hashFiles('**/yarn.lock') }}" >> $GITHUB_ENV 27 | 28 | - name: Display yarn.lock hash 29 | run: echo "Yarn.lock hash $YARN_LOCK_HASH" 30 | 31 | - name: Cache node modules 32 | uses: actions/cache@v3 33 | id: cache-dependencies 34 | with: 35 | path: '**/node_modules' 36 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 37 | 38 | - name: Install Dependencies 39 | run: yarn install --frozen-lockfile 40 | 41 | test-core: 42 | name: Run Core tests 43 | needs: install-dependencies 44 | runs-on: ubuntu-latest 45 | 46 | steps: 47 | - name: Checkout 48 | uses: actions/checkout@v3 49 | 50 | - name: Setup Node.js 51 | uses: actions/setup-node@v3 52 | with: 53 | node-version: '20' 54 | 55 | - name: Use Cached Dependencies 56 | uses: actions/cache@v3 57 | with: 58 | path: '**/node_modules' 59 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 60 | 61 | - name: Run Core tests 62 | run: yarn workspace @azot-dev/cortex test 63 | 64 | - name: Test Types 65 | run: yarn workspace @azot-dev/cortex build 66 | 67 | test-react: 68 | name: Run React tests 69 | needs: install-dependencies 70 | runs-on: ubuntu-latest 71 | 72 | steps: 73 | - name: Checkout 74 | uses: actions/checkout@v3 75 | 76 | - name: Setup Node.js 77 | uses: actions/setup-node@v3 78 | with: 79 | node-version: '20' 80 | 81 | - name: Use Cached Dependencies 82 | uses: actions/cache@v3 83 | with: 84 | path: '**/node_modules' 85 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 86 | 87 | - name: Run React tests 88 | run: yarn workspace @azot-dev/react-cortex test 89 | 90 | - name: Run React tests 91 | run: yarn workspace @azot-dev/react-cortex build 92 | 93 | build-doc: 94 | runs-on: ubuntu-latest 95 | needs: install-dependencies 96 | steps: 97 | - name: Checkout 98 | uses: actions/checkout@v3 99 | 100 | - name: Set up Node.js 101 | uses: actions/setup-node@v3 102 | with: 103 | node-version: '20' 104 | 105 | - name: Use Cached Dependencies 106 | uses: actions/cache@v3 107 | with: 108 | path: '**/node_modules' 109 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 110 | 111 | - name: Check mdx 112 | run: yarn workspace doc check-mdx 113 | 114 | - name: Build 115 | run: yarn workspace doc build 116 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: deployment workflow 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | inputs: 9 | run_release_chrome_extension: 10 | description: 'Set to true to run release-chrome-extension job' 11 | required: false 12 | default: 'false' 13 | 14 | jobs: 15 | install-dependencies: 16 | name: Install Dependencies 17 | runs-on: ubuntu-latest 18 | outputs: 19 | cache-key: ${{ steps.cache-dependencies.outputs.cache-key }} 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v3 24 | 25 | - name: Setup Node.js 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: '20' 29 | 30 | - name: Get yarn.lock hash 31 | id: get-hash 32 | run: echo "YARN_LOCK_HASH=${{ hashFiles('**/yarn.lock') }}" >> $GITHUB_ENV 33 | 34 | - name: Display yarn.lock hash 35 | run: echo "Yarn.lock hash $YARN_LOCK_HASH" 36 | 37 | - name: Cache node modules 38 | uses: actions/cache@v3 39 | id: cache-dependencies 40 | with: 41 | path: '**/node_modules' 42 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 43 | 44 | - name: Install Dependencies 45 | run: yarn install --frozen-lockfile 46 | 47 | test-core: 48 | name: Test cortex 49 | needs: install-dependencies 50 | runs-on: ubuntu-latest 51 | 52 | steps: 53 | - name: Checkout 54 | uses: actions/checkout@v3 55 | 56 | - name: Setup Node.js 57 | uses: actions/setup-node@v3 58 | with: 59 | node-version: '20' 60 | 61 | - name: Use Cached Dependencies 62 | uses: actions/cache@v3 63 | with: 64 | path: '**/node_modules' 65 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 66 | 67 | - name: Run Core tests 68 | run: yarn workspace @azot-dev/cortex test 69 | 70 | test-react: 71 | name: Test react-cortex 72 | needs: install-dependencies 73 | runs-on: ubuntu-latest 74 | 75 | steps: 76 | - name: Checkout 77 | uses: actions/checkout@v3 78 | 79 | - name: Setup Node.js 80 | uses: actions/setup-node@v3 81 | with: 82 | node-version: '20' 83 | 84 | - name: Use Cached Dependencies 85 | uses: actions/cache@v3 86 | with: 87 | path: '**/node_modules' 88 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 89 | 90 | - name: Run React tests 91 | run: yarn workspace @azot-dev/react-cortex test 92 | 93 | release-core: 94 | needs: [test-core, test-react] 95 | name: NPM Release cortex 96 | env: 97 | DEBUG: 'semantic-release:*' 98 | runs-on: ubuntu-latest 99 | steps: 100 | - name: Checkout 101 | uses: actions/checkout@v3 102 | with: 103 | fetch-depth: 0 104 | 105 | - name: Setup Node.js 106 | uses: actions/setup-node@v3 107 | with: 108 | node-version: '20' 109 | 110 | - name: Use Cached Dependencies 111 | uses: actions/cache@v3 112 | with: 113 | path: '**/node_modules' 114 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 115 | 116 | - name: Build Core 117 | run: yarn workspace @azot-dev/cortex build 118 | 119 | - name: Release Core 120 | env: 121 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 122 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 123 | run: | 124 | echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc 125 | yarn workspace @azot-dev/cortex semantic-release 126 | 127 | update-react-dep: 128 | name: Update react-cortex version 129 | needs: release-core 130 | runs-on: ubuntu-latest 131 | steps: 132 | - name: Checkout 133 | uses: actions/checkout@v3 134 | 135 | - name: Setup Node.js 136 | uses: actions/setup-node@v3 137 | with: 138 | node-version: '20' 139 | 140 | - name: Pull changes 141 | run: git pull --rebase origin main 142 | 143 | - name: Install jq 144 | run: sudo apt-get install jq 145 | 146 | - name: Update peer dependency and package version 147 | run: | 148 | CORTEX_VERSION=$(jq -r ".version" packages/core/package.json) 149 | jq '.peerDependencies["@azot-dev/cortex"] = "'"$CORTEX_VERSION"'"' packages/react/package.json > packages/react/package.temp.json 150 | mv packages/react/package.temp.json packages/react/package.json 151 | jq '.version = "'"$CORTEX_VERSION"'"' packages/react/package.json > packages/react/package.temp.json 152 | mv packages/react/package.temp.json packages/react/package.json 153 | 154 | - name: Update yarn.lock 155 | run: yarn workspace @azot-dev/react-cortex install 156 | 157 | - name: Commit and push if changed 158 | run: | 159 | git config user.name 'GitHub Actions Bot' 160 | git config user.email 'githubactions@example.com' 161 | git add -A 162 | git diff-index --quiet HEAD || git commit -m "Update peerDependency to match local cortex version" 163 | git pull --rebase origin main 164 | git push 165 | 166 | release-react: 167 | name: NPM Release react-cortex 168 | needs: update-react-dep 169 | runs-on: ubuntu-latest 170 | steps: 171 | - name: Checkout 172 | uses: actions/checkout@v3 173 | with: 174 | fetch-depth: 0 175 | 176 | - name: Setup Node.js 177 | uses: actions/setup-node@v3 178 | with: 179 | node-version: '20' 180 | registry-url: 'https://registry.npmjs.org' 181 | 182 | - name: Pull changes 183 | run: git pull --rebase origin main 184 | 185 | - name: Install Dependencies for react 186 | run: yarn workspace @azot-dev/react-cortex install --frozen-lockfile 187 | 188 | - name: Build 189 | run: yarn workspace @azot-dev/react-cortex build 190 | 191 | - name: Release react 192 | env: 193 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 194 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 195 | run: | 196 | echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc 197 | yarn workspace @azot-dev/react-cortex publish --access public 198 | 199 | deploy-doc: 200 | name: Deploy documentation 201 | needs: [release-react] 202 | permissions: 203 | id-token: write 204 | pages: write 205 | environment: 206 | name: github-pages 207 | url: ${{ steps.deployment.outputs.page_url }} 208 | runs-on: ubuntu-latest 209 | steps: 210 | - name: Checkout 211 | uses: actions/checkout@v3 212 | 213 | - name: Set up Node.js 214 | uses: actions/setup-node@v3 215 | with: 216 | node-version: '20' 217 | 218 | - name: Use Cached Dependencies 219 | uses: actions/cache@v3 220 | with: 221 | path: '**/node_modules' 222 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 223 | 224 | - name: Build 225 | run: yarn workspace doc build 226 | 227 | - name: Setup Pages 228 | uses: actions/configure-pages@v3 229 | 230 | - name: Upload artifact 231 | uses: actions/upload-pages-artifact@v2 232 | with: 233 | path: packages/doc/build 234 | 235 | - name: Deploy to GitHub Pages 236 | id: deployment 237 | uses: actions/deploy-pages@v2 238 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules/* 2 | **/.DS_STORE 3 | .idea 4 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/jsLinters/eslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/test-arch.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.wordWrap": "wordWrapColumn", 3 | "editor.wordWrapColumn": 160, 4 | "prettier.printWidth": 160, 5 | "emeraldwalk.runonsave": { 6 | "commands": [ 7 | { 8 | "match": "/chrome-extension/dist/.*", 9 | "cmd": "open -a 'Google Chrome' http://reload.extensions" 10 | } 11 | ] 12 | }, 13 | } -------------------------------------------------------------------------------- /examples/authentication/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"] 3 | } -------------------------------------------------------------------------------- /examples/authentication/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/authentication/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/authentication/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /examples/authentication/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Vite + React + TS 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/authentication/jest.config.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.(ts|tsx)$': 'ts-jest', 6 | }, 7 | testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'], 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | }; 10 | -------------------------------------------------------------------------------- /examples/authentication/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authentication", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "test": "jest" 12 | }, 13 | "dependencies": { 14 | "@azot-dev/cortex": "^1.15.3", 15 | "@azot-dev/react-cortex": "^1.15.3", 16 | "@babel/preset-env": "^7.23.6", 17 | "@legendapp/state": "^2.1.4", 18 | "babel-jest": "^29.7.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0" 21 | }, 22 | "devDependencies": { 23 | "@testing-library/react": "^14.1.2", 24 | "@types/jest": "^29.5.11", 25 | "@types/react": "^18.2.43", 26 | "@types/react-dom": "^18.2.17", 27 | "@typescript-eslint/eslint-plugin": "^6.14.0", 28 | "@typescript-eslint/parser": "^6.14.0", 29 | "@vitejs/plugin-react": "^4.2.1", 30 | "eslint": "^8.55.0", 31 | "eslint-plugin-react-hooks": "^4.6.0", 32 | "eslint-plugin-react-refresh": "^0.4.5", 33 | "jest": "^29.7.0", 34 | "ts-jest": "^29.1.1", 35 | "ts-node": "^10.9.2", 36 | "typescript": "^5.2.2", 37 | "vite": "^5.0.8" 38 | } 39 | } -------------------------------------------------------------------------------- /examples/authentication/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/authentication/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from './components/container'; 2 | import { Button } from './components/button'; 3 | import { Counter } from './components/counter'; 4 | import { useAppSelector, useService } from './cortex/utils/hooks'; 5 | 6 | function App() { 7 | const { increment, decrement } = useService('counter'); 8 | const count = useAppSelector((state) => state.counter.count.get()); 9 | return ( 10 | 11 | 12 | {count} 13 | 14 | 15 | ); 16 | } 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /examples/authentication/src/__tests__/counter.spec.ts: -------------------------------------------------------------------------------- 1 | import { Core } from '../cortex/_core'; 2 | 3 | describe('counter', () => { 4 | it('should be instantiate with 0', () => { 5 | const core = new Core(); 6 | 7 | expect(core.store.counter.count.get()).toBe(0); 8 | }); 9 | 10 | it('should be incremented', () => { 11 | const core = new Core(); 12 | 13 | core.getService('counter').increment(); 14 | expect(core.store.counter.count.get()).toBe(1); 15 | }); 16 | 17 | it('should be decremented', () => { 18 | const core = new Core(); 19 | 20 | core.store.counter.count.set(2); 21 | core.getService('counter').decrement(); 22 | expect(core.store.counter.count.get()).toBe(1); 23 | }); 24 | 25 | it('should not be decremented under 0', () => { 26 | const core = new Core(); 27 | 28 | core.getService('counter').decrement(); 29 | expect(core.store.counter.count.get()).toBe(0); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /examples/authentication/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/authentication/src/components/button.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react'; 2 | 3 | export const Button: FC< 4 | PropsWithChildren> 5 | > = ({ children, ...props }) => { 6 | return ( 7 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /examples/authentication/src/components/container.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react'; 2 | 3 | export const Container: FC< 4 | PropsWithChildren> 5 | > = ({ children, ...props }) => { 6 | return ( 7 |
11 | {children} 12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /examples/authentication/src/components/counter.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react'; 2 | 3 | export const Counter: FC< 4 | PropsWithChildren> 5 | > = ({ children, ...props }) => { 6 | return ( 7 |
11 |
{children}
12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /examples/authentication/src/cortex/_core.ts: -------------------------------------------------------------------------------- 1 | import { createCortexFactory } from "@azot-dev/cortex"; 2 | import { services } from "./services/_services"; 3 | import { Dependencies } from "./dependencies/_dependencies"; 4 | 5 | export const Core = createCortexFactory()(services); 6 | -------------------------------------------------------------------------------- /examples/authentication/src/cortex/dependencies/_dependencies.ts: -------------------------------------------------------------------------------- 1 | export interface Dependencies {} 2 | -------------------------------------------------------------------------------- /examples/authentication/src/cortex/services/_services.ts: -------------------------------------------------------------------------------- 1 | import { CounterService } from './counter.service'; 2 | 3 | export const services = { 4 | counter: CounterService, 5 | }; 6 | -------------------------------------------------------------------------------- /examples/authentication/src/cortex/services/counter.service.ts: -------------------------------------------------------------------------------- 1 | import { Service } from '../utils/service'; 2 | 3 | type State = { 4 | count: number; 5 | }; 6 | 7 | export class CounterService extends Service { 8 | static initialState: State = { 9 | count: 0, 10 | }; 11 | 12 | increment() { 13 | this.state.count.set((count) => count + 1); 14 | } 15 | 16 | decrement() { 17 | if (this.state.count.get() !== 0) { 18 | this.state.count.set((count) => count - 1); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/authentication/src/cortex/utils/hooks.ts: -------------------------------------------------------------------------------- 1 | // utils/hooks.ts 2 | 3 | import { createCortexHooks } from '@azot-dev/react-cortex'; 4 | import { Services } from './types'; 5 | 6 | export const { 7 | useAppSelector, 8 | useLazyMethod, 9 | useMethod, 10 | useService, 11 | useStore, 12 | } = createCortexHooks(); 13 | -------------------------------------------------------------------------------- /examples/authentication/src/cortex/utils/service.ts: -------------------------------------------------------------------------------- 1 | // utils/service.ts 2 | 3 | import { BaseService } from '@azot-dev/cortex'; 4 | import { Dependencies } from '../dependencies/_dependencies'; 5 | import { services } from '../services/_services'; 6 | 7 | export abstract class Service extends BaseService< 8 | T, 9 | typeof services, 10 | Dependencies 11 | > { 12 | constructor(...args: [any, any, any]) { 13 | super(...args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/authentication/src/cortex/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { services } from '../services/_services'; 2 | 3 | export type Services = typeof services; 4 | -------------------------------------------------------------------------------- /examples/authentication/src/input.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /examples/authentication/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | import { CortexProvider } from '@azot-dev/react-cortex'; 5 | import { Core } from './cortex/_core.ts'; 6 | 7 | ReactDOM.createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /examples/authentication/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/authentication/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /examples/authentication/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "esModuleInterop": true, 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "declaration": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true, 24 | 25 | }, 26 | "include": ["src"], 27 | "references": [{ "path": "./tsconfig.node.json" }] 28 | } 29 | -------------------------------------------------------------------------------- /examples/authentication/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/authentication/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /examples/counter/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"] 3 | } -------------------------------------------------------------------------------- /examples/counter/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/counter/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/counter/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /examples/counter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Vite + React + TS 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/counter/jest.config.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.(ts|tsx)$': 'ts-jest', 6 | }, 7 | testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'], 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | }; 10 | -------------------------------------------------------------------------------- /examples/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "test": "jest" 12 | }, 13 | "dependencies": { 14 | "@azot-dev/cortex": "^1.15.3", 15 | "@azot-dev/react-cortex": "^1.15.3", 16 | "@babel/preset-env": "^7.23.6", 17 | "@legendapp/state": "^2.1.4", 18 | "babel-jest": "^29.7.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0" 21 | }, 22 | "devDependencies": { 23 | "@testing-library/react": "^14.1.2", 24 | "@types/jest": "^29.5.11", 25 | "@types/react": "^18.2.43", 26 | "@types/react-dom": "^18.2.17", 27 | "@typescript-eslint/eslint-plugin": "^6.14.0", 28 | "@typescript-eslint/parser": "^6.14.0", 29 | "@vitejs/plugin-react": "^4.2.1", 30 | "eslint": "^8.55.0", 31 | "eslint-plugin-react-hooks": "^4.6.0", 32 | "eslint-plugin-react-refresh": "^0.4.5", 33 | "jest": "^29.7.0", 34 | "ts-jest": "^29.1.1", 35 | "ts-node": "^10.9.2", 36 | "typescript": "^5.2.2", 37 | "vite": "^5.0.8" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/counter/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/counter/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Container } from './components/container'; 2 | import { Button } from './components/button'; 3 | import { Counter } from './components/counter'; 4 | import { useAppSelector, useService } from './cortex/utils/hooks'; 5 | 6 | function App() { 7 | const { increment, decrement } = useService('counter'); 8 | const count = useAppSelector((state) => state.counter.count.get()); 9 | return ( 10 | 11 | 12 | {count} 13 | 14 | 15 | ); 16 | } 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /examples/counter/src/__tests__/counter.spec.ts: -------------------------------------------------------------------------------- 1 | import { Core } from '../cortex/_core'; 2 | 3 | describe('counter', () => { 4 | it('should be instantiate with 0', () => { 5 | const core = new Core(); 6 | 7 | expect(core.store.counter.count.get()).toBe(0); 8 | }); 9 | 10 | it('should be incremented', () => { 11 | const core = new Core(); 12 | 13 | core.getService('counter').increment(); 14 | expect(core.store.counter.count.get()).toBe(1); 15 | }); 16 | 17 | it('should be decremented', () => { 18 | const core = new Core(); 19 | 20 | core.store.counter.count.set(2); 21 | core.getService('counter').decrement(); 22 | expect(core.store.counter.count.get()).toBe(1); 23 | }); 24 | 25 | it('should not be decremented under 0', () => { 26 | const core = new Core(); 27 | 28 | core.getService('counter').decrement(); 29 | expect(core.store.counter.count.get()).toBe(0); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /examples/counter/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/counter/src/components/button.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react'; 2 | 3 | export const Button: FC< 4 | PropsWithChildren> 5 | > = ({ children, ...props }) => { 6 | return ( 7 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /examples/counter/src/components/container.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react'; 2 | 3 | export const Container: FC< 4 | PropsWithChildren> 5 | > = ({ children, ...props }) => { 6 | return ( 7 |
11 | {children} 12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /examples/counter/src/components/counter.tsx: -------------------------------------------------------------------------------- 1 | import { FC, PropsWithChildren } from 'react'; 2 | 3 | export const Counter: FC< 4 | PropsWithChildren> 5 | > = ({ children, ...props }) => { 6 | return ( 7 |
11 |
{children}
12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /examples/counter/src/cortex/_core.ts: -------------------------------------------------------------------------------- 1 | import { createCortexFactory } from "@azot-dev/cortex"; 2 | import { services } from "./services/_services"; 3 | import { Dependencies } from "./dependencies/_dependencies"; 4 | 5 | export const Core = createCortexFactory()(services); 6 | -------------------------------------------------------------------------------- /examples/counter/src/cortex/dependencies/_dependencies.ts: -------------------------------------------------------------------------------- 1 | export interface Dependencies {} 2 | -------------------------------------------------------------------------------- /examples/counter/src/cortex/services/_services.ts: -------------------------------------------------------------------------------- 1 | import { CounterService } from './counter.service'; 2 | 3 | export const services = { 4 | counter: CounterService, 5 | }; 6 | -------------------------------------------------------------------------------- /examples/counter/src/cortex/services/counter.service.ts: -------------------------------------------------------------------------------- 1 | import { Service } from '../utils/service'; 2 | 3 | type State = { 4 | count: number; 5 | }; 6 | 7 | export class CounterService extends Service { 8 | static initialState: State = { 9 | count: 0, 10 | }; 11 | 12 | increment() { 13 | this.state.count.set((count) => count + 1); 14 | } 15 | 16 | decrement() { 17 | if (this.state.count.get() !== 0) { 18 | this.state.count.set((count) => count - 1); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/counter/src/cortex/utils/hooks.ts: -------------------------------------------------------------------------------- 1 | // utils/hooks.ts 2 | 3 | import { createCortexHooks } from '@azot-dev/react-cortex'; 4 | import { Services } from './types'; 5 | 6 | export const { 7 | useAppSelector, 8 | useLazyMethod, 9 | useMethod, 10 | useService, 11 | useStore, 12 | } = createCortexHooks(); 13 | -------------------------------------------------------------------------------- /examples/counter/src/cortex/utils/service.ts: -------------------------------------------------------------------------------- 1 | // utils/service.ts 2 | 3 | import { BaseService } from '@azot-dev/cortex'; 4 | import { Dependencies } from '../dependencies/_dependencies'; 5 | import { services } from '../services/_services'; 6 | 7 | export abstract class Service extends BaseService< 8 | T, 9 | typeof services, 10 | Dependencies 11 | > { 12 | constructor(...args: [any, any, any]) { 13 | super(...args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/counter/src/cortex/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { services } from '../services/_services'; 2 | 3 | export type Services = typeof services; 4 | -------------------------------------------------------------------------------- /examples/counter/src/input.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /examples/counter/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | import { CortexProvider } from '@azot-dev/react-cortex'; 5 | import { Core } from './cortex/_core.ts'; 6 | 7 | ReactDOM.createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /examples/counter/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/counter/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /examples/counter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "esModuleInterop": true, 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "declaration": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | 19 | /* Linting */ 20 | "strict": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "noFallthroughCasesInSwitch": true, 24 | 25 | }, 26 | "include": ["src"], 27 | "references": [{ "path": "./tsconfig.node.json" }] 28 | } 29 | -------------------------------------------------------------------------------- /examples/counter/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/counter/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /examples/dog-api/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/dog-api/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/dog-api/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /examples/dog-api/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/dog-api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dog-api", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@azot-dev/cortex": "^1.16.5", 14 | "@azot-dev/react-cortex": "^1.16.5", 15 | "@emotion/react": "^11.11.3", 16 | "@emotion/styled": "^11.11.0", 17 | "@legendapp/state": "^2.1.4", 18 | "@mui/lab": "^5.0.0-alpha.163", 19 | "@mui/material": "^5.15.7", 20 | "axios": "^1.6.7", 21 | "react": "^18.2.0", 22 | "react-dom": "^18.2.0" 23 | }, 24 | "devDependencies": { 25 | "@types/react": "^18.2.43", 26 | "@types/react-dom": "^18.2.17", 27 | "@typescript-eslint/eslint-plugin": "^6.14.0", 28 | "@typescript-eslint/parser": "^6.14.0", 29 | "@vitejs/plugin-react": "^4.2.1", 30 | "eslint": "^8.55.0", 31 | "eslint-plugin-react-hooks": "^4.6.0", 32 | "eslint-plugin-react-refresh": "^0.4.5", 33 | "typescript": "^5.2.2", 34 | "vite": "^5.0.8" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/dog-api/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/dog-api/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .image { 9 | height: 10em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | 15 | .select { 16 | padding: .5rem; 17 | } 18 | -------------------------------------------------------------------------------- /examples/dog-api/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import { useAppSelector, useMethod, useService } from './cortex/utils/hooks'; 4 | 5 | function App() { 6 | const { loadBreeds, generateImage, selectBreed } = useService('dog'); 7 | const { isSuccess } = useMethod(loadBreeds); 8 | 9 | const breeds = useAppSelector((state) => state.dog.breeds.get()); 10 | const image = useAppSelector((state) => state.dog.currentImage.get()); 11 | 12 | if (!isSuccess) { 13 | return; 14 | } 15 | 16 | return ( 17 | <> 18 |
19 | 26 |
27 |
28 | image 29 |
30 | 31 | 32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /examples/dog-api/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/_core.ts: -------------------------------------------------------------------------------- 1 | import { createCortexFactory } from "@azot-dev/cortex"; 2 | import { services } from "./services/_services"; 3 | import { Dependencies } from "./dependencies/_dependencies"; 4 | 5 | export const Core = createCortexFactory()(services); 6 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/dependencies/_dependencies.ts: -------------------------------------------------------------------------------- 1 | import { ApiGateway } from './api/api.gateway'; 2 | 3 | export interface Dependencies { 4 | api: ApiGateway; 5 | } 6 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/dependencies/api/api.gateway.ts: -------------------------------------------------------------------------------- 1 | export type Breed = string; 2 | export type BreedVariant = string; 3 | export type ImageUri = string; 4 | export type ResponseStatus = 'success' | 'error'; 5 | 6 | export interface ApiGateway { 7 | getBreeds(): Promise<{ message: Record; status: ResponseStatus }>; 8 | getBreedRandomImage(breed: Breed): Promise<{ message: ImageUri; status: ResponseStatus }>; 9 | } 10 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/dependencies/api/axios.api.adapter.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { ApiGateway, Breed } from './api.gateway'; 3 | 4 | export class AxiosApiAdapter implements ApiGateway { 5 | async getBreeds() { 6 | const response = await axios.get('https://dog.ceo/api/breeds/list/all'); 7 | return response.data; 8 | } 9 | 10 | async getBreedRandomImage(breed: Breed) { 11 | const response = await axios.get(`https://dog.ceo/api/breed/${breed}/images/random`); 12 | return response.data; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/dependencies/api/fake.api.adapter.ts: -------------------------------------------------------------------------------- 1 | import { ApiGateway, Breed, ImageUri } from './api.gateway'; 2 | 3 | const simpsons: Record = { 4 | homer: [ 5 | 'https://upload.wikimedia.org/wikipedia/en/0/02/Homer_Simpson_2006.png', 6 | 'https://i.pinimg.com/474x/aa/33/1f/aa331f1a8ce02c2723a0bd4ad69465b4.jpg', 7 | ], 8 | bart: [ 9 | 'https://i.pinimg.com/736x/33/fd/f9/33fdf9b75dbd2fbf6eec0e50ba44ef6f.jpg', 10 | 'https://logowik.com/content/uploads/images/simpson-bart81811.logowik.com.webp', 11 | ], 12 | marge: [ 13 | 'https://simpsonsfamilyblog.files.wordpress.com/2014/02/marge_simpson-copy.png', 14 | 'https://cache.marieclaire.fr/data/photo/w700_c17/138/margeeee.jpg', 15 | ], 16 | lisa: [ 17 | 'https://logowik.com/content/uploads/images/lisa-simpson7517.logowik.com.webp', 18 | 'https://st5.depositphotos.com/37050820/66068/v/450/depositphotos_660688560-stock-illustration-lisa-simpson-cartoon-character.jpg', 19 | ], 20 | maggie: [ 21 | 'https://www.simpsonspark.com/images/persos/contributions/maggie-simpson-24389.jpg', 22 | 'https://e1.pngegg.com/pngimages/115/872/png-clipart-los-simpsons-maggie-simpson-illustration.png', 23 | ], 24 | }; 25 | 26 | export class FakeApiAdapter implements ApiGateway { 27 | async getBreeds() { 28 | return { 29 | message: Object.keys(simpsons).reduce((prev, current) => ({ ...prev, [current]: [] }), {}), 30 | status: 'success' as const, 31 | }; 32 | } 33 | 34 | async getBreedRandomImage(breed: Breed) { 35 | const images = simpsons[breed]; 36 | return { 37 | message: images[Math.floor(Math.random() * images.length)], 38 | status: 'success' as const, 39 | }; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/dependencies/api/fetch.api.adapter.ts: -------------------------------------------------------------------------------- 1 | import { ApiGateway, Breed } from './api.gateway'; 2 | 3 | export class FetchApiAdapter implements ApiGateway { 4 | async getBreeds() { 5 | const response = await fetch('https://dog.ceo/api/breeds/list/all'); 6 | if (!response.ok) { 7 | throw new Error(`HTTP error! status: ${response.status}`); 8 | } 9 | return await response.json(); 10 | } 11 | 12 | async getBreedRandomImage(breed: Breed) { 13 | const response = await fetch(`https://dog.ceo/api/breed/${breed}/images/random`); 14 | if (!response.ok) { 15 | throw new Error(`HTTP error! status: ${response.status}`); 16 | } 17 | return await response.json(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/services/_services.ts: -------------------------------------------------------------------------------- 1 | import { DogService } from './dog.service'; 2 | 3 | export const services = { 4 | dog: DogService, 5 | }; 6 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/services/dog.service.ts: -------------------------------------------------------------------------------- 1 | import { Breed } from '../dependencies/api/api.gateway'; 2 | import { Service } from '../utils/service'; 3 | 4 | type State = { 5 | breeds: Breed[]; 6 | selectedBreed: Breed | null; 7 | currentImage?: string; 8 | }; 9 | 10 | export class DogService extends Service { 11 | public static initialState: State = { breeds: [], selectedBreed: null }; 12 | 13 | async loadBreeds() { 14 | const response = await this.dependencies.api.getBreeds(); 15 | const breeds = Object.keys(response.message); 16 | this.state.breeds.set(breeds); 17 | if (breeds.length > 0) { 18 | this.selectBreed(breeds[0]); 19 | } 20 | } 21 | 22 | selectBreed(breed: Breed) { 23 | this.state.selectedBreed.set(breed); 24 | this.generateImage(); 25 | } 26 | 27 | async generateImage() { 28 | const response = await this.dependencies.api.getBreedRandomImage(this.state.selectedBreed.get()); 29 | this.state.currentImage.set(response.message); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/utils/hooks.ts: -------------------------------------------------------------------------------- 1 | // utils/hooks.ts 2 | 3 | import { createCortexHooks } from '@azot-dev/react-cortex'; 4 | import { Services } from './types'; 5 | 6 | export const { 7 | useAppSelector, 8 | useLazyMethod, 9 | useMethod, 10 | useService, 11 | useStore, 12 | } = createCortexHooks(); 13 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/utils/service.ts: -------------------------------------------------------------------------------- 1 | // utils/service.ts 2 | 3 | import { BaseService } from '@azot-dev/cortex'; 4 | import { Dependencies } from '../dependencies/_dependencies'; 5 | import { services } from '../services/_services'; 6 | 7 | export abstract class Service extends BaseService< 8 | T, 9 | typeof services, 10 | Dependencies 11 | > { 12 | constructor(...args: [any, any, any]) { 13 | super(...args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/dog-api/src/cortex/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { services } from '../services/_services'; 2 | 3 | export type Services = typeof services; 4 | -------------------------------------------------------------------------------- /examples/dog-api/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/dog-api/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | import './index.css'; 5 | import { CortexProvider } from '@azot-dev/react-cortex'; 6 | import { FetchApiAdapter } from './cortex/dependencies/api/fetch.api.adapter.ts'; 7 | import { Core } from './cortex/_core.ts'; 8 | import { AxiosApiAdapter } from './cortex/dependencies/api/axios.api.adapter.ts'; 9 | import { FakeApiAdapter } from './cortex/dependencies/api/fake.api.adapter.ts'; 10 | import { Box, Tab } from '@mui/material'; 11 | import { TabContext, TabList } from '@mui/lab'; 12 | 13 | const adapters = [ 14 | { name: 'Fake adapter', adapter: new FakeApiAdapter() }, 15 | { name: 'Axios adapter', adapter: new AxiosApiAdapter() }, 16 | { name: 'Fetch adapter', adapter: new FetchApiAdapter() }, 17 | ]; 18 | 19 | const Apps: FC = () => { 20 | const [value, setValue] = React.useState(0); 21 | 22 | const handleChange = (event: React.SyntheticEvent, newValue: string) => { 23 | setValue(Number(newValue)); 24 | }; 25 | 26 | return ( 27 | 28 | 29 | 30 | 31 | {adapters.map((adapter, index) => ( 32 | 33 | ))} 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | ); 42 | }; 43 | 44 | ReactDOM.createRoot(document.getElementById('root')!).render( 45 | 46 | 47 | 48 | ); 49 | -------------------------------------------------------------------------------- /examples/dog-api/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/dog-api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /examples/dog-api/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/dog-api/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /examples/todo/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /examples/todo/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/todo/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /examples/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/todo/jest.config.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | transform: { 5 | '^.+\\.(ts|tsx)$': 'ts-jest', 6 | }, 7 | testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'], 8 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 9 | }; 10 | -------------------------------------------------------------------------------- /examples/todo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "test": "jest" 12 | }, 13 | "dependencies": { 14 | "@azot-dev/cortex": "^1.15.6", 15 | "@azot-dev/react-cortex": "^1.15.6", 16 | "@fortawesome/fontawesome-svg-core": "^6.5.1", 17 | "@fortawesome/free-solid-svg-icons": "^6.5.1", 18 | "@fortawesome/react-fontawesome": "^0.2.0", 19 | "@legendapp/state": "^2.1.4", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "uuid": "^9.0.1" 23 | }, 24 | "devDependencies": { 25 | "@types/react": "^18.2.43", 26 | "@types/react-dom": "^18.2.17", 27 | "@types/uuid": "^9.0.7", 28 | "@typescript-eslint/eslint-plugin": "^6.14.0", 29 | "@typescript-eslint/parser": "^6.14.0", 30 | "@vitejs/plugin-react": "^4.2.1", 31 | "eslint": "^8.55.0", 32 | "eslint-plugin-react-hooks": "^4.6.0", 33 | "eslint-plugin-react-refresh": "^0.4.5", 34 | "jest": "^29.7.0", 35 | "ts-jest": "^29.1.1", 36 | "ts-node": "^10.9.2", 37 | "typescript": "^5.2.2", 38 | "vite": "^5.0.8" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/todo/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/todo/src/App.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); 2 | 3 | * { 4 | font-family: 'Poppins', sans-serif; 5 | margin: 0; 6 | padding: 0; 7 | box-sizing: border-box; 8 | } 9 | 10 | body { 11 | background: #8758ff; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | } 16 | 17 | .App { 18 | text-align: center; 19 | } 20 | 21 | h1 { 22 | color: #fff; 23 | margin-bottom: 0.5rem; 24 | font-size: 1.75rem; 25 | } 26 | 27 | .TodoWrapper { 28 | background: #1A1A40; 29 | margin-top: 5rem; 30 | padding: 2rem; 31 | border-radius: 5px; 32 | } 33 | 34 | .TodoForm { 35 | width: 100%; 36 | } 37 | 38 | .todo-input { 39 | outline: none; 40 | background: none; 41 | border: 1px solid #8758ff; 42 | padding: 0.5rem 1rem; 43 | margin-top: 1rem; 44 | margin-bottom: 2rem; 45 | width: 300px; 46 | color: #fff; 47 | } 48 | 49 | .todo-input::placeholder { 50 | color: #ffffff4d; 51 | } 52 | 53 | .todo-btn { 54 | background: #8758ff; 55 | color: #fff; 56 | border: none; 57 | padding: 0.55rem; 58 | cursor: pointer; 59 | 60 | } 61 | 62 | .Todo { 63 | display: flex; 64 | justify-content: space-between; 65 | align-items: center; 66 | background: #8758ff; 67 | color: #fff; 68 | padding: 0.75rem 1rem; 69 | border-radius: 5px; 70 | margin-bottom: 1rem; 71 | cursor: pointer; 72 | } 73 | 74 | .fa-trash { 75 | margin-left: 0.75rem; 76 | } 77 | 78 | .completed { 79 | color: #c5aeff; 80 | text-decoration: line-through; 81 | } 82 | 83 | .text-error { 84 | color: lightcoral; 85 | } 86 | -------------------------------------------------------------------------------- /examples/todo/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import { TodoWrapper } from './Components/TodoWrapper'; 3 | 4 | function App() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /examples/todo/src/Components/EditTodoForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useState } from 'react'; 2 | import { Todo } from '../cortex/services/todo/todo.service'; 3 | import { useService } from '../cortex/utils/hooks'; 4 | 5 | export const EditTodoForm: FC<{ todo: Todo }> = ({ todo }) => { 6 | const [value, setValue] = useState(todo.title); 7 | const { modify } = useService('todo'); 8 | 9 | const handleSubmit = (e) => { 10 | e.preventDefault(); 11 | modify(todo.id, value); 12 | }; 13 | return ( 14 |
15 | setValue(e.target.value)} className="todo-input" placeholder="Update task" /> 16 | 19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /examples/todo/src/Components/Todo.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | import { faPenToSquare } from '@fortawesome/free-solid-svg-icons'; 4 | import { faTrash } from '@fortawesome/free-solid-svg-icons'; 5 | import type { Todo as TodoType } from '../cortex/services/todo/todo.service'; 6 | import { useService } from '../cortex/utils/hooks'; 7 | 8 | export const Todo: FC<{ todo: TodoType }> = ({ todo }) => { 9 | const { remove, toggleDone, edit } = useService('todo'); 10 | 11 | return ( 12 |
13 |

toggleDone(todo.id)}> 14 | {todo.title} 15 |

16 |
17 | edit(todo.id)} /> 18 | remove(todo.id)} /> 19 |
20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /examples/todo/src/Components/TodoForm.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useService } from '../cortex/utils/hooks'; 3 | 4 | export const TodoForm = () => { 5 | const [value, setValue] = useState(''); 6 | const { add } = useService('todo'); 7 | 8 | const handleSubmit = (e) => { 9 | e.preventDefault(); 10 | if (value) { 11 | add(value); 12 | setValue(''); 13 | } 14 | }; 15 | return ( 16 |
17 | setValue(e.target.value)} className="todo-input" placeholder="What is the task today?" /> 18 | 21 |
22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /examples/todo/src/Components/TodoWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { Todo } from './Todo'; 2 | import { TodoForm } from './TodoForm'; 3 | import { EditTodoForm } from './EditTodoForm'; 4 | import { useAppSelector, useService } from '../cortex/utils/hooks'; 5 | 6 | export const TodoWrapper = () => { 7 | const { get } = useService('todo'); 8 | const todos = useAppSelector(get); 9 | 10 | return ( 11 |
12 |

Get Things Done !

13 | 14 | {todos.map((todo) => (todo.isEditing ? : ))} 15 |
16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /examples/todo/src/cortex/_core.ts: -------------------------------------------------------------------------------- 1 | import { createCortexFactory } from "@azot-dev/cortex"; 2 | import { services } from "./services/_services"; 3 | import { Dependencies } from "./dependencies/_dependencies"; 4 | 5 | export const Core = createCortexFactory()(services); 6 | -------------------------------------------------------------------------------- /examples/todo/src/cortex/dependencies/_dependencies.ts: -------------------------------------------------------------------------------- 1 | export interface Dependencies {} 2 | -------------------------------------------------------------------------------- /examples/todo/src/cortex/services/_services.ts: -------------------------------------------------------------------------------- 1 | import { TodoService } from './todo/todo.service'; 2 | 3 | export const services = { 4 | todo: TodoService, 5 | }; 6 | -------------------------------------------------------------------------------- /examples/todo/src/cortex/services/todo/todo.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Core } from '../../_core'; 2 | import { TodoService } from './todo.service'; 3 | 4 | describe('todo service', () => { 5 | let core: InstanceType; 6 | let service: InstanceType; 7 | 8 | beforeEach(() => { 9 | core = new Core(); 10 | service = core.getService('todo'); 11 | }); 12 | 13 | describe('add', () => { 14 | it('adds a todo to the todoList', () => { 15 | service.add('eat'); 16 | 17 | expect(service.get().length).toBe(1); 18 | expect(service.get()[0].title).toBe('eat'); 19 | }); 20 | }); 21 | 22 | describe('remove', () => { 23 | it('removes a todo to the todoList', () => { 24 | service.add('eat'); 25 | service.add('go to ski'); 26 | const idToRemove = service.get().find((todo) => todo.title === 'eat')!.id; 27 | 28 | service.remove(idToRemove); 29 | expect(service.get().length).toBe(1); 30 | expect(service.get()[0].title).toBe('go to ski'); 31 | }); 32 | }); 33 | 34 | describe('modify', () => { 35 | it('modifies a todo', () => { 36 | service.add('eat'); 37 | const idToModify = service.get().find((todo) => todo.title === 'eat')!.id; 38 | 39 | service.modify(idToModify, 'drink'); 40 | expect(service.get()[0].title).toBe('drink'); 41 | }); 42 | 43 | it('is no longer in editing mode', () => { 44 | service.add('eat'); 45 | const idToModify = service.get().find((todo) => todo.title === 'eat')!.id; 46 | 47 | service.modify(idToModify, 'drink'); 48 | expect(service.get()[0].isEditing).toBeFalsy(); 49 | }); 50 | }); 51 | 52 | describe('toggle done', () => { 53 | it('toggles done for a todo', () => { 54 | service.add('eat'); 55 | const idToModify = service.get().find((todo) => todo.title === 'eat')!.id; 56 | 57 | service.toggleDone(idToModify); 58 | expect(service.get()[0].isDone).toBeTruthy(); 59 | 60 | // called twice to be sure it is toggled 61 | service.toggleDone(idToModify); 62 | expect(service.get()[0].isDone).toBeFalsy(); 63 | }); 64 | }); 65 | 66 | describe('edit', () => { 67 | it('makes the todo editable', () => { 68 | service.add('eat'); 69 | const idToModify = service.get().find((todo) => todo.title === 'eat')!.id; 70 | 71 | service.edit(idToModify); 72 | expect(service.get()[0].isEditing).toBeTruthy(); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /examples/todo/src/cortex/services/todo/todo.service.ts: -------------------------------------------------------------------------------- 1 | import { Service } from '../../utils/service'; 2 | import { v4 as uuid } from 'uuid'; 3 | 4 | export type Todo = { title: string; isEditing: boolean; isDone: boolean; id: string }; 5 | 6 | type State = Record>; 7 | 8 | export class TodoService extends Service { 9 | static initialState: State = {}; 10 | 11 | add(title: string) { 12 | this.state[uuid()].set({ title, isEditing: false, isDone: false }); 13 | } 14 | 15 | remove(id: string) { 16 | this.state[id].delete(); 17 | } 18 | 19 | modify(id: string, title: string) { 20 | this.state[id].title.set(title); 21 | this.state[id].isEditing.set(false); 22 | } 23 | 24 | toggleDone(id: string) { 25 | this.state[id].isDone.set((isDone) => !isDone); 26 | } 27 | 28 | edit(id: string) { 29 | this.state[id].isEditing.set(true); 30 | } 31 | 32 | get(): Todo[] { 33 | const state = this.state.get(); 34 | return Object.keys(state).map((id) => ({ id, ...state[id] })); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/todo/src/cortex/utils/hooks.ts: -------------------------------------------------------------------------------- 1 | // utils/hooks.ts 2 | 3 | import { createCortexHooks } from '@azot-dev/react-cortex'; 4 | import { Services } from './types'; 5 | 6 | export const { 7 | useAppSelector, 8 | useLazyMethod, 9 | useMethod, 10 | useService, 11 | useStore, 12 | } = createCortexHooks(); 13 | -------------------------------------------------------------------------------- /examples/todo/src/cortex/utils/service.ts: -------------------------------------------------------------------------------- 1 | // utils/service.ts 2 | 3 | import { BaseService } from '@azot-dev/cortex'; 4 | import { Dependencies } from '../dependencies/_dependencies'; 5 | import { services } from '../services/_services'; 6 | 7 | export abstract class Service extends BaseService< 8 | T, 9 | typeof services, 10 | Dependencies 11 | > { 12 | constructor(...args: [any, any, any]) { 13 | super(...args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/todo/src/cortex/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { services } from '../services/_services'; 2 | 3 | export type Services = typeof services; 4 | -------------------------------------------------------------------------------- /examples/todo/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/todo/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App.tsx'; 4 | import './index.css'; 5 | import { CortexProvider } from '@azot-dev/react-cortex'; 6 | import { Core } from './cortex/_core.ts'; 7 | 8 | ReactDOM.createRoot(document.getElementById('root')!).render( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /examples/todo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/todo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true 23 | }, 24 | "include": ["src"], 25 | "references": [{ "path": "./tsconfig.node.json" }] 26 | } 27 | -------------------------------------------------------------------------------- /examples/todo/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/todo/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /logo/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/logo/favicon.ico -------------------------------------------------------------------------------- /logo/generate-logo.py: -------------------------------------------------------------------------------- 1 | import cv2 2 | import numpy as np 3 | import shutil 4 | import os 5 | from PIL import Image 6 | 7 | 8 | def create_favicon(png_path, ico_path, sizes=[(16, 16), (32, 32), (48, 48), (64, 64)]): 9 | image = Image.open(png_path) 10 | image.save(ico_path, format='ICO', sizes=sizes) 11 | 12 | 13 | def copy_file(source, destination): 14 | if not os.path.isfile(source): 15 | print("The source file doesn't exist") 16 | return 17 | 18 | destination_folder = os.path.dirname(destination) 19 | if not os.path.exists(destination_folder): 20 | os.makedirs(destination_folder) 21 | 22 | shutil.copy(source, destination) 23 | print(f"Filed copied from {source} to {destination}") 24 | 25 | 26 | def copy_file_with_size(size, destination): 27 | source = 'logo_' + str(size) + '.png' 28 | copy_file(source, destination) 29 | 30 | 31 | # def remove_background_and_shadow(image_path): 32 | # image = cv2.imread(image_path, cv2.IMREAD_UNCHANGED) 33 | # gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 34 | # blur = cv2.GaussianBlur(gray, (5, 5), 0) 35 | # _, thresh = cv2.threshold(blur, 180, 255, cv2.THRESH_BINARY_INV) 36 | # contours, _ = cv2.findContours( 37 | # thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 38 | # mask = np.zeros_like(image) 39 | # cv2.drawContours(mask, contours, -1, (255, 255, 255), cv2.FILLED) 40 | # mask_gray = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY) 41 | # _, binary_mask = cv2.threshold(mask_gray, 180, 255, cv2.THRESH_BINARY) 42 | # result = cv2.bitwise_and(image, image, mask=binary_mask) 43 | # result[binary_mask == 0] = [255, 255, 255, 0] 44 | # if result.shape[2] < 4: 45 | # result = cv2.cvtColor(result, cv2.COLOR_BGR2BGRA) 46 | # return result 47 | 48 | 49 | # def crop_logo_to_square(image): 50 | # contours, _ = cv2.findContours(cv2.cvtColor( 51 | # image, cv2.COLOR_BGR2GRAY), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 52 | # largest_contour = max(contours, key=cv2.contourArea) 53 | # x, y, w, h = cv2.boundingRect(largest_contour) 54 | # cropped_image = image[y:y+h, x:x+w] 55 | 56 | # cv2.imwrite('cropped.png', cropped_image) 57 | # rect_image = image.copy() 58 | # print(x, y, w,) 59 | # cv2.rectangle(rect_image, (x, y), (x + w, y + h), (0, 255, 0), 2) 60 | # cv2.imwrite('rect_image.png', rect_image) 61 | # size = max(w, h) 62 | 63 | # square_image = cv2.copyMakeBorder(cropped_image, 64 | # top=(size-h)//2, 65 | # bottom=(size-h)//2, 66 | # left=(size-w)//2, 67 | # right=(size-w)//2, 68 | # borderType=cv2.BORDER_CONSTANT, 69 | # value=[255, 255, 255, 0]) 70 | # return square_image 71 | 72 | 73 | # image_path = 'original_logo.png' 74 | # result_img_no_shadow = remove_background_and_shadow(image_path) 75 | # result_img_square = crop_logo_to_square(result_img_no_shadow) 76 | # cv2.imwrite('square.png', result_img_square) 77 | 78 | image = cv2.imread('logo.png', cv2.IMREAD_UNCHANGED) 79 | 80 | create_favicon('logo.png', './favicon.ico') 81 | 82 | sizes = [16, 48, 128, 256, 512] 83 | for size in sizes: 84 | resized_image_square = cv2.resize( 85 | image, (size, size), interpolation=cv2.INTER_AREA) 86 | resized_square_path = f'logo_{size}.png' 87 | cv2.imwrite(resized_square_path, resized_image_square) 88 | 89 | 90 | # Chrome extension 91 | 92 | copy_file_with_size(16, '../chrome-extension/src/images') 93 | copy_file_with_size(48, '../chrome-extension/src/images') 94 | copy_file_with_size(128, '../chrome-extension/src/images') 95 | 96 | copy_file_with_size(16, '../chrome-extension/dist/images') 97 | copy_file_with_size(48, '../chrome-extension/dist/images') 98 | copy_file_with_size(128, '../chrome-extension/dist/images') 99 | 100 | # Core 101 | 102 | copy_file_with_size(512, '../core/assets') 103 | 104 | # doc 105 | copy_file_with_size(256, '../doc/static/img') 106 | copy_file_with_size(512, '../doc/static/img') 107 | copy_file('./favicon.ico', '../doc/static/img') 108 | -------------------------------------------------------------------------------- /logo/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/logo/logo.png -------------------------------------------------------------------------------- /logo/logo_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/logo/logo_128.png -------------------------------------------------------------------------------- /logo/logo_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/logo/logo_16.png -------------------------------------------------------------------------------- /logo/logo_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/logo/logo_256.png -------------------------------------------------------------------------------- /logo/logo_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/logo/logo_48.png -------------------------------------------------------------------------------- /logo/logo_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/logo/logo_512.png -------------------------------------------------------------------------------- /logo/original_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/logo/original_logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "MIT", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*" 6 | ] 7 | } -------------------------------------------------------------------------------- /packages/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "printWidth": 160 5 | } -------------------------------------------------------------------------------- /packages/core/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist 3 | -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /packages/core/__tests__/core.spec.ts: -------------------------------------------------------------------------------- 1 | import { BaseService, createCortexFactory } from "../src"; 2 | 3 | class UserService extends BaseService { 4 | static initialState = { name: "John", age: 28 }; 5 | 6 | changeName(newName: string) { 7 | this.state.name.set(newName); 8 | } 9 | } 10 | 11 | const Cortex = createCortexFactory()({ user: UserService }); 12 | 13 | describe("core", () => { 14 | let core = new Cortex(); 15 | beforeEach(() => { 16 | core = new Cortex(); 17 | }); 18 | 19 | it("should access to the store", () => { 20 | const userName = core.store.user.name.get(); 21 | expect(userName).toBe("John"); 22 | }); 23 | 24 | it("should modify the store", () => { 25 | core.store.user.name.set("David"); 26 | const userName = core.store.user.name.get(); 27 | expect(userName).toBe("David"); 28 | }); 29 | 30 | it("should call the services methods", () => { 31 | core.getService("user").changeName("Max"); 32 | const userName = core.store.user.name.get(); 33 | expect(userName).toBe("Max"); 34 | }); 35 | 36 | it("should get the store from the service", () => { 37 | const state = core.getService("user").getState(); 38 | expect(core.getService("user").getState().name).toBe("John"); 39 | core.getService("user").changeName("Max"); 40 | expect(core.getService("user").getState().name).toBe("Max"); 41 | }); 42 | 43 | it("should get the store from the service", () => { 44 | const state = core.getService("user").getState(); 45 | expect(core.getService("user").getState().name).toBe("John"); 46 | core.getService("user").changeName("Max"); 47 | expect(core.getService("user").getState().name).toBe("Max"); 48 | }); 49 | 50 | it("should modify the store from the service", () => { 51 | core.getService("user").setState({ name: "Xavier", age: 27 }); 52 | expect(core.getService("user").getState().name).toBe("Xavier"); 53 | core.getService("user").setState((state: any) => ({ ...state, age: 35 })); 54 | expect(core.getService("user").getState().age).toBe(35); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /packages/core/__tests__/services.spec.ts: -------------------------------------------------------------------------------- 1 | import { BaseService, createCortexFactory } from '../src'; 2 | 3 | class UserService extends BaseService { 4 | static initialState = { name: 'John', age: 28 }; 5 | 6 | changeName(newName: string) { 7 | this.state.name.set(newName); 8 | } 9 | 10 | getName() { 11 | return this.state.name.get(); 12 | } 13 | } 14 | 15 | class OtherService extends BaseService { 16 | changeName(newName: string) { 17 | this.getService('user').changeName(newName); 18 | } 19 | } 20 | 21 | const Cortex = createCortexFactory()({ 22 | user: UserService, 23 | other: OtherService, 24 | }); 25 | 26 | describe('core', () => { 27 | let core = new Cortex(); 28 | beforeEach(() => { 29 | core = new Cortex(); 30 | }); 31 | 32 | it('should instanciate its store slice', () => { 33 | const name = core.getService('user').getName(); 34 | expect(name).not.toBeUndefined(); 35 | }); 36 | 37 | it('should access the store', () => { 38 | const name = core.getService('user').getName(); 39 | expect(name).toBe('John'); 40 | }); 41 | 42 | it('should modify the store', () => { 43 | core.getService('user').changeName('Max'); 44 | const name = core.getService('user').getName(); 45 | expect(name).toBe('Max'); 46 | }); 47 | 48 | it('should call another service method', () => { 49 | core.getService('other').changeName('David'); 50 | const userName = core.store.user.name.get(); 51 | expect(userName).toBe('David'); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/core/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/core/assets/logo.png -------------------------------------------------------------------------------- /packages/core/assets/logo_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/core/assets/logo_512.png -------------------------------------------------------------------------------- /packages/core/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | verbose: true, 4 | testEnvironment: "node", 5 | moduleFileExtensions: ["ts", "tsx", "js"], 6 | transform: { 7 | "^.+\\.(ts|tsx)$": [ 8 | "ts-jest", 9 | { 10 | tsConfig: "./tsconfig.json", 11 | }, 12 | ], 13 | }, 14 | testMatch: ["**/*.(test|spec).(ts|tsx)"], 15 | }; 16 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@azot-dev/cortex", 3 | "version": "1.24.2", 4 | "license": "MIT", 5 | "private": false, 6 | "description": "A lib to make TDD and clean architecture easy to use with React", 7 | "main": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "homepage": "https://azot-dev.github.io/cortex/", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/azot-dev/cortex" 13 | }, 14 | "scripts": { 15 | "test": "jest --passWithNoTests", 16 | "build": "tsc" 17 | }, 18 | "bin": { 19 | "cortex": "./scripts/cli.js" 20 | }, 21 | "peerDependencies": { 22 | "@legendapp/state": ">=1.0.0", 23 | "react": ">=16.8.0" 24 | }, 25 | "dependencies": { 26 | "lodash": "^4.17.21", 27 | "redux": "^5.0.1", 28 | "remote-redux-devtools": "^0.5.16", 29 | "socket.io-client": "^4.7.2", 30 | "ws": "^8.14.2", 31 | "yargs": "^17.7.2" 32 | }, 33 | "devDependencies": { 34 | "@legendapp/state": "^2.1.11", 35 | "@react-native-async-storage/async-storage": "^2.0.0", 36 | "@semantic-release/changelog": "^6.0.3", 37 | "@semantic-release/git": "^10.0.1", 38 | "@semantic-release/github": "^11.0.2", 39 | "@semantic-release/npm": "^12.0.1", 40 | "@types/jest": "^29.5.11", 41 | "@types/lodash": "^4.14.200", 42 | "@types/redux": "^3.6.0", 43 | "@types/remote-redux-devtools": "^0.5.8", 44 | "@types/ws": "^8.5.10", 45 | "axios": "^1.6.7", 46 | "jest": "^29.7.0", 47 | "react": "^18.2.0", 48 | "semantic-release": "^24.1.0", 49 | "ts-jest": "^29.1.1", 50 | "typescript": "^5.5.0-dev.20240402" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/core/release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | repositoryUrl: "https://github.com/azot-dev/cortex", 3 | branches: ["main"], 4 | plugins: [ 5 | [ 6 | "@semantic-release/commit-analyzer", 7 | { 8 | releaseRules: [ 9 | { type: "doc", release: "patch" }, 10 | { type: "fix", release: "patch" }, 11 | { type: "ci", release: "patch" }, 12 | { type: "refactor", release: "patch" }, 13 | { type: "bump", release: "patch" }, 14 | { type: "feat", release: "minor" }, 15 | ], 16 | preset: "angular", 17 | }, 18 | ], 19 | "@semantic-release/release-notes-generator", 20 | [ 21 | "@semantic-release/changelog", 22 | { 23 | changelogFile: "CHANGELOG.md", 24 | }, 25 | ], 26 | [ 27 | "@semantic-release/npm", 28 | { 29 | npmPublish: true, 30 | }, 31 | ], 32 | [ 33 | "@semantic-release/github", 34 | { 35 | assets: ["dist/**/*"], 36 | labels: ["automated-release"], 37 | tagFormat: "core-v${version}", 38 | }, 39 | ], 40 | [ 41 | "@semantic-release/git", 42 | { 43 | assets: ["CHANGELOG.md", "package.json"], 44 | message: "chore(release): core-v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}", 45 | tagFormat: "core-v${version}", 46 | }, 47 | ], 48 | ], 49 | }; 50 | -------------------------------------------------------------------------------- /packages/core/scripts/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const yargs = require("yargs"); 4 | const fs = require("fs"); 5 | const path = require("path"); 6 | 7 | function displayCode() { 8 | const RESET = "\x1b[0m"; 9 | const RED = "\x1b[31m"; 10 | const GREEN = "\x1b[32m"; 11 | const YELLOW = "\x1b[33m"; 12 | const BLUE = "\x1b[34m"; 13 | 14 | let code = ` 15 | 16 | 17 | 18 | `; 19 | 20 | code = code.replace(/CortexProvider/g, `${GREEN}CortexProvider${RESET}`); 21 | code = code.replace(/App/g, `${GREEN}App${RESET}`); 22 | code = code.replace(/Core/g, `${GREEN}Core${RESET}`); 23 | code = code.replace(/coreInstance/g, `${BLUE}coreInstance${RESET}`); 24 | code = code.replace(/\bnew\b/g, `${BLUE}new${RESET}`); 25 | code = code.replace(/{/g, `${YELLOW}{${RESET}`); 26 | code = code.replace(/}/g, `${YELLOW}}${RESET}`); 27 | code = code.replace(/\(/g, `${RED}(${RESET}`); 28 | code = code.replace(/\)/g, `${RED})${RESET}`); 29 | 30 | console.info(code); 31 | } 32 | 33 | function copyRecursively(src, dest) { 34 | const exists = fs.existsSync(src); 35 | const stats = exists && fs.statSync(src); 36 | const isDirectory = exists && stats.isDirectory(); 37 | 38 | if (isDirectory) { 39 | if (!fs.existsSync(dest)) { 40 | fs.mkdirSync(dest); 41 | } 42 | fs.readdirSync(src).forEach((childItemName) => { 43 | copyRecursively(path.join(src, childItemName), path.join(dest, childItemName)); 44 | }); 45 | } else { 46 | fs.copyFileSync(src, dest); 47 | } 48 | } 49 | 50 | yargs 51 | .command( 52 | "init [library]", 53 | "Initialize cortex with a specific library", 54 | (yargs) => { 55 | yargs.positional("library", { 56 | describe: "The library to initialize (e.g., react)", 57 | type: "string", 58 | }); 59 | }, 60 | (argv) => { 61 | if (argv.library) { 62 | const sourceDirectory = path.join(__dirname, argv.library); 63 | const destinationDirectory = process.cwd(); 64 | 65 | console.info(); 66 | if (fs.existsSync(sourceDirectory)) { 67 | copyRecursively(sourceDirectory, destinationDirectory); 68 | console.log(`Initialization of Cortex with ${argv.library} completed 👌`); 69 | } else { 70 | console.error(`The library "${argv.library}" is not recognized.`); 71 | } 72 | console.info("You need to wrap your app with the CortexProvider to use it with React"); 73 | displayCode(); 74 | } 75 | } 76 | ) 77 | .help().argv; 78 | -------------------------------------------------------------------------------- /packages/core/scripts/react/cortex/_core.ts: -------------------------------------------------------------------------------- 1 | import { createCortexFactory } from "@azot-dev/cortex"; 2 | import { services } from "./services/_services"; 3 | import { Dependencies } from "./dependencies/_dependencies"; 4 | import { coreServices } from "./core_services/core_services"; 5 | 6 | export const Core = createCortexFactory()(services, coreServices); 7 | -------------------------------------------------------------------------------- /packages/core/scripts/react/cortex/core_services/core_services.ts: -------------------------------------------------------------------------------- 1 | export const coreServices = {}; 2 | -------------------------------------------------------------------------------- /packages/core/scripts/react/cortex/dependencies/_dependencies.ts: -------------------------------------------------------------------------------- 1 | export interface Dependencies {} 2 | -------------------------------------------------------------------------------- /packages/core/scripts/react/cortex/services/_services.ts: -------------------------------------------------------------------------------- 1 | import { CounterService } from './counter.service'; 2 | 3 | export const services = { 4 | counter: CounterService, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/core/scripts/react/cortex/services/counter.service.ts: -------------------------------------------------------------------------------- 1 | import { Service } from '../utils/service'; 2 | 3 | type State = { 4 | count: number; 5 | }; 6 | 7 | export class CounterService extends Service { 8 | static initialState: State = { 9 | count: 0, 10 | }; 11 | 12 | increment() { 13 | this.state.count.set((count) => count + 1); 14 | } 15 | 16 | decrement() { 17 | this.state.count.set((count) => count - 1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/scripts/react/cortex/utils/hooks.ts: -------------------------------------------------------------------------------- 1 | // utils/hooks.ts 2 | 3 | import { createCortexHooks } from "@azot-dev/react-cortex"; 4 | import { Services } from "./types"; 5 | 6 | export const { useAppSelector, useLazyMethod, useMethod, useService, useStore, useAppState } = createCortexHooks(); 7 | -------------------------------------------------------------------------------- /packages/core/scripts/react/cortex/utils/service.ts: -------------------------------------------------------------------------------- 1 | // utils/service.ts 2 | 3 | import { BaseService } from "@azot-dev/cortex"; 4 | import { Dependencies } from "../dependencies/_dependencies"; 5 | import { AllServices } from "./types"; 6 | 7 | export abstract class Service extends BaseService { 8 | constructor(...args: [any, any, any, any]) { 9 | super(...args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/scripts/react/cortex/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { coreServices } from "../core_services/core_services"; 2 | import { services } from "../services/_services"; 3 | 4 | export type Services = typeof services; 5 | export type CoreServices = typeof coreServices; 6 | export type AllServices = Services & CoreServices; 7 | -------------------------------------------------------------------------------- /packages/core/src/base-service.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "@legendapp/state"; 2 | import { ConstructedServiceTypes, GetStore, InferStoreType, ServiceConstructor } from "./types/service-constructor"; 3 | import { ServiceRegistry } from "./service-registry"; 4 | 5 | export abstract class BaseService>, DependenciesType> { 6 | constructor( 7 | _store: Observable>, 8 | protected state: Observable, 9 | protected dependencies: DependenciesType, 10 | private serviceRegistry: ServiceRegistry, DependenciesType> 11 | ) { 12 | if ("initialState" in this && !("initialState" in this.constructor)) { 13 | throw new Error(`Service ${this.constructor.name}: initialState must be declared static (static initialState = { ... })`); 14 | } 15 | } 16 | 17 | init() {} 18 | 19 | public getState(): State { 20 | return this.state.peek(); 21 | } 22 | 23 | public setState(state: State | ((currentState: State) => State)) { 24 | // @ts-ignore 25 | this.state.set(state); 26 | } 27 | 28 | protected getService( 29 | name: K 30 | ): ConstructedServiceTypes, DependenciesType>[K] { 31 | return this.serviceRegistry.get(name); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/src/core-services/debugger/create-debugger-service.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "@legendapp/state"; 2 | import { legacy_createStore as createStore, applyMiddleware, compose } from "redux"; 3 | import type { GetStore } from "../../types/service-constructor"; 4 | 5 | declare global { 6 | interface Window { 7 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose; 8 | } 9 | } 10 | 11 | enum ACTIONS { 12 | STORE_CHANGED = "STORE_CHANGED", 13 | INIT_STORE = "INIT_STORE", 14 | } 15 | 16 | type Action = { type: typeof ACTIONS.STORE_CHANGED; payload: any } | { type: typeof ACTIONS.INIT_STORE; payload: any }; 17 | 18 | const initStore = (store: any) => { 19 | return { 20 | type: ACTIONS.INIT_STORE, 21 | payload: store, 22 | }; 23 | }; 24 | 25 | const changeStore = (store: any) => { 26 | return { 27 | type: ACTIONS.STORE_CHANGED, 28 | payload: store, 29 | }; 30 | }; 31 | 32 | const methodCalled = (serviceName: string, methodName: string) => { 33 | return { 34 | type: `[${serviceName}] ${methodName}`, 35 | }; 36 | }; 37 | 38 | type Params = { 39 | active?: boolean; 40 | host?: string; 41 | port?: number; 42 | }; 43 | 44 | export const createDebuggerService = ({ host, port, active }: Params) => { 45 | if (!active) { 46 | return class DebuggerService {}; 47 | } 48 | type Store = Observable>; 49 | 50 | let composeEnhancers = compose; 51 | let store: Store; 52 | let reduxStore: any; 53 | 54 | const rootReducer = (state: any = {}, action: Action) => { 55 | switch (action.type) { 56 | case ACTIONS.STORE_CHANGED: 57 | return action.payload; 58 | case ACTIONS.INIT_STORE: 59 | return action.payload; 60 | default: 61 | return state; 62 | } 63 | }; 64 | 65 | const initDebugger = () => { 66 | if (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { 67 | composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; 68 | 69 | reduxStore = createStore(rootReducer, composeEnhancers(applyMiddleware())); 70 | 71 | return; 72 | } 73 | const composeWithDevTools = require("remote-redux-devtools").composeWithDevTools; 74 | composeEnhancers = composeWithDevTools({ 75 | name: "Cortex Debugger", 76 | realtime: true, 77 | port: port ?? 8000, 78 | host: host ?? "192.168.1.1", 79 | }); 80 | 81 | reduxStore = createStore(rootReducer, composeEnhancers(applyMiddleware())); 82 | }; 83 | 84 | const decorateAllMethods = (serviceName: string, instance: any) => { 85 | const prototype = Object.getPrototypeOf(instance); 86 | 87 | Object.getOwnPropertyNames(prototype).forEach((methodName) => { 88 | if (methodName === "constructor") return; 89 | 90 | const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName); 91 | if (descriptor && typeof descriptor.value === "function") { 92 | const originalMethod = descriptor.value; 93 | 94 | instance[methodName] = function (...args: any[]) { 95 | reduxStore?.dispatch(methodCalled(serviceName, methodName)); 96 | // @ts-ignore 97 | return originalMethod.apply(this, args); 98 | }.bind(instance); 99 | } 100 | }); 101 | }; 102 | 103 | return class DebuggerService { 104 | constructor(injectedStore: Observable>, _state: any, _dependencies: Record, serviceRegistry: any) { 105 | store = injectedStore as Observable>; 106 | initDebugger(); 107 | 108 | const serviceNames: string[] = serviceRegistry.getNames(); 109 | serviceNames.forEach((serviceName) => { 110 | const service = serviceRegistry.get(serviceName); 111 | decorateAllMethods(serviceName, service); 112 | }); 113 | reduxStore?.dispatch(initStore(store.get())); 114 | store.onChange((newStore) => { 115 | reduxStore?.dispatch(changeStore(newStore.value)); 116 | }); 117 | } 118 | }; 119 | }; 120 | -------------------------------------------------------------------------------- /packages/core/src/core-services/persistence/create-persistence-service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "@legendapp/state"; 2 | import { GetStore, ServiceConstructor } from "../../types/service-constructor"; 3 | import { STORAGE_KEY, createPersistenceService } from "./create-persistence-service"; 4 | import { BaseService } from "../../base-service"; 5 | import { createCortexFactory } from "../../create-cortex-factory"; 6 | 7 | describe("createPersistenceService", () => { 8 | let storageService: StorageService; 9 | let core: InstanceType; 10 | 11 | beforeEach(() => { 12 | storageService = new StorageService(); 13 | core = new Core({ storage: storageService }); 14 | }); 15 | 16 | it("should call setItem of the storage adapter when user.name is changed", async () => { 17 | core.getService("user").changeName("Xavier"); 18 | const value = await storageService.getItem(`${STORAGE_KEY}/user.name`); 19 | 20 | expect(value).toBe("Xavier"); 21 | }); 22 | 23 | it("should not call setItem of the storage adapter when user.age is changed", async () => { 24 | core.getService("user").changeAge(30); 25 | expect(await storageService.getItem("user.age")).toBeUndefined(); 26 | }); 27 | 28 | it("should instantiate a new core with the persisted value", async () => { 29 | core.getService("user").changeName("Xavier"); 30 | 31 | const newCore = new Core({ storage: storageService }); 32 | 33 | await sleep(10); 34 | 35 | expect(newCore.store.user.name.get()).toBe("Xavier"); 36 | }); 37 | }); 38 | 39 | function sleep(milliseconds: number) { 40 | return new Promise((resolve) => setTimeout(resolve, milliseconds)); 41 | } 42 | 43 | export abstract class Service extends BaseService { 44 | constructor(...args: [any, any, any, any]) { 45 | super(...args); 46 | } 47 | } 48 | 49 | type UserState = { name: string; age: number }; 50 | 51 | class UserService extends Service { 52 | static initialState: UserState = { name: "John", age: 28 }; 53 | 54 | init() { 55 | this.getService("persistence").persist("user.name"); 56 | } 57 | 58 | changeName(newName: string) { 59 | this.state.name.set(newName); 60 | } 61 | 62 | changeAge(newAge: number) { 63 | this.state.age.set(newAge); 64 | } 65 | 66 | getName() { 67 | return this.state.name.get(); 68 | } 69 | } 70 | 71 | interface Storage { 72 | getItem(key: string): Promise; 73 | setItem(key: string, value: any): Promise; 74 | removeItem(key: string): Promise; 75 | clear(): Promise; 76 | getAllKeys(): Promise; 77 | } 78 | 79 | const PersistenceService = createPersistenceService("storage"); 80 | 81 | class StorageService implements Storage { 82 | private storage: Record = {}; 83 | 84 | async getItem(key: string): Promise { 85 | return this.storage[key]; 86 | } 87 | 88 | async setItem(key: string, value: any): Promise { 89 | this.storage[key] = value; 90 | } 91 | 92 | async removeItem(key: string): Promise { 93 | delete this.storage[key]; 94 | } 95 | 96 | async clear(): Promise { 97 | this.storage = {}; 98 | } 99 | 100 | async getAllKeys(): Promise { 101 | return Object.keys(this.storage); 102 | } 103 | } 104 | 105 | const services = { 106 | user: UserService, 107 | }; 108 | 109 | const coreServices = { 110 | persistence: PersistenceService, 111 | }; 112 | 113 | type Dependencies = { 114 | storage: Storage; 115 | }; 116 | 117 | const Core = createCortexFactory()(services, coreServices); 118 | -------------------------------------------------------------------------------- /packages/core/src/core-services/persistence/create-persistence-service.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "@legendapp/state"; 2 | import _ from "lodash"; 3 | import { GetStore, ServiceConstructor } from "../../types/service-constructor"; 4 | 5 | export const STORAGE_KEY = "@cortex-persist"; 6 | export interface Storage { 7 | getItem(key: string): Promise; 8 | setItem(key: string, value: any): Promise; 9 | removeItem(key: string): Promise; 10 | clear(): Promise; 11 | getAllKeys(): Promise; 12 | } 13 | 14 | type PathImpl = K extends string 15 | ? T[K] extends Record 16 | ? T[K] extends Array 17 | ? K | `${K}.${PathImpl>}` 18 | : K | `${K}.${PathImpl}` 19 | : K 20 | : never; 21 | 22 | type Path = PathImpl | keyof T; 23 | 24 | type KeysMatching = { [K in keyof T]: T[K] extends V ? K : never }[keyof T]; 25 | 26 | type ExtractObservableType = O extends Observable ? T : never; 27 | 28 | export const createPersistenceService = , ServiceConstructorsType extends Record>>( 29 | key: KeysMatching, 30 | storageKey: string = STORAGE_KEY 31 | ) => { 32 | type Store = Observable>; 33 | 34 | let storage: Storage; // no class with private or protected member can be exported in a Typescript function 35 | let store: Store; 36 | 37 | const getAllKeys = async () => { 38 | const allKeys = await storage.getAllKeys(); 39 | return allKeys.filter((key) => key.startsWith(storageKey)); 40 | }; 41 | 42 | return class PersistenceService { 43 | static initialState = { 44 | hydrated: false, 45 | }; 46 | 47 | constructor( 48 | injectedStore: Observable>, 49 | _state: any, 50 | dependencies: { [P in KeysMatching]: Storage } & Record, 51 | _serviceRegistry: any 52 | ) { 53 | store = injectedStore as Observable>; 54 | storage = dependencies[key]; 55 | } 56 | 57 | async init() { 58 | const fullKeys = await getAllKeys(); 59 | 60 | fullKeys.forEach(async (fullKey) => { 61 | const key = fullKey.split(`${storageKey}/`)[1]; 62 | const value = await storage.getItem(fullKey); 63 | 64 | const matchingPath = _.get(store, key); 65 | if (!matchingPath) { 66 | storage.removeItem(fullKey); 67 | } else { 68 | matchingPath.set(value); 69 | } 70 | }); 71 | } 72 | 73 | persist(key: Path>) { 74 | const matchingPath = _.get(store, key); 75 | 76 | matchingPath.onChange((data: any) => { 77 | storage.setItem(`${storageKey}/${String(key)}`, data.value); 78 | }); 79 | } 80 | 81 | async clean() { 82 | const keys = await getAllKeys(); 83 | keys.forEach(async (key) => { 84 | await storage.removeItem(key); 85 | }); 86 | } 87 | }; 88 | }; 89 | -------------------------------------------------------------------------------- /packages/core/src/create-cortex-factory.ts: -------------------------------------------------------------------------------- 1 | import { Observable, observable } from "@legendapp/state"; 2 | import { ServiceRegistry } from "./service-registry"; 3 | import { GetStore, ServiceConstructor } from "./types/service-constructor"; 4 | import { cloneDeep } from "lodash"; 5 | 6 | export function createCortexFactory() { 7 | return < 8 | ServiceConstructorsType extends Record>, 9 | CoreServiceConstructorsType extends Record>, 10 | States extends GetStore 11 | >( 12 | serviceConstructors: ServiceConstructorsType, 13 | coreServices?: CoreServiceConstructorsType 14 | ) => { 15 | type ServiceInstances = { 16 | [K in keyof ServiceConstructorsType]: InstanceType; 17 | }; 18 | 19 | type CoreServiceInstances = { 20 | [K in keyof CoreServiceConstructorsType]: InstanceType; 21 | }; 22 | 23 | return class Core { 24 | public store: Observable; 25 | #serviceRegistry: ServiceRegistry; 26 | 27 | constructor(dependencies: Partial = {}) { 28 | this.#serviceRegistry = new ServiceRegistry(); 29 | 30 | const rawStates: States = {} as States; 31 | for (const key in serviceConstructors) { 32 | if ("initialState" in serviceConstructors[key]) { 33 | rawStates[key as unknown as keyof States] = serviceConstructors[key].initialState; 34 | } 35 | } 36 | 37 | this.store = observable(cloneDeep(rawStates)); 38 | for (const [key, ServiceConstructor] of Object.entries(serviceConstructors)) { 39 | const instance = new ServiceConstructor( 40 | // @ts-ignore 41 | this.store, 42 | this.store[key as unknown as keyof Observable], 43 | dependencies as DependenciesType, 44 | this.#serviceRegistry 45 | ); 46 | this.#serviceRegistry.setInstance(key as keyof ServiceInstances, instance); 47 | } 48 | 49 | for (const [key, ServiceConstructor] of Object.entries(coreServices || {})) { 50 | const instance = new ServiceConstructor( 51 | // @ts-ignore 52 | this.store, 53 | this.store[key as unknown as keyof Observable], 54 | dependencies as DependenciesType, 55 | this.#serviceRegistry 56 | ); 57 | this.#serviceRegistry.setInstance(key as keyof ServiceInstances, instance); 58 | } 59 | 60 | Object.keys({ ...serviceConstructors, ...coreServices }).forEach((serviceName) => { 61 | const serviceInstance = this.#serviceRegistry.get(serviceName as keyof typeof serviceConstructors & keyof typeof coreServices); 62 | if (serviceInstance) { 63 | bindAllMethods(serviceInstance); 64 | } 65 | }); 66 | 67 | Object.keys(serviceConstructors).forEach((service) => { 68 | this.#serviceRegistry.get(service).init?.(); 69 | }); 70 | 71 | Object.keys(coreServices || {}).forEach((service) => { 72 | this.#serviceRegistry.get(service).init?.(); 73 | }); 74 | } 75 | 76 | getService(name: K): ServiceInstances[K] { 77 | return this.#serviceRegistry.get(name); 78 | } 79 | }; 80 | }; 81 | } 82 | 83 | const bindAllMethods = (service: any) => { 84 | Object.getOwnPropertyNames(Object.getPrototypeOf(service)).forEach((methodName) => { 85 | const method = service[methodName]; 86 | if (typeof method === "function") { 87 | service[methodName] = method.bind(service); 88 | } 89 | }); 90 | }; 91 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | import { createCortexFactory } from "./create-cortex-factory"; 2 | import { BaseService } from "./base-service"; 3 | import { createDebuggerService } from "./core-services/debugger/create-debugger-service"; 4 | import { createPersistenceService, Storage } from "./core-services/persistence/create-persistence-service"; 5 | 6 | export { createCortexFactory, BaseService, createDebuggerService, createPersistenceService }; 7 | export type { Storage }; 8 | -------------------------------------------------------------------------------- /packages/core/src/service-registry.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from "@legendapp/state"; 2 | import { ServiceConstructor, ConstructedServiceTypes } from "./types/service-constructor"; 3 | 4 | export class ServiceRegistry< 5 | ServiceConstructorsType extends Record, DependenciesType>>, 6 | StoreType, 7 | DependenciesType 8 | > { 9 | private instances: Partial> = {}; 10 | 11 | setInstance>( 12 | name: K, 13 | instance: ConstructedServiceTypes[K] 14 | ) { 15 | this.instances[name] = instance; 16 | } 17 | 18 | getNames(): (keyof ConstructedServiceTypes)[] { 19 | return Object.keys(this.instances); 20 | } 21 | 22 | get>( 23 | name: K 24 | ): ConstructedServiceTypes[K] { 25 | if (!this.instances[name]) { 26 | throw new Error(`Service ${String(name)} has not been registered.`); 27 | } 28 | return this.instances[name] as ConstructedServiceTypes[K]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/core/src/types/service-constructor.ts: -------------------------------------------------------------------------------- 1 | // lib/types/service-constructor.ts 2 | 3 | import { Observable } from "@legendapp/state"; 4 | import { ServiceRegistry } from "../service-registry"; 5 | 6 | export type ConstructedServiceTypes< 7 | ServiceConstructorsType extends Record, DependenciesType>>, 8 | StateType, 9 | DependenciesType 10 | > = { 11 | [K in keyof ServiceConstructorsType]: InstanceType; 12 | }; 13 | 14 | export type GetStore>> = { 15 | [K in keyof Services]: Services[K]["initialState"]; 16 | }; 17 | 18 | export type ServiceConstructor = { 19 | initialState?: StateType; 20 | new ( 21 | store: Observable>, 22 | state: Observable, 23 | dependencies: DependenciesType, 24 | serviceRegistry: ServiceRegistry, DependenciesType> 25 | ): ServiceType; 26 | }; 27 | 28 | export type InferStoreType = { 29 | [K in keyof ServiceConstructorsType]: ServiceConstructorsType[K] extends ServiceConstructor ? I : never; 30 | }; 31 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "CommonJS", 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "experimentalDecorators": true, 11 | "emitDecoratorMetadata": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "strictPropertyInitialization": false, 17 | "declaration": true 18 | }, 19 | "include": ["src/**/*.ts"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/doc/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /packages/doc/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /packages/doc/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/doc/blog/2019-05-28-first-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: first-blog-post 3 | title: First Blog Post 4 | authors: 5 | name: Gao Wei 6 | title: Docusaurus Core Team 7 | url: https://github.com/wgao19 8 | image_url: https://github.com/wgao19.png 9 | tags: [hola, docusaurus] 10 | --- 11 | 12 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 13 | -------------------------------------------------------------------------------- /packages/doc/blog/2019-05-29-long-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: long-blog-post 3 | title: Long Blog Post 4 | authors: endi 5 | tags: [hello, docusaurus] 6 | --- 7 | 8 | This is the summary of a very long blog post, 9 | 10 | Use a `` comment to limit blog post size in the list view. 11 | 12 | 13 | 14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 15 | 16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 17 | 18 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 19 | 20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 21 | 22 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 23 | 24 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 25 | 26 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 27 | 28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 29 | 30 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 31 | 32 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 33 | 34 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 35 | 36 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 37 | 38 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 39 | 40 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 41 | 42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 43 | 44 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 45 | -------------------------------------------------------------------------------- /packages/doc/blog/2021-08-01-mdx-blog-post.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: mdx-blog-post 3 | title: MDX Blog Post 4 | authors: [slorber] 5 | tags: [docusaurus] 6 | --- 7 | 8 | Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). 9 | 10 | :::tip 11 | 12 | Use the power of React to create interactive blog posts. 13 | 14 | ```js 15 | 16 | ``` 17 | 18 | 19 | 20 | ::: 21 | -------------------------------------------------------------------------------- /packages/doc/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg -------------------------------------------------------------------------------- /packages/doc/blog/2021-08-26-welcome/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | authors: [slorber, yangshun] 5 | tags: [facebook, hello, docusaurus] 6 | --- 7 | 8 | [Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). 9 | 10 | Simply add Markdown files (or folders) to the `blog` directory. 11 | 12 | Regular blog authors can be added to `authors.yml`. 13 | 14 | The blog post date can be extracted from filenames, such as: 15 | 16 | - `2019-05-30-welcome.md` 17 | - `2019-05-30-welcome/index.md` 18 | 19 | A blog post folder can be convenient to co-locate blog post images: 20 | 21 | ![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) 22 | 23 | The blog supports tags as well! 24 | 25 | **And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. 26 | -------------------------------------------------------------------------------- /packages/doc/blog/authors.yml: -------------------------------------------------------------------------------- 1 | endi: 2 | name: Endilie Yacop Sucipto 3 | title: Maintainer of Docusaurus 4 | url: https://github.com/endiliey 5 | image_url: https://github.com/endiliey.png 6 | 7 | yangshun: 8 | name: Yangshun Tay 9 | title: Front End Engineer @ Facebook 10 | url: https://github.com/yangshun 11 | image_url: https://github.com/yangshun.png 12 | 13 | slorber: 14 | name: Sébastien Lorber 15 | title: Docusaurus maintainer 16 | url: https://sebastienlorber.com 17 | image_url: https://github.com/slorber.png 18 | -------------------------------------------------------------------------------- /packages/doc/docs/React hooks/useAppSelector.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # useAppSelector 5 | 6 | Access to the store and return the processed value you want 7 | 8 | ```tsx 9 | const counter = useAppSelector((store) => store.counter.get()) 10 | ``` 11 | -------------------------------------------------------------------------------- /packages/doc/docs/React hooks/useAppState.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # useAppState 5 | 6 | Access and modify an app observable in a view like React `useState` 7 | 8 | ```tsx 9 | const [name, setName] = useAppState(state => state.user.name) 10 | ``` 11 | -------------------------------------------------------------------------------- /packages/doc/docs/React hooks/useLazyMethod.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # useLazyMethod 5 | 6 | Same behavior as useMethod excepts it is not called when the component first renders 7 | 8 | ```tsx 9 | export const LoginComponent = () => { 10 | const shoesService = useService('auth') 11 | const { 12 | isLoading, 13 | isError, 14 | isSuccess, 15 | isCalled, 16 | error, 17 | call: login, 18 | data, 19 | } = useLazyMethod(authService.login) 20 | 21 | if (!isCalled || !isLoading) { 22 | return 23 | } 24 | 25 | return 26 | } 27 | ``` 28 | 29 | It can also be used in one line by using the method string 30 | 31 | ```tsx 32 | export const LoginComponent = () => { 33 | const { 34 | isLoading, 35 | isError, 36 | isSuccess, 37 | isCalled, 38 | error, 39 | call, 40 | data, 41 | } = useLazyMethod('authService.login') 42 | 43 | 44 | if (!isCalled || !isLoading) { 45 | return 46 | } 47 | 48 | return 49 | } 50 | ``` 51 | -------------------------------------------------------------------------------- /packages/doc/docs/React hooks/useMethod.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # useMethod 5 | 6 | Useful for async services, it returns the state of the promise 7 | and executes automatically when the component where it is encapsulated first renders 8 | 9 | It is similar to the `react-query` and `apollo-graphql` hooks behavior 10 | but since the complete async logic will be encapsulated in one service method, no need to listen in a useEffect or a useMemo a query to finish, the useMethod just wraps a complete use case and generates the data useful for the UI (in most of the cases no need to store a isLoading boolean in the store) 11 | 12 | ```tsx 13 | export const ShoesComponent = () => { 14 | const shoesService = useService('shoes') 15 | const { 16 | isLoading, 17 | isError, 18 | isSuccess, 19 | isCalled, 20 | error, 21 | call, 22 | data, 23 | } = useMethod(shoesService.load) 24 | 25 | if (!isCalled || !isLoading) { 26 | return 27 | } 28 | 29 | if (isError) { 30 | return An error occured 31 | } 32 | 33 | return 34 | } 35 | ``` 36 | 37 | It can also be used in one line by using the method string 38 | 39 | ```ts 40 | export const ShoesComponent = () => { 41 | const { 42 | isLoading, 43 | isError, 44 | isSuccess, 45 | isCalled, 46 | error, 47 | call, 48 | data, 49 | } = useMethod('shoes.load') 50 | 51 | ... 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /packages/doc/docs/React hooks/useService.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | # useService 6 | 7 | Access to services 8 | 9 | ```tsx 10 | const counterService = useService('counter') 11 | 12 | return ( 13 | 14 | ) 15 | ``` -------------------------------------------------------------------------------- /packages/doc/docs/React hooks/useStore.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | # useStore 5 | 6 | Access to the store 7 | 8 | ```tsx 9 | const store = useStore(); 10 | const counter = store.counter.get() 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/doc/docs/basic-example.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Basic example 6 | 7 | The classic example of a counter 8 | 9 | :::tip What You'll Learn 10 | 11 | - How to create a simple service and bind it to your React app 12 | 13 | ::: 14 | 15 | :::info Prerequisites 16 | 17 | - Familiarity with [Typescript](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) 18 | - Familiarity with [Typescript class syntax](https://www.typescriptlang.org/docs/handbook/classes.html) 19 | 20 | ::: 21 | 22 | ## The service 23 | 24 | ### Create the new service 25 | 26 | First we create the file `counter.service.ts` 27 | 28 | The created service must extend `Service` which has been auto-generated in the setup step 29 | 30 | ```ts 31 | export class CounterService extends Service { 32 | 33 | } 34 | ``` 35 | 36 | ### Service state 37 | 38 | Each service has a local state, it is referenced as initialState which must be static 39 | 40 | Pass the `State` type to the extended Service as `Service` 41 | 42 | ```ts 43 | // highlight-next-line 44 | type State = { count: number }; 45 | 46 | export class CounterService extends Service { 47 | // highlight-next-line 48 | static initialState: State = { count: 0 }; 49 | } 50 | ``` 51 | 52 | ### Append a method 53 | 54 | Each method has access to the local state as an observable 55 | 56 | To access a local state use its method `get` 57 | 58 | To modify a local state use its method `set` 59 | 60 | ```ts 61 | type State = { count: number }; 62 | 63 | export class CounterService extends Service { 64 | static initialState: State = { count: 0 }; 65 | 66 | // highlight-start 67 | increment() { 68 | this.state.count.set((count) => count + 1); 69 | } 70 | // highlight-end 71 | } 72 | ``` 73 | 74 | ### Register the service 75 | 76 | In the file `_services` 77 | 78 | ```ts 79 | // highlight-next-line 80 | import { CounterService } from './counter.service'; 81 | 82 | export const services = { 83 | // highlight-next-line 84 | counter: CounterService, 85 | }; 86 | ``` 87 | 88 | ### Wrap the App with the CortexProvider 89 | 90 | ```tsx 91 | {/* highlight-next-line */} 92 | 93 | 94 | {/* highlight-next-line */} 95 | 96 | ``` 97 | 98 | ### Use the methods in the app 99 | 100 | Use the hook `useService` with the name of the service, here `counter` 101 | 102 | You can access any method by destructuring this hook return value, and use it in the JSX 103 | 104 | ```tsx 105 | const InnerApp: FC = () => { 106 | {/* highlight-next-line */} 107 | const { increment } = useService('counter'); 108 | 109 | return ( 110 |
111 | {/* highlight-next-line */} 112 | 113 |
114 | ); 115 | }; 116 | ``` 117 | 118 | ### Access the state in the app 119 | 120 | You can access to any value thanks to the useAppSelector, anytime the value `store.counter.count` changes, the value will be re-rendered in the JSX 121 | 122 | ```tsx 123 | const InnerApp: FC = () => { 124 | const { increment } = useService('counter'); 125 | {/* highlight-next-line */} 126 | const count = useAppSelector((store) => store.counter.count.get()); 127 | 128 | return ( 129 |
130 | 131 | {/* highlight-next-line */} 132 | {count} 133 |
134 | ); 135 | }; 136 | ``` 137 | 138 | ### Test the behavior 139 | 140 | Since all the logic is completely decoupled from React, we can test the behavior of our app with Jest 141 | 142 | ```typescript 143 | describe('counter', () => { 144 | it('should be incremented', () => { 145 | const core = new Core() 146 | 147 | expect(core.store.counter.get()).toBe(0) 148 | 149 | core.getService("counter").increment() 150 | expect(core.store.counter.get()).toBe(1) 151 | }) 152 | }) 153 | ``` 154 | -------------------------------------------------------------------------------- /packages/doc/docs/debugger.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | # Cortex devtools 6 | 7 | Cortex is compatible with [Redux Devtools](https://chromewebstore.google.com/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=fr,) 8 | 9 | To activate it: 10 | 11 | ```ts 12 | export const Core = createCortexFactory()(services, { debugger: createDebuggerService() }); 13 | ``` 14 | 15 | To use remotely, so it can be used with React Native Debugger: 16 | 17 | ```ts 18 | export const Core = createCortexFactory()(services, { debugger: createDebuggerService({ host: "192.168.1.1", port: 8081 }) }); 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/doc/docs/intro.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Overview 6 | 7 |
8 | scheme 9 |
10 | 11 | React is a library not a framework, it has been created to reflect the changes of some variables (the states) to the UI, nothing else. 12 | Cortex comes as the missing brick of React, and will give all the keys to create the perfect architecture of your app, keeping your code readable and your app scalable. 13 | 14 | With this you could: 15 | 16 | - share your code between React and React Native (and any other JS framework) 17 | - test your logic directly with Jest (no more react-testing-library to test your data over the UI) 18 | - code in test driven development (TDD) 19 | - create a clean architecture with the port/adapter pattern 20 | - keep each part of your logic well separated thanks to services 21 | 22 | All of that using oriented object programming! 23 | 24 | ## Technical choices 25 | 26 | It is built over [the legend app state lib](https://legendapp.com/open-source/state/), and add a strongly typed system of services and dependency injections 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/doc/docs/next.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 10 3 | --- 4 | 5 | # Usage with NextJS 6 | 7 | Next is a server side rendering framework. 8 | It means that the same instance of the core must be used over the different client pages. Each client page makes loose the context of the app when it is reloaded or changed. 9 | 10 | You must create another provider: 11 | 12 | ```tsx 13 | "use client"; 14 | import { useRef, FC, PropsWithChildren } from "react"; 15 | import { CortexProvider } from "@azot-dev/react-cortex"; 16 | import { Core } from "../_core"; 17 | 18 | const core = new Core(); 19 | 20 | export const SSRCortexProvider: FC = ({ children }) => { 21 | const storeRef = useRef(); 22 | if (!storeRef.current) { 23 | storeRef.current = core; 24 | } 25 | 26 | return {children}; 27 | }; 28 | ``` 29 | 30 | Once it is done, wrap the root layout by the `SSRCortexProvider` like so: 31 | 32 | ```tsx 33 | export default function RootLayout({ 34 | children, 35 | }: Readonly<{ 36 | children: React.ReactNode; 37 | }>) { 38 | return ( 39 | 40 | 41 | {children} 42 | 43 | 44 | ); 45 | } 46 | ``` -------------------------------------------------------------------------------- /packages/doc/docs/persistence.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 8 3 | --- 4 | 5 | # Persistence 6 | 7 | It is possible to persist the store with the adapter you want, it can be `LocalStorage`, `AsyncStorage`, `SecureStorage` or whatever. 8 | 9 | To do so, an adapter must be created before, and being injected in the core. 10 | 11 | Example with AsyncStorage 12 | 13 | ```ts 14 | import { AsyncStorage } from "@react-native-async-storage/async-storage"; 15 | 16 | export class AsyncStorageAdapter implements Storage { 17 | async getItem(key: string) { 18 | return AsyncStorage.getItem(key); 19 | } 20 | 21 | async setItem(key: string, value: any) { 22 | AsyncStorage.setItem(key, value); 23 | } 24 | 25 | async clear() { 26 | AsyncStorage.clear(); 27 | } 28 | 29 | async removeItem(key: string) { 30 | AsyncStorage.removeItem(key); 31 | } 32 | 33 | async getAllKeys() { 34 | const keys = await AsyncStorage.getAllKeys(); 35 | return [...keys]; 36 | } 37 | } 38 | ``` 39 | 40 | Then create the `PersistenceService` and the core: 41 | 42 | ```ts 43 | type Dependencies = { 44 | storage: Storage; 45 | }; 46 | 47 | const PersistenceService = createPersistenceService("storage"); 48 | 49 | const Core = createCortexFactory()(services, { persistence: PersistenceService }); 50 | ``` 51 | 52 | In any service it can be used this way: 53 | 54 | ```ts 55 | type State = { count: number }; 56 | 57 | export class CounterService extends Service { 58 | static initialState: State = { count: 0 }; 59 | 60 | init() { 61 | this.dependencies.persistence.persist("counter.count"); 62 | // or for the whole service 63 | this.dependencies.persistence.persist("counter"); 64 | } 65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /packages/doc/docs/services.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Services 6 | 7 | A service contain methods, it can be simple methods or event hooks 8 | 9 | ```ts 10 | type State = { count: number }; 11 | 12 | export class CounterService extends Service { 13 | static initialState: State = { count: 0 }; 14 | 15 | async decrement() { 16 | const value = this.state.count.get(); 17 | if (value === 0) { 18 | return; 19 | } 20 | this.state.count.set(value - 1); 21 | } 22 | 23 | useDecrementOnFirstRender() { 24 | useEffect(() => { 25 | this.decrement(); 26 | }, []); 27 | } 28 | } 29 | ``` 30 | 31 | The methods of a service can: 32 | 33 | ## Access to the state 34 | 35 | You can access the local state by reading and writing on it 36 | 37 | In this example the counter is decremented only if it is not 0 38 | 39 | ```ts 40 | type State = { count: number }; 41 | 42 | export class CounterService extends Service { 43 | static initialState: State = { count: 0 }; 44 | 45 | async decrement() { 46 | const value = this.state.count.get(); 47 | if (value === 0) { 48 | return; 49 | } 50 | this.state.count.set(value - 1); 51 | } 52 | } 53 | ``` 54 | 55 | ## Access to the other services 56 | 57 | For exemple we have a todoList form, it should append the form values to a todoList, 58 | then the form resets. 59 | 60 | The `submit` method of `TodoFormService` calls the method `append` of `TodoListService` 61 | 62 | ```ts 63 | type Form = { name: string }; 64 | 65 | type TodoFormState = Form; 66 | export class TodoFormService extends Service { 67 | static initialState: TodoFormState = { name: "" }; 68 | 69 | submit() { 70 | // highlight-next-line 71 | const todoListService = this.services.get("todoList"); 72 | // highlight-next-line 73 | todoListService.append(this.state.get()); 74 | 75 | this.state.name.set(""); 76 | } 77 | } 78 | ``` 79 | 80 | This way each service can have a single responsibility 81 | 82 | ## init() 83 | 84 | You can write an init method that will be executed right after the core is instantiated 85 | It is the perfect place to listen to some parts of the store that have been changed 86 | 87 | ```ts 88 | export class MessageService extends Service { 89 | init() { 90 | this.dependencies.notifications.onReceive(this.onReceive); 91 | } 92 | 93 | onReceive(receivedNotification: string) { 94 | this.store.messages.push(receivedNotification); 95 | } 96 | } 97 | ``` 98 | 99 | ## The dependencies 100 | 101 | If your app is in clean architecture and uses the port-adapter pattern, you can access any of the dependencies in any service method 102 | 103 | ```ts 104 | interface ShoesApiGateway { 105 | get(): Promise; 106 | } 107 | 108 | type Dependencies = { 109 | shoesApi: ShoesApiGateway; 110 | }; 111 | 112 | export const Core = createCortexFactory()(services); 113 | ``` 114 | 115 | ```ts 116 | type State = Shoe[]; 117 | 118 | export class CounterService extends Service { 119 | static initialState: State = []; 120 | 121 | async loadShoes() { 122 | // highlight-next-line 123 | const shoes = await this.dependencies.shoesApi.get(); 124 | this.state.set(shoes); 125 | } 126 | } 127 | ``` 128 | 129 | The dependencies can then be injected in the core 130 | 131 | ```ts 132 | class RealShoesApiAdapter implements ShoesApiGateway { 133 | async get() { 134 | return axios.get("https://my-api/shoes/get"); 135 | } 136 | } 137 | 138 | const core = new Core({ shoesApi: RealShoesApiAdapter }); 139 | ``` 140 | -------------------------------------------------------------------------------- /packages/doc/docs/setup.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | import Tabs from "@theme/Tabs"; 6 | import TabItem from "@theme/TabItem"; 7 | 8 | # Setup 9 | 10 | :::tip What You'll Learn 11 | 12 | - How to setup a Cortex project in few minutes 13 | 14 | ::: 15 | 16 | ## Packages installation 17 | 18 | 19 | 20 | 21 | ```bash 22 | yarn add @azot-dev/cortex @azot-dev/react-cortex @legendapp/state 23 | ``` 24 | 25 | 26 | 27 | 28 | ```bash 29 | npm i @azot-dev/cortex @azot-dev/react-cortex @legendapp/state 30 | ``` 31 | 32 | 33 | 34 | 35 | ```bash 36 | pnpm i @azot-dev/cortex @azot-dev/react-cortex @legendapp/state 37 | ``` 38 | 39 | 40 | 41 | 42 | ## Automatic installation of the template 43 | 44 | In the folder you want to instantiate cortex (inside your React project) 45 | It will install a basic template of the Cortex structure, you can then modify it as you wish 46 | 47 | ```bash 48 | npx @azot-dev/cortex@latest init react 49 | ``` 50 | 51 | The structure installed with the above command line 52 | 53 | ```sh 54 | ├── cortex 55 | │ ├── dependencies 56 | │ │   ├── _dependencies.ts 57 | │ ├── services 58 | │ │   ├── _services.ts 59 | │ │   └── counter.service.ts 60 | │ ├── utils 61 | │ │ ├── service.ts 62 | │ │ ├── hooks.ts 63 | │ │ └── types.ts 64 | │ ├── _core.ts 65 | ``` 66 | 67 | Then wrap your root component with the Cortex provider: 68 | 69 | ```tsx 70 | const App = () => { 71 | return ( 72 | 73 | 74 | 75 | ); 76 | }; 77 | ``` 78 | 79 | ## Manual installation 80 | 81 | If you don't want to use the basic template and want to start the project from scratch, there are few files you will need to create 82 | 83 | ### Service registry 84 | 85 | A simple object where all the services will be registered 86 | 87 | ```ts 88 | import { CounterService } from "./counter.service"; 89 | import { LoginService } from "./login.service"; 90 | 91 | export const services = { 92 | counter: CounterService, 93 | login: LoginService, 94 | }; 95 | ``` 96 | 97 | ### Cortex factory 98 | 99 | ```ts 100 | export const Core = createCortexFactory()(services); 101 | ``` 102 | 103 | You can inject your dependencies in the services if you use clean architecture 104 | 105 | ```ts 106 | export const Core = createCortexFactory()(services); 107 | ``` 108 | 109 | ### Service class 110 | 111 | Each service has to extend from this typed class, it provides a very strong types for the services 112 | 113 | ```ts 114 | export abstract class Service extends BaseService { 115 | constructor(...args: [any, any, any]) { 116 | super(...args); 117 | } 118 | } 119 | ``` 120 | 121 | ### Typed hooks 122 | 123 | ```ts 124 | import { createCortexHooks } from "@azot-dev/react-cortex"; 125 | 126 | export const { useAppSelector, useLazyMethod, useMethod, useService, useStore, useAppService } = createCortexHooks(); 127 | ``` 128 | -------------------------------------------------------------------------------- /packages/doc/docs/store.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Store 6 | 7 | The store is based on [legend app state](https://legendapp.com/open-source/state/) 8 | 9 | The most you have to know is that every node of your store can be modified using `set()` and accessed using `get()`. 10 | 11 | Each service has a local state accessible from itself 12 | 13 | ```ts 14 | type State = { count: number }; 15 | 16 | export class CounterService extends Service { 17 | static initialState: State = { count: 0 }; 18 | 19 | // highlight-start 20 | increment() { 21 | this.state.count.set((count) => count + 1); 22 | } 23 | // highlight-end 24 | } 25 | ``` 26 | 27 | The global store is accessible using the instantiated Cortex object 28 | 29 | ```ts 30 | const core = new Core() 31 | 32 | const count = core.counter.count.get() 33 | ``` 34 | 35 | For further technical information you can refer to the official [documentation](https://legendapp.com/open-source/state/) -------------------------------------------------------------------------------- /packages/doc/docs/testing.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Testing 6 | 7 | No need to test your logic through the UI or using React with libraries like `react-testing-library` or `react-hook-testing-library`. 8 | 9 | You can simply test your logic with Jest, pretty quickly and easily develop some features using test driven development (TDD) 10 | 11 | ```typescript 12 | // counter.spec.ts 13 | 14 | describe('counter', () => { 15 | let core: InstanceType; 16 | let service: InstanceType; 17 | 18 | beforeEach(() => { 19 | core = new Core(); 20 | service = core.getService('counter'); 21 | }); 22 | 23 | 24 | it('should be incremented', () => { 25 | expect(core.store.counter.get()).toBe(0) 26 | 27 | service.increment() 28 | expect(core.store.counter.get()).toBe(1) 29 | 30 | service.increment() 31 | expect(core.store.counter.get()).toBe(2) 32 | }) 33 | 34 | it('should be decremented', () => { 35 | service.setValue(5) 36 | 37 | service.decrement() 38 | expect(core.store.counter.get()).toBe(4) 39 | 40 | service.decrement() 41 | expect(core.store.counter.get()).toBe(3) 42 | }) 43 | 44 | it('should not be decremented at a lower value than 0', () => { 45 | service.setValue(1) 46 | 47 | service.decrement() 48 | expect(core.store.counter.get()).toBe(0) 49 | 50 | service.decrement() 51 | expect(core.store.counter.get()).toBe(0) 52 | }) 53 | }) 54 | ``` 55 | -------------------------------------------------------------------------------- /packages/doc/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | 4 | const { themes } = require("prism-react-renderer"); 5 | 6 | const organizationName = "azot-dev"; 7 | const projectName = "cortex"; 8 | 9 | const url = `https://${organizationName}.github.io`; 10 | const baseUrl = process.env.NETLIFY ? "/" : `/${projectName}/`; 11 | 12 | /** @type {import('@docusaurus/types').Config} */ 13 | const config = { 14 | title: "cortex", 15 | tagline: "The React missing brick for TDD and clean architecture", 16 | favicon: "img/favicon.ico", 17 | 18 | // Set the production url of your site here 19 | url, 20 | // Set the // pathname under which your site is served 21 | // For GitHub pages deployment, it is often '//' 22 | baseUrl, 23 | 24 | // GitHub pages deployment config. 25 | // If you aren't using GitHub pages, you don't need these. 26 | organizationName, // Usually your GitHub org/user name. 27 | projectName, // Usually your repo name. 28 | 29 | onBrokenLinks: "throw", 30 | onBrokenMarkdownLinks: "warn", 31 | 32 | // Even if you don't use internalization, you can use this field to set useful 33 | // metadata like html lang. For example, if your site is Chinese, you may want 34 | // to replace "en" with "zh-Hans". 35 | i18n: { 36 | defaultLocale: "en", 37 | locales: ["en"], 38 | }, 39 | 40 | presets: [ 41 | [ 42 | "classic", 43 | /** @type {import('@docusaurus/preset-classic').Options} */ 44 | ({ 45 | docs: { 46 | sidebarPath: require.resolve("./sidebars.js"), 47 | // Please change this to your repo. 48 | // Remove this to remove the "edit this page" links. 49 | editUrl: `https://github.com/${organizationName}/${projectName}/tree/main/doc/`, 50 | }, 51 | blog: { 52 | showReadingTime: true, 53 | // Please change this to your repo. 54 | // Remove this to remove the "edit this page" links. 55 | editUrl: `https://github.com/${organizationName}/${projectName}/tree/main/doc/`, 56 | }, 57 | theme: { 58 | customCss: require.resolve("./src/css/custom.css"), 59 | }, 60 | }), 61 | ], 62 | ], 63 | 64 | themeConfig: 65 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 66 | ({ 67 | // Replace with your project's social card 68 | image: "img/logo_512.png", 69 | navbar: { 70 | title: "cortex", 71 | logo: { 72 | alt: "My Site Logo", 73 | src: "img/logo_256.png", 74 | }, 75 | items: [ 76 | { 77 | type: "docSidebar", 78 | sidebarId: "tutorialSidebar", 79 | position: "left", 80 | label: "Tutorial", 81 | }, 82 | { 83 | href: `https://github.com/${organizationName}/${projectName}`, 84 | position: "right", 85 | className: "header-github-link", 86 | }, 87 | { 88 | href: `https://www.npmjs.com/package/@${organizationName}/${projectName}`, 89 | position: "right", 90 | className: "header-npm-link", 91 | }, 92 | ], 93 | }, 94 | footer: { 95 | style: "dark", 96 | links: [], 97 | copyright: `Copyright © ${new Date().getFullYear()} cortex documentation`, 98 | }, 99 | prism: { 100 | theme: themes.vsDark, 101 | }, 102 | }), 103 | }; 104 | 105 | module.exports = config; 106 | -------------------------------------------------------------------------------- /packages/doc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "doc", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc", 16 | "check-mdx": "docusaurus-mdx-checker" 17 | }, 18 | "dependencies": { 19 | "@docusaurus/core": "^3.1.1", 20 | "@docusaurus/module-type-aliases": "^3.1.1", 21 | "@docusaurus/preset-classic": "^3.1.1", 22 | "@docusaurus/types": "^3.1.1", 23 | "@mdx-js/react": "^3.0.1", 24 | "clsx": "^2.1.1", 25 | "prism-react-renderer": "2.4.1", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0" 28 | }, 29 | "devDependencies": { 30 | "@docusaurus/module-type-aliases": "^3.1.1", 31 | "@docusaurus/types": "^3.1.1", 32 | "@tsconfig/docusaurus": "^2.0.3", 33 | "docusaurus-mdx-checker": "^3.0.0", 34 | "npx": "^10.2.2", 35 | "typescript": "^5.4.5" 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.5%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | }, 49 | "engines": { 50 | "node": ">=16.14" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/doc/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | 'intro', 23 | 'hello', 24 | { 25 | type: 'category', 26 | label: 'Tutorial', 27 | items: ['tutorial-basics/create-a-document'], 28 | }, 29 | ], 30 | */ 31 | }; 32 | 33 | module.exports = sidebars; 34 | -------------------------------------------------------------------------------- /packages/doc/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | type FeatureItem = { 6 | title: string; 7 | Svg: React.ComponentType>; 8 | description: JSX.Element; 9 | }; 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: 'The Real TDD and clean architecture in front-end', 14 | Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, 15 | description: ( 16 | <> 17 | Separate your data from your UI, so you can test directly the data 18 | without passing by the UI, it gives you the possibility to write your 19 | tests before the code and use TDD 20 | 21 | ), 22 | }, 23 | { 24 | title: 'Port / Adapters pattern', 25 | Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, 26 | description: ( 27 | <> 28 | If you want to you can inject dependencies you can change then, so you 29 | can easily mock data and create an adapter for each device, so your code 30 | is device agnostic 31 | 32 | ), 33 | }, 34 | { 35 | title: 'Framework agnostic', 36 | Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, 37 | description: ( 38 | <> 39 | Make your code scalable and cross-plateform, easily use React or React 40 | Native, or any Javascript framework 41 | 42 | ), 43 | }, 44 | ]; 45 | 46 | function Feature({ title, Svg, description }: FeatureItem) { 47 | return ( 48 |
49 |
50 | 51 |
52 |
53 |

{title}

54 |

{description}

55 |
56 |
57 | ); 58 | } 59 | 60 | export default function HomepageFeatures(): JSX.Element { 61 | return ( 62 |
63 |
64 |
65 | {FeatureList.map((props, idx) => ( 66 | 67 | ))} 68 |
69 |
70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /packages/doc/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | -------------------------------------------------------------------------------- /packages/doc/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #0588ED; 10 | --ifm-color-primary-dark: #0588ED; 11 | --ifm-color-primary-darker: #0588ED; 12 | --ifm-color-primary-darkest: #0588ED; 13 | --ifm-color-primary-light: #0588ED; 14 | --ifm-color-primary-lighter: #0588ED; 15 | --ifm-color-primary-lightest: #0588ED; 16 | --code-color: var(--ifm-color-primary); 17 | } 18 | 19 | code { 20 | color: var(--code-color); 21 | } 22 | 23 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 24 | html[data-theme='dark'] { 25 | --ifm-color-primary: #0588ED; 26 | --ifm-color-primary-dark: #0588ED; 27 | --ifm-color-primary-darker: #0588ED; 28 | --ifm-color-primary-darkest: #0588ED; 29 | --ifm-color-primary-light: #0588ED; 30 | --ifm-color-primary-lighter: #0588ED; 31 | --ifm-color-primary-lightest: #0588ED; 32 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 33 | 34 | --ifm-color-primary-dark: #111827; 35 | --ifm-background-color: #111827; 36 | --ifm-background-surface-color: #111827; 37 | } 38 | 39 | /* */ 40 | 41 | .header-npm-link:hover { 42 | opacity: 0.6; 43 | } 44 | 45 | .header-npm-link:before { 46 | content: ''; 47 | width: 40px; 48 | height: 40px; 49 | margin-right: 15px; 50 | display: flex; 51 | background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%20576%20512%22%3E%3Cpath%20d%3D%22M288%20288h-32v-64h32v64zm288-128v192H288v32H160v-32H0V160h576zm-416%2032H32v128h64v-96h32v96h32V192zm160%200H192v160h64v-32h64V192zm224%200H352v128h64v-96h32v96h32v-96h32v96h32V192z%22/%3E%3C/svg%3E") no-repeat; 52 | } 53 | 54 | html[data-theme='dark'] .header-npm-link:before { 55 | background: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20viewBox%3D%220%200%20576%20512%22%3E%3Cpath%20fill%3D%22white%22%20d%3D%22M288%20288h-32v-64h32v64zm288-128v192H288v32H160v-32H0V160h576zm-416%2032H32v128h64v-96h32v96h32V192zm160%200H192v160h64v-32h64V192zm224%200H352v128h64v-96h32v96h32v-96h32v96h32V192z%22/%3E%3C/svg%3E") no-repeat; 56 | } 57 | 58 | 59 | 60 | .header-github-link:hover { 61 | opacity: 0.6; 62 | } 63 | 64 | .header-github-link:before { 65 | content: ''; 66 | width: 24px; 67 | height: 24px; 68 | margin-right: 15px; 69 | display: flex; 70 | background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") 71 | no-repeat; 72 | } 73 | 74 | html[data-theme='dark'] .header-github-link:before { 75 | background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") 76 | no-repeat; 77 | } 78 | -------------------------------------------------------------------------------- /packages/doc/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /packages/doc/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | import Link from "@docusaurus/Link"; 4 | import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; 5 | import Layout from "@theme/Layout"; 6 | import HomepageFeatures from "@site/src/components/HomepageFeatures"; 7 | 8 | import styles from "./index.module.css"; 9 | 10 | function HomepageHeader() { 11 | const { siteConfig } = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 | logo 16 |

{siteConfig.title}

17 |

{siteConfig.tagline}

18 |
19 | 20 | Get Started 21 | 22 |
23 |
24 |
25 | ); 26 | } 27 | 28 | export default function Home(): JSX.Element { 29 | const { siteConfig } = useDocusaurusContext(); 30 | return ( 31 | 32 | 33 |
34 | 35 |
36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /packages/doc/src/pages/markdown-page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Markdown page example 3 | --- 4 | 5 | # Markdown page example 6 | 7 | You don't need React to write simple standalone pages. 8 | -------------------------------------------------------------------------------- /packages/doc/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/static/.nojekyll -------------------------------------------------------------------------------- /packages/doc/static/img/adapters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/static/img/adapters.png -------------------------------------------------------------------------------- /packages/doc/static/img/cortex-scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/static/img/cortex-scheme.png -------------------------------------------------------------------------------- /packages/doc/static/img/docusaurus-social-card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/static/img/docusaurus-social-card.jpg -------------------------------------------------------------------------------- /packages/doc/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/static/img/docusaurus.png -------------------------------------------------------------------------------- /packages/doc/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/static/img/favicon.ico -------------------------------------------------------------------------------- /packages/doc/static/img/github-icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 6 | -------------------------------------------------------------------------------- /packages/doc/static/img/logo-heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/static/img/logo-heart.png -------------------------------------------------------------------------------- /packages/doc/static/img/logo_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/static/img/logo_256.png -------------------------------------------------------------------------------- /packages/doc/static/img/logo_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/doc/static/img/logo_512.png -------------------------------------------------------------------------------- /packages/doc/static/img/npm-icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 6 | -------------------------------------------------------------------------------- /packages/doc/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/example/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/example/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /packages/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.17.0", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.5.2", 10 | "@types/node": "^16.18.97", 11 | "@types/react": "^18.3.2", 12 | "@types/react-dom": "^18.3.0", 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1", 15 | "react-scripts": "5.0.1", 16 | "typescript": "^4.9.5", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/example/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/example/public/favicon.ico -------------------------------------------------------------------------------- /packages/example/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/example/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/example/public/logo192.png -------------------------------------------------------------------------------- /packages/example/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azot-dev/cortex/81e30500dc489ca24db6c62c7d6b45c8ca99b9e1/packages/example/public/logo512.png -------------------------------------------------------------------------------- /packages/example/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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/example/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/example/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/example/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/example/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import logo from "./logo.svg"; 3 | import "./App.css"; 4 | import { CortexProvider } from "@azot-dev/react-cortex/dist"; 5 | import { Core } from "./cortex/_core"; 6 | import { useService } from "./cortex/utils/hooks"; 7 | 8 | const core = new Core(); 9 | function App() { 10 | return ( 11 |
12 |
13 | logo 14 | 15 | 16 |

17 | Edit src/App.tsx and save to reload. 18 |

19 | 20 | Learn React 21 | 22 |
23 |
24 | ); 25 | } 26 | 27 | const AppWrapper = () => { 28 | return ( 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default AppWrapper; 36 | -------------------------------------------------------------------------------- /packages/example/src/cortex/_core.ts: -------------------------------------------------------------------------------- 1 | import { createCortexFactory, createDebuggerService } from "@azot-dev/cortex/dist"; 2 | import { services } from "./services/_services"; 3 | import { Dependencies } from "./dependencies/_dependencies"; 4 | 5 | export const Core = createCortexFactory()(services, { debugger: createDebuggerService({}) }); 6 | -------------------------------------------------------------------------------- /packages/example/src/cortex/dependencies/_dependencies.ts: -------------------------------------------------------------------------------- 1 | export interface Dependencies {} 2 | -------------------------------------------------------------------------------- /packages/example/src/cortex/services/_services.ts: -------------------------------------------------------------------------------- 1 | import { CounterService } from './counter.service'; 2 | 3 | export const services = { 4 | counter: CounterService, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/example/src/cortex/services/counter.service.ts: -------------------------------------------------------------------------------- 1 | import { Service } from '../utils/service'; 2 | 3 | type State = { 4 | count: number; 5 | }; 6 | 7 | export class CounterService extends Service { 8 | static initialState: State = { 9 | count: 0, 10 | }; 11 | 12 | increment() { 13 | this.state.count.set((count) => count + 1); 14 | } 15 | 16 | decrement() { 17 | this.state.count.set((count) => count - 1); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/example/src/cortex/utils/hooks.ts: -------------------------------------------------------------------------------- 1 | // utils/hooks.ts 2 | 3 | import { createCortexHooks } from '@azot-dev/react-cortex'; 4 | import { Services } from './types'; 5 | 6 | export const { 7 | useAppSelector, 8 | useLazyMethod, 9 | useMethod, 10 | useService, 11 | useStore, 12 | } = createCortexHooks(); 13 | -------------------------------------------------------------------------------- /packages/example/src/cortex/utils/service.ts: -------------------------------------------------------------------------------- 1 | // utils/service.ts 2 | 3 | import { BaseService } from "@azot-dev/cortex"; 4 | import { Dependencies } from "../dependencies/_dependencies"; 5 | import { services } from "../services/_services"; 6 | 7 | export abstract class Service extends BaseService { 8 | constructor(...args: [any, any, any, any]) { 9 | super(...args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/example/src/cortex/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { services } from '../services/_services'; 2 | 3 | export type Services = typeof services; 4 | -------------------------------------------------------------------------------- /packages/example/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /packages/example/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById('root') as HTMLElement 9 | ); 10 | root.render( 11 | 12 | 13 | 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /packages/example/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/example/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/example/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /packages/example/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /packages/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/react/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist 3 | -------------------------------------------------------------------------------- /packages/react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.2.10](https://github.com/azot-dev/cortex/compare/v1.2.9...v1.2.10) (2023-10-08) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * Update peerDependency to match local cortex version ([31a8174](https://github.com/azot-dev/cortex/commit/31a8174433f764103890b78ed0f6a014ac411ec8)) 7 | 8 | ## [1.2.2](https://github.com/azot-dev/cortex/compare/v1.2.1...v1.2.2) (2023-10-08) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * attempt to deploy from ci ([d7d3884](https://github.com/azot-dev/cortex/commit/d7d3884e99f2a71b9d3bd4c14b73b496b496960f)) 14 | * attempt to deploy from ci ([a34d674](https://github.com/azot-dev/cortex/commit/a34d6743daf89220e70d7e7c1c61d944f4c3dbf2)) 15 | * circular dependency in package.json ([83263d3](https://github.com/azot-dev/cortex/commit/83263d371b5f41fbbded3d73155781fcf0c94be4)) 16 | * react package.json ([c884677](https://github.com/azot-dev/cortex/commit/c8846773e526e1323a185634e6f6fc8476f9f39a)) 17 | 18 | ## [1.2.1](https://github.com/azot-dev/cortex/compare/v1.2.0...v1.2.1) (2023-10-07) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * react package.json ([69b3a55](https://github.com/azot-dev/cortex/commit/69b3a5511ecb77143f06fa1f9031aeeb3dbc2f78)) 24 | 25 | # [1.2.0](https://github.com/azot-dev/cortex/compare/v1.1.3...v1.2.0) (2023-10-07) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * separate packages react and core (update peer dependency) ([b47f884](https://github.com/azot-dev/cortex/commit/b47f88492d15d3e936ef5b1276bc89feb201866c)) 31 | 32 | 33 | ### Features 34 | 35 | * add initialization script ([9f3372c](https://github.com/azot-dev/cortex/commit/9f3372c183597099c818b8cb4b42015dfbb7f300)) 36 | 37 | ## [1.1.1](https://github.com/azot-dev/cortex/compare/v1.1.0...v1.1.1) (2023-10-06) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * separate packages react and core (update peer dependency) ([39adbbc](https://github.com/azot-dev/cortex/commit/39adbbcbf12fc4c6e4bed8284c9877542a1d700c)) 43 | -------------------------------------------------------------------------------- /packages/react/__tests__/provider.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import { CortexProvider, useAppContext } from '../src/provider'; 3 | import React from 'react'; 4 | 5 | describe('provider', () => { 6 | it('should provide the coreInstance to the context', () => { 7 | const dummyCoreInstance = { store: {}, getService: jest.fn() }; 8 | 9 | const TestComponent = () => { 10 | const context = useAppContext(); 11 | expect(context).toEqual(dummyCoreInstance); 12 | return null; 13 | }; 14 | 15 | render( 16 | 17 | 18 | 19 | ); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/react/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | verbose: true, 4 | testEnvironment: 'jsdom', 5 | moduleFileExtensions: ['ts', 'tsx', 'js'], 6 | transform: { 7 | '^.+\\.(ts|tsx)$': [ 8 | 'ts-jest', 9 | { 10 | tsconfig: './tsconfig.json', 11 | }, 12 | ], 13 | }, 14 | testMatch: ['**/*.(test|spec).(ts|tsx)'], 15 | }; 16 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@azot-dev/react-cortex", 3 | "version": "1.24.2", 4 | "license": "MIT", 5 | "description": "A React Provider to use Cortex", 6 | "main": "dist/index.js", 7 | "types": "dist/index.d.ts", 8 | "homepage": "https://azot-dev.github.io/cortex/", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/azot-dev/cortex/react" 12 | }, 13 | "scripts": { 14 | "test": "jest --passWithNoTests", 15 | "build": "tsc" 16 | }, 17 | "peerDependencies": { 18 | "@azot-dev/cortex": "1.24.2", 19 | "react": ">=16.8.0" 20 | }, 21 | "devDependencies": { 22 | "@azot-dev/cortex": "*", 23 | "@legendapp/state": "^2.1.3", 24 | "@semantic-release/changelog": "^6.0.3", 25 | "@semantic-release/git": "^10.0.1", 26 | "@semantic-release/github": "^10.1.7", 27 | "@testing-library/react": "^15.0.7", 28 | "@testing-library/react-hooks": "^8.0.1", 29 | "@types/jest": "^29.5.5", 30 | "@types/react-dom": "^18.2.14", 31 | "jest": "^29.7.0", 32 | "jest-environment-jsdom": "^29.7.0", 33 | "react": "^18.2.0", 34 | "react-dom": "^18.2.0", 35 | "react-test-renderer": "^18.2.0", 36 | "semantic-release": "^24.1.0", 37 | "ts-jest": "^29.1.1", 38 | "typescript": "^5.4.5" 39 | }, 40 | "dependencies": { 41 | "react-test-renderer": "^18.2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/react/release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | repositoryUrl: 'https://github.com/azot-dev/cortex', 3 | branches: ['main'], 4 | plugins: [ 5 | '@semantic-release/commit-analyzer', 6 | '@semantic-release/release-notes-generator', 7 | [ 8 | '@semantic-release/changelog', 9 | { 10 | changelogFile: 'CHANGELOG.md', 11 | }, 12 | ], 13 | [ 14 | '@semantic-release/npm', 15 | { 16 | npmPublish: true, 17 | }, 18 | ], 19 | [ 20 | '@semantic-release/github', 21 | { 22 | assets: ['dist/**/*', 'build/**/*'], 23 | labels: ['automated-release'], 24 | tagFormat: 'react-v${version}', 25 | }, 26 | ], 27 | [ 28 | '@semantic-release/git', 29 | { 30 | assets: ['CHANGELOG.md', 'package.json'], 31 | message: 32 | 'chore(release): react-v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}', 33 | tagFormat: 'react-v${version}', 34 | }, 35 | ], 36 | ], 37 | }; 38 | -------------------------------------------------------------------------------- /packages/react/src/index.ts: -------------------------------------------------------------------------------- 1 | import { CortexProvider, createCortexHooks } from './provider'; 2 | 3 | export { CortexProvider, createCortexHooks }; 4 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "target": "es2017", 5 | "skipLibCheck": true, 6 | "module": "CommonJS", 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "strict": true, 10 | "esModuleInterop": true, 11 | "experimentalDecorators": true, 12 | "emitDecoratorMetadata": true, 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "declaration": true, 18 | "baseUrl": ".", 19 | }, 20 | "include": [ 21 | "src/**/*.ts", 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } --------------------------------------------------------------------------------