├── .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 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jsLinters/eslint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/prettier.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 |
8 | {children}
9 |
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 |
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 |
8 | {children}
9 |
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 |
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 | selectBreed(event.target.value)} className="select">
20 | {breeds.map((breedName) => (
21 |
22 | {breedName}
23 |
24 | ))}
25 |
26 |
27 |
28 |
29 |
30 | generate random image
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 |
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 |
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 | alert('button clicked!')}>Click me!
16 | ```
17 |
18 | alert('button clicked!')}>Click me!
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 | 
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 login
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 login
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 | counterService.increment()}>increment
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 | increment counter value
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 |
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 |
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 | You need to enable JavaScript to run this app.
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 |
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 | }
--------------------------------------------------------------------------------