├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md └── workflows │ └── lib-test.yml ├── .gitignore ├── .prettierignore ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── logo.svg ├── examples ├── cra │ ├── .gitignore │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── DomReady.todo-example.jsx │ │ ├── Main.tsx │ │ ├── components │ │ │ ├── Controls.module.css │ │ │ ├── Controls.tsx │ │ │ ├── EnsureKitchen.tsx │ │ │ ├── ErrControls.tsx │ │ │ ├── MainLayoutControl.module.css │ │ │ ├── MainLayoutControl.tsx │ │ │ ├── PizzaPlace.module.css │ │ │ └── PizzaPlace.tsx │ │ ├── containers │ │ │ ├── _AppRoot.tsx │ │ │ ├── _container.hooks.ts │ │ │ ├── _root.store.ts │ │ │ ├── _utils.ts │ │ │ ├── container.a.ts │ │ │ ├── container.auth.ts │ │ │ ├── container.b.ts │ │ │ ├── container.config.ts │ │ │ ├── container.fat-lib1.ts │ │ │ ├── container.fat-lib2.ts │ │ │ ├── container.kitchein-manipulator.ts │ │ │ ├── container.kitchen.ts │ │ │ ├── container.pizza-place.ts │ │ │ └── my-jest.ts │ │ ├── fat-libs │ │ │ ├── fat-lib1.ts │ │ │ └── fat-lib2.ts │ │ ├── index.css │ │ ├── index.tsx │ │ ├── react-app-env.d.ts │ │ ├── reportWebVitals.ts │ │ ├── services │ │ │ ├── ingredients-manager.ts │ │ │ └── url-param.ts │ │ ├── setupTests.ts │ │ └── stores │ │ │ ├── _controllers │ │ │ └── controller.kitchen.ts │ │ │ ├── store.a.ts │ │ │ ├── store.auth.ts │ │ │ ├── store.authorization.ts │ │ │ ├── store.b.ts │ │ │ ├── store.ingrediets.ts │ │ │ ├── store.kitchen.ts │ │ │ ├── store.oven.ts │ │ │ ├── store.pizza-place.ts │ │ │ └── store.pizza.ts │ ├── tsconfig.json │ └── yarn.lock ├── node-cjs │ ├── .gitignore │ ├── node-1.js │ ├── node-2.js │ └── package.json ├── node-cli │ ├── .gitignore │ ├── package.json │ ├── src │ │ └── server.ts │ └── tsconfig.json ├── node-js │ ├── .gitignore │ ├── package.json │ └── src │ │ └── server.ts ├── node-mjs │ ├── node-1.mjs │ └── package.json └── vite-app │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── public │ └── vite.svg │ ├── src │ ├── App.css │ ├── App.tsx │ ├── _bl.ts │ ├── assets │ │ └── react.svg │ ├── hooks.ts │ ├── index.css │ ├── main.tsx │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── iti-react ├── .gitignore ├── README.md ├── jest.config.cjs ├── notes-and-ideas.md ├── package.json ├── src │ ├── API.md │ ├── _utils.ts │ ├── index.ts │ └── react │ │ ├── library.generate-ensure-hooks.ts │ │ ├── library.hook-generator.ts │ │ └── library.hooks.ts ├── tests │ ├── _utils.ts │ ├── mocks │ │ ├── _mock-app-components.tsx │ │ ├── _mock-app-container.ts │ │ ├── _mock-app-hooks.ts │ │ ├── container.a.ts │ │ ├── container.b.ts │ │ ├── container.c.ts │ │ ├── store.a.ts │ │ ├── store.b.ts │ │ └── store.c.ts │ ├── react-hooks-check-types.tsd-only.ts │ ├── types-for-tests.d.ts │ └── z.container-hook.spec.tsx.bkp ├── tsconfig.json └── tsd_project │ ├── dummy.d.ts │ └── package.json ├── iti ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── notes-and-ideas.md ├── package.json ├── src │ ├── _utils.ts │ ├── errors.ts │ ├── index.ts │ ├── iti.ts │ └── nanoevents.ts ├── stryker.conf.json ├── tests │ ├── __snapshots__ │ │ ├── container-getter.vi.spec.ts.snap │ │ ├── container-set.vi.spec.ts.snap │ │ └── getter.vi.spec.ts.snap │ ├── _utils.ts │ ├── container-get-values.vi.spec.ts │ ├── container-getter.vi.spec.ts │ ├── container-set.vi.spec.ts │ ├── dispose-graph.ts.api.vi.spec.ts │ ├── dispose.ts.api.vi.spec.ts │ ├── exotic.vi.spec.ts │ ├── getter.vi.spec.ts │ ├── mock-graph.ts │ ├── mocks │ │ ├── _mock-app-container.ts │ │ ├── container.a.ts │ │ ├── container.b.ts │ │ ├── container.c.ts │ │ ├── store.a.ts │ │ ├── store.b.ts │ │ └── store.c.ts │ ├── tsd.container-set-types.vi.spec.ts │ ├── tsd.getter.tsd-only.vi.spec.ts │ ├── types-for-tests.d.ts │ └── update.api.vi.spec.ts ├── tsconfig.json ├── tsd_project │ ├── dummy.d.ts │ └── package.json └── vitest.config.ts ├── package.json ├── turbo.json ├── website ├── .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 │ ├── 1.intro.md │ ├── 10.faq.mdx │ ├── 2.quick-start.mdx │ ├── 2.when-not-to-use-iti.md │ ├── 4.usage.md │ ├── 5.patterns-and-tips.md │ ├── 6.getting-started.md.del │ ├── 6_async_start-PART-2 │ │ ├── 0_by_hand.ts │ │ ├── 1_ts-inject.ts │ │ ├── 2_tsyringe.ts │ │ ├── 3_inversify.ts │ │ ├── main-example.ts │ │ └── main │ │ │ ├── app.ts │ │ │ ├── business-logic.ts │ │ │ ├── loggers.ts │ │ │ ├── manual-di.ts │ │ │ ├── with.tsx │ │ │ └── without.ts │ ├── 7.5.playground.mdx │ ├── 7.alternatives.md │ ├── 8.benefits-of-iti.md │ ├── 9.api.mdx │ ├── 9.api │ │ ├── addDisposer.ts │ │ └── disposeAll.ts │ ├── assets │ │ ├── stackblitz.png │ │ └── ts │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ └── 4.png │ ├── async-di │ │ ├── 1.manual-di.mdx │ │ ├── 2.inversify.mdx.hidden │ │ ├── 3.syringe.mdx.hidden │ │ ├── 4.typed-inject.mdx.hi │ │ ├── 5.iti.mdx │ │ └── _category_.json │ ├── basic-di │ │ ├── 1.manual-di.mdx │ │ ├── 2.inversify.mdx.hidden │ │ ├── 3.syringe.mdx.hidden │ │ ├── 4.typed-inject.mdx.hidden │ │ ├── 5.iti.mdx │ │ ├── 6.iti-vs-pure.mdx │ │ └── _category_.json │ ├── examples │ │ ├── circular-dependency.ts │ │ └── oven-business-logic.ts │ └── with-react │ │ ├── _category_.json │ │ ├── basic.md │ │ ├── configuration.md │ │ └── react-full.md ├── docusaurus.config.js ├── package.json ├── sidebars.js ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.js │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ ├── index.module.css │ │ └── markdown-page.md ├── static │ ├── .nojekyll │ ├── google8b69c6f2456eb7a7.html │ └── img │ │ ├── docusaurus.png │ │ ├── favicon.ico │ │ ├── iti │ │ ├── favicon.ico │ │ ├── logo.svg │ │ ├── rastr │ │ │ ├── icon-180.png │ │ │ ├── icon-192.png │ │ │ ├── icon-512.png │ │ │ └── logo-2048.png │ │ └── raw.svg │ │ ├── logo.svg │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg └── yarn.lock └── yarn.lock /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | Can you reproduce it on an external resource, like Stackblitz? 14 | https://stackblitz.com/github/molszanski/iti-playground/tree/main?file=src%2F_0.business-logic.ts&file=src%2FApp.tsx 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **What would be an ideal API for your use case?** 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Describe your changes 2 | 3 | ## Checklist before requesting a review 4 | 5 | - [ ] I have performed a self-review of my code 6 | - [ ] I did run tests on the whole monorepo 7 | -------------------------------------------------------------------------------- /.github/workflows/lib-test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - beta 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | env: 14 | STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Use Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: "22" 21 | 22 | ## Cache not important 23 | - name: Get yarn cache directory path 24 | id: yarn-cache-dir-path 25 | run: echo "::set-output name=dir::$(yarn cache dir)" 26 | - uses: actions/cache@v3 27 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 28 | with: 29 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 30 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 31 | restore-keys: | 32 | ${{ runner.os }}-yarn- 33 | 34 | - name: Install Deps 35 | run: yarn install 36 | 37 | - name: Check types of a core package 38 | run: cd iti && yarn tsd 39 | 40 | - name: Run tests of a core package 41 | run: cd iti && yarn test 42 | 43 | - name: Check if a core package builds 44 | run: cd iti && yarn build 45 | 46 | - name: Check types of a react package 47 | run: cd iti-react && yarn tsd 48 | 49 | - name: Check if a react package builds 50 | run: cd iti-react && yarn build 51 | 52 | - name: Add Stryker Report 53 | run: | 54 | cd iti && yarn run stryker:run 55 | 56 | # cd reports/mutation/ && curl -X PUT \ 57 | # https://dashboard.stryker-mutator.io/api/reports/github.com/molszanski/iti/beta \ 58 | # -H 'Content-Type: application/json' \ 59 | # -H 'Host: dashboard.stryker-mutator.io' \ 60 | # -H "X-Api-Key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}" \ 61 | # -d @mutation.json 62 | 63 | env: 64 | STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/** 3 | dist/** 4 | .next/** 5 | .turbo 6 | 7 | yarn-error.log -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | mock-graph.ts -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Ecommerce", 4 | "fatlib" 5 | ], 6 | "cSpell.enableFiletypes": [ 7 | "astro", 8 | "mdx" 9 | ] 10 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This document is intended for developers interest in making contributions to Iti. 4 | 5 | In the future, it will be a guide for new developers. 6 | 7 | For now, the best contribution would be create an issue or PR 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nick Olszanski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ./iti/README.md -------------------------------------------------------------------------------- /docs/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/docs/1.png -------------------------------------------------------------------------------- /docs/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/docs/2.png -------------------------------------------------------------------------------- /docs/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/docs/3.png -------------------------------------------------------------------------------- /docs/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/docs/4.png -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/cra/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules -------------------------------------------------------------------------------- /examples/cra/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti-cra-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@types/jest": "^29.5.14", 7 | "@types/node": "^18.0.6", 8 | "@types/react": "^18.0.15", 9 | "@types/react-dom": "^18.0.6", 10 | "classnames": "^2.3.1", 11 | "debug": "^4.3.4", 12 | "iti": "0.7.0", 13 | "iti-react": "0.7.0", 14 | "lodash": "^4.17.21", 15 | "mobx": "^6.6.1", 16 | "mobx-react": "^7.5.2", 17 | "mobx-react-lite": "^3.4.0", 18 | "react": "^18.2.0", 19 | "react-dom": "^18.2.0", 20 | "react-scripts": "5.0.1", 21 | "ts-node": "^10.7.0", 22 | "typescript": "^4.7.4", 23 | "web-vitals": "^2.1.0" 24 | }, 25 | "scripts": { 26 | "start": "react-scripts start", 27 | "_build": "react-scripts build", 28 | "_test": "react-scripts test", 29 | "eject": "react-scripts eject" 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 | "license": "MIT", 44 | "authors": [ 45 | "Nick Olszanski " 46 | ], 47 | "prettier": { 48 | "semi": false, 49 | "singleQuote": false, 50 | "arrowParens": "always", 51 | "trailingComma": "all" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/cra/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/examples/cra/public/favicon.ico -------------------------------------------------------------------------------- /examples/cra/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/cra/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/examples/cra/public/logo192.png -------------------------------------------------------------------------------- /examples/cra/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/examples/cra/public/logo512.png -------------------------------------------------------------------------------- /examples/cra/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 | -------------------------------------------------------------------------------- /examples/cra/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/cra/src/App.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | } 6 | 7 | .App { 8 | text-align: center; 9 | } 10 | .App code { 11 | background: #fff3; 12 | padding: 4px 8px; 13 | border-radius: 4px; 14 | } 15 | .App p { 16 | margin: 0.4rem; 17 | } 18 | 19 | .App-logo { 20 | height: 40vmin; 21 | pointer-events: none; 22 | } 23 | 24 | @media (prefers-reduced-motion: no-preference) { 25 | .App-logo { 26 | animation: App-logo-spin infinite 20s linear; 27 | } 28 | } 29 | 30 | .App-header { 31 | background-color: #282c34; 32 | min-height: 100vh; 33 | display: flex; 34 | flex-direction: column; 35 | align-items: center; 36 | justify-content: center; 37 | font-size: calc(10px + 2vmin); 38 | color: white; 39 | } 40 | 41 | .App-link { 42 | color: #61dafb; 43 | } 44 | 45 | @keyframes App-logo-spin { 46 | from { 47 | transform: rotate(0deg); 48 | } 49 | to { 50 | transform: rotate(360deg); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/cra/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from "react" 2 | import { configure } from "mobx" 3 | import { MyRootCont } from "./containers/_container.hooks" 4 | import { getMainPizzaAppContainer } from "./containers/_root.store" 5 | import { Main } from "./Main" 6 | import "./App.css" 7 | 8 | // don't allow state modifications outside actions 9 | configure({ enforceActions: "always" }) 10 | 11 | interface AppProps {} 12 | 13 | function App({}: AppProps) { 14 | const store = useMemo(() => getMainPizzaAppContainer(), []) 15 | return ( 16 |
17 | 18 |
19 | 20 |
21 | ) 22 | } 23 | 24 | export default App 25 | -------------------------------------------------------------------------------- /examples/cra/src/DomReady.todo-example.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | 3 | // async flow 4 | class domController { 5 | constructor() { 6 | this._domReady = new Promise((resolve, reject) => { 7 | this.cb = resolve 8 | }) 9 | } 10 | 11 | markDomAsDone() { 12 | this.cb() 13 | } 14 | 15 | domIsReady() { 16 | return this._domReady 17 | } 18 | } 19 | 20 | export async function provideProductDriverContainer(domController, a) { 21 | await domController.domIsReady() 22 | 23 | const pd = new PD() 24 | await pd.initRenderer() 25 | 26 | await wait(200) 27 | const b2 = new B2(auth.auth, a.a1) 28 | 29 | return { 30 | b1, 31 | b2, 32 | } 33 | } 34 | 35 | export const Main = () => { 36 | const { domController } = useStufContainer() 37 | useEffect(() => { 38 | setTimeout(() => { 39 | domController.markDomAsDone() 40 | }, 500) 41 | }, []) 42 | return ( 43 |
44 | Main Component: 45 |
46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /examples/cra/src/Main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { useContainer } from "./containers/_container.hooks" 3 | import { MainLayoutControl } from "./components/MainLayoutControl" 4 | 5 | export const Main = () => { 6 | return ( 7 |
8 | Main Component: 9 |
10 | ) 11 | } 12 | 13 | export const Profile = () => { 14 | const [a] = useContainer().aCont 15 | if (!a) return null 16 | const { a1, a2 } = a 17 | 18 | return ( 19 | <> 20 | {a1.getName()} 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /examples/cra/src/components/Controls.module.css: -------------------------------------------------------------------------------- 1 | .controlsSections { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | justify-content: space-around; 6 | } 7 | 8 | .controlsSections > * + * { 9 | border-top: 1px solid lightgray; 10 | padding-top: 16px; 11 | } 12 | -------------------------------------------------------------------------------- /examples/cra/src/components/EnsureKitchen.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react" 2 | import { generateEnsureContainerSet } from "iti-react" 3 | import { useContainerSet } from "../containers/_container.hooks" 4 | 5 | const x = generateEnsureContainerSet(() => 6 | useContainerSet(["kitchen", "pizzaContainer", "auth"]), 7 | ) 8 | export const EnsureKitchenProvider = x.EnsureWrapper 9 | export const useNewKitchenContext = x.contextHook 10 | -------------------------------------------------------------------------------- /examples/cra/src/components/ErrControls.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react" 2 | import { observer } from "mobx-react-lite" 3 | import { useContainer, useContainerSet } from "../containers/_container.hooks" 4 | import { EnsureKitchenProvider, useNewKitchenContext } from "./EnsureKitchen" 5 | 6 | export const ErrControls = () => { 7 | return ( 8 |
9 | new Err Controls: 10 | {/* 11 | 12 | */} 13 | 14 |
15 | ) 16 | } 17 | 18 | const Simple = () => { 19 | const [a, err] = useContainer().aCont 20 | if (!a) return <>A is loading 21 | return
Sync Err: {a.a1.getName()}
22 | } 23 | 24 | const SimpleSyncErr = () => { 25 | const [a, err] = useContainer().errSync 26 | if (!err) { 27 | return null 28 | } 29 | 30 | return
Sync Err1: {err.message}
31 | } 32 | 33 | const SimpleAsyncErr = () => { 34 | const [a, err] = useContainer().errAsync 35 | if (!err) { 36 | return null 37 | } 38 | 39 | return
Async Sync Err2: {err.message}
40 | } 41 | 42 | const NestedErr = () => { 43 | const [a, err] = useContainer().errNested 44 | console.log("~~~ err", err) 45 | if (!a) { 46 | return null 47 | } 48 | 49 | console.log(a) 50 | 51 | return
Async Nested Sync Err2:
52 | } 53 | -------------------------------------------------------------------------------- /examples/cra/src/components/MainLayoutControl.module.css: -------------------------------------------------------------------------------- 1 | .controls { 2 | display: grid; 3 | grid-template-columns: 3fr 1fr; 4 | grid-template-rows: 3fr 1fr; 5 | grid-column-gap: 0px; 6 | grid-row-gap: 0px; 7 | } 8 | 9 | .main { 10 | grid-area: 1 / 1 / 2 / 2; 11 | } 12 | .aside { 13 | grid-area: 1 / 2 / 2 / 3; 14 | } 15 | .bottom { 16 | grid-area: 2 / 1 / 3 / 3; 17 | } 18 | -------------------------------------------------------------------------------- /examples/cra/src/components/MainLayoutControl.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import s from "./MainLayoutControl.module.css" 3 | import { PizzaPlace } from "./PizzaPlace" 4 | import { Controls } from "./Controls" 5 | 6 | export const MainLayoutControl = () => { 7 | return ( 8 | <> 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 |

Pizza Place1

18 |
19 |
20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /examples/cra/src/components/PizzaPlace.module.css: -------------------------------------------------------------------------------- 1 | .pizzaPlace { 2 | display: grid; 3 | } 4 | 5 | .kitchenData { 6 | display: flex; 7 | flex-direction: row; 8 | width: 100%; 9 | justify-content: space-around; 10 | } 11 | 12 | /* .bricks { 13 | background-color: silver; 14 | background-image: linear-gradient(335deg, #b00 23px, transparent 23px), 15 | linear-gradient(155deg, #d00 23px, transparent 23px), 16 | linear-gradient(335deg, #b00 23px, transparent 23px), 17 | linear-gradient(155deg, #d00 23px, transparent 23px); 18 | background-size: 58px 58px; 19 | background-position: 0px 2px, 4px 35px, 29px 31px, 34px 6px; 20 | } */ 21 | -------------------------------------------------------------------------------- /examples/cra/src/components/PizzaPlace.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { observer } from "mobx-react-lite" 3 | import cx from "classnames" 4 | import s from "./PizzaPlace.module.css" 5 | import { useContainer } from "../containers/_container.hooks" 6 | 7 | export const PizzaPlace = observer(() => { 8 | const [pizza] = useContainer().pizzaContainer 9 | if (!pizza) return <>Pizza Place is loading 10 | 11 | const { pizzaPlace, diningTables } = pizza 12 | 13 | return ( 14 | <> 15 |
16 |

Pizza Place: {pizzaPlace.name}

17 |

Open?: {pizzaPlace.isOpen ? "true" : "false"}

18 |

Dining Tables: {diningTables.tables.length}

19 | 20 | 21 |
22 | 23 | ) 24 | }) 25 | 26 | export const KitchenData = observer(() => { 27 | const [kitchenCont] = useContainer().kitchen 28 | 29 | if (!kitchenCont) return <>Kitchen is loading 30 | 31 | const { kitchen, orderManager } = kitchenCont 32 | return ( 33 | <> 34 |

Kitchen data: ({kitchen.kitchenName})

35 | 36 |
37 |
38 |

Orders:

39 |
    40 | {orderManager.orders.map((order, idx) => { 41 | return ( 42 |
  • 43 | table: {order.table.name} | pizzastate: {order.pizza.state} 44 |
      45 | {order.pizza.ingredients.map((ingredient, idx) => ( 46 |
    • {ingredient.name}
    • 47 | ))} 48 |
    49 |
  • 50 | ) 51 | })} 52 |
53 |
54 |
55 |

Ingredients:

56 | 57 |
58 |
59 | 60 | ) 61 | }) 62 | 63 | export const Inventory = observer(() => { 64 | const [kitchenCont] = useContainer().kitchen 65 | if (!kitchenCont) return <>Kitchen is loading 66 | const { ingredients } = kitchenCont 67 | return ( 68 | <> 69 | 76 | 77 | ) 78 | }) 79 | -------------------------------------------------------------------------------- /examples/cra/src/containers/_AppRoot.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, useMemo } from "react" 2 | import { MyRootCont } from "../containers/_container.hooks" 3 | import { getMainPizzaAppContainer } from "../containers/_root.store" 4 | 5 | export function PizzaAppWrapper({ children }: { children: ReactNode }) { 6 | const store = useMemo(() => getMainPizzaAppContainer(), []) 7 | 8 | return {children} 9 | } 10 | -------------------------------------------------------------------------------- /examples/cra/src/containers/_container.hooks.ts: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react" 2 | import { getContainerSetHooks } from "iti-react" 3 | import { PizzaAppContainer } from "./_root.store" 4 | 5 | export const MyRootCont = React.createContext({}) 6 | 7 | let mega = getContainerSetHooks(MyRootCont) 8 | export const useContainerSet = mega.useContainerSet 9 | export const useContainer = mega.useContainer 10 | -------------------------------------------------------------------------------- /examples/cra/src/containers/_root.store.ts: -------------------------------------------------------------------------------- 1 | import { createContainer } from "iti" 2 | 3 | import { provideAContainer } from "./container.a" 4 | import { provideAuthContainer } from "./container.auth" 5 | import { provideBContainer } from "./container.b" 6 | import { provideKitchenManipulatorContainer } from "./container.kitchein-manipulator" 7 | import { providePizzaPlaceContainer } from "./container.pizza-place" 8 | import { provideKitchenContainer } from "./container.kitchen" 9 | 10 | import { provideFatLib1 } from "./container.fat-lib1" 11 | import { provideFatLib2 } from "./container.fat-lib2" 12 | 13 | export type PizzaAppCoreContainer = ReturnType 14 | export function pizzaAppCore() { 15 | return createContainer() 16 | .add(() => ({ 17 | auth: async () => provideAuthContainer(), 18 | })) 19 | .add((c) => ({ 20 | aCont: async () => provideAContainer(await c.auth), 21 | })) 22 | .add((ctx) => ({ 23 | bCont: async () => provideBContainer(await ctx.auth, await ctx.aCont), 24 | })) 25 | .add((c) => ({ 26 | // fat libs 27 | fatlib1: async () => provideFatLib1(), 28 | fatlib2: async () => provideFatLib2(), 29 | })) 30 | .add((ctx, node) => ({ 31 | // pizza stuff 32 | pizzaContainer: async () => providePizzaPlaceContainer(await ctx.fatlib2), 33 | kitchen: async () => provideKitchenContainer(), 34 | })) 35 | .add((ctx) => ({ 36 | errSync: () => { 37 | throw new Error("errSync") 38 | }, 39 | errAsync: async () => { 40 | throw new Error("errAsync") 41 | }, 42 | errNested: async () => ({ 43 | nestedSyncErr: () => { 44 | throw new Error("errSync") 45 | }, 46 | nestedAsyncErr: async () => { 47 | throw new Error("nestedAsyncErr") 48 | }, 49 | }), 50 | })) 51 | } 52 | 53 | export type PizzaAppContainer = ReturnType 54 | export function getMainPizzaAppContainer() { 55 | return pizzaAppCore().add((ctx, node) => ({ 56 | kitchenManipulator: async () => provideKitchenManipulatorContainer(node), 57 | })) 58 | } 59 | -------------------------------------------------------------------------------- /examples/cra/src/containers/_utils.ts: -------------------------------------------------------------------------------- 1 | export const wait = (w: number) => new Promise((r) => setTimeout(r, w)) 2 | -------------------------------------------------------------------------------- /examples/cra/src/containers/container.a.ts: -------------------------------------------------------------------------------- 1 | import type { AuthContainer } from "./container.auth" 2 | import { A1, A2, A3 } from "../stores/store.a" 3 | 4 | export interface A_Container { 5 | a1: A1 6 | a2: A2 7 | a3: A3 8 | } 9 | 10 | export async function provideAContainer( 11 | auth: AuthContainer, 12 | ): Promise { 13 | const a1 = new A1(auth.auth) 14 | const a2 = new A2(a1, auth.auth) 15 | const a3 = new A3(a1, a2) 16 | 17 | return { 18 | a1, 19 | a2, 20 | a3, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/cra/src/containers/container.auth.ts: -------------------------------------------------------------------------------- 1 | import { Authorization } from "../stores/store.authorization" 2 | import { Auth } from "../stores/store.auth" 3 | import { wait } from "./_utils" 4 | 5 | export interface AuthContainer { 6 | auth: Auth 7 | authorization: Authorization 8 | } 9 | 10 | export async function provideAuthContainer(): Promise { 11 | const auth = new Auth() 12 | await wait(50) 13 | await auth.getToken() 14 | let x = await auth.getUserType() 15 | return { 16 | auth: auth, 17 | authorization: new Authorization(x), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/cra/src/containers/container.b.ts: -------------------------------------------------------------------------------- 1 | import type { A_Container } from "./container.a" 2 | import type { AuthContainer } from "./container.auth" 3 | import { B1, B2 } from "../stores/store.b" 4 | import { wait } from "./_utils" 5 | 6 | export interface B_Container { 7 | b1: B1 8 | b2: B2 9 | } 10 | 11 | export async function provideBContainer( 12 | auth: AuthContainer, 13 | a: A_Container, 14 | ): Promise { 15 | const b1 = new B1(auth.auth, a.a2) 16 | 17 | await wait(70) 18 | const b2 = new B2(auth.auth, a.a1) 19 | 20 | return { 21 | b1, 22 | b2, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/cra/src/containers/container.config.ts: -------------------------------------------------------------------------------- 1 | import { DayType, getDayType } from "../services/url-param" 2 | import { wait } from "./_utils" 3 | 4 | export interface B_Container { 5 | dayType: DayType 6 | } 7 | 8 | export async function provideConfigContainer(): Promise { 9 | const b1 = getDayType() 10 | await wait(90) 11 | 12 | return { 13 | dayType: b1, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/cra/src/containers/container.fat-lib1.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Fat Lib 1 shows an example how we can use async modules from react 3 | */ 4 | export async function provideFatLib1() { 5 | console.log("~~~ provider for fat lib initiated 0") 6 | let x = await import("../fat-libs/fat-lib1") 7 | console.log("~~~ provider for fat lib initiated 1") 8 | console.log(x) 9 | let s = x.fatLib300kb() 10 | 11 | return { 12 | pizzaPlace: 12, 13 | fatLibData: s, 14 | 15 | getFatLibData: async function () { 16 | let x = await import("../fat-libs/fat-lib1") 17 | console.log("~~~ provider for fat lib initiated 1") 18 | console.log(x) 19 | x.fatLib300kb() 20 | }, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/cra/src/containers/container.fat-lib2.ts: -------------------------------------------------------------------------------- 1 | import type { FatLib2_3MB } from "../fat-libs/fat-lib2" 2 | import type { GetContainerFormat } from "iti" 3 | 4 | let x: FatLib2_3MB 5 | /** 6 | * Fat Lib 1 shows an example how we can use async modules from react 7 | */ 8 | export async function provideFatLib2() { 9 | console.log("~~~ provider for fat lib 2 initiated 0") 10 | return { 11 | getFatLib2Data: async function () { 12 | let { FatLib2_3MB } = await import("../fat-libs/fat-lib2") 13 | 14 | console.log("~~~ provider for fat lib 2 initiated 1") 15 | console.log(FatLib2_3MB) 16 | 17 | return new FatLib2_3MB() 18 | }, 19 | } 20 | } 21 | 22 | // export interface FatLib2Container { 23 | // getFatLib2Data: () => Promise 24 | // } 25 | 26 | export type FatLib2Container = GetContainerFormat 27 | -------------------------------------------------------------------------------- /examples/cra/src/containers/container.kitchein-manipulator.ts: -------------------------------------------------------------------------------- 1 | import { KitchenSizeUIController } from "../stores/_controllers/controller.kitchen" 2 | import { provideUpgradedKitchenContainer } from "./container.kitchen" 3 | import type { PizzaAppCoreContainer } from "./_root.store" 4 | 5 | export interface KitchenManipulator_Container { 6 | kitchenSizeController: KitchenSizeUIController 7 | } 8 | 9 | export interface KitchenUpgrader { 10 | upgradeKitchenConatiner: () => Promise 11 | } 12 | 13 | export async function provideKitchenManipulatorContainer( 14 | node: PizzaAppCoreContainer, 15 | ): Promise { 16 | let ksc = new KitchenSizeUIController({ 17 | onKitchenResize: async () => { 18 | const currentKitchen = await node.items.kitchen 19 | return await node.upsert({ 20 | kitchen: () => provideUpgradedKitchenContainer(currentKitchen), 21 | }) 22 | }, 23 | }) 24 | 25 | return { 26 | kitchenSizeController: ksc, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/cra/src/containers/container.kitchen.ts: -------------------------------------------------------------------------------- 1 | import { IngredientsService } from "../services/ingredients-manager" 2 | import type { Ingredients } from "../stores/store.ingrediets" 3 | import { Kitchen, OrderManager } from "../stores/store.kitchen" 4 | import { Oven } from "../stores/store.oven" 5 | 6 | export interface Kitchen_Container { 7 | oven: Oven 8 | ingredients: Ingredients 9 | orderManager: OrderManager 10 | kitchen: Kitchen 11 | } 12 | 13 | export interface KitchenUpgrader { 14 | upgradeKitchenConatiner: () => Promise 15 | } 16 | 17 | export async function provideKitchenContainer(): Promise { 18 | let oven = new Oven() 19 | let ingredients = await IngredientsService.buySomeIngredients() 20 | 21 | let kitchen = new Kitchen(oven, ingredients) 22 | let orders = new OrderManager(kitchen) 23 | 24 | return { 25 | oven: oven, 26 | orderManager: orders, 27 | ingredients: ingredients, 28 | kitchen: kitchen, 29 | } 30 | } 31 | 32 | export async function provideUpgradedKitchenContainer( 33 | prevContainer: Kitchen_Container, 34 | ): Promise { 35 | let biggerOven = new Oven(8) 36 | 37 | // This is one way of migrating data 38 | let kitchen = new Kitchen(biggerOven, prevContainer.ingredients) 39 | let orderManager = new OrderManager(kitchen) 40 | orderManager.orders = prevContainer.orderManager.orders 41 | 42 | return { 43 | oven: biggerOven, 44 | orderManager: orderManager, 45 | ingredients: prevContainer.ingredients, 46 | kitchen: kitchen, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/cra/src/containers/container.pizza-place.ts: -------------------------------------------------------------------------------- 1 | import { PizzaPlace, DiningTables } from "../stores/store.pizza-place" 2 | import type { FatLib2Container } from "./container.fat-lib2" 3 | 4 | export interface PizzaPlace_Container { 5 | pizzaPlace: PizzaPlace 6 | diningTables: DiningTables 7 | } 8 | 9 | export async function providePizzaPlaceContainer( 10 | fatLib2Container: FatLib2Container, 11 | ): Promise { 12 | const a1 = new PizzaPlace(fatLib2Container.getFatLib2Data) 13 | const a2 = new DiningTables() 14 | 15 | return { 16 | pizzaPlace: a1, 17 | diningTables: a2, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/cra/src/containers/my-jest.ts: -------------------------------------------------------------------------------- 1 | export function itAsyncDone( 2 | name: string, 3 | cb: (done: jest.DoneCallback) => Promise, 4 | timeout?: number, 5 | ) { 6 | it( 7 | name, 8 | (done) => { 9 | let doneCalled = false 10 | const wrappedDone: jest.DoneCallback = (...args) => { 11 | if (doneCalled) { 12 | return 13 | } 14 | 15 | doneCalled = true 16 | done(...args) 17 | } 18 | 19 | wrappedDone.fail = (err) => { 20 | if (doneCalled) { 21 | return 22 | } 23 | 24 | doneCalled = true 25 | 26 | done.fail(err) 27 | } 28 | 29 | cb(wrappedDone).catch(wrappedDone) 30 | }, 31 | timeout, 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /examples/cra/src/fat-libs/fat-lib1.ts: -------------------------------------------------------------------------------- 1 | export function fatLib300kb() { 2 | console.log("getting 300kb lib") 3 | return "300KB fatlib string" 4 | } 5 | -------------------------------------------------------------------------------- /examples/cra/src/fat-libs/fat-lib2.ts: -------------------------------------------------------------------------------- 1 | export class FatLib2_3MB { 2 | public getData() { 3 | console.log("getting 3MB lib") 4 | return "3 MB fatlib string" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/cra/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 | -------------------------------------------------------------------------------- /examples/cra/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { createRoot } from "react-dom/client" 3 | import "./index.css" 4 | import App from "./App" 5 | import reportWebVitals from "./reportWebVitals" 6 | 7 | const container = document.getElementById("root") 8 | const root = createRoot(container!) // createRoot(container!) if you use TypeScript 9 | root.render( 10 | 11 | 12 | , 13 | ) 14 | 15 | // If you want to start measuring performance in your app, pass a function 16 | // to log results (for example: reportWebVitals(console.log)) 17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 18 | reportWebVitals() 19 | -------------------------------------------------------------------------------- /examples/cra/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/cra/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 | -------------------------------------------------------------------------------- /examples/cra/src/services/ingredients-manager.ts: -------------------------------------------------------------------------------- 1 | import { Ingredients } from "../stores/store.ingrediets" 2 | import _sampleSize from "lodash/sampleSize" 3 | import _sample from "lodash/sample" 4 | 5 | const INGREDIENTS = [ 6 | "tomatoes", 7 | "olives", 8 | "salami", 9 | "cheese", 10 | "mushrooms", 11 | "bell pepper", 12 | "mozzarella", 13 | ] as const 14 | 15 | /** 16 | * Fetches Ingredients from the supermarket 17 | */ 18 | export class IngredientsService { 19 | public static async buyManyIngredients(): Promise { 20 | return this.buyIngredients(150) 21 | } 22 | 23 | public static async buySomeIngredients(): Promise { 24 | return this.buyIngredients(30) 25 | } 26 | 27 | private static buyIngredients(n = 10): Ingredients { 28 | let x = new Ingredients() 29 | 30 | for (let i = n; i > 0; i--) { 31 | let z = _sample(INGREDIENTS) 32 | if (z != null) { 33 | x.addNewIngredient(z) 34 | } 35 | } 36 | return x 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/cra/src/services/url-param.ts: -------------------------------------------------------------------------------- 1 | export type DayType = "normal" | "friday" | "weekend" 2 | 3 | export function getDayType(): DayType { 4 | const uri = new URL(window.location.href) 5 | 6 | if (uri.searchParams.has("dayType")) { 7 | const mode = uri.searchParams.get("dayType") 8 | 9 | if (mode === "friday") { 10 | return "friday" 11 | } 12 | 13 | if (mode === "weekend") { 14 | return "weekend" 15 | } 16 | } 17 | 18 | return "normal" 19 | } 20 | 21 | export type AuthType = "unauthenticated" | "manager" | "admin" 22 | 23 | export function getAuthType(): AuthType { 24 | const uri = new URL(window.location.href) 25 | 26 | if (uri.searchParams.has("authType")) { 27 | const mode = uri.searchParams.get("authType") 28 | 29 | if (mode === "manager") { 30 | return "manager" 31 | } 32 | 33 | if (mode === "admin") { 34 | return "admin" 35 | } 36 | } 37 | 38 | return "unauthenticated" 39 | } 40 | export function setAuthType(at: AuthType) { 41 | const url = new URL(window.location.href) 42 | url.searchParams.set("authType", at) 43 | console.log("changing user and url to", at) 44 | window.history.pushState({ authType: at }, at, url) 45 | } 46 | -------------------------------------------------------------------------------- /examples/cra/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 | -------------------------------------------------------------------------------- /examples/cra/src/stores/_controllers/controller.kitchen.ts: -------------------------------------------------------------------------------- 1 | export class KitchenSizeUIController { 2 | constructor( 3 | private cbs: { 4 | onKitchenResize: () => Promise 5 | }, 6 | ) {} 7 | 8 | public async increaseKitchenSize() { 9 | console.log("kitchen resize") 10 | this.cbs.onKitchenResize() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/cra/src/stores/store.a.ts: -------------------------------------------------------------------------------- 1 | import type { Auth } from "./store.auth" 2 | 3 | export class A1 { 4 | constructor(private auth: Auth) {} 5 | public getName() { 6 | return "Jon Snow" 7 | } 8 | } 9 | export class A2 { 10 | constructor(private a1: A1, private auth: Auth) {} 11 | } 12 | export class A3 { 13 | constructor(private a1: A1, private a2: A2) {} 14 | } 15 | -------------------------------------------------------------------------------- /examples/cra/src/stores/store.auth.ts: -------------------------------------------------------------------------------- 1 | import { getAuthType, setAuthType, AuthType } from "../services/url-param" 2 | 3 | const wait = (w: number) => new Promise((r) => setTimeout(r, w)) 4 | 5 | export class Auth { 6 | public async getToken(): Promise { 7 | await wait(200) 8 | return "token123" 9 | } 10 | 11 | public async getUser(): Promise { 12 | await wait(200) 13 | return { name: "lol" } 14 | } 15 | 16 | public async getUserType() { 17 | await wait(500) 18 | const t = getAuthType() 19 | return t 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/cra/src/stores/store.authorization.ts: -------------------------------------------------------------------------------- 1 | import { makeObservable, observable, computed, action, autorun } from "mobx" 2 | import { getAuthType, setAuthType, AuthType } from "../services/url-param" 3 | 4 | const wait = (w: number) => new Promise((r) => setTimeout(r, w)) 5 | 6 | const RightsDB = { 7 | unauthenticated: { 8 | orderPizza: true, 9 | addTables: false, 10 | upgradeKitchen: false, 11 | }, 12 | manager: { 13 | orderPizza: true, 14 | addTables: true, 15 | upgradeKitchen: false, 16 | }, 17 | admin: { 18 | orderPizza: true, 19 | addTables: true, 20 | upgradeKitchen: true, 21 | }, 22 | } as const 23 | 24 | function rightsLookup(rights: T) { 25 | return rights 26 | } 27 | 28 | export class Authorization { 29 | constructor(private _userType: AuthType) { 30 | makeObservable(this, { 31 | // @ts-expect-error 32 | _userType: observable, 33 | userType: computed, 34 | changeUser: action, 35 | }) 36 | } 37 | 38 | get userType() { 39 | return this._userType 40 | } 41 | 42 | /** 43 | * this API might look weird but this helps typescript 44 | * k.getAvailableActions()["admin"] 45 | */ 46 | public getAvailableActions() { 47 | return rightsLookup(RightsDB) 48 | } 49 | 50 | public async getToken(): Promise { 51 | await wait(200) 52 | return "token123" 53 | } 54 | 55 | public async getUser(): Promise { 56 | await wait(200) 57 | return { name: "lol" } 58 | } 59 | 60 | public async getUserType() { 61 | await wait(500) 62 | const t = getAuthType 63 | return t 64 | } 65 | 66 | public async changeUser(at: AuthType) { 67 | await wait(400) 68 | this._userType = at 69 | setAuthType(at) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/cra/src/stores/store.b.ts: -------------------------------------------------------------------------------- 1 | import type { A1, A2 } from "./store.a" 2 | import type { Auth } from "./store.auth" 3 | 4 | export class B1 { 5 | constructor(private auth: Auth, private a2: A2) { 6 | // 1. Subscribe to event emitter 7 | // 2. Pub update 8 | } 9 | } 10 | export class B2 { 11 | constructor(private auth: Auth, private a1: A1) {} 12 | } 13 | -------------------------------------------------------------------------------- /examples/cra/src/stores/store.ingrediets.ts: -------------------------------------------------------------------------------- 1 | import { 2 | makeAutoObservable, 3 | makeObservable, 4 | action, 5 | computed, 6 | observable, 7 | } from "mobx" 8 | import _sample from "lodash/sample" 9 | import _pullAt from "lodash/pullAt" 10 | import _countBy from "lodash/countBy" 11 | 12 | export class Ingredients { 13 | public ingredients: Ingredient[] = [] 14 | 15 | constructor() { 16 | makeObservable(this, { 17 | ingredients: observable, 18 | addNewIngredient: action, 19 | getRandomPizzaIngredients: action, 20 | ingredientsStats: computed, 21 | }) 22 | } 23 | 24 | public addNewIngredient(n: string) { 25 | this.ingredients.push(new Ingredient(n)) 26 | } 27 | 28 | public getRandomPizzaIngredients() { 29 | let pi: Ingredient[] = [] 30 | 31 | let k = 4 32 | while (k > 0) { 33 | const i = Math.floor(Math.random() * this.ingredients.length) 34 | pi.push(this.ingredients[i]) 35 | _pullAt(this.ingredients, i) 36 | k-- 37 | } 38 | return pi 39 | } 40 | 41 | public get ingredientsStats() { 42 | const stats = _countBy(this.ingredients, (ing) => ing.name) 43 | return Object.entries(stats) 44 | } 45 | } 46 | 47 | export class Ingredient { 48 | constructor(public name: string) { 49 | makeAutoObservable(this) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/cra/src/stores/store.kitchen.ts: -------------------------------------------------------------------------------- 1 | import { makeAutoObservable } from "mobx" 2 | import type { Ingredients } from "./store.ingrediets" 3 | import type { Oven } from "./store.oven" 4 | import { Pizza } from "./store.pizza" 5 | import type { Table } from "./store.pizza-place" 6 | import debug from "debug" 7 | const log = debug("oven") 8 | 9 | export class Kitchen { 10 | public kitchenName: string 11 | 12 | constructor(private oven: Oven, private ingredients: Ingredients) { 13 | this.kitchenName = "Random Name " + Math.round(Math.random() * 100) 14 | log("new kitchen: " + this.kitchenName) 15 | 16 | makeAutoObservable(this) 17 | } 18 | 19 | public getRandomPizzaIngredients() { 20 | return this.ingredients.getRandomPizzaIngredients() 21 | } 22 | 23 | public async bakePizza(p: Pizza) { 24 | return this.oven.bakePizza(p) 25 | } 26 | } 27 | 28 | export class OrderManager { 29 | public orders: Order[] = [] 30 | 31 | constructor(private kitchen: Kitchen) { 32 | makeAutoObservable(this) 33 | } 34 | 35 | public async orderPizza(table: Table) { 36 | let i = this.kitchen.getRandomPizzaIngredients() 37 | let p = new Pizza(i) 38 | 39 | this.orders.push(new Order(p, table)) 40 | 41 | this.kitchen.bakePizza(p) 42 | } 43 | } 44 | 45 | class Order { 46 | constructor(public pizza: Pizza, public table: Table) {} 47 | } 48 | -------------------------------------------------------------------------------- /examples/cra/src/stores/store.oven.ts: -------------------------------------------------------------------------------- 1 | import { makeAutoObservable, computed } from "mobx" 2 | import type { Pizza } from "./store.pizza" 3 | import debug from "debug" 4 | const log = debug("oven") 5 | 6 | const BAKING_TIME_MS = 400 7 | const BAKING_TEMPERATURE = 260 8 | 9 | export class Oven { 10 | public currentTemperature = 20 11 | public pizInside: Pizza[] = [] 12 | 13 | constructor(private _pizzaCapacity = 4) { 14 | log("new Oven. capacity:", this._pizzaCapacity) 15 | makeAutoObservable(this) 16 | } 17 | public get pizzaCapacity() { 18 | return this._pizzaCapacity 19 | } 20 | 21 | public async preheatOven() { 22 | setTimeout(() => { 23 | this.updateTemperature(BAKING_TEMPERATURE) 24 | }, 200) 25 | } 26 | 27 | public async bakePizza(pizza: Pizza) { 28 | if (this.currentTemperature < BAKING_TEMPERATURE) { 29 | await this.preheatOven() 30 | } 31 | 32 | if (this.pizzasInOven() < this._pizzaCapacity) { 33 | this.addPizzaToOven(pizza) 34 | setTimeout(() => { 35 | pizza.updatePizzaState("baked") 36 | }, BAKING_TIME_MS) 37 | } 38 | } 39 | 40 | private updateTemperature(nc: number) { 41 | this.currentTemperature = nc 42 | } 43 | 44 | private addPizzaToOven(p: Pizza) { 45 | this.pizInside.push(p) 46 | } 47 | 48 | @computed 49 | public pizzasInOven() { 50 | return this.pizInside.length 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/cra/src/stores/store.pizza-place.ts: -------------------------------------------------------------------------------- 1 | import { makeObservable, makeAutoObservable, observable, action } from "mobx" 2 | import type { FatLib2_3MB } from "../fat-libs/fat-lib2" 3 | 4 | export class PizzaPlace { 5 | public isOpen = false 6 | public name = "Rocket Pizza" 7 | 8 | constructor(private fatlib: () => Promise) { 9 | console.log("new pizza place") 10 | // makeAutoObservable(this) 11 | makeObservable(this, { 12 | isOpen: observable, 13 | openPizzaPlace: action, 14 | closePizzaPlace: action, 15 | }) 16 | } 17 | 18 | public openPizzaPlace() { 19 | this.isOpen = true 20 | } 21 | 22 | public closePizzaPlace() { 23 | this.isOpen = false 24 | } 25 | 26 | public async getFatLibImage() { 27 | const fatLib = await this.fatlib() 28 | return fatLib.getData() 29 | } 30 | } 31 | 32 | export class DiningTables { 33 | public tables: Table[] = [] 34 | 35 | constructor() { 36 | makeAutoObservable(this) 37 | } 38 | 39 | public addNewTable() { 40 | console.log("adding new table") 41 | const name = (this.tables.length + 1).toString() 42 | this.tables.push(new Table(name)) 43 | } 44 | } 45 | 46 | export class Table { 47 | isEmpty = true 48 | constructor(public name: string) { 49 | makeAutoObservable(this) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/cra/src/stores/store.pizza.ts: -------------------------------------------------------------------------------- 1 | import { makeAutoObservable } from "mobx" 2 | import type { Ingredient } from "./store.ingrediets" 3 | 4 | type PizzaState = "raw" | "hasIngredients" | "baked" 5 | 6 | export class Pizza { 7 | public state: PizzaState = "raw" 8 | 9 | constructor(public ingredients: Ingredient[]) { 10 | console.log("new pizza with ingredients created") 11 | makeAutoObservable(this) 12 | } 13 | 14 | public updatePizzaState(ns: PizzaState) { 15 | this.state = ns 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/cra/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "noImplicitAny": false, 10 | "experimentalDecorators": true, 11 | "allowJs": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "strict": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "noEmit": true, 23 | "jsx": "react-jsx" 24 | }, 25 | "include": [ 26 | "src" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /examples/node-cjs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | dist -------------------------------------------------------------------------------- /examples/node-cjs/node-1.js: -------------------------------------------------------------------------------- 1 | const iti = require("iti") 2 | 3 | const container = iti 4 | .createContainer() 5 | .add({ 6 | doggoName: "Moon Moon", 7 | }) 8 | .add((c) => ({ 9 | sayName: () => console.log("Doggo's name is " + c.doggoName), 10 | })) 11 | let a = container.items.sayName 12 | let b = container.items.doggoName 13 | 14 | console.log(a, b) 15 | -------------------------------------------------------------------------------- /examples/node-cjs/node-2.js: -------------------------------------------------------------------------------- 1 | const { createContainer } = require("iti") 2 | 3 | function Doggo(name) { 4 | return { 5 | greetText: "Doggo name is " + name, 6 | } 7 | } 8 | 9 | let root = createContainer() 10 | .add({ 11 | doggoName: "Moon Moon", 12 | }) 13 | .add((c) => ({ 14 | doggo: () => new Doggo(c.doggoName), 15 | })) 16 | .add((c) => ({ 17 | sayName: () => { 18 | console.log(c.doggo.greetText) 19 | }, 20 | })) 21 | root.get("sayName") 22 | -------------------------------------------------------------------------------- /examples/node-cjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti-node-example-cjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "debug": "^4.3.3", 7 | "lodash": "^4.17.21", 8 | "mobx": "^6.3.12", 9 | "nodemon": "^2.0.15", 10 | "iti": "0.7.0", 11 | "react": "^17.0.2", 12 | "ts-node": "^10.4.0", 13 | "typescript": "^4.5.5" 14 | }, 15 | "scripts": { 16 | "start": "node ./dist/server", 17 | "watch": "tsc -w", 18 | "serverWatch": "nodemon -x 'clear;node' ./dist/server" 19 | }, 20 | "license": "MIT", 21 | "authors": [ 22 | "Nick Olszanski " 23 | ], 24 | "prettier": { 25 | "semi": false, 26 | "singleQuote": false, 27 | "arrowParens": "always", 28 | "trailingComma": "all" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/node-cli/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | dist -------------------------------------------------------------------------------- /examples/node-cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti-node-example", 3 | "type": "module", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "debug": "^4.3.3", 8 | "lodash": "^4.17.21", 9 | "mobx": "^6.3.12", 10 | "nodemon": "^2.0.15", 11 | "iti": "0.7.0", 12 | "react": "^17.0.2", 13 | "ts-node": "^10.4.0", 14 | "typescript": "^4.5.5" 15 | }, 16 | "scripts": { 17 | "start": "node ./dist/server", 18 | "watch": "tsc -w", 19 | "serverWatch": "nodemon -x 'clear;node' ./dist/server" 20 | }, 21 | "license": "MIT", 22 | "authors": [ 23 | "Nick Olszanski " 24 | ], 25 | "prettier": { 26 | "semi": false, 27 | "singleQuote": false, 28 | "arrowParens": "always", 29 | "trailingComma": "all" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/node-cli/src/server.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash" 2 | import { createContainer } from "iti" 3 | 4 | // Step 1: Your application logic stays clean 5 | class Oven { 6 | public pizzasInOven() { 7 | return 3 8 | } 9 | public async preheat() {} 10 | } 11 | class Kitchen { 12 | constructor(public oven: Oven, public manual: string) {} 13 | } 14 | 15 | // Step 2: Add and read simple tokens 16 | let root = createContainer().add({ 17 | userManual: "Please preheat before use", 18 | oven: () => new Oven(), 19 | }) 20 | root.get("oven") 21 | 22 | // Step 3: Add a useful async provider / container 23 | const kitchenContainer = async ({ oven, userManual }) => { 24 | await oven.preheat() 25 | return { 26 | kitchen: new Kitchen(oven, userManual), 27 | } 28 | } 29 | 30 | // Step 4: Add an async provider 31 | const node = root.add((ctx, node) => ({ 32 | kitchen: async () => 33 | kitchenContainer(await node.getContainerSet(["userManual", "oven"])), 34 | })) 35 | await node.get("kitchen") 36 | 37 | // A SHORT USE MANUAL 38 | // A SHORT USE MANUAL 39 | // A SHORT USE MANUAL 40 | 41 | // ---- Reading 42 | 43 | // Get a single instance 44 | root.get("oven") // Creates a new Oven instance 45 | root.get("oven") // Gets a cached Oven instance 46 | 47 | await node.get("kitchen") // { kitchen: Kitchen } also cached 48 | await node.items.kitchen // same as above 49 | 50 | // Get multiple instances at once 51 | await root.getContainerSet(["oven", "userManual"]) // { userManual: '...', oven: Oven } 52 | await root.getContainerSet((c) => [c.userManual, c.oven]) // same as above 53 | 54 | // Subscribe to container changes 55 | node.subscribeToContainer("oven", (oven) => {}) 56 | node.subscribeToContainerSet(["oven", "kitchen"], ({ oven, kitchen }) => {}) 57 | // prettier-ignore 58 | node.subscribeToContainerSet((c) => [c.kitchen], ({ oven, kitchen }) => {}) 59 | node.on("containerUpdated", ({ key, newContainer }) => {}) 60 | node.on("containerUpserted", ({ key, newContainer }) => {}) 61 | 62 | // ----Adding 63 | 64 | let node1 = createContainer() 65 | .add({ 66 | userManual: "Please preheat before use", 67 | oven: () => new Oven(), 68 | }) 69 | .upsert((ctx) => ({ 70 | userManual: "Works better when hot", 71 | preheatedOven: async () => { 72 | await ctx.oven.preheat() 73 | return ctx.oven 74 | }, 75 | })) 76 | 77 | // `add` is typesafe and a runtime safe method. Hence we've used `upsert` 78 | try { 79 | node1.add({ 80 | // @ts-expect-error 81 | userManual: "You shall not pass", 82 | // Type Error: (property) userManual: "You are overwriting this token. It is not safe. Use an unsafe `upsert` method" 83 | }) 84 | } catch (err) { 85 | err.message // Error Tokens already exist: ['userManual'] 86 | } 87 | 88 | // // async function runStuff() { 89 | // // let a = new AppContainer() 90 | 91 | // // let k = await a.getKitchenContainer() 92 | // // let pp = await a.getPizzaPlaceContainer() 93 | 94 | // // pp.diningTables.addNewTable() 95 | // // pp.diningTables.addNewTable() 96 | // // pp.diningTables.addNewTable() 97 | 98 | // // k.orderManager.orderPizza(pp.diningTables.tables[1]) 99 | // // k.orderManager.orderPizza(pp.diningTables.tables[2]) 100 | 101 | // // console.log(k.orderManager.orders) 102 | // // k.orderManager.orders.forEach((order) => { 103 | // // console.log(order.pizza.state) 104 | // // console.log(JSON.stringify(order.pizza.ingredients)) 105 | // // }) 106 | // // } 107 | 108 | // // runStuff().then(() => { 109 | // // console.log("done") 110 | // // }) 111 | 112 | // export const x = { a: 12 } 113 | -------------------------------------------------------------------------------- /examples/node-cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "target": "esnext", 6 | "outDir": "dist", 7 | "moduleResolution": "node", 8 | "jsx": "preserve", 9 | "baseUrl": "./", 10 | /* Additional Options */ 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "resolveJsonModule": true, 14 | "allowSyntheticDefaultImports": true, 15 | "experimentalDecorators": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/node-js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | dist -------------------------------------------------------------------------------- /examples/node-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti-node-example", 3 | "type": "module", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "debug": "^4.3.3", 8 | "lodash": "^4.17.21", 9 | "mobx": "^6.3.12", 10 | "nodemon": "^2.0.15", 11 | "iti": "0.7.0", 12 | "react": "^17.0.2", 13 | "ts-node": "^10.4.0", 14 | "typescript": "^4.5.5" 15 | }, 16 | "scripts": { 17 | "start": "node ./dist/server", 18 | "watch": "tsc -w", 19 | "serverWatch": "nodemon -x 'clear;node' ./dist/server" 20 | }, 21 | "license": "MIT", 22 | "authors": [ 23 | "Nick Olszanski " 24 | ], 25 | "prettier": { 26 | "semi": false, 27 | "singleQuote": false, 28 | "arrowParens": "always", 29 | "trailingComma": "all" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/node-js/src/server.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash" 2 | import { createContainer } from "iti" 3 | 4 | // Step 1: Your application logic stays clean 5 | class Oven { 6 | public pizzasInOven() { 7 | return 3 8 | } 9 | public async preheat() {} 10 | } 11 | class Kitchen { 12 | constructor(public oven: Oven, public manual: string) {} 13 | } 14 | 15 | // Step 2: Add and read simple tokens 16 | let root = createContainer().add({ 17 | userManual: "Please preheat before use", 18 | oven: () => new Oven(), 19 | }) 20 | root.get("oven") 21 | 22 | // Step 3: Add a useful async provider / container 23 | const kitchenContainer = async ({ oven, userManual }) => { 24 | await oven.preheat() 25 | return { 26 | kitchen: new Kitchen(oven, userManual), 27 | } 28 | } 29 | 30 | // Step 4: Add an async provider 31 | const node = root.add((ctx, node) => ({ 32 | kitchen: async () => 33 | kitchenContainer(await node.getContainerSet(["userManual", "oven"])), 34 | })) 35 | await node.get("kitchen") 36 | 37 | // A SHORT USE MANUAL 38 | // A SHORT USE MANUAL 39 | // A SHORT USE MANUAL 40 | 41 | // ---- Reading 42 | 43 | // Get a single instance 44 | root.get("oven") // Creates a new Oven instance 45 | root.get("oven") // Gets a cached Oven instance 46 | 47 | await node.get("kitchen") // { kitchen: Kitchen } also cached 48 | await node.items.kitchen // same as above 49 | 50 | // Get multiple instances at once 51 | await root.getContainerSet(["oven", "userManual"]) // { userManual: '...', oven: Oven } 52 | await root.getContainerSet((c) => [c.userManual, c.oven]) // same as above 53 | 54 | // Subscribe to container changes 55 | node.subscribeToContainer("oven", (oven) => {}) 56 | node.subscribeToContainerSet(["oven", "kitchen"], ({ oven, kitchen }) => {}) 57 | // prettier-ignore 58 | node.subscribeToContainerSet((c) => [c.kitchen], ({ oven, kitchen }) => {}) 59 | node.on("containerUpdated", ({ key, newContainer }) => {}) 60 | node.on("containerUpserted", ({ key, newContainer }) => {}) 61 | 62 | // ----Adding 63 | 64 | let node1 = createContainer() 65 | .add({ 66 | userManual: "Please preheat before use", 67 | oven: () => new Oven(), 68 | }) 69 | .upsert((ctx) => ({ 70 | userManual: "Works better when hot", 71 | preheatedOven: async () => { 72 | await ctx.oven.preheat() 73 | return ctx.oven 74 | }, 75 | })) 76 | 77 | // `add` is typesafe and a runtime safe method. Hence we've used `upsert` 78 | try { 79 | node1.add({ 80 | // @ts-expect-error 81 | userManual: "You shall not pass", 82 | // Type Error: (property) userManual: "You are overwriting this token. It is not safe. Use an unsafe `upsert` method" 83 | }) 84 | } catch (err) { 85 | err.message // Error Tokens already exist: ['userManual'] 86 | } 87 | 88 | // // async function runStuff() { 89 | // // let a = new AppContainer() 90 | 91 | // // let k = await a.getKitchenContainer() 92 | // // let pp = await a.getPizzaPlaceContainer() 93 | 94 | // // pp.diningTables.addNewTable() 95 | // // pp.diningTables.addNewTable() 96 | // // pp.diningTables.addNewTable() 97 | 98 | // // k.orderManager.orderPizza(pp.diningTables.tables[1]) 99 | // // k.orderManager.orderPizza(pp.diningTables.tables[2]) 100 | 101 | // // console.log(k.orderManager.orders) 102 | // // k.orderManager.orders.forEach((order) => { 103 | // // console.log(order.pizza.state) 104 | // // console.log(JSON.stringify(order.pizza.ingredients)) 105 | // // }) 106 | // // } 107 | 108 | // // runStuff().then(() => { 109 | // // console.log("done") 110 | // // }) 111 | 112 | // export const x = { a: 12 } 113 | -------------------------------------------------------------------------------- /examples/node-mjs/node-1.mjs: -------------------------------------------------------------------------------- 1 | import { createContainer } from "iti" 2 | 3 | const container = createContainer() 4 | .add({ 5 | doggoName: "Moon Moon", 6 | }) 7 | .add((c) => ({ 8 | sayName: () => console.log("Doggo's name is " + c.doggoName), 9 | })) 10 | container.items.sayName 11 | -------------------------------------------------------------------------------- /examples/node-mjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti-node-example-mjs", 3 | "type": "module", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "debug": "^4.3.3", 8 | "lodash": "^4.17.21", 9 | "mobx": "^6.3.12", 10 | "nodemon": "^2.0.15", 11 | "iti": "0.7.0", 12 | "react": "^17.0.2", 13 | "ts-node": "^10.4.0", 14 | "typescript": "^4.5.5" 15 | }, 16 | "scripts": { 17 | "start": "node ./dist/server", 18 | "watch": "tsc -w", 19 | "serverWatch": "nodemon -x 'clear;node' ./dist/server" 20 | }, 21 | "license": "MIT", 22 | "authors": [ 23 | "Nick Olszanski " 24 | ], 25 | "prettier": { 26 | "semi": false, 27 | "singleQuote": false, 28 | "arrowParens": "always", 29 | "trailingComma": "all" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/vite-app/.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/vite-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/vite-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti-vite-example", 3 | "private": true, 4 | "version": "0.0.1", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "iti": "0.7.0", 13 | "iti-react": "0.7.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.0.15", 19 | "@types/react-dom": "^18.0.6", 20 | "@vitejs/plugin-react": "^2.0.0", 21 | "typescript": "^4.6.4", 22 | "vite": "^3.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/vite-app/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/vite-app/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | } 13 | .logo:hover { 14 | filter: drop-shadow(0 0 2em #646cffaa); 15 | } 16 | .logo.react:hover { 17 | filter: drop-shadow(0 0 2em #61dafbaa); 18 | } 19 | 20 | @keyframes logo-spin { 21 | from { 22 | transform: rotate(0deg); 23 | } 24 | to { 25 | transform: rotate(360deg); 26 | } 27 | } 28 | 29 | @media (prefers-reduced-motion: no-preference) { 30 | a:nth-of-type(2) .logo { 31 | animation: logo-spin infinite 20s linear; 32 | } 33 | } 34 | 35 | .card { 36 | padding: 2em; 37 | } 38 | 39 | .read-the-docs { 40 | color: #888; 41 | } 42 | -------------------------------------------------------------------------------- /examples/vite-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react" 2 | import reactLogo from "./assets/react.svg" 3 | import "./App.css" 4 | import { MyAppContext, useContainer } from "./hooks" 5 | import { app } from "./_bl" 6 | 7 | const Lol = () => { 8 | const [b, bErr] = useContainer().b 9 | 10 | console.log("render", b, bErr) 11 | return

123

12 | } 13 | 14 | function App() { 15 | const [count, setCount] = useState(0) 16 | 17 | return ( 18 | 19 |
20 | 21 | 29 |

Vite + React

30 |
31 | 34 |

35 | Edit src/App.tsx and save to test HMR 36 |

37 |
38 |

39 | Click on the Vite and React logos to learn more 40 |

41 |
42 |
43 | ) 44 | } 45 | 46 | export default App 47 | -------------------------------------------------------------------------------- /examples/vite-app/src/_bl.ts: -------------------------------------------------------------------------------- 1 | import { createContainer } from "iti" 2 | 3 | export class A {} 4 | export class B { 5 | constructor(a: A) {} 6 | } 7 | export class C { 8 | constructor(a: A, b: B) {} 9 | } 10 | 11 | export const app = createContainer() 12 | .add(() => ({ 13 | a: () => new A(), 14 | })) 15 | .add((ctx) => ({ 16 | b: async () => new B(ctx.a), 17 | })) 18 | .add((ctx) => ({ 19 | c: () => new C(ctx.a, ctx.b), 20 | })) 21 | -------------------------------------------------------------------------------- /examples/vite-app/src/hooks.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { getContainerSetHooks } from "iti-react" 3 | import { app } from "./_bl" 4 | 5 | export const MyAppContext = React.createContext({} as any) 6 | 7 | const hooks = getContainerSetHooks(MyAppContext) 8 | export const useContainerSet = hooks.useContainerSet 9 | export const useContainer = hooks.useContainer 10 | -------------------------------------------------------------------------------- /examples/vite-app/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | @media (prefers-color-scheme: light) { 60 | :root { 61 | color: #213547; 62 | background-color: #ffffff; 63 | } 64 | a:hover { 65 | color: #747bff; 66 | } 67 | button { 68 | background-color: #f9f9f9; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/vite-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /examples/vite-app/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/vite-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /examples/vite-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /examples/vite-app/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 | -------------------------------------------------------------------------------- /iti-react/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | yarn-error.log 4 | coverage -------------------------------------------------------------------------------- /iti-react/jest.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | /** @type {import('@types/jest')} */ 3 | module.exports = { 4 | preset: "ts-jest/presets/js-with-ts-esm", 5 | testEnvironment: "jsdom", 6 | testTimeout: 500, 7 | } 8 | -------------------------------------------------------------------------------- /iti-react/notes-and-ideas.md: -------------------------------------------------------------------------------- 1 | ### lookup table 2 | 3 | For runtime optimizations of the search in async nodes we can provide tokens as a second argument in an `addPromise ` 4 | 5 | ```ts 6 | let n = createContainer() 7 | .addNode({ a: 1, b: 2 }) 8 | .addPromise( 9 | async (c) => { 10 | return { c: 3, d: 4 } 11 | }, 12 | ["c", "d"], // <-- this might be a purely runtime optimization to index the lookup and make code even more lazy 13 | ) 14 | .addPromiseStrict( 15 | // Or even better add a new method 16 | async (c) => { 17 | return { c: 3, d: 4 } 18 | }, 19 | ["c", "d"], // that forces you to list all deps keys and TS can actually check it!!! 20 | ) 21 | ``` 22 | 23 | ### Nano emitter 24 | 25 | try nano emitter from evil martians but check if they support multiple subscribes gracefully via `node lol.js` 26 | 27 | ### Disable promise for `addNode` 28 | 29 | add node should TS throw if pass async. TYpescirpt should lookup return type and dissallow promise return type 30 | 31 | ### Disable overrides for `addNode` 32 | 33 | we can TS throw if we see that user has provided a duplicate token. 34 | Dublicate tokens maybe then could be added via `overrideNode` 35 | 36 | ### Add options for `addNode` 37 | 38 | - first option is a lookup table of tokens 39 | - second idea would be a force override parameter when you want to force flush changes 40 | -------------------------------------------------------------------------------- /iti-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti-react", 3 | "version": "0.7.0", 4 | "description": "Handy React bindings for iti, a ~1kB Typesafe dependency injection framework for TypeScript and JavaScript with a unique support for async flow", 5 | "type": "module", 6 | "sideEffects": false, 7 | "module": "./dist/iti-react.js", 8 | "typings": "./dist/index.d.ts", 9 | "types": "./dist/index.d.ts", 10 | "exports": { 11 | ".": { 12 | "import": { 13 | "types": "./dist/index.d.ts", 14 | "default": "./dist/iti-react.js" 15 | }, 16 | "default": "./dist/iti-react.js" 17 | }, 18 | "./package.json": "./package.json" 19 | }, 20 | "source": "src/index.ts", 21 | "publishConfig": { 22 | "source": "./src/index.ts", 23 | "module": "./dist/iti-react.js" 24 | }, 25 | "files": [ 26 | "dist" 27 | ], 28 | "scripts": { 29 | "build": "rm -rf ./dist && microbundle -o dist/ --jsx React.createElement --format modern", 30 | "dev": "microbundle watch -o dist/ --jsx React.createElement", 31 | "test": "yarn tsd", 32 | "tsd": "tsd tsd_project", 33 | "prepare-not": "install-peers" 34 | }, 35 | "dependencies": { 36 | "iti": "0.7.0", 37 | "utility-types": "^3.10.0" 38 | }, 39 | "devDependencies": { 40 | "@types/react": "^18.0.15", 41 | "install-peers-cli": "^2.2.0", 42 | "microbundle": "^0.15.1", 43 | "nodemon": "^2.0.19", 44 | "tsd": "^0.22.0", 45 | "typescript": "^4.7.4" 46 | }, 47 | "peerDependencies": { 48 | "react": ">=16", 49 | "react-dom": ">=17.0.2" 50 | }, 51 | "repository": "https://github.com/molszanski/iti", 52 | "homepage": "https://itijs.org", 53 | "author": "Nick Olszanski", 54 | "license": "MIT", 55 | "prettier": { 56 | "semi": false, 57 | "singleQuote": false, 58 | "arrowParens": "always", 59 | "trailingComma": "all" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /iti-react/src/API.md: -------------------------------------------------------------------------------- 1 | For container set there where a couple of options. 2 | I coded option 1 because it was easy. 3 | 4 | But now I want option 5 | 6 | ```js 7 | // Option 1 8 | let containerSet1 = await cont.getContainerSet(["aCont", "bCont", "cCont"]) 9 | 10 | // Option 2 11 | let containerSet2 = await cont.getContainerSet((c) => [ 12 | c.aCont, 13 | c.bCont, 14 | c.Cont, 15 | ]) 16 | 17 | // Option 3 18 | let containerSet2 = await cont.getContainerSet(({ aCont, bCont, cCont }) => ({ 19 | aCont, 20 | bCont, 21 | cCont, 22 | })) 23 | 24 | // Option 4 25 | let c = cont.tokens 26 | let containerSet2 = await cont.getContainerSet([c.aCont, c.bCont, c.Cont]) 27 | ``` 28 | -------------------------------------------------------------------------------- /iti-react/src/_utils.ts: -------------------------------------------------------------------------------- 1 | export type UnPromisify = T extends Promise ? U : T 2 | 3 | export type GetContainerFormat any> = 4 | UnPromisify> 5 | 6 | export function addGetter(object, key, fn: any) { 7 | Object.defineProperty(object, key, { 8 | get() { 9 | return fn() 10 | }, 11 | enumerable: true, 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /iti-react/src/index.ts: -------------------------------------------------------------------------------- 1 | // export { createContainer, RootContainer } from "./library.root-container" 2 | export type { GetContainerFormat, UnPromisify } from "./_utils" 3 | 4 | // React 5 | export { generateEnsureContainerSet } from "./react/library.generate-ensure-hooks.js" 6 | export { getContainerSetHooks } from "./react/library.hook-generator.js" 7 | -------------------------------------------------------------------------------- /iti-react/src/react/library.generate-ensure-hooks.ts: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react" 2 | 3 | export function generateEnsureContainerSet( 4 | containerSetGetterHook: (...args: any) => [ContainerSetContext, any], 5 | ) { 6 | const EnsureReactContext = React.createContext({} as any) 7 | 8 | function useThatContext() { 9 | return useContext(EnsureReactContext) 10 | } 11 | 12 | const EnsureContainer = (props: { 13 | fallback?: JSX.Element 14 | children: React.ReactNode 15 | }) => { 16 | let [containerSet, err] = containerSetGetterHook() 17 | if (!containerSet || err != null) { 18 | if (props.fallback) { 19 | return props.fallback 20 | } else { 21 | return null 22 | } 23 | } 24 | 25 | return React.createElement( 26 | EnsureReactContext.Provider, 27 | { value: containerSet }, 28 | props.children, 29 | ) 30 | } 31 | 32 | return { 33 | EnsureWrapper: EnsureContainer, 34 | contextHook: useThatContext, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /iti-react/src/react/library.hook-generator.ts: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react" 2 | import { useBetterGenericContainer } from "./library.hooks.js" 3 | import { addGetter } from "../_utils.js" 4 | 5 | import type { UnPromisify } from "../_utils" 6 | import type { Container, UnpackFunction, Prettify } from "iti" 7 | 8 | type UnpackTokenFromContext< 9 | CK extends keyof Context, 10 | Context extends {}, 11 | > = UnPromisify> 12 | 13 | type ContainerSet = { 14 | [S in Tokens]: UnpackTokenFromContext 15 | } 16 | 17 | export function getContainerSetHooks< 18 | Context extends object, 19 | DisposeContext extends object, 20 | >(reactContext: React.Context>) { 21 | function useContainer() { 22 | const root = useContext(reactContext) 23 | return useRootStores(root) 24 | } 25 | 26 | function useRootStores< 27 | /** 28 | * Basically a nice api for hooks 29 | * { 30 | * name: () => [containerInstance, err ] 31 | * } 32 | */ 33 | ContainerGetter extends { 34 | [CK in keyof Context]: Context[CK] extends any 35 | ? [UnpackTokenFromContext | undefined, any, CK] 36 | : never 37 | }, 38 | >(appRoot: Container): ContainerGetter { 39 | let FFF = {} 40 | let tokens = appRoot.getTokens() 41 | 42 | for (let contKey in tokens) { 43 | addGetter(FFF, contKey, () => 44 | useBetterGenericContainer( 45 | () => appRoot.containers[contKey as any], 46 | // @ts-expect-error 47 | (cb: () => any) => appRoot.subscribeToContainer(contKey, cb), 48 | contKey, 49 | ), 50 | ) 51 | } 52 | 53 | return FFF 54 | } 55 | 56 | function useContainerSet< 57 | Tokens extends keyof Context, 58 | TokenMap extends { [T in keyof Context]: T }, 59 | >( 60 | tokensOrCallback: Tokens[] | ((keyMap: TokenMap) => Tokens[]), 61 | ): [Prettify>, any] { 62 | const [all, setAll] = useState>( 63 | undefined as any, 64 | ) 65 | const [err, setErr] = useState(undefined as any) 66 | const root = useContext(reactContext) 67 | 68 | // WIP 69 | const tokens = 70 | typeof tokensOrCallback === "function" 71 | ? root._extractTokens(tokensOrCallback as any) 72 | : tokensOrCallback 73 | 74 | useEffect(() => { 75 | root.getContainerSet(tokens).then((contSet) => { 76 | setAll(contSet) 77 | }) 78 | }, tokens) 79 | 80 | useEffect(() => { 81 | const unsubscribe = root.subscribeToContainerSet( 82 | tokens, 83 | (err, contSet) => { 84 | if (err) { 85 | setErr(err) 86 | return 87 | } 88 | setAll(contSet) 89 | }, 90 | ) 91 | return unsubscribe 92 | }, tokens) 93 | 94 | return [all as any, err] 95 | } 96 | return { 97 | useContainer: useContainer, 98 | useContainerSet: useContainerSet, 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /iti-react/src/react/library.hooks.ts: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react" 2 | // -- Generic 3 | 4 | export type ContainerGeneric = { 5 | container?: T 6 | error?: Error 7 | key?: string 8 | } 9 | 10 | export type ContainerGenericBetter = [ 11 | container?: T, 12 | error?: Error, 13 | key?: string, 14 | ] 15 | 16 | export function useBetterGenericContainer( 17 | containerPromise: () => Promise, 18 | subscribeFunction: (cb: (err: any, container: T) => void) => () => void, 19 | containerKey: string, 20 | ): ContainerGenericBetter { 21 | const [data, setData] = useState(undefined) 22 | const [error, setError] = useState(undefined) 23 | 24 | // Update container 25 | useEffect(() => { 26 | return subscribeFunction((err, cont) => { 27 | if (err) { 28 | setError(err) 29 | setData(null) 30 | } 31 | setData(cont) 32 | }) 33 | }, [subscribeFunction]) 34 | 35 | // We can add optimizations later. 36 | useEffect(() => { 37 | try { 38 | // Apparently it will not always be a promise??? 39 | // Not sure what I meant to code :/ 40 | const providedValue = containerPromise() 41 | if (providedValue instanceof Promise) { 42 | providedValue 43 | .then((container) => { 44 | setData(container) 45 | }) 46 | .catch((e) => { 47 | setError(e) 48 | }) 49 | } else { 50 | setData(providedValue) 51 | } 52 | } catch (e) { 53 | setError(e) 54 | } 55 | }, []) 56 | 57 | return [data, error, containerKey] 58 | } 59 | -------------------------------------------------------------------------------- /iti-react/tests/_utils.ts: -------------------------------------------------------------------------------- 1 | export const wait = (w: number) => new Promise((r) => setTimeout(r, w)) 2 | -------------------------------------------------------------------------------- /iti-react/tests/mocks/_mock-app-components.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useMemo, ReactNode } from "react" 2 | import { generateEnsureContainerSet } from "../../src/index" 3 | import { useMockAppContainerSet, MyRootCont } from "./_mock-app-hooks" 4 | import { getMainMockAppContainer } from "./_mock-app-container" 5 | 6 | const x = generateEnsureContainerSet(() => 7 | useMockAppContainerSet(["aCont", "bCont"]), 8 | ) 9 | export const EnsureNewKitchenConainer = x.EnsureWrapper 10 | export const useNewKitchenContext = x.contextHook 11 | 12 | export function MockAppWrapper({ children }: { children: ReactNode }) { 13 | const store = useMemo(() => getMainMockAppContainer(), []) 14 | 15 | return {children} 16 | } 17 | -------------------------------------------------------------------------------- /iti-react/tests/mocks/_mock-app-container.ts: -------------------------------------------------------------------------------- 1 | import { createContainer } from "iti" 2 | 3 | import { provideAContainer } from "./container.a" 4 | import { provideBContainer } from "./container.b" 5 | import { provideCContainer } from "./container.c" 6 | 7 | export type MockAppNode = ReturnType 8 | export function getMainMockAppContainer() { 9 | let node = createContainer() 10 | let k = node 11 | .upsert({ aCont: async () => provideAContainer() }) 12 | .upsert((c) => { 13 | return { 14 | bCont: async () => provideBContainer(await c.aCont), 15 | } 16 | }) 17 | .upsert((c, node) => { 18 | return { 19 | cCont: async () => 20 | provideCContainer(await c.aCont, await node.get("bCont"), k), 21 | } 22 | }) 23 | return k 24 | } 25 | -------------------------------------------------------------------------------- /iti-react/tests/mocks/_mock-app-hooks.ts: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { getContainerSetHooks } from "../../src/react/library.hook-generator" 3 | import { MockAppNode } from "./_mock-app-container" 4 | 5 | export const MyRootCont = React.createContext({}) 6 | 7 | let mega = getContainerSetHooks(MyRootCont) 8 | export const useMockAppContainerSet = mega.useContainerSet 9 | export const useMockAppContainer = mega.useContainer 10 | -------------------------------------------------------------------------------- /iti-react/tests/mocks/container.a.ts: -------------------------------------------------------------------------------- 1 | import { A1, A2, A3 } from "./store.a" 2 | 3 | export interface A_Container { 4 | a1: A1 5 | a2: A2 6 | a3: A3 7 | } 8 | 9 | export async function provideAContainer(): Promise { 10 | const a1 = new A1() 11 | const a2 = new A2(a1) 12 | const a3 = new A3(a1, a2) 13 | 14 | return { 15 | a1, 16 | a2, 17 | a3, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /iti-react/tests/mocks/container.b.ts: -------------------------------------------------------------------------------- 1 | import type { A_Container } from "./container.a" 2 | import { B1, B2 } from "./store.b" 3 | 4 | export interface B_Container { 5 | b1: B1 6 | b2: B2 7 | } 8 | 9 | export async function provideBContainer(a: A_Container): Promise { 10 | const b1 = new B1(a.a2) 11 | 12 | const b2 = new B2(a.a1) 13 | 14 | return { 15 | b1, 16 | b2, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /iti-react/tests/mocks/container.c.ts: -------------------------------------------------------------------------------- 1 | import type { A_Container } from "./container.a" 2 | import { B_Container } from "./container.b" 3 | import { C1, C2 } from "./store.c" 4 | import { MockAppNode } from "./_mock-app-container" 5 | // import { MockAppContainer } from "./_mock-app-container" 6 | 7 | export interface C_Container { 8 | c1: C1 9 | c2: C2 10 | upgradeCContainer: (x?: number) => void 11 | } 12 | 13 | export async function provideCContainer( 14 | a: A_Container, 15 | b: B_Container, 16 | container: MockAppNode, 17 | ): Promise { 18 | const c1 = new C1(a.a2) 19 | const c2 = new C2(a.a1, b.b2, 5) 20 | 21 | async function replacer(ovenSize = 10) { 22 | const c1 = new C1(a.a2) 23 | const c2 = new C2(a.a1, b.b2, ovenSize) 24 | container.upsert(() => ({ 25 | cCont: async () => { 26 | return { 27 | c1, 28 | c2, 29 | upgradeCContainer: (ovenSize = 10) => replacer(ovenSize), 30 | } 31 | }, 32 | })) 33 | } 34 | 35 | return { 36 | c1, 37 | c2, 38 | upgradeCContainer: replacer, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /iti-react/tests/mocks/store.a.ts: -------------------------------------------------------------------------------- 1 | export class A1 { 2 | constructor() {} 3 | } 4 | export class A2 { 5 | constructor(private a1: A1) {} 6 | } 7 | export class A3 { 8 | constructor(private a1: A1, private a2: A2) {} 9 | } 10 | -------------------------------------------------------------------------------- /iti-react/tests/mocks/store.b.ts: -------------------------------------------------------------------------------- 1 | import type { A1, A2 } from "./store.a" 2 | 3 | export class B1 { 4 | constructor(private a2: A2) { 5 | // 1. Subscribe to event emitter 6 | // 2. Pub update 7 | } 8 | } 9 | export class B2 { 10 | constructor(private a1: A1) {} 11 | } 12 | -------------------------------------------------------------------------------- /iti-react/tests/mocks/store.c.ts: -------------------------------------------------------------------------------- 1 | import type { A1, A2 } from "./store.a" 2 | import { B2 } from "./store.b" 3 | 4 | export class C1 { 5 | constructor(private a2: A2) { 6 | // 1. Subscribe to event emitter 7 | // 2. Pub update 8 | } 9 | } 10 | export class C2 { 11 | constructor(private a1: A1, b2: B2, readonly size) {} 12 | } 13 | -------------------------------------------------------------------------------- /iti-react/tests/react-hooks-check-types.tsd-only.ts: -------------------------------------------------------------------------------- 1 | import { expectType, expectNotType } from "tsd" 2 | import { 3 | useMockAppContainer, 4 | useMockAppContainerSet, 5 | } from "./mocks/_mock-app-hooks" 6 | import { A_Container } from "./mocks/container.a" 7 | import { B_Container } from "./mocks/container.b" 8 | import { C_Container } from "./mocks/container.c" 9 | import { MockAppWrapper } from "./mocks/_mock-app-components" 10 | 11 | // useMockAppContainer should not return `any` type" 12 | ;(() => { 13 | const containers = useMockAppContainer() 14 | expectNotType(containers) 15 | })() 16 | 17 | // useMockAppContainer should test if useMockAppContainer gets correct types 18 | ;(() => { 19 | const [aContainer] = useMockAppContainer().aCont 20 | expectType(aContainer) 21 | 22 | if (aContainer != null) { 23 | expectType(aContainer) 24 | } 25 | })() 26 | 27 | // useMockAppContainerSet should not return any 28 | ;(() => { 29 | const containerSet = useMockAppContainerSet(["aCont", "bCont"]) 30 | expectNotType(containerSet) 31 | })() 32 | 33 | // useMockAppContainerSet should return exact types 34 | ;(() => { 35 | const [containerSet, containerSetErr] = useMockAppContainerSet([ 36 | "aCont", 37 | "bCont", 38 | ]) 39 | 40 | const [containerSet2, containerSetErr2] = useMockAppContainerSet((c) => [ 41 | c.aCont, 42 | c.bCont, 43 | ]) 44 | 45 | if (containerSet != null) { 46 | expectType(containerSet.aCont) 47 | // expectType(containerSet.cCont) 48 | } 49 | 50 | if (containerSet2 != null) { 51 | expectType(containerSet2.bCont) 52 | // expectType(containerSet.cCont) 53 | } 54 | })() 55 | -------------------------------------------------------------------------------- /iti-react/tests/types-for-tests.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /iti-react/tests/z.container-hook.spec.tsx.bkp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | THIS file is on hold because I am not that smart to test these react hooks 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | import React from "react" 15 | import { renderHook, act } from "@testing-library/react-hooks/dom" 16 | // import { renderHook, } from '@testing-library/react-hooks" 17 | import { expectType, expectError } from "tsd" 18 | 19 | import { 20 | useMockAppContainer, 21 | useMockAppContainerSet, 22 | useMockAppContainerSetNew, 23 | } from "./mocks/_mock-app-hooks" 24 | import { A_Container } from "./mocks/container.a" 25 | import { B_Container } from "./mocks/container.b" 26 | import { C_Container } from "./mocks/container.c" 27 | import { MockAppWrapper } from "./mocks/_mock-app-components" 28 | 29 | // it("should pass basic ts test", () => { 30 | // let [authCont] = useContainer().auth 31 | // expectType(authCont) 32 | // }) 33 | 34 | // const App = () => <>learn react 35 | // // https://github.com/SamVerschueren/tsd 36 | 37 | // test("renders learn react link", () => { 38 | // render() 39 | // const linkElement = screen.getByText(/learn react/i) 40 | // expect(linkElement).toBeInTheDocument() 41 | // }) 42 | 43 | /** 44 | * Testing react hooks is waste of time 45 | */ 46 | 47 | it.only("should pass basic ts test", (cb) => { 48 | ;(async () => { 49 | let a = 12 50 | // 51 | // result.current.increment() 52 | const wrapper = (p) => {p.children} 53 | const { result } = renderHook(() => useMockAppContainer(), { 54 | wrapper, 55 | }) 56 | console.log("rez", result) 57 | console.log("rez", result.all) 58 | console.log("rez", result.current) 59 | 60 | await act(async () => { 61 | return result.current.aCont 62 | }) 63 | // const { result } = renderHook(() => useMockAppContainer().aCont) 64 | 65 | expect(a).toBe(12) 66 | cb() 67 | })() 68 | 69 | // let [aContainer] = 70 | // let contSet = useMockAppContainerSetNew((c) => [c.aCont, c.bCont, c.cCont]) 71 | 72 | // // @ts-expect-error 73 | // expectType(aContainer) // because aContainer should be undefined on first render 74 | 75 | // expect(() => { 76 | // // @ts-expect-error 77 | // expectError(contSet.cCont) 78 | // }).toThrow() 79 | 80 | // if (aContainer == null) return null 81 | // if (contSet == null) return null 82 | 83 | // expectType(aContainer) 84 | // expectType(contSet.bCont) 85 | 86 | // return null 87 | 88 | // const App = () => { 89 | // let [aContainer] = useMockAppContainer().aCont 90 | // let contSet = useMockAppContainerSetNew((c) => [c.aCont, c.bCont, c.cCont]) 91 | 92 | // // @ts-expect-error 93 | // expectType(aContainer) // because aContainer should be undefined on first render 94 | 95 | // expect(() => { 96 | // // @ts-expect-error 97 | // expectError(contSet.cCont) 98 | // }).toThrow() 99 | 100 | // if (aContainer == null) return null 101 | // if (contSet == null) return null 102 | 103 | // expectType(aContainer) 104 | // expectType(contSet.bCont) 105 | 106 | // return null 107 | // } 108 | 109 | // render( 110 | // 111 | // 112 | // , 113 | // ) 114 | }) 115 | -------------------------------------------------------------------------------- /iti-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "outDir": "dist/", 5 | "declarationDir": "dist/", 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "types": ["@types/jest"], 8 | "noImplicitAny": false, 9 | "experimentalDecorators": true, 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "declaration": true, 22 | "jsx": "react" 23 | }, 24 | "include": ["src/**/*"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /iti-react/tsd_project/dummy.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/iti-react/tsd_project/dummy.d.ts -------------------------------------------------------------------------------- /iti-react/tsd_project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": "dummy.d.ts", 3 | "tsd": { 4 | "directory": "../tests" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iti/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | coverage 4 | # stryker temp files 5 | .stryker-tmp 6 | .env 7 | reports -------------------------------------------------------------------------------- /iti/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nick Olszanski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /iti/Makefile: -------------------------------------------------------------------------------- 1 | # Sane defaults 2 | SHELL := /bin/bash 3 | .ONESHELL: 4 | .SHELLFLAGS := -eu -o pipefail -c 5 | .DELETE_ON_ERROR: 6 | MAKEFLAGS += --warn-undefined-variables 7 | MAKEFLAGS += --no-builtin-rules 8 | # Default params 9 | # environment ?= "dev" 10 | 11 | # ---------------------- COMMANDS --------------------------- 12 | 13 | 14 | bin = ./node_modules/.bin 15 | 16 | build: ## Build the project 17 | @ rm -rf ./dist 18 | $(bin)/microbundle build -o dist/ --format cjs,modern 19 | find ./dist -name "*.d.ts" -exec sh -c 'cp "$$1" "$${1%%.d.ts}.d.mts"' sh {} \; 20 | find ./dist -name "*.d.ts" -exec sh -c 'cp "$$1" "$${1%%.d.ts}.d.cts"' sh {} \; 21 | mv ./dist/iti.cjs ./dist/index.cjs 22 | mv ./dist/iti.cjs.map ./dist/index.cjs.map 23 | mv ./dist/iti.mjs ./dist/index.mjs 24 | mv ./dist/iti.mjs.map ./dist/index.mjs.map 25 | sed -i.bak "s/iti.mjs.map/index.mjs.map/g" ./dist/index.mjs && rm -f ./dist/index.mjs.bak 26 | sed -i.bak "s/iti.cjs.map/index.cjs.map/g" ./dist/index.cjs && rm -f ./dist/index.cjs.bak 27 | ls -l ./dist 28 | 29 | # https://www.npmjs.com/package/@arethetypeswrong/cli 30 | check: ## Check the project 31 | make build 32 | npm pack 33 | attw ./iti-0.7.0-alpha.4.tgz 34 | # find ./dist -name "*.d.ts" -delete; 35 | 36 | 37 | # ----------------------------------------------------------- 38 | # CAUTION: If you have a file with the same name as make 39 | # command, you need to add it to .PHONY below, otherwise it 40 | # won't work. E.g. `make run` wouldn't work if you have 41 | # `run` file in pwd. 42 | .PHONY: help ssl 43 | 44 | # ----------------------------------------------------------- 45 | # ----- (Makefile helpers and decoration) -------- 46 | # ----------------------------------------------------------- 47 | 48 | .DEFAULT_GOAL := help 49 | # check https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences 50 | NC = \033[0m 51 | ERR = \033[31;1m 52 | TAB := '%-20s' # Increase if you have long commands 53 | 54 | # tput colors 55 | red := $(shell tput setaf 1) 56 | green := $(shell tput setaf 2) 57 | yellow := $(shell tput setaf 3) 58 | blue := $(shell tput setaf 4) 59 | cyan := $(shell tput setaf 6) 60 | cyan80 := $(shell tput setaf 86) 61 | grey500 := $(shell tput setaf 244) 62 | grey300 := $(shell tput setaf 240) 63 | bold := $(shell tput bold) 64 | underline := $(shell tput smul) 65 | reset := $(shell tput sgr0) 66 | 67 | help: 68 | @printf '\n' 69 | @printf ' $(underline)$(grey500)Available make commands:$(reset)\n\n' 70 | @# Print non-check commands with comments 71 | @grep -E '^([a-zA-Z0-9_-]+\.?)+:.+#.+$$' $(MAKEFILE_LIST) \ 72 | | grep -v '^check-' \ 73 | | grep -v '^env-' \ 74 | | grep -v '^arg-' \ 75 | | sed 's/:.*#/: #/g' \ 76 | | awk 'BEGIN {FS = "[: ]+#[ ]+"}; \ 77 | {printf " $(grey300) make $(reset)$(cyan80)$(bold)$(TAB) $(reset)$(grey300)# %s$(reset)\n", \ 78 | $$1, $$2}' 79 | @grep -E '^([a-zA-Z0-9_-]+\.?)+:( +\w+-\w+)*$$' $(MAKEFILE_LIST) \ 80 | | grep -v help \ 81 | | awk 'BEGIN {FS = ":"}; \ 82 | {printf " $(grey300) make $(reset)$(cyan80)$(bold)$(TAB)$(reset)\n", \ 83 | $$1}' 84 | @echo -e "\n $(underline)$(grey500)Helper/Checks$(reset)\n" 85 | @grep -E '^([a-zA-Z0-9_-]+\.?)+:.+#.+$$' $(MAKEFILE_LIST) \ 86 | | grep -E '^(check|arg|env)-' \ 87 | | awk 'BEGIN {FS = "[: ]+#[ ]+"}; \ 88 | {printf " $(grey300) make $(reset)$(grey500)$(TAB) $(reset)$(grey300)# %s$(reset)\n", \ 89 | $$1, $$2}' 90 | @echo -e "" 91 | -------------------------------------------------------------------------------- /iti/notes-and-ideas.md: -------------------------------------------------------------------------------- 1 | ### lookup table 2 | 3 | For runtime optimizations of the search in async nodes we can provide tokens as a second argument in an `addPromise ` 4 | 5 | ```ts 6 | let n = createContainer() 7 | .addNode({ a: 1, b: 2 }) 8 | .addPromise( 9 | async (c) => { 10 | return { c: 3, d: 4 } 11 | }, 12 | ["c", "d"], // <-- this might be a purely runtime optimization to index the lookup and make code even more lazy 13 | ) 14 | .addPromiseStrict( 15 | // Or even better add a new method 16 | async (c) => { 17 | return { c: 3, d: 4 } 18 | }, 19 | ["c", "d"], // that forces you to list all deps keys and TS can actually check it!!! 20 | ) 21 | ``` 22 | 23 | ### Nano emitter 24 | 25 | try nano emitter from evil martians but check if they support multiple subscribes gracefully via `node lol.js` 26 | 27 | ### Disable promise for `addNode` 28 | 29 | add node should TS throw if pass async. TYpescirpt should lookup return type and dissallow promise return type 30 | 31 | ### Disable overrides for `addNode` 32 | 33 | we can TS throw if we see that user has provided a duplicate token. 34 | Dublicate tokens maybe then could be added via `overrideNode` 35 | 36 | ### Add options for `addNode` 37 | 38 | - first option is a lookup table of tokens 39 | - second idea would be a force override parameter when you want to force flush changes 40 | 41 | # notes 42 | 43 | For container set there where a couple of options. 44 | I coded option 1 because it was easy. 45 | 46 | But now I want option 47 | 48 | ```js 49 | // Option 1 50 | let containerSet1 = await cont.getContainerSet(["aCont", "bCont", "cCont"]) 51 | 52 | // Option 2 53 | let containerSet2 = await cont.getContainerSet((c) => [ 54 | c.aCont, 55 | c.bCont, 56 | c.Cont, 57 | ]) 58 | 59 | // Option 3 60 | let containerSet2 = await cont.getContainerSet(({ aCont, bCont, cCont }) => ({ 61 | aCont, 62 | bCont, 63 | cCont, 64 | })) 65 | 66 | // Option 4 67 | let c = cont.tokens 68 | let containerSet2 = await cont.getContainerSet([c.aCont, c.bCont, c.Cont]) 69 | ``` 70 | 71 | ```js 72 | // fromEntries :: [[a, b]] -> {a: b} 73 | // Does the reverse of Object.entries. 74 | const fromEntries = (list) => { 75 | const result = {} 76 | 77 | for (let [key, value] of list) { 78 | result[key] = value 79 | } 80 | 81 | return result 82 | } 83 | 84 | // addAsset :: (k, Promise a) -> Promise (k, a) 85 | const addAsset = ([name, assetPromise]) => 86 | assetPromise.then((asset) => [name, asset]) 87 | 88 | // loadAll :: {k: Promise a} -> Promise {k: a} 89 | const loadAll = (assets) => 90 | Promise.all(Object.entries(assets).map(addAsset)).then(fromEntries) 91 | ``` 92 | -------------------------------------------------------------------------------- /iti/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti", 3 | "version": "0.7.0", 4 | "description": "~1kB Dependency Injection Library for Typescript and React with a unique async flow support", 5 | "sideEffects": false, 6 | "type": "module", 7 | "main": "./dist/index.cjs", 8 | "module": "./dist/index.mjs", 9 | "typings": "./dist/index.d.mts", 10 | "types": "./dist/index.d.mts", 11 | "exports": { 12 | ".": { 13 | "import": { 14 | "types": "./dist/index.d.mts", 15 | "default": "./dist/index.mjs" 16 | }, 17 | "require": { 18 | "types": "./dist/index.d.cts", 19 | "default": "./dist/index.cjs" 20 | }, 21 | "default": "./dist/index.mjs" 22 | }, 23 | "./package.json": "./package.json" 24 | }, 25 | "source": "src/index.ts", 26 | "publishConfig": { 27 | "source": "./src/index.ts", 28 | "main": "./dist/index.cjs", 29 | "module": "./dist/index.mjs" 30 | }, 31 | "files": [ 32 | "dist" 33 | ], 34 | "scripts": { 35 | "build": "make build", 36 | "stryker": "stryker", 37 | "stryker:run": "stryker run", 38 | "test": "vitest && yarn tsd", 39 | "tsd": "tsd tsd_project", 40 | "v": "vitest", 41 | "vitest:types": "vitest --typecheck" 42 | }, 43 | "dependencies": { 44 | "utility-types": "^3.10.0" 45 | }, 46 | "devDependencies": { 47 | "@stryker-mutator/core": "^8.6.0", 48 | "@stryker-mutator/jest-runner": "^8.6.0", 49 | "@stryker-mutator/typescript-checker": "^8.6.0", 50 | "@stryker-mutator/vitest-runner": "^8.6.0", 51 | "@types/jest": "^29.5.14", 52 | "@types/react": "^18.0.15", 53 | "jest": "^29.7.0", 54 | "microbundle": "^0.15.1", 55 | "nodemon": "^2.0.19", 56 | "stryker-cli": "^1.0.2", 57 | "ts-jest": "^29.2.5", 58 | "tsd": "^0.22.0", 59 | "typescript": "^4.7.4", 60 | "vitest": "^2.1.4" 61 | }, 62 | "engines": { 63 | "node": ">=12" 64 | }, 65 | "license": "MIT", 66 | "authors": [ 67 | "Nick Olszanski " 68 | ], 69 | "repository": "molszanski/iti", 70 | "keywords": [ 71 | "ioc", 72 | "di", 73 | "inversion of control", 74 | "dependency injection", 75 | "dependency inversion", 76 | "inversion of control container", 77 | "container", 78 | "javascript", 79 | "typescript", 80 | "type-safe" 81 | ], 82 | "homepage": "https://itijs.org", 83 | "// CONFIGS: ": "Package configs", 84 | "prettier": { 85 | "semi": false, 86 | "singleQuote": false, 87 | "arrowParens": "always", 88 | "trailingComma": "all" 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /iti/src/_utils.ts: -------------------------------------------------------------------------------- 1 | export type UnPromisify = T extends Promise ? U : T 2 | 3 | export type GetContainerFormat any> = 4 | UnPromisify> 5 | 6 | export function addGetter(object, key, fn: any) { 7 | Object.defineProperty(object, key, { 8 | get() { 9 | return fn() 10 | }, 11 | enumerable: true, 12 | }) 13 | } 14 | 15 | export type Assign = { 16 | [Token in keyof ({ 17 | [K in keyof OldContext]: OldContext[K] 18 | } & { 19 | [K in keyof NewContext]: NewContext[K] 20 | })]: Token extends keyof NewContext 21 | ? NewContext[Token] 22 | : Token extends keyof OldContext 23 | ? OldContext[Token] 24 | : never 25 | } 26 | 27 | export type Prettify = T extends infer U ? { [K in keyof U]: U[K] } : never 28 | 29 | export type UnpackFunction = T extends (...args: any) => infer U ? U : T 30 | export type UnpackObject = { 31 | [K in keyof T]: UnpackFunction 32 | } 33 | 34 | export type UnpromisifyObject = { 35 | [K in keyof T]: UnPromisify 36 | } 37 | // keep 38 | // type AssignAndUnpackObjects = UnpromisifyObject< 39 | // UnpackObject> 40 | // > 41 | 42 | export type FullyUnpackObject = UnpromisifyObject> 43 | 44 | export type KeysOrCb = 45 | | Array 46 | | ((t: { [K in keyof Context]: K }) => Array) 47 | 48 | export type MyRecord = { 49 | [K in keyof O]: T 50 | } 51 | export type ContextGetter = { 52 | [CK in keyof Context]: UnpackFunction 53 | } 54 | 55 | export type UnpackFunctionReturn = T extends (arg: T) => infer U ? U : T 56 | export type ContextGetterWithCache = { 57 | [CK in keyof Context]: UnpackFunctionReturn 58 | } 59 | 60 | export function _intersectionKeys( 61 | needle: { [key: string]: any }, 62 | haystack: { [key: string]: any }, 63 | ) { 64 | let haystackKeys = Object.keys(haystack) 65 | let duplicates = haystackKeys.filter((x) => x in needle) 66 | if (duplicates.length === 0) { 67 | return undefined 68 | } 69 | return duplicates.join("', '") 70 | } 71 | -------------------------------------------------------------------------------- /iti/src/errors.ts: -------------------------------------------------------------------------------- 1 | export class ItiError extends Error {} 2 | export class ItiResolveError extends ItiError {} 3 | export class ItiTokenError extends ItiError {} 4 | -------------------------------------------------------------------------------- /iti/src/index.ts: -------------------------------------------------------------------------------- 1 | // Main lib 2 | export { 3 | createContainer, 4 | createContainer as makeRoot, 5 | Container, 6 | } from "./iti.js" 7 | 8 | // Helper types 9 | export type { 10 | GetContainerFormat, 11 | UnPromisify, 12 | UnpackFunction, 13 | Prettify, 14 | } from "./_utils.js" 15 | export type { Emitter } from "./nanoevents.js" 16 | -------------------------------------------------------------------------------- /iti/src/nanoevents.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * From 3 | * https://github.com/ai/nanoevents 4 | * 5 | * Sadly, can't install it via npm. Some build issue 6 | */ 7 | 8 | ////////////////// 9 | 10 | interface EventsMap { 11 | [event: string]: any 12 | } 13 | 14 | interface DefaultEvents extends EventsMap { 15 | [event: string]: (...args: any) => void 16 | } 17 | 18 | export interface Unsubscribe { 19 | (): void 20 | } 21 | 22 | export declare class Emitter { 23 | /** 24 | * Event names in keys and arrays with listeners in values. 25 | * 26 | * ```js 27 | * emitter1.events = emitter2.events 28 | * emitter2.events = { } 29 | * ``` 30 | */ 31 | events: Partial<{ [E in keyof Events]: Events[E][] }> 32 | 33 | /** 34 | * Add a listener for a given event. 35 | * 36 | * ```js 37 | * const unbind = ee.on('tick', (tickType, tickDuration) => { 38 | * count += 1 39 | * }) 40 | * 41 | * disable () { 42 | * unbind() 43 | * } 44 | * ``` 45 | * 46 | * @param event The event name. 47 | * @param cb The listener function. 48 | * @returns Unbind listener from event. 49 | */ 50 | on(this: this, event: K, cb: Events[K]): Unsubscribe 51 | 52 | /** 53 | * Calls each of the listeners registered for a given event. 54 | * 55 | * ```js 56 | * ee.emit('tick', tickType, tickDuration) 57 | * ``` 58 | * 59 | * @param event The event name. 60 | * @param args The arguments for listeners. 61 | */ 62 | emit( 63 | this: this, 64 | event: K, 65 | ...args: Parameters 66 | ): void 67 | } 68 | 69 | /** 70 | * Create event emitter. 71 | * 72 | * ```js 73 | * import { createNanoEvents } from 'nanoevents' 74 | * 75 | * class Ticker { 76 | * constructor() { 77 | * this.emitter = createNanoEvents() 78 | * } 79 | * on(...args) { 80 | * return this.emitter.on(...args) 81 | * } 82 | * tick() { 83 | * this.emitter.emit('tick') 84 | * } 85 | * } 86 | * ``` 87 | */ 88 | 89 | export function createNanoEvents< 90 | Events extends EventsMap = DefaultEvents, 91 | >(): Emitter { 92 | return { 93 | events: {}, 94 | emit(event, ...args) { 95 | // @ts-ignore 96 | ;(this.events[event] || []).forEach((i) => i(...args)) 97 | }, 98 | on(event, cb) { 99 | // @ts-ignore 100 | ;(this.events[event] = this.events[event] || []).push(cb) 101 | return () => 102 | // @ts-ignore 103 | (this.events[event] = (this.events[event] || []).filter( 104 | (i) => i !== cb, 105 | )) 106 | }, 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /iti/stryker.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/stryker-mutator/stryker/master/packages/api/schema/stryker-core.json", 3 | "_comment": "This config was generated using 'stryker init'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information", 4 | "packageManager": "yarn", 5 | "_commandRunner": { "command": "yarn vitest" }, 6 | "reporters": ["html", "clear-text", "progress", "dashboard", "json"], 7 | "testRunner": "vitest", 8 | "disableTypeChecks": "{test,tests,src,lib}/**/*.{js,ts,jsx,tsx,html,vue}", 9 | "coverageAnalysis": "off", 10 | "checkers": ["typescript"], 11 | "tsconfigFile": "tsconfig.json" 12 | } 13 | -------------------------------------------------------------------------------- /iti/tests/__snapshots__/container-getter.vi.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Getter tests > should get a single container 1`] = ` 4 | { 5 | "b1": B1 { 6 | "a2": A2 { 7 | "a1": A1 {}, 8 | }, 9 | }, 10 | "b2": B2 { 11 | "a1": A1 {}, 12 | }, 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /iti/tests/__snapshots__/container-set.vi.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Container set: > should get container set via a new API 1`] = ` 4 | { 5 | "aCont": { 6 | "a1": {}, 7 | "a2": { 8 | "a1": {}, 9 | }, 10 | "a3": { 11 | "a1": {}, 12 | "a2": { 13 | "a1": {}, 14 | }, 15 | }, 16 | }, 17 | "bCont": { 18 | "b1": { 19 | "a2": { 20 | "a1": {}, 21 | }, 22 | }, 23 | "b2": { 24 | "a1": {}, 25 | }, 26 | }, 27 | } 28 | `; 29 | 30 | exports[`Container set: > should get two containers that are async 1`] = ` 31 | { 32 | "aCont": { 33 | "a1": {}, 34 | "a2": { 35 | "a1": {}, 36 | }, 37 | "a3": { 38 | "a1": {}, 39 | "a2": { 40 | "a1": {}, 41 | }, 42 | }, 43 | }, 44 | "bCont": { 45 | "b1": { 46 | "a2": { 47 | "a1": {}, 48 | }, 49 | }, 50 | "b2": { 51 | "a1": {}, 52 | }, 53 | }, 54 | } 55 | `; 56 | -------------------------------------------------------------------------------- /iti/tests/__snapshots__/getter.vi.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`Node getter > should get nested containers 1`] = ` 4 | { 5 | "b1": B1 { 6 | "a2": A2 { 7 | "a1": A1 {}, 8 | }, 9 | }, 10 | "b2": B2 { 11 | "a1": A1 {}, 12 | }, 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /iti/tests/_utils.ts: -------------------------------------------------------------------------------- 1 | export const wait = (w: number) => new Promise((r) => setTimeout(r, w)) 2 | -------------------------------------------------------------------------------- /iti/tests/container-get-values.vi.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach, vi } from "vitest" 2 | import { createContainer } from "../src/iti" 3 | 4 | describe("Node.get()", () => { 5 | let c0 = createContainer() 6 | beforeEach(() => (c0 = createContainer())) 7 | 8 | it("should return a value as a value", async () => { 9 | const c1 = c0.add({ a: 123 }) 10 | expect(c1.get("a")).toBe(123) 11 | }) 12 | 13 | it("should throw if a token is missing", async () => { 14 | const c1 = c0.add({ a: 123 }) 15 | expect(() => { 16 | // @ts-expect-error 17 | c1.get("c") 18 | }).toThrowError() 19 | }) 20 | it("should return function result and not a function", async () => { 21 | const c1 = c0.add({ 22 | functionTOken: () => "optimus", 23 | }) 24 | expect(c1.get("functionTOken")).toBe("optimus") 25 | }) 26 | 27 | it("should return correct tokens for merged and overridden nodes", () => { 28 | const c = c0.add({ optimus: () => "prime", a: 123 }).upsert({ a: "123" }) 29 | expect(c.getTokens()).toMatchObject({ 30 | optimus: "optimus", 31 | a: "a", 32 | }) 33 | }) 34 | 35 | it("should return cached value of a function", async () => { 36 | let fn = vi.fn() 37 | const c1 = c0.add({ 38 | optimus: () => { 39 | fn() 40 | return "prime" 41 | }, 42 | }) 43 | c1.get("optimus") 44 | c1.get("optimus") 45 | c1.get("optimus") 46 | expect(c1.get("optimus")).toBe("prime") 47 | expect(fn).toHaveBeenCalledTimes(1) 48 | }) 49 | 50 | it("should return promises of async functions", async () => { 51 | const c1 = c0.add({ 52 | optimus: async () => "prime", 53 | }) 54 | expect(await c1.get("optimus")).toBe("prime") 55 | }) 56 | 57 | it("should handle async errors with a simple try/catch", async () => { 58 | const node = c0 59 | .add({ 60 | optimus: async () => "prime", 61 | megatron: async () => { 62 | throw "all hail megatron" 63 | }, 64 | }) 65 | .add((ctx) => ({ 66 | decepticons: async () => { 67 | leader: await ctx.megatron 68 | }, 69 | })) 70 | 71 | expect(await node.get("optimus")).toBe("prime") 72 | try { 73 | await node.get("megatron") 74 | } catch (e) { 75 | expect(e).toBe("all hail megatron") 76 | } 77 | 78 | try { 79 | await node.items.decepticons 80 | } catch (e) { 81 | expect(e).toBe("all hail megatron") 82 | } 83 | }) 84 | 85 | it("should handle async errors with for getContainerSet", async () => { 86 | const node = c0 87 | .add({ 88 | optimus: async () => "prime", 89 | megatron: async () => { 90 | throw "all hail megatron" 91 | }, 92 | }) 93 | .add((ctx) => ({ 94 | decepticons: async () => { 95 | leader: await ctx.megatron 96 | }, 97 | })) 98 | 99 | try { 100 | await node.getContainerSet(["optimus", "decepticons"]) 101 | } catch (e) { 102 | expect(e).toBe("all hail megatron") 103 | } 104 | }) 105 | 106 | it("should call container provider once, but container token twice", () => { 107 | const fn1 = vi.fn() 108 | const fn2 = vi.fn() 109 | 110 | const node = c0.add({ 111 | autobots: () => { 112 | fn1() 113 | return { 114 | optimus: () => { 115 | fn2() 116 | return "autobots assemble" 117 | }, 118 | bumblebee: "bumblebee", 119 | jazz: "jazz", 120 | } 121 | }, 122 | }) 123 | 124 | expect(fn1).not.toBeCalled() 125 | expect(fn2).not.toBeCalled() 126 | 127 | let a1 = node.get("autobots") 128 | a1.optimus() 129 | let a2 = node.get("autobots") 130 | a2.optimus() 131 | expect(fn1).toHaveBeenCalledTimes(1) 132 | expect(fn2).toHaveBeenCalledTimes(2) 133 | }) 134 | }) 135 | -------------------------------------------------------------------------------- /iti/tests/container-getter.vi.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, beforeEach, vi } from "vitest" 2 | import { createContainer } from "../src/iti" 3 | import { getMainMockAppContainer } from "./mocks/_mock-app-container" 4 | import { wait } from "./_utils" 5 | 6 | describe("Getter tests", () => { 7 | let c0 = createContainer() 8 | beforeEach(() => (c0 = createContainer())) 9 | 10 | it("should get a single container", async () => { 11 | const cont = getMainMockAppContainer() 12 | expect(cont.items).toHaveProperty("bCont") 13 | expect(cont.items.aCont).toBeInstanceOf(Promise) 14 | 15 | let b = await cont.items.bCont 16 | expect(b).toHaveProperty("b2") 17 | expect(b).toMatchSnapshot() 18 | }) 19 | 20 | it("should subscribe to a single container", async () => { 21 | const cont = getMainMockAppContainer() 22 | expect(cont.items).toHaveProperty("bCont") 23 | expect(cont.items.aCont).toBeInstanceOf(Promise) 24 | 25 | let m = vi.fn() 26 | cont.subscribeToContainer("cCont", m) 27 | let cCont = await cont.get("cCont") 28 | cCont.upgradeCContainer() 29 | 30 | let c = await cont.get("cCont") 31 | expect(m).toHaveBeenCalled() 32 | expect(c.c2.size).toBe(10) 33 | await wait(20) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /iti/tests/container-set.vi.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from "vitest" 2 | import { getMainMockAppContainer } from "./mocks/_mock-app-container" 3 | import { wait } from "./_utils" 4 | 5 | describe("Container set:", () => { 6 | it("should get two containers that are async", async () => { 7 | const cont = getMainMockAppContainer() 8 | let containerSet = await cont.getContainerSet(["aCont", "bCont"]) 9 | 10 | expect(containerSet).toHaveProperty("aCont") 11 | expect(containerSet).toHaveProperty("bCont") 12 | expect(containerSet.bCont.b2).toMatchObject({ a1: {} }) 13 | 14 | expect(containerSet).toMatchSnapshot(containerSet) 15 | }) 16 | 17 | it("should subscribe to container set change", async () => { 18 | const cont = getMainMockAppContainer() 19 | let containerSet = await cont.getContainerSet(["aCont", "bCont", "cCont"]) 20 | 21 | expect(containerSet).toHaveProperty("aCont") 22 | expect(containerSet).toHaveProperty("bCont") 23 | expect(containerSet.bCont.b2).toMatchObject({ a1: {} }) 24 | expect(containerSet.cCont.c2.size).toBe(5) 25 | 26 | containerSet.cCont.upgradeCContainer() 27 | cont.subscribeToContainerSet( 28 | ["aCont", "bCont", "cCont"], 29 | (err, containerSet) => { 30 | expect(containerSet.cCont.c2.size).toBe(10) 31 | }, 32 | ) 33 | await cont.get("cCont") 34 | await wait(10) 35 | }) 36 | 37 | it("should get container set via a new API", async () => { 38 | const cont = getMainMockAppContainer() 39 | let containerSet = await cont.getContainerSet((c) => [c.aCont, c.bCont]) 40 | 41 | expect(containerSet).toHaveProperty("aCont") 42 | expect(containerSet).toHaveProperty("bCont") 43 | expect(containerSet.bCont.b2).toMatchObject({ a1: {} }) 44 | expect(containerSet).toMatchSnapshot(containerSet) 45 | }) 46 | 47 | it("should subscribe to container set change via a new APi", async () => { 48 | const cont = getMainMockAppContainer() 49 | let containerSet = await cont.getContainerSet((c) => [c.aCont, c.cCont]) 50 | expect(containerSet).toHaveProperty("aCont") 51 | 52 | const a = vi.fn() 53 | cont.subscribeToContainerSet( 54 | (c) => { 55 | return [c.aCont, c.cCont] 56 | }, 57 | (err, containerSet) => { 58 | expect(containerSet.cCont.c2.size).toBe(10) 59 | a() 60 | }, 61 | ) 62 | containerSet.cCont.upgradeCContainer() 63 | await wait(10) 64 | expect(a).toHaveBeenCalledTimes(2) 65 | }) 66 | 67 | it("should subscribe to container set change via a old APi", async () => { 68 | const cont = getMainMockAppContainer() 69 | let containerSet = await cont.getContainerSet(["aCont", "cCont"]) 70 | expect(containerSet).toHaveProperty("aCont") 71 | 72 | cont.subscribeToContainerSet( 73 | (c) => { 74 | return [c.aCont, c.cCont] 75 | }, 76 | (err, containerSet) => { 77 | expect(containerSet.cCont.c2.size).toBe(10) 78 | }, 79 | ) 80 | 81 | containerSet.cCont.upgradeCContainer() 82 | await wait(10) 83 | }) 84 | 85 | it("should be able to unsubscribe from container set change", async () => { 86 | const cont = getMainMockAppContainer() 87 | let containerSet = await cont.getContainerSet((c) => [c.aCont, c.cCont]) 88 | 89 | const fn = vi.fn() 90 | const unsub = cont.subscribeToContainerSet( 91 | (c) => [c.cCont], 92 | () => { 93 | fn() 94 | unsub() 95 | }, 96 | ) 97 | containerSet.cCont.upgradeCContainer() 98 | await wait(10) 99 | containerSet.cCont.upgradeCContainer() 100 | await wait(10) 101 | // Here we have two calls. And this should probably be double checked 102 | expect(fn).toHaveBeenCalledTimes(2) 103 | }) 104 | }) 105 | -------------------------------------------------------------------------------- /iti/tests/dispose-graph.ts.api.vi.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from "vitest" 2 | 3 | import { createContainer } from "../src" 4 | import { wait } from "./_utils" 5 | import { A, X, B, C, D, L, K, E, M, F } from "./mock-graph" 6 | 7 | /* 8 | ┌─────┐ 9 | │ A │ 10 | └─────┘ 11 | ┌────────┴────────┐ 12 | ▼ ▼ 13 | ┌─────┐ ┌─────┐ 14 | │ B │ │ C │────────────┐ 15 | └─────┘ └─────┘ │ 16 | └────────┬────────┘ │ 17 | ▼ ▼ 18 | ┌─────┐ ┌─────┐ ┌─────┐ 19 | │ X │─────▶│ D │─────────────────▶│ E │ 20 | └─────┘ └─────┘ └─────┘ 21 | ┌─────┴────┐ ┌────┴────┐ 22 | ▼ ▼ ▼ ▼ 23 | ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ 24 | │ L │ │ K │ │ M │ │ F │ 25 | └─────┘ └─────┘ └─────┘ └─────┘ 26 | */ 27 | function getGraph() { 28 | return createContainer() 29 | .add({ a: () => new A(), x: () => new X() }) 30 | .add((ctx) => ({ 31 | b: () => new B(ctx.a), 32 | c: () => new C(ctx.a), 33 | })) 34 | .add((ctx) => ({ 35 | d: () => new D(ctx.b, ctx.c, ctx.x), 36 | m: async () => 123, 37 | })) 38 | } 39 | /** 40 | * warning, partial graph disposal should not be implemented 41 | * 42 | * Due to teh async nature of the problem, the only way to implement it is to 43 | * track dependencies and dispose them in the reverse order of creation. Visitor pattern. 44 | * But since there might multiple async resolution requests, we might accidentally "track" 45 | * an unrelated dependency as a "visited" one. 46 | * 47 | * Hence when disposing, we would dispose unrelated dependencies as well. 48 | * 49 | * I don't think it would be really that useful anyway. 50 | * 51 | */ 52 | describe("Disposing graph: [warning, this should never be implemented]", () => { 53 | let root = getGraph() 54 | beforeEach(() => { 55 | root = getGraph() 56 | }) 57 | 58 | it("should call graph", async () => { 59 | const disposeLog: string[] = [] 60 | const dis = (token: string) => disposeLog.push(token) 61 | 62 | const dDisposer = vi.fn() 63 | const node = root.addDisposer((ctx, node) => ({ 64 | a: () => dis("a"), 65 | b: () => dis("b"), 66 | c: () => dis("c"), 67 | d: () => { 68 | dDisposer() 69 | dis("d") 70 | }, 71 | x: (x) => { 72 | return dis("x") 73 | }, 74 | })) 75 | 76 | const d = node.get("d") 77 | node.dispose("d") 78 | 79 | expect(d).toBeInstanceOf(D) 80 | await wait(10) 81 | expect(dDisposer).toHaveBeenCalledTimes(1) 82 | 83 | // for the future disposer graph 84 | // expect(disposeLog).toEqual(["d"]) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /iti/tests/exotic.vi.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from "vitest" 2 | import { createContainer } from "../src/iti" 3 | 4 | describe("Perf and exotic tests:", () => { 5 | let root = createContainer() 6 | 7 | beforeEach(() => { 8 | root = createContainer() 9 | }) 10 | 11 | describe("Node get:", () => { 12 | it("should not run into an infinite loop with recursive search", async () => { 13 | let r = root 14 | .add((c) => ({ a: async () => "A", b: "B", c: "C" })) 15 | .add((c, node) => ({ 16 | d: async () => { 17 | expect(await node.items.b).toBe("B") 18 | return "D" 19 | }, 20 | })) 21 | .upsert((c) => ({ 22 | d: async () => { 23 | expect(await c.b).toBe("B") 24 | return "D2" 25 | }, 26 | })) 27 | 28 | expect(await r.items.d).toBe("D2") 29 | }, 100) 30 | 31 | it("should never evaluate unrequested tokens, but pass correct reference to child node ", async () => { 32 | let r = root 33 | .add((c) => ({ 34 | b: async () => { 35 | throw new Error() 36 | return { x: "x", y: "y" } 37 | }, 38 | c: "C", 39 | })) 40 | .add((c) => { 41 | return { 42 | d: async () => { 43 | expect(c.c).toBe("C") 44 | return "D" 45 | }, 46 | } 47 | }) 48 | r.get("d") 49 | expect(await r.items.d).toBe("D") 50 | }, 100) 51 | 52 | // getTokens must be async welp 53 | it("should never evaluate unrequested tokens, but pass correct reference to child node \ 54 | without a manual seal", async () => { 55 | let r = root 56 | .add((c) => ({ 57 | b: async () => { 58 | throw new Error() 59 | return { x: "x", y: "y" } 60 | }, 61 | c: "C", 62 | })) 63 | .add((c, node) => { 64 | return { 65 | d: async () => { 66 | expect(await node.items.c).toBe("C") 67 | return "D" 68 | }, 69 | } 70 | }) 71 | 72 | expect(await r.items.d).toBe("D") 73 | }, 100) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /iti/tests/mock-graph.ts: -------------------------------------------------------------------------------- 1 | /* 2 | ┌─────┐ 3 | │ A │ 4 | └─────┘ 5 | ┌────────┴────────┐ 6 | ▼ ▼ 7 | ┌─────┐ ┌─────┐ 8 | │ B │ │ C │────────────┐ 9 | └─────┘ └─────┘ │ 10 | └────────┬────────┘ │ 11 | ▼ ▼ 12 | ┌─────┐ ┌─────┐ ┌─────┐ 13 | │ X │─────▶│ D │─────────────────▶│ E │ 14 | └─────┘ └─────┘ └─────┘ 15 | ┌─────┴────┐ ┌────┴────┐ 16 | ▼ ▼ ▼ ▼ 17 | ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ 18 | │ L │ │ K │ │ M │ │ F │ 19 | └─────┘ └─────┘ └─────┘ └─────┘ 20 | */ 21 | 22 | class A {} 23 | class X {} 24 | class B { constructor(a: A) {} } 25 | class C { constructor(a: A) {} } 26 | class D { constructor(b: B, c: C, x: X) {} } 27 | class L { constructor(d: D) {} } 28 | class K { constructor(d: D) {} } 29 | class E { constructor(c: C, d: D) {} } 30 | class M { constructor(e: E) {} } 31 | class F { constructor(e: E) {} } 32 | 33 | export { A, X, B, C, D, L, K, E, M, F } 34 | -------------------------------------------------------------------------------- /iti/tests/mocks/_mock-app-container.ts: -------------------------------------------------------------------------------- 1 | import { createContainer } from "../../src/iti" 2 | 3 | import { provideAContainer } from "./container.a" 4 | import { provideBContainer } from "./container.b" 5 | import { provideCContainer } from "./container.c" 6 | 7 | export type MockAppNode = ReturnType 8 | export function getMainMockAppContainer() { 9 | let node = createContainer() 10 | let k = node 11 | .add({ aCont: async () => provideAContainer() }) 12 | .add((c, node) => { 13 | return { 14 | bCont: async () => provideBContainer(await node.get("aCont")), 15 | } 16 | }) 17 | .add((c) => { 18 | return { 19 | cCont: async () => provideCContainer(await c.aCont, await c.bCont, k), 20 | } 21 | }) 22 | return k 23 | } 24 | -------------------------------------------------------------------------------- /iti/tests/mocks/container.a.ts: -------------------------------------------------------------------------------- 1 | import { A1, A2, A3 } from "./store.a" 2 | 3 | export interface A_Container { 4 | a1: A1 5 | a2: A2 6 | a3: A3 7 | } 8 | 9 | export async function provideAContainer(): Promise { 10 | const a1 = new A1() 11 | const a2 = new A2(a1) 12 | const a3 = new A3(a1, a2) 13 | 14 | return { 15 | a1, 16 | a2, 17 | a3, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /iti/tests/mocks/container.b.ts: -------------------------------------------------------------------------------- 1 | import type { A_Container } from "./container.a" 2 | import { B1, B2 } from "./store.b" 3 | 4 | export interface B_Container { 5 | b1: B1 6 | b2: B2 7 | } 8 | 9 | export async function provideBContainer(a: A_Container): Promise { 10 | const b1 = new B1(a.a2) 11 | 12 | const b2 = new B2(a.a1) 13 | 14 | return { 15 | b1, 16 | b2, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /iti/tests/mocks/container.c.ts: -------------------------------------------------------------------------------- 1 | import type { A_Container } from "./container.a" 2 | import { B_Container } from "./container.b" 3 | import { C1, C2 } from "./store.c" 4 | import { MockAppNode } from "./_mock-app-container" 5 | // import { MockAppContainer } from "./_mock-app-container" 6 | 7 | export interface C_Container { 8 | c1: C1 9 | c2: C2 10 | upgradeCContainer: (x?: number) => void 11 | } 12 | 13 | export async function provideCContainer( 14 | a: A_Container, 15 | b: B_Container, 16 | container: MockAppNode, 17 | ): Promise { 18 | const c1 = new C1(a.a2) 19 | const c2 = new C2(a.a1, b.b2, 5) 20 | 21 | async function replacer(ovenSize = 10) { 22 | const c1 = new C1(a.a2) 23 | const c2 = new C2(a.a1, b.b2, ovenSize) 24 | container.upsert(() => ({ 25 | cCont: async () => { 26 | return { 27 | c1, 28 | c2, 29 | upgradeCContainer: (ovenSize = 10) => replacer(ovenSize), 30 | } 31 | }, 32 | })) 33 | } 34 | 35 | return { 36 | c1, 37 | c2, 38 | upgradeCContainer: replacer, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /iti/tests/mocks/store.a.ts: -------------------------------------------------------------------------------- 1 | export class A1 { 2 | constructor() {} 3 | } 4 | export class A2 { 5 | constructor(private a1: A1) {} 6 | } 7 | export class A3 { 8 | constructor(private a1: A1, private a2: A2) {} 9 | } 10 | -------------------------------------------------------------------------------- /iti/tests/mocks/store.b.ts: -------------------------------------------------------------------------------- 1 | import type { A1, A2 } from "./store.a" 2 | 3 | export class B1 { 4 | constructor(private a2: A2) { 5 | // 1. Subscribe to event emitter 6 | // 2. Pub update 7 | } 8 | } 9 | export class B2 { 10 | constructor(private a1: A1) {} 11 | } 12 | -------------------------------------------------------------------------------- /iti/tests/mocks/store.c.ts: -------------------------------------------------------------------------------- 1 | import type { A1, A2 } from "./store.a" 2 | import { B2 } from "./store.b" 3 | 4 | export class C1 { 5 | constructor(private a2: A2) { 6 | // 1. Subscribe to event emitter 7 | // 2. Pub update 8 | } 9 | } 10 | export class C2 { 11 | constructor(private a1: A1, b2: B2, readonly size) {} 12 | } 13 | -------------------------------------------------------------------------------- /iti/tests/tsd.container-set-types.vi.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from "vitest" 2 | import { getMainMockAppContainer } from "./mocks/_mock-app-container" 3 | import { expectType, expectError, printType, expectNotType } from "tsd" 4 | import type { A_Container } from "./mocks/container.a" 5 | import type { B_Container } from "./mocks/container.b" 6 | import type { C_Container } from "./mocks/container.c" 7 | 8 | it("should check token types", () => { 9 | const cont = getMainMockAppContainer() 10 | expectType<{ aCont: "aCont"; bCont: "bCont"; cCont: "cCont" }>( 11 | cont.getTokens(), 12 | ) 13 | }) 14 | 15 | it("should check getContainerSet types", async () => { 16 | const cont = getMainMockAppContainer() 17 | let containerSet = await cont.getContainerSet(["aCont", "bCont"]) 18 | expectNotType(containerSet) 19 | expectNotType(containerSet.aCont) 20 | expectType(containerSet.aCont) 21 | }) 22 | 23 | it("should check getContainerSet function types", async () => { 24 | const cont = getMainMockAppContainer() 25 | let containerSet = await cont.getContainerSet((c) => [c.aCont, c.bCont]) 26 | expectNotType(containerSet) 27 | expectNotType(containerSet.aCont) 28 | expectType(containerSet.aCont) 29 | }) 30 | 31 | it("should check subscribe types", async () => { 32 | const cont = getMainMockAppContainer() 33 | cont.subscribeToContainerSet( 34 | (c) => { 35 | expectNotType(c) 36 | expectType<"aCont">(c.aCont) 37 | return [c.aCont, c.cCont] 38 | }, 39 | (err, containerSet) => { 40 | expectNotType(containerSet) 41 | expectType(containerSet.aCont) 42 | expectType(containerSet.cCont) 43 | }, 44 | ) 45 | }) 46 | 47 | it("should be able to delete token types", () => { 48 | const cont = getMainMockAppContainer().delete("aCont") 49 | expectNotType(cont.items) 50 | expectType<{ bCont: "bCont"; cCont: "cCont" }>(cont.getTokens()) 51 | }) 52 | -------------------------------------------------------------------------------- /iti/tests/tsd.getter.tsd-only.vi.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from "vitest" 2 | import { expectType, expectNotType } from "tsd" 3 | 4 | import { createContainer } from "../src/iti" 5 | 6 | enum UniqueResult { 7 | A, 8 | B, 9 | C, 10 | D, 11 | } 12 | // results produced by an add should valid 13 | it("should check getter types", () => { 14 | const node = createContainer() 15 | .add({ 16 | a: UniqueResult.A, 17 | b: () => UniqueResult.B, 18 | }) 19 | .add(() => ({ 20 | c: () => UniqueResult.C, 21 | })) 22 | 23 | expectType(node.get("a")) 24 | expectType(node.get("b")) 25 | expectType(node.get("c")) 26 | 27 | expectNotType(node) 28 | }) 29 | -------------------------------------------------------------------------------- /iti/tests/types-for-tests.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /iti/tests/update.api.vi.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi, beforeEach } from "vitest" 2 | import { createContainer } from "../src/iti" 3 | import { wait } from "./_utils" 4 | 5 | describe("Deleting and destructuring: ", () => { 6 | let root: ReturnType 7 | 8 | beforeEach(() => { 9 | root = createContainer() 10 | }) 11 | it("should be able to store null", () => { 12 | let r = root.add({ a: "A", b: null }) 13 | 14 | expect(r.get("a")).toBe("A") 15 | expect(r.get("b")).toBe(null) 16 | }) 17 | 18 | it("should be able to upsert null and back", () => { 19 | let r = root.add({ a: "A", b: "B" }) 20 | 21 | // normal state 22 | expect(r.get("a")).toBe("A") 23 | expect(r.get("b")).toBe("B") 24 | 25 | // upsert null 26 | r.upsert({ b: null }) 27 | expect(r.get("b")).toBe(null) 28 | 29 | // tokens should be fine too 30 | expect(r.getTokens()).toMatchObject({ a: "a", b: "b" }) 31 | 32 | // update B from null to a Primitive 33 | r.upsert({ b: "new B" }) 34 | expect(r.get("b")).toBe("new B") 35 | }) 36 | 37 | it("should be able to delete a token", () => { 38 | let r = root.add({ a: "A", b: "B", c: "C" }) 39 | 40 | expect(r.getTokens()).toMatchObject({ a: "a", b: "b", c: "c" }) 41 | 42 | let updated = r.delete("b") 43 | expect(r.getTokens()).toMatchObject({ a: "a", c: "c" }) 44 | 45 | // should throw 46 | expect(() => { 47 | // @ts-expect-error 48 | updated.get("b") 49 | }).toThrow() 50 | }) 51 | 52 | it("should send containerUpdated event on overwrite", async () => { 53 | const cb = vi.fn() 54 | root.on("containerDeleted", async (k) => { 55 | expect(k.key).toBe("b") 56 | root.on("containerUpserted", (k) => { 57 | expect(k.key).toBe("b") 58 | expect(k.newContainer).toBe("new B") 59 | cb() 60 | }) 61 | await wait(5) 62 | root.upsert({ b: "new B" }) 63 | }) 64 | 65 | root.add({ a: "A", b: "B" }).delete("b") 66 | await wait(20) 67 | expect(cb).toHaveBeenCalledTimes(1) 68 | }) 69 | 70 | it("should send containerUpdated event on overwrite", async () => { 71 | const node = root.add(() => ({ 72 | a: "A", 73 | b: "B", 74 | })) 75 | const f1 = vi.fn() 76 | const f2 = vi.fn() 77 | 78 | node.subscribeToContainer("a", f1) 79 | node.subscribeToContainerSet(["a", "b"], f2) 80 | 81 | node.delete("a") 82 | 83 | await wait(10) 84 | 85 | expect(f1).toHaveBeenCalledTimes(1) 86 | /** 87 | * 2 because we have subscribed to two container, and this will provide us 88 | * with two of those, hence two updates because two creations 89 | */ 90 | expect(f2).toHaveBeenCalledTimes(1) 91 | }) 92 | 93 | it("should send error if we remove a token some container listens to", async () => { 94 | const cb = vi.fn() 95 | const node = root.add(() => ({ 96 | a: "A", 97 | b: "B", 98 | })) 99 | node.subscribeToContainer("a", (err) => { 100 | expect(err).not.toBe(null) 101 | cb() 102 | }) 103 | node.delete("a") 104 | await wait(10) 105 | expect(cb).toHaveBeenCalledTimes(1) 106 | }) 107 | 108 | it("should send error if we remove a token some containerSet listens to", async () => { 109 | const cb = vi.fn() 110 | const node = root.add(() => ({ 111 | a: "A", 112 | b: "B", 113 | })) 114 | node.subscribeToContainerSet(["a", "b"], (err) => { 115 | expect(err).not.toBe(null) 116 | cb() 117 | }) 118 | node.delete("a") 119 | await wait(10) 120 | expect(cb).toHaveBeenCalledTimes(1) 121 | }) 122 | }) 123 | -------------------------------------------------------------------------------- /iti/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "baseUrl": "./src", 5 | "outDir": "dist/", 6 | "declarationDir": "dist/", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | // "types": ["@types/jest"], 9 | "noImplicitAny": false, 10 | "experimentalDecorators": true, 11 | "allowJs": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "strict": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noFallthroughCasesInSwitch": true, 18 | "module": "esnext", 19 | "moduleResolution": "node", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "declaration": true, 23 | "jsx": "react" 24 | }, 25 | "include": ["src/**/*"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /iti/tsd_project/dummy.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/iti/tsd_project/dummy.d.ts -------------------------------------------------------------------------------- /iti/tsd_project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": "dummy.d.ts", 3 | "tsd": { 4 | "directory": "../tests" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iti/vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from "vite" 3 | 4 | export default defineConfig({ 5 | test: { 6 | globals: false, 7 | include: ["**/*.vi.{test,spec}.?(c|m)[jt]s?(x)"], 8 | }, 9 | }) 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti-workspace", 3 | "version": "0.0.1", 4 | "description": "iti yarn monorepo root", 5 | "private": true, 6 | "workspaces": [ 7 | "iti", 8 | "iti-react", 9 | "examples/cra", 10 | "examples/node-cli", 11 | "examples/vite-app" 12 | ], 13 | "scripts": { 14 | "test": "turbo run test", 15 | "build": "turbo run build" 16 | }, 17 | "devDependencies": { 18 | "turbo": "^1.8.3" 19 | }, 20 | "author": "Nick Olszanski ", 21 | "license": "MIT", 22 | "packageManager": "yarn@1.22.17", 23 | "dependencies": { 24 | "jest-environment-jsdom": "^29.7.0", 25 | "prettier": "^2.7.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turborepo.org/schema.json", 3 | "baseBranch": "origin/main", 4 | "pipeline": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "outputs": [".next/**"] 8 | }, 9 | "test": { 10 | "dependsOn": ["^build"], 11 | "outputs": [] 12 | }, 13 | "lint": { 14 | "outputs": [] 15 | }, 16 | "dev": { 17 | "cache": false 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /website/.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 | -------------------------------------------------------------------------------- /website/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 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/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 | -------------------------------------------------------------------------------- /website/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 | -------------------------------------------------------------------------------- /website/blog/2021-08-01-mdx-blog-post.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | slug: mdx-blog-post 3 | title: MDX Blog Post 4 | authors: [slorber] 5 | tags: [docusaurus] 6 | --- 7 | 8 | Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). 9 | 10 | :::tip 11 | 12 | Use the power of React to create interactive blog posts. 13 | 14 | ```js 15 | 16 | ``` 17 | 18 | 19 | 20 | ::: 21 | -------------------------------------------------------------------------------- /website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg -------------------------------------------------------------------------------- /website/blog/2021-08-26-welcome/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | authors: [slorber, yangshun] 5 | tags: [facebook, hello, docusaurus] 6 | --- 7 | 8 | [Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog). 9 | 10 | Simply add Markdown files (or folders) to the `blog` directory. 11 | 12 | Regular blog authors can be added to `authors.yml`. 13 | 14 | The blog post date can be extracted from filenames, such as: 15 | 16 | - `2019-05-30-welcome.md` 17 | - `2019-05-30-welcome/index.md` 18 | 19 | A blog post folder can be convenient to co-locate blog post images: 20 | 21 | ![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg) 22 | 23 | The blog supports tags as well! 24 | 25 | **And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config. 26 | -------------------------------------------------------------------------------- /website/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 | -------------------------------------------------------------------------------- /website/docs/1.intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Intro 6 | 7 | Iti is 1kB Typesafe dependency injection framework for TypeScript and JavaScript with a unique support for **async flow** 8 | 9 | _This library doesn't try be scientifically correct. I just want to go home early_ 10 | 11 | ## Features 12 | 13 | - **supports async(!) dependencies:** merges async code and constructor injection via plain **async** functions 14 | - **strongly typed:** has great IDE autocomplete and compile time check. Without any [manual type casting](https://github.com/inversify/InversifyJS/blob/master/wiki/container_api.md#containergettserviceidentifier-interfacesserviceidentifiert-t) 15 | - **non-invasive:** does not require imported `@decorators` or framework `extends` in your application business logic 16 | - **lazy:** initializes your app modules and containers on demand 17 | - **split chunks:** enables **[dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports)** via a [one liner](#dynamic-imports) thanks to a fully async core 18 | - **React friendly:** has useful **[React](https://github.com/molszanski/iti/tree/master/iti-react)** bindings to help you separate application business logic and a React view layer 19 | - **starter friendly:** works with starters like [Create React App](https://create-React-app.dev/) or [Next.js](https://nextjs.org/docs/getting-started) unlike existing libraries 20 | - **no Babel config:** doesn't require `reflect-metadata` or decorators so there is no need to hack in decorator and `"decoratorMetadata"` support in to your build configs 21 | - **tiny:** less than 1kB 22 | 23 | IoC is an amazing pattern and it should **easy to adopt**, fully support async and without hard to learn APIs or complex tooling requirements. 24 | 25 | Iti relies on plain JS functions, objects and familiar patterns. API is simple so you can make a **proof of concept integration in minutes**. 26 | 27 | It is an alternative to [InversifyJS](https://github.com/inversify/InversifyJS) and [microsoft/tsyringe](https://github.com/microsoft/tsyringe) for constructor injection. 28 | 29 | > _At [Packhelp](https://unpacked.packhelp.com) we’ve refactored most of our 65K SLOC Editor app, that didn't have any IoC, to Iti in under 5 hours_ 30 | -------------------------------------------------------------------------------- /website/docs/10.faq.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 11 3 | slug: faq 4 | --- 5 | 6 | import TOCInline from "@theme/TOCInline" 7 | 8 | import CodeBlock from "@theme/CodeBlock" 9 | import circularDependency from "!!raw-loader!./examples/circular-dependency.ts" 10 | 11 | # FAQ 12 | 13 | :::note 14 | 15 | Please know, that the docs is still work in progress. Many features or use cases are probably already in the lib but not documented well. We are working on it. 16 | 17 | ::: 18 | 19 | 20 | 21 | ## Questions 22 | 23 | **Can I have multiple application containers?** 24 | 25 | Yes, no problem at all. If you want, they can even share tokens and hence instances! 26 | 27 | **Why `getContainerSet` is always async?** 28 | 29 | This is temporary(?) limitation to keep typescript happy and typescript types reasonably sane. 30 | In most real world scenarios your frontend dependencies are already async. 31 | 32 | ### Why should I use ITI? 33 | 34 | We strongly believe that helps to implement good DI patterns in your codebase and offers 35 | better tradeoffs compared to alternative DI frameworks or solutions. 36 | Check our [alternatives](/docs/alternatives) section 37 | 38 | ### How does handle circular dependency? 39 | 40 | You can not create a circular dependency with iti and typescript. 41 | It will throw a typescript error if you try :) 42 | 43 | {circularDependency} 44 | -------------------------------------------------------------------------------- /website/docs/2.quick-start.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | import CodeBlock from "@theme/CodeBlock" 6 | import withIti from "!!raw-loader!./6_async_start-PART-2/main/with.tsx" 7 | import without from "!!raw-loader!./6_async_start-PART-2/main/without.ts" 8 | import quickStartCode from "!!raw-loader!./6_async_start-PART-2/main/business-logic.ts" 9 | import wiringCode from "!!raw-loader!./6_async_start-PART-2/main/app.ts" 10 | 11 | # Quick Start 12 | 13 | ITI is a 1kb dependency injections framework for asynchronous applications. 14 | You are probably using some form of manual DI and ITI is a logical upgrade. 15 | 16 | If you are building a react there are useful react bindings are available. 17 | 18 | :::note 19 | 20 | Please know, that the docs is still work in progress. Many features or use cases are probably already in the lib but not documented well. We are working on it. 21 | 22 | ::: 23 | 24 | ## Adding ITI to your Project 25 | 26 | ``` 27 | # with npm 28 | npm install -S iti iti-react 29 | 30 | # or with yarn 31 | yarn add iti iti-react 32 | ``` 33 | 34 | :::tip 35 | 36 | If you are building a React App there are useful React bindings (`iti-react`) are available. 37 | 38 | ::: 39 | 40 | ## TL;DR 41 | 42 | **with ITI** 43 | 44 | {withIti} 45 | 46 | **Without ITI** 47 | 48 | {without} 49 | 50 | ## Usage 51 | 52 | ```tsx 53 | // (Optional) With React 54 | import { useContainer } from "./_containers/main-app" 55 | 56 | function Profile() { 57 | const [user, userErr] = useContainer().userData 58 | if (!user || userErr) return
loading... or error
59 | 60 | return
Hello {user.name}!
61 | } 62 | ``` 63 | 64 | {quickStartCode} 65 | {wiringCode} 66 | 67 | ```tsx 68 | // Part 3: Usage 69 | import { app } from "./app" 70 | 71 | // Will lazily fetch data and create PaymentService instance 72 | const paymentService = await app.items.paymentService 73 | paymentService.sendMoney() 74 | ``` 75 | 76 | ```tsx 77 | // (Optional) With React 78 | import { useContainer } from "./_containers/main-app" 79 | 80 | function Profile() { 81 | const [user, userErr] = useContainer().userData 82 | if (!user || userErr) return
loading... or error
83 | 84 | return
Hello {user.name}!
85 | } 86 | ``` 87 | -------------------------------------------------------------------------------- /website/docs/4.usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | title: Usage 4 | --- 5 | 6 | :::note 7 | 8 | Please know, that the docs is still work in progress. Many features or use cases are probably already in the lib but not documented well. We are working on it. 9 | 10 | ::: 11 | 12 | # Short Manual 13 | 14 | ## Reading and Writing data 15 | 16 | **Reading** 17 | 18 | ```ts 19 | // Get a single instance 20 | root.get("oven") // Creates a new Oven instance 21 | root.get("oven") // Gets a cached Oven instance 22 | 23 | await node.get("kitchen") // { kitchen: Kitchen } also cached 24 | await node.items.kitchen // same as above 25 | 26 | // Plain deletion 27 | node.delete("kitchen") 28 | 29 | // Get multiple instances at once 30 | await root.getContainerSet(["oven", "userManual"]) // { userManual: '...', oven: Oven } 31 | await root.getContainerSet((c) => [c.userManual, c.oven]) // same as above 32 | 33 | // Subscribe to container changes 34 | node.subscribeToContainer("oven", (oven) => {}) 35 | node.subscribeToContainerSet(["oven", "kitchen"], ({ oven, kitchen }) => {}) 36 | // prettier-ignore 37 | node.subscribeToContainerSet((c) => [c.kitchen], ({ oven, kitchen }) => {}) 38 | node.on("containerUpdated", ({ key, newItem }) => {}) 39 | node.on("containerUpserted", ({ key, newItem }) => {}) 40 | node.on("containerDeleted", ({ key, newItem }) => {}) 41 | 42 | // Disposing 43 | node 44 | .add({ dbConnection: () => connectToDb(process.env.dbUrl) }) 45 | .addDisposer({ dbConnection: (db) => db.disconnect() }) // waits for promise 46 | await node.dispose("dbConnection") 47 | await node.disposeAll() 48 | ``` 49 | 50 | **Writing** 51 | 52 | ```ts 53 | let node1 = createContainer() 54 | .add({ 55 | userManual: "Please preheat before use", 56 | oven: () => new Oven(), 57 | }) 58 | .upsert((containers, node) => ({ 59 | userManual: "Works better when hot", 60 | preheatedOven: async () => { 61 | await containers.oven.preheat() 62 | return containers.oven 63 | }, 64 | })) 65 | 66 | // `add` is typesafe and a runtime safe method. Hence we've used `upsert` 67 | try { 68 | node1.add({ 69 | // @ts-expect-error 70 | userManual: "You shall not pass", 71 | // Type Error: (property) userManual: "You are overwriting this token. It is not safe. Use an unsafe `upsert` method" 72 | }) 73 | } catch (err) { 74 | err.message // Error Tokens already exist: ['userManual'] 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /website/docs/5.patterns-and-tips.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | title: Patterns and Tips 4 | --- 5 | 6 | :::note 7 | 8 | Please know, that the docs is still work in progress. Many features or use cases are probably already in the lib but not documented well. We are working on it. 9 | 10 | ::: 11 | 12 | # Patterns and Tips 13 | 14 | ## Patterns and tips 15 | 16 | ### Make lazy simple 17 | 18 | Prefer functions over eager init. Why? This will make the app lazy, hence faster. 19 | 20 | ```ts 21 | // Good 22 | createContainer().add({ 23 | eventBus: () => new EventBus(), 24 | userAuthService: () => new UserAuthService(), 25 | }) 26 | 27 | // Meh... 28 | createContainer().add({ 29 | eventBus: new EventBus(), 30 | userAuthService: new UserAuthService(), 31 | }) 32 | ``` 33 | 34 | In the second example we create instances on IoC container start. Which in most cases is not desirable. With the first example, we use functions, and they will be executed only when requested 35 | 36 | ### Lifecycle 37 | 38 | **Single Instance (a.k.a. Singleton)** 39 | 40 | ```ts 41 | let node = createContainer().add({ 42 | oven: () => new Oven(), 43 | }) 44 | node.get("oven") === node.get("oven") // true 45 | ``` 46 | 47 | **Transient** 48 | 49 | ```ts 50 | let node = createContainer().add({ 51 | oven: () => () => new Oven(), 52 | }) 53 | node.get("oven") === node.get("oven") // false 54 | ``` 55 | 56 | ### Dynamic Imports 57 | 58 | ```ts 59 | // ./kitchen/index.ts 60 | export async function provideKitchenContainer() { 61 | const { Kitchen } = await import("./kitchen/kitchen") 62 | return { 63 | kitchen: () => new Kitchen(), 64 | oven: async () => { 65 | const { Oven } = await import("./kitchen/oven") 66 | const oven = new Oven() 67 | await oven.preheat() 68 | return oven 69 | }, 70 | } 71 | } 72 | ``` 73 | 74 | ```ts 75 | // ./index.ts 76 | import { createContainer } from "iti" 77 | import { provideKitchenContainer } from "./kitchen" 78 | let node = createContainer().add({ 79 | kitchen: async () => provideKitchenContainer(), 80 | }) 81 | 82 | // Next line will load `./kitchen/kitchen` module 83 | await node.items.kitchen 84 | 85 | // Next line will load `./kitchen/oven` module 86 | await node.items.kitchen.oven 87 | ``` 88 | 89 | ### Tip: Prefer callbacks over of strings (in progress) 90 | 91 | If you use callback pattern across your app, you will be able to mass rename your containerKeys using typescript. With strings, you will have to manually go through the app. But even if you use string literals compiler will not compile until you fix your rename manually across the app. 92 | 93 | ```ts 94 | const node = createContainer().addNode({ 95 | a: "A", 96 | b: "B", 97 | }) 98 | 99 | await node.get((containerKeys) => containerKeys.a) // BEST!!! 100 | await node.get("a") // it will work but... 101 | ``` 102 | 103 | ## Anti Patterns 104 | 105 | in progress 106 | 107 | ## Known issues 108 | 109 | ### TS2589: Type instantiation is excessively deep and possibly infinite 110 | 111 | This bug is caused by a TS hard limit on 50 `instantiationDepth`. 112 | 113 | https://github.com/i18next/react-i18next/issues/1417 114 | https://github.com/microsoft/TypeScript/issues/34933 115 | 116 | As a quick workaround we suggest: 117 | 118 | 1. **Reduce the number of `.add` steps** - this will help in most cases 119 | 2. **Reduce the number of unique tokens** - group some tokens together 120 | 3. **Create multiple containers** - it seems that your app is getting pretty big and complex. Maybe create to 2 containers via `createContainer`? 121 | 4. **Upgrade to TS 4.5 or higher** 122 | 5. **Optimize ITI** 123 | -------------------------------------------------------------------------------- /website/docs/6.getting-started.md.del: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Getting Started 6 | 7 | 8 | ## Getting Started 9 | 10 | The best way to get started is to check [a CRA Pizza example](https://github.com/molszanski/iti/tree/master/examples/cra/src/containers) 11 | 12 | Initial wiring 13 | 14 | ```ts 15 | import { createContainer } from "../../src/library.new-root-container" 16 | 17 | import { provideAContainer } from "./container.a" 18 | import { provideBContainer } from "./container.b" 19 | import { provideCContainer } from "./container.c" 20 | 21 | export type MockAppNode = ReturnType 22 | export function getMainMockAppContainer() { 23 | return createContainer() 24 | .add({ aCont: async () => provideAContainer() }) 25 | .add((containers) => { 26 | return { 27 | bCont: async () => provideBContainer(await containers.aCont), 28 | } 29 | }) 30 | .add((c) => { 31 | return { 32 | cCont: async () => provideCContainer(await c.aCont, await c.bCont, k), 33 | } 34 | }) 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/0_by_hand.ts: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | interface Logger { info: (msg: string) => void } 3 | // prettier-ignore 4 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } } 5 | // prettier-ignore 6 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } } 7 | 8 | // Part 1: Business Entities 9 | interface UserData { 10 | name: string 11 | } 12 | 13 | class AuthService { 14 | async getUserData(): Promise { 15 | return { name: "Big Lebowski" } 16 | } 17 | } 18 | 19 | class User { 20 | constructor(private data: UserData) {} 21 | name = () => this.data.name 22 | } 23 | 24 | class PaymentService { 25 | constructor(private readonly logger: Logger, private readonly user: User) {} 26 | sendMoney() { 27 | this.logger.info(`Sending monery to the: ${this.user.name()} `) 28 | return true 29 | } 30 | } 31 | 32 | // Step 2: Manual DI 33 | export async function runMyApp() { 34 | const logger = 35 | process.env.NODE_ENV === "production" 36 | ? new PinoLogger() 37 | : new ConsoleLogger() 38 | 39 | const auth = new AuthService() 40 | const user = new User(await auth.getUserData()) 41 | 42 | const paymentService = new PaymentService(logger, user) 43 | paymentService.sendMoney() 44 | } 45 | 46 | console.log(" ---- My App START \n\n") 47 | runMyApp().then(() => { 48 | console.log("\n\n ---- My App END") 49 | }) 50 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/1_ts-inject.ts: -------------------------------------------------------------------------------- 1 | import { createInjector } from "typed-inject" 2 | // prettier-ignore 3 | interface Logger { info: (msg: string) => void } 4 | // prettier-ignore 5 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } } 6 | // prettier-ignore 7 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } } 8 | 9 | interface UserData { 10 | name: string 11 | } 12 | 13 | class AuthService { 14 | async getUserData(): Promise { 15 | return { name: "Big Lebowski" } 16 | } 17 | } 18 | 19 | const Token = Object.freeze({ 20 | logger: "logger", 21 | consoleLogger: "consoleLogger", 22 | pinoLogger: "pinoLogger", 23 | authService: "authService", 24 | user: "user", 25 | userData: "userData", 26 | paymentService: "paymentService", 27 | } as const) 28 | 29 | class User { 30 | public static inject = [Token.userData] as const 31 | constructor(private data: UserData) { 32 | console.log("constructing user") 33 | } 34 | name = () => this.data.name 35 | } 36 | 37 | class PaymentService { 38 | public static inject = [Token.logger, Token.user] as const 39 | constructor(private readonly logger: Logger, private readonly user: User) {} 40 | sendMoney() { 41 | this.logger.info(`Sending monery to the: ${this.user.name()} `) 42 | return true 43 | } 44 | } 45 | 46 | /// INJECTING 47 | 48 | export async function runMyApp() { 49 | const container = createInjector() 50 | .provideFactory("logger", () => 51 | process.env.NODE_ENV === "production" 52 | ? new PinoLogger() 53 | : new ConsoleLogger() 54 | ) 55 | .provideClass(Token.authService, AuthService) 56 | 57 | const authService = container.resolve(Token.authService) 58 | const userData = await authService.getUserData() 59 | 60 | const container2 = container 61 | .provideValue(Token.userData, userData) 62 | .provideClass(Token.user, User) 63 | .provideClass(Token.paymentService, PaymentService) 64 | 65 | const ps = container2.resolve(Token.paymentService) 66 | ps.sendMoney() 67 | } 68 | 69 | console.log(" ---- My App START \n\n") 70 | runMyApp().then(() => { 71 | console.log("\n\n ---- My App END") 72 | }) 73 | 74 | /** 75 | * There is another option where we add an async function 76 | * to a factory like this: 77 | * 78 | * container.provideFactory('user', async () => { name: 'Alice' }) 79 | * 80 | * but then all typings go crazy and basically 81 | * we write in JS :/ 82 | */ 83 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/2_tsyringe.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata" 2 | import { 3 | container, 4 | injectable, 5 | inject, 6 | singleton, 7 | predicateAwareClassFactory, 8 | } from "tsyringe" 9 | // prettier-ignore 10 | interface Logger { info: (msg: string) => void } 11 | // prettier-ignore 12 | @injectable() 13 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } } 14 | // prettier-ignore 15 | @injectable() 16 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } } 17 | 18 | interface UserData { 19 | name: string 20 | } 21 | 22 | @injectable() 23 | class AuthService { 24 | async getUserData(): Promise { 25 | return { name: "Big Lebowski" } 26 | } 27 | } 28 | 29 | const Token = Object.freeze({ 30 | logger: "logger", 31 | consoleLogger: "consoleLogger", 32 | pinoLogger: "pinoLogger", 33 | authService: "authService", 34 | user: "user", 35 | userData: "userData", 36 | paymentService: "paymentService", 37 | } as const) 38 | 39 | // @singleton() 40 | @injectable() 41 | class User { 42 | constructor(@inject(Token.userData) private data: UserData) { 43 | console.log("constructing user") 44 | } 45 | name = () => this.data.name 46 | } 47 | 48 | @injectable() 49 | class PaymentService { 50 | constructor( 51 | @inject(Token.logger) private readonly logger: Logger, 52 | @inject(User) private readonly user: User 53 | ) {} 54 | sendMoney() { 55 | this.logger.info(`Sending monery to the: ${this.user.name()} `) 56 | return true 57 | } 58 | } 59 | 60 | /// INJECTING 61 | 62 | export async function runMyApp() { 63 | container.register(Token.logger, { 64 | useFactory: predicateAwareClassFactory( 65 | (c) => { 66 | // c.resolve(PinoLogger) 67 | return process.env.NODE_ENV === "production" 68 | }, 69 | ConsoleLogger, 70 | PinoLogger 71 | ), 72 | }) 73 | 74 | // Danger zone 1 75 | const auth = container.resolve(AuthService) 76 | const d = await auth.getUserData() 77 | container.register(Token.userData, { useValue: d }) 78 | 79 | // Dange zone 2 80 | container.resolve(User) 81 | container.resolve(User) 82 | 83 | const ps = container.resolve(PaymentService) 84 | ps.sendMoney() 85 | } 86 | 87 | console.log(" ---- My App START \n\n") 88 | runMyApp().then(() => { 89 | console.log("\n\n ---- My App END") 90 | }) 91 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/main-example.ts: -------------------------------------------------------------------------------- 1 | import { createContainer } from "iti" 2 | // prettier-ignore 3 | interface Logger { info: (msg: string) => void } 4 | // prettier-ignore 5 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } } 6 | // prettier-ignore 7 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } } 8 | 9 | // Part 1: Business Entities 10 | interface UserData { 11 | name: string 12 | } 13 | 14 | class AuthService { 15 | async getUserData(): Promise { 16 | return { name: "Big Lebowski" } 17 | } 18 | } 19 | 20 | class PaymentService { 21 | constructor( 22 | private readonly logger: Logger, 23 | private readonly user: UserData 24 | ) {} 25 | sendMoney() { 26 | this.logger.info(`Sending money to the: ${this.user.name} `) 27 | return true 28 | } 29 | } 30 | 31 | // Step 2: Manual DI 32 | export async function runMyApp() { 33 | const logger = 34 | process.env.NODE_ENV === "production" 35 | ? new PinoLogger() 36 | : new ConsoleLogger() 37 | 38 | const auth = new AuthService() 39 | const userData = await auth.getUserData() 40 | 41 | const paymentService = new PaymentService(logger, userData) 42 | paymentService.sendMoney() 43 | } 44 | 45 | export async function runMyApp2() { 46 | const root = createContainer() 47 | .add({ 48 | logger: () => 49 | process.env.NODE_ENV === "production" 50 | ? new PinoLogger() 51 | : new ConsoleLogger(), 52 | auth: () => new AuthService(), 53 | }) 54 | .add((ctx) => ({ 55 | paymentService: async () => 56 | new PaymentService(ctx.logger, await ctx.auth.getUserData()), 57 | })) 58 | 59 | const paymentService = await root.items.paymentService 60 | paymentService.sendMoney() 61 | } 62 | 63 | console.log(" ---- My App START \n\n") 64 | runMyApp().then(() => { 65 | console.log("\n\n ---- My App END") 66 | }) 67 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/main/app.ts: -------------------------------------------------------------------------------- 1 | // Part 2: ITI boilerplate. Manual DI alternative 2 | import { createContainer } from "iti" 3 | import { 4 | PaymentService, 5 | AuthService, 6 | CookieStorageService, 7 | } from "./business-logic" 8 | import { PinoLogger, ConsoleLogger } from "./loggers" 9 | 10 | export const app = createContainer() 11 | .add({ 12 | // Add token `logger` and assign some logger instance 13 | logger: () => 14 | process.env.NODE_ENV === "production" 15 | ? new PinoLogger() 16 | : new ConsoleLogger(), 17 | // Add token `cookieStorage` ... 18 | cookieStorage: () => new CookieStorageService(), 19 | }) 20 | .add((ctx) => ({ 21 | auth: () => new AuthService(ctx.cookieStorage), 22 | })) 23 | .add((ctx) => ({ 24 | userData: async () => await ctx.auth.getUserData(), 25 | })) 26 | .add((ctx) => ({ 27 | paymentService: async () => 28 | new PaymentService(ctx.logger, await ctx.userData), 29 | })) 30 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/main/business-logic.ts: -------------------------------------------------------------------------------- 1 | // Part 1: Normal Application Business Logic 2 | interface Logger { 3 | info: (msg: string) => void 4 | } 5 | 6 | interface UserData { 7 | name: string 8 | } 9 | 10 | export class CookieStorageService { 11 | async getSessionToken(): Promise<{ token: string }> { 12 | return { token: "magicToken123" } 13 | } 14 | } 15 | 16 | export class AuthService { 17 | constructor(private cookieStorageService: CookieStorageService) {} 18 | async getUserData(): Promise { 19 | const { token } = await this.cookieStorageService.getSessionToken() 20 | if (token === "magicToken123") { 21 | return { name: "Big Lebowski" } 22 | } 23 | throw new Error("Unauthorized") 24 | } 25 | } 26 | 27 | export class PaymentService { 28 | constructor( 29 | private readonly logger: Logger, 30 | private readonly user: UserData 31 | ) {} 32 | sendMoney() { 33 | this.logger.info(`Sending money to the: ${this.user.name} `) 34 | return true 35 | } 36 | } 37 | 38 | // Application code is free of framework dependencies like decorators 39 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/main/loggers.ts: -------------------------------------------------------------------------------- 1 | interface Logger { 2 | info: (msg: string) => void 3 | } 4 | export class ConsoleLogger implements Logger { 5 | info(msg: string): void { 6 | console.log("[Console]:", msg) 7 | } 8 | } 9 | export class PinoLogger implements Logger { 10 | info(msg: string): void { 11 | console.log("[Pino]:", msg) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/main/manual-di.ts: -------------------------------------------------------------------------------- 1 | const logger = 2 | process.env.NODE_ENV === "production" ? new PinoLogger() : new ConsoleLogger() 3 | 4 | const auth = new AuthService() 5 | const userData = await auth.getUserData() 6 | 7 | const paymentService = new PaymentService(logger, userData) 8 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/main/with.tsx: -------------------------------------------------------------------------------- 1 | import { app } from "./app" 2 | 3 | // Proxy Getter: Lazily creates PaymentService instance 4 | const paymentService = await app.items.paymentService 5 | paymentService.sendMoney() 6 | -------------------------------------------------------------------------------- /website/docs/6_async_start-PART-2/main/without.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PaymentService, 3 | AuthService, 4 | CookieStorageService, 5 | } from "./business-logic" 6 | import { PinoLogger, ConsoleLogger } from "./loggers" 7 | 8 | const logger = 9 | process.env.NODE_ENV === "production" ? new PinoLogger() : new ConsoleLogger() 10 | 11 | const app = async () => { 12 | const cookieStorage = new CookieStorageService() 13 | const auth = new AuthService(cookieStorage) 14 | const userData = await auth.getUserData() 15 | const paymentService = new PaymentService(logger, userData) 16 | 17 | return { 18 | paymentService, 19 | } 20 | } 21 | 22 | app().then(({ paymentService }) => { 23 | paymentService.sendMoney() 24 | }) 25 | -------------------------------------------------------------------------------- /website/docs/7.5.playground.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | slug: playground 4 | title: Playground 5 | --- 6 | 7 | # Playground 8 | 9 | You can launch a live browser [playground on Stackblitz website][1] or `checkout` the 10 | [playground repo](https://github.com/molszanski/iti-playground) 11 | 12 | ```bash 13 | git clone git@github.com:molszanski/iti-playground.git 14 | cd iti-playground/src/ 15 | ``` 16 | 17 | [Try it out live (on Stacklitz, an instant browser dev environment )][1] 18 | 19 | 20 | 21 | ![Autocomplete](./assets/stackblitz.png) 22 | 23 | 24 | 25 | https://stackblitz.com/github/molszanski/iti-playground/tree/main 26 | 27 | [0]: https://michel.codes/blogs/ui-as-an-afterthought 28 | [1]: https://stackblitz.com/github/molszanski/iti-playground/tree/main?file=src%2F_0.business-logic.ts&file=src%2FApp.tsx 29 | -------------------------------------------------------------------------------- /website/docs/8.benefits-of-iti.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Benefits 6 | 7 | _work in progress_ 8 | 9 | ## Typescript 10 | 11 | Iti has a great typescript support. All types are resolved automatically and check at compile time. 12 | 13 | ![Autocomplete](./assets/ts/1.png) 14 | ![Autocomplete](./assets/ts/2.png) 15 | ![Autocomplete](./assets/ts/3.png) 16 | ![Autocomplete](./assets/ts/4.png) 17 | -------------------------------------------------------------------------------- /website/docs/9.api/addDisposer.ts: -------------------------------------------------------------------------------- 1 | import { createContainer } from "iti" 2 | import { Client } from "pg" 3 | 4 | class A {} 5 | 6 | createContainer() 7 | .add({ 8 | db: async () => { 9 | const pg = new Client(process.env["DB_CONNECTION_URL"]) 10 | await pg.connect() 11 | return pg 12 | }, 13 | }) 14 | .add((ctx) => ({ 15 | a: () => new A(), 16 | b: async () => { 17 | const db = await ctx.db 18 | db.query("SELECT 1") 19 | }, 20 | })) 21 | // ↓ `ctx` to access any other dependency 22 | .addDisposer((ctx) => ({ 23 | // ↓ `db` is a resolved value of a `DB` 24 | db: (db) => { 25 | console.log(ctx.a) 26 | return db.disconnect() 27 | }, 28 | })) 29 | 30 | class B {} 31 | const container = createContainer() 32 | .add({ 33 | a: () => new A(), 34 | b: () => new B(), 35 | }) 36 | .addDisposer({ 37 | a: (a) => console.log("disposing a", a), 38 | }) 39 | 40 | container.dispose("a") 41 | -------------------------------------------------------------------------------- /website/docs/9.api/disposeAll.ts: -------------------------------------------------------------------------------- 1 | import { Client } from "pg" 2 | import { createContainer } from "iti" 3 | 4 | const container = createContainer() 5 | .add(() => ({ 6 | dbConnection: async () => { 7 | const pg = new Client(process.env["DB_CONNECTION_URL"]) 8 | await pg.connect() 9 | return pg 10 | }, 11 | })) 12 | .addDisposer({ 13 | dbConnection: (dbConnection) => dbConnection.end(), 14 | }) 15 | 16 | const db = await container.get("dbConnection") 17 | await db.query("...") 18 | 19 | // Later.. 20 | await container.disposeAll() 21 | 22 | console.log("All dependencies disposed, you can exit now. :)") 23 | -------------------------------------------------------------------------------- /website/docs/assets/stackblitz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/stackblitz.png -------------------------------------------------------------------------------- /website/docs/assets/ts/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/ts/1.png -------------------------------------------------------------------------------- /website/docs/assets/ts/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/ts/2.png -------------------------------------------------------------------------------- /website/docs/assets/ts/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/ts/3.png -------------------------------------------------------------------------------- /website/docs/assets/ts/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/assets/ts/4.png -------------------------------------------------------------------------------- /website/docs/async-di/1.manual-di.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | import CodeBlock from "@theme/CodeBlock" 6 | import manualDi from "!!raw-loader!../6_async_start-PART-2/0_by_hand.ts" 7 | 8 | # Pure DI 9 | 10 | {manualDi} 11 | -------------------------------------------------------------------------------- /website/docs/async-di/3.syringe.mdx.hidden: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Syringe 6 | 7 | _work in progress_ 8 | -------------------------------------------------------------------------------- /website/docs/async-di/4.typed-inject.mdx.hi: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # Typed Inject 6 | 7 | _work in progress_ 8 | -------------------------------------------------------------------------------- /website/docs/async-di/5.iti.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | tags: 4 | - Demo 5 | - Getting started 6 | --- 7 | 8 | # ITI 9 | 10 | ```ts 11 | import { createContainer } from "iti" 12 | // prettier-ignore 13 | interface Logger { info: (msg: string) => void } 14 | // prettier-ignore 15 | class ConsoleLogger implements Logger { info(msg: string): void { console.log("[Console]:", msg) } } 16 | // prettier-ignore 17 | class PinoLogger implements Logger { info(msg: string): void { console.log("[Pino]:" , msg) } } 18 | 19 | interface UserData { 20 | name: string 21 | } 22 | 23 | class AuthService { 24 | async getUserData(): Promise { 25 | return { name: "Big Lebowski" } 26 | } 27 | } 28 | 29 | class User { 30 | constructor(private data: UserData) {} 31 | name = () => this.data.name 32 | } 33 | 34 | class PaymentService { 35 | constructor(private readonly logger: Logger, private readonly user: User) {} 36 | sendMoney() { 37 | this.logger.info(`Sending monery to the: ${this.user.name()} `) 38 | return true 39 | } 40 | } 41 | 42 | export async function runMyApp() { 43 | const root = createContainer() 44 | .add({ 45 | logger: () => 46 | process.env.NODE_ENV === "production" 47 | ? new PinoLogger() 48 | : new ConsoleLogger(), 49 | }) 50 | .add({ auth: new AuthService() }) 51 | .add((ctx) => ({ 52 | user: async () => new User(await ctx.auth.getUserData()), 53 | })) 54 | .add((ctx) => ({ 55 | paymentService: async () => 56 | new PaymentService(ctx.logger, await ctx.user), 57 | })) 58 | 59 | const ps = await root.items.paymentService 60 | ps.sendMoney() 61 | } 62 | 63 | console.log(" ---- My App START \n\n") 64 | runMyApp().then(() => { 65 | console.log("\n\n ---- My App END") 66 | }) 67 | ``` 68 | -------------------------------------------------------------------------------- /website/docs/async-di/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Async DI Example", 3 | "position": 40, 4 | "link": { 5 | "type": "generated-index" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /website/docs/basic-di/1.manual-di.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Pure DI 6 | 7 | ```ts 8 | export interface Logger { 9 | info: (msg: string) => void 10 | warn: (msg: string) => void 11 | error: (msg: string) => void 12 | } 13 | 14 | export class ConsoleLogger implements Logger { 15 | info(msg: string): void { 16 | console.log("Console Logger: ", msg) 17 | } 18 | warn(msg: string): void {} 19 | error(msg: string): void {} 20 | } 21 | 22 | export class PinoLogger implements Logger { 23 | info(msg: string): void { 24 | console.log("Pino Logger: ", msg) 25 | } 26 | warn(msg: string): void {} 27 | error(msg: string): void {} 28 | } 29 | 30 | export class PaymentService { 31 | private readonly logger: Logger 32 | constructor(_logger: Logger) { 33 | this.logger = _logger 34 | } 35 | 36 | sendMoney() { 37 | this.logger.info("inversify logger info") 38 | return true 39 | } 40 | } 41 | 42 | const logger = 43 | process.env.NODE_ENV === "production" ? new PinoLogger() : new ConsoleLogger() 44 | 45 | const paymentService = new PaymentService(logger) 46 | paymentService.sendMoney() 47 | ``` 48 | -------------------------------------------------------------------------------- /website/docs/basic-di/2.inversify.mdx.hidden: -------------------------------------------------------------------------------- 1 | _work in progress_ 2 | -------------------------------------------------------------------------------- /website/docs/basic-di/3.syringe.mdx.hidden: -------------------------------------------------------------------------------- 1 | _work in progress_ 2 | -------------------------------------------------------------------------------- /website/docs/basic-di/4.typed-inject.mdx.hidden: -------------------------------------------------------------------------------- 1 | _work in progress_ 2 | -------------------------------------------------------------------------------- /website/docs/basic-di/5.iti.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # ITI DI 6 | 7 | ```ts 8 | import { createContainer } from "iti" 9 | export interface Logger { 10 | info: (msg: string) => void 11 | warn: (msg: string) => void 12 | error: (msg: string) => void 13 | } 14 | 15 | export class ConsoleLogger implements Logger { 16 | info(msg: string): void { 17 | console.log("Console Logger: ", msg) 18 | } 19 | warn(msg: string): void {} 20 | error(msg: string): void {} 21 | } 22 | 23 | export class PinoLogger implements Logger { 24 | info(msg: string): void { 25 | console.log("Pino Logger: ", msg) 26 | } 27 | warn(msg: string): void {} 28 | error(msg: string): void {} 29 | } 30 | 31 | export class PaymentService { 32 | private readonly logger: Logger 33 | constructor(_logger: Logger) { 34 | this.logger = _logger 35 | } 36 | 37 | sendMoney() { 38 | this.logger.info("inversify logger info") 39 | return true 40 | } 41 | } 42 | 43 | const root = createContainer() 44 | .add({ 45 | logger: () => 46 | process.env.NODE_ENV === "production" 47 | ? new PinoLogger() 48 | : new ConsoleLogger(), 49 | }) 50 | .add((ctx) => ({ 51 | paymentService: () => new PaymentService(ctx.logger), 52 | })) 53 | 54 | const ps = root.congainers.paymentService 55 | ps.sendMoney() 56 | ``` 57 | -------------------------------------------------------------------------------- /website/docs/basic-di/6.iti-vs-pure.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | --- 4 | 5 | import Tabs from "@theme/Tabs" 6 | import TabItem from "@theme/TabItem" 7 | 8 | # Pure DI vs ITI 9 | 10 | ## Business logic 11 | 12 | With ITI and Pure DI your business logic stays exactly the same. 13 | There is no need to add any framework specific decorators or `extends`. 14 | 15 | ```ts 16 | export interface Logger { 17 | info: (msg: string) => void 18 | warn: (msg: string) => void 19 | error: (msg: string) => void 20 | } 21 | 22 | export class ConsoleLogger implements Logger { 23 | info(msg: string): void { 24 | console.log("Console Logger: ", msg) 25 | } 26 | warn(msg: string): void {} 27 | error(msg: string): void {} 28 | } 29 | 30 | export class PinoLogger implements Logger { 31 | info(msg: string): void { 32 | console.log("Pino Logger: ", msg) 33 | } 34 | warn(msg: string): void {} 35 | error(msg: string): void {} 36 | } 37 | 38 | export class PaymentService { 39 | private readonly logger: Logger 40 | constructor(_logger: Logger) { 41 | this.logger = _logger 42 | } 43 | 44 | sendMoney() { 45 | this.logger.info("inversify logger info") 46 | return true 47 | } 48 | } 49 | ``` 50 | 51 | ## Wiring 52 | 53 | 54 | 55 | 56 | ```ts title="./src/app.ts" 57 | const logger = 58 | process.env.NODE_ENV === "production" ? new PinoLogger() : new ConsoleLogger() 59 | 60 | const paymentService = new PaymentService(logger) 61 | paymentService.sendMoney() 62 | ``` 63 | 64 | 65 | 66 | 67 | ```tsx title="./src/app.ts" 68 | const root = createContainer() 69 | .add({ 70 | logger: () => 71 | process.env.NODE_ENV === "production" 72 | ? new PinoLogger() 73 | : new ConsoleLogger(), 74 | }) 75 | .add((ctx) => ({ 76 | paymentService: () => new PaymentService(ctx.logger), 77 | })) 78 | 79 | const ps = root.items.paymentService 80 | ps.sendMoney() 81 | ``` 82 | 83 | 84 | 85 | 86 | 87 | ## Async Request for single item in ITI 88 | -------------------------------------------------------------------------------- /website/docs/basic-di/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Basic DI Example", 3 | "position": 30, 4 | "link": { 5 | "type": "generated-index" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /website/docs/examples/circular-dependency.ts: -------------------------------------------------------------------------------- 1 | import { createContainer } from "iti" 2 | 3 | /* 4 | // Part 1: You can create a circular dependency 5 | // in your business logic 6 | ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ 7 | │ A │──▶│ B │──▶│ C │──▶│ D │──▶│ E │ 8 | └───┘ └───┘ └───┘ └───┘ └───┘ 9 | ▲ │ 10 | │ │ 11 | └───────────────┘ 12 | */ 13 | class A { 14 | constructor() {} 15 | } 16 | class B { 17 | constructor(a: A) {} 18 | } 19 | class C { 20 | constructor(b: B, e: E) {} 21 | } 22 | class D { 23 | constructor(c: C) {} 24 | } 25 | class E { 26 | constructor(d: E) {} 27 | } 28 | 29 | // Part 2: You can't express a circular dependency because of typescript checks 30 | createContainer() 31 | .add((ctx) => ({ 32 | a: () => new A(), 33 | })) 34 | .add((ctx) => ({ 35 | b: () => new B(ctx.a), 36 | })) 37 | .add((ctx) => ({ 38 | // This line will throw a Typescript error at compile time 39 | c: () => new C(ctx.a, ctx.e), 40 | })) 41 | -------------------------------------------------------------------------------- /website/docs/examples/oven-business-logic.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/docs/examples/oven-business-logic.ts -------------------------------------------------------------------------------- /website/docs/with-react/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "React", 3 | "position": 20, 4 | "link": { 5 | "type": "generated-index", 6 | "description": "Working with React" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /website/docs/with-react/basic.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Ensure Sync Containers 6 | 7 | _work in progress_ 8 | 9 | ```tsx title="./src/_containers-react/EnsureEcommerce.tsx" 10 | import React, { useContext } from "react" 11 | import { generateEnsureContainerSet } from "iti-react" 12 | import { useContainerSet } from "./_editor-app-hooks" 13 | 14 | const x = generateEnsureContainerSet(() => 15 | useContainerSet(["ecommerce", "auth"]) 16 | ) 17 | export const EnsureEcommerceContainer = x.EnsureWrapper 18 | export const useEcommerceContext = x.contextHook 19 | ``` 20 | 21 | ```tsx title="./src/App.tsx" 22 | import { EnsureEcommerceContainer } from "./_containers-react/EnsureEcommerce" 23 | 24 | export const App = () => ( 25 |
26 | Loading}> 27 | 28 | 29 |
30 | ) 31 | ``` 32 | 33 | ```tsx title="./src/Currency.tsx" 34 | import { useEcommerceContext } from "../../../../_containers-react/EnsureEcommerce" 35 | 36 | export const CurrencyInfo = () => { 37 | const { currencyStore, taxStore } = useEcommerceContext().ecommerce 38 | 39 | return
{currencyStore.currency}
40 | } 41 | ``` 42 | -------------------------------------------------------------------------------- /website/docs/with-react/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Full Example Configuration 6 | 7 | Basically, we create an ITI instance, add it to `React.Context` and generate fetch hooks. 8 | 9 | ```tsx title="./src/_containers/main-app" 10 | import { createContainer } from "iti" 11 | 12 | export const mainApp = () => 13 | createContainer().add(() => ({ 14 | auth: async () => { 15 | const res = await fetch("/api/profile") 16 | return { 17 | profile: res.json(), 18 | } 19 | }, 20 | })) 21 | export type MainAppContainer = ReturnType 22 | ``` 23 | 24 | ```tsx title="./src/_containers/hooks" 25 | import React from "react" 26 | import { getContainerSetHooks } from "iti-react" 27 | import { MainAppContainer } from "./_containers/main-app" 28 | 29 | export const MyAppContext = React.createContext({}) 30 | 31 | const hooks = getContainerSetHooks(MyAppContext) 32 | export const useContainerSet = hooks.useContainerSet 33 | export const useContainer = hooks.useContainer 34 | ``` 35 | 36 | ```tsx title="./src/App.tsx" 37 | import React, { useContext } from "react" 38 | import { mainApp } from "./_containers/main-app" 39 | import { MyAppContext } from "./_containers/hooks" 40 | import { Profile } from "./Profile" 41 | 42 | function App() { 43 | const itiContainerInstance = useMemo(() => mainApp(), []) 44 | 45 | return ( 46 | <> 47 | 48 | 49 | 50 | 51 | ) 52 | } 53 | ``` 54 | 55 | ```tsx title="./src/Profile.tsx" 56 | import { useContainer } from "./_containers/main-app" 57 | 58 | function Profile() { 59 | const [auth, authErr] = useContainer().auth 60 | 61 | if (authErr) return
failed to load
62 | if (!auth) return
loading...
63 | 64 | return
hello {auth.profile.name}!
65 | } 66 | ``` 67 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iti-docs-docosaurus", 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 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.1.0", 18 | "@docusaurus/preset-classic": "2.1.0", 19 | "@mdx-js/react": "^1.6.22", 20 | "clsx": "^1.2.1", 21 | "docusaurus-lunr-search": "^2.2.0", 22 | "iti": "^0.5.0", 23 | "iti-react": "^0.5.0", 24 | "prism-react-renderer": "^1.3.5", 25 | "raw-loader": "^4.0.2", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0" 28 | }, 29 | "devDependencies": { 30 | "@docusaurus/module-type-aliases": "2.1.0", 31 | "prettier": "^2.7.1" 32 | }, 33 | "prettier": { 34 | "printWidth": 80, 35 | "semi": false, 36 | "trailingComma": "es5", 37 | "arrowParens": "always", 38 | "endOfLine": "lf" 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.5%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /website/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 | { 23 | type: 'category', 24 | label: 'Tutorial', 25 | items: ['hello'], 26 | }, 27 | ], 28 | */ 29 | }; 30 | 31 | module.exports = sidebars; 32 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures/index.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import clsx from "clsx" 3 | import styles from "./styles.module.css" 4 | 5 | const FeatureList = [ 6 | { 7 | title: "Pure DI Upgrade", 8 | Svg: require("@site/static/img/undraw_docusaurus_react.svg").default, 9 | description: ( 10 | <> 11 | You are using a DI already. Progressively upgrade and reap 12 | ITI benefits 13 | 14 | ), 15 | }, 16 | { 17 | title: "Type Safe", 18 | Svg: require("@site/static/img/undraw_docusaurus_mountain.svg").default, 19 | description: ( 20 | <> 21 | If your project compiles your dependencies will resolve correctly at a 22 | runtime 23 | 24 | ), 25 | }, 26 | { 27 | title: "Asynchronous", 28 | Svg: require("@site/static/img/undraw_docusaurus_tree.svg").default, 29 | description: <>Your app is asynchronousm so should be your framework, 30 | }, 31 | { 32 | title: "React Compatible", 33 | Svg: require("@site/static/img/undraw_docusaurus_react.svg").default, 34 | description: ( 35 | <> 36 | Useful React bindings. Extract 37 | application business logic from a React hooks and components 38 | 39 | ), 40 | }, 41 | ] 42 | 43 | function Feature({ Svg, title, description }) { 44 | return ( 45 |
46 | {/*
47 | 48 |
*/} 49 |
50 |

{title}

51 |

{description}

52 |
53 |
54 | ) 55 | } 56 | 57 | export default function HomepageFeatures() { 58 | return ( 59 |
60 | {FeatureList.map((props, idx) => ( 61 | 62 | ))} 63 |
64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: grid; 3 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 4 | /* align-items: center; */ 5 | /* justify-content: space-around; */ 6 | /* flex-wrap: wrap; */ 7 | gap: 16px; 8 | padding: 2rem 0; 9 | maring: 1rem; 10 | width: 100%; 11 | } 12 | 13 | .featureSvg { 14 | height: 200px; 15 | width: 200px; 16 | } 17 | 18 | .featureBlock { 19 | /* background-color: #f9f9f9; 20 | border: 1px solid #f9f9f9; */ 21 | color: var(--ifm-color-info-contrast-foreground); 22 | background-color: var(--ifm-color-info-contrast-background); 23 | border: 2px solid var(--ifm-color-info-lightest); 24 | border-radius: 12px; 25 | height: 100%; 26 | padding: 24px; 27 | } 28 | /* .features div { 29 | width: 25%; 30 | } */ 31 | -------------------------------------------------------------------------------- /website/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: #2e8555; 10 | --ifm-color-primary-dark: #29784c; 11 | --ifm-color-primary-darker: #277148; 12 | --ifm-color-primary-darkest: #205d3b; 13 | --ifm-color-primary-light: #33925d; 14 | --ifm-color-primary-lighter: #359962; 15 | --ifm-color-primary-lightest: #3cad6e; 16 | --ifm-code-font-size: 80%; 17 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 18 | } 19 | 20 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 21 | [data-theme="dark"] { 22 | --ifm-color-primary: #25c2a0; 23 | --ifm-color-primary-dark: #21af90; 24 | --ifm-color-primary-darker: #1fa588; 25 | --ifm-color-primary-darkest: #1a8870; 26 | --ifm-color-primary-light: #29d5b0; 27 | --ifm-color-primary-lighter: #32d8b4; 28 | --ifm-color-primary-lightest: #4fddbf; 29 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 30 | } 31 | -------------------------------------------------------------------------------- /website/src/pages/index.js: -------------------------------------------------------------------------------- 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 | const Logo = require("@site/static/img/iti/logo.svg").default 11 | 12 | function HomepageHeader() { 13 | const { siteConfig } = useDocusaurusContext() 14 | return ( 15 |
16 |
17 |
18 | {/* */} 19 | 20 |

{siteConfig.title}

21 |
22 | 23 |

{siteConfig.tagline}

24 | 25 |
26 | 30 | Get Started 31 | 32 | 36 | Try a Demo 37 | 38 | 42 | Features 43 | 44 | 45 | 46 | Why ITI 47 | 48 | 49 | 53 | View on Github 54 | 55 |
56 |
57 |
58 | ) 59 | } 60 | 61 | export default function Home() { 62 | const { siteConfig } = useDocusaurusContext() 63 | return ( 64 | 68 | 69 |
70 | 71 |
72 |
73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /website/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 | .heroLogo { 14 | width: 100%; 15 | max-width: 500px; 16 | margin: 0 auto; 17 | } 18 | 19 | .heroLogo svg { 20 | width: 30%; 21 | } 22 | 23 | .main { 24 | padding: 2rem; 25 | } 26 | 27 | @media screen and (max-width: 996px) { 28 | .heroBanner { 29 | padding: 2rem; 30 | } 31 | } 32 | 33 | .buttons { 34 | display: flex; 35 | flex-wrap: wrap; 36 | flex-direction: row; 37 | justify-content: center; 38 | gap: 1rem; 39 | } 40 | -------------------------------------------------------------------------------- /website/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 | -------------------------------------------------------------------------------- /website/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/.nojekyll -------------------------------------------------------------------------------- /website/static/google8b69c6f2456eb7a7.html: -------------------------------------------------------------------------------- 1 | google-site-verification: google8b69c6f2456eb7a7.html -------------------------------------------------------------------------------- /website/static/img/docusaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/docusaurus.png -------------------------------------------------------------------------------- /website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/favicon.ico -------------------------------------------------------------------------------- /website/static/img/iti/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/favicon.ico -------------------------------------------------------------------------------- /website/static/img/iti/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /website/static/img/iti/rastr/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/rastr/icon-180.png -------------------------------------------------------------------------------- /website/static/img/iti/rastr/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/rastr/icon-192.png -------------------------------------------------------------------------------- /website/static/img/iti/rastr/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/rastr/icon-512.png -------------------------------------------------------------------------------- /website/static/img/iti/rastr/logo-2048.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/molszanski/iti/890f3bfa6d5923e6fe6c9e03c9bfd186b276b4da/website/static/img/iti/rastr/logo-2048.png --------------------------------------------------------------------------------