├── .clean-publish ├── .editorconfig ├── .github └── workflows │ ├── deploy-site.yaml │ └── tests.yaml ├── .gitignore ├── .npmrc ├── .prettierrc ├── .size-limit.json ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── knip.json ├── package-lock.json ├── package.json ├── sandbox.ts ├── src ├── compose │ ├── __fixtures__ │ │ └── createRandomContainer.ts │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── compose-up.spec.ts.snap │ │ ├── compose-up.spec.ts │ │ └── index.spec-d.ts │ ├── commands │ │ ├── diff │ │ │ ├── __tests__ │ │ │ │ └── index.spec.ts │ │ │ ├── getSkippedContainers.ts │ │ │ └── index.ts │ │ ├── graph │ │ │ ├── __tests__ │ │ │ │ ├── example.spec.ts │ │ │ │ ├── index.spec.ts │ │ │ │ └── transformToDomainsGraph.spec.ts │ │ │ ├── computeTransitiveDependencies.ts │ │ │ ├── createViewMapper.ts │ │ │ ├── index.ts │ │ │ ├── relations │ │ │ │ ├── __tests__ │ │ │ │ │ ├── dependsOn.spec-d.ts │ │ │ │ │ ├── dependsOn.spec.ts │ │ │ │ │ ├── requiredBy.spec-d.ts │ │ │ │ │ └── requiredBy.spec.ts │ │ │ │ ├── dependsOn.ts │ │ │ │ ├── index.ts │ │ │ │ └── requiredBy.ts │ │ │ ├── transformToDomainsGraph.ts │ │ │ └── types.ts │ │ └── up │ │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── debug.spec.ts.snap │ │ │ ├── debug.spec.ts │ │ │ ├── index.spec.ts │ │ │ └── normalize-сonfig.spec.ts │ │ │ ├── createStageUpFn.ts │ │ │ ├── index.ts │ │ │ ├── normalizeConfig.ts │ │ │ └── validateStageUp │ │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── startupFailedError.spec.ts.snap │ │ │ ├── startupFailedError.spec.ts │ │ │ └── validateStageUp.spec.ts │ │ │ ├── index.ts │ │ │ └── startupFailedError.ts │ ├── index.ts │ └── prepareStages │ │ ├── __tests__ │ │ ├── get-containers-to-boot.spec.ts │ │ ├── prepare-stages.spec.ts │ │ ├── traverse-containers.spec.ts │ │ ├── uniq-container-id.spec.ts │ │ └── uniq-stage-id.spec.ts │ │ ├── getContainersToBoot.ts │ │ ├── index.ts │ │ ├── partitionOptionalDeps.ts │ │ ├── traverseContainers.ts │ │ ├── types.ts │ │ └── validators.ts ├── createContainer │ ├── __tests__ │ │ ├── deps.spec-d.ts │ │ ├── domain.spec-d.ts │ │ ├── id.spec-d.ts │ │ ├── start.spec-d.ts │ │ ├── types.ts │ │ └── validate.spec.ts │ ├── index.ts │ ├── types.ts │ └── validate.ts ├── index.ts └── shared │ ├── colors.ts │ ├── index.ts │ ├── isNil.ts │ └── pick.ts ├── tsconfig.json ├── vitest.config.ts ├── vitest.setup.ts └── website ├── .gitignore ├── README.md ├── astro.config.mjs ├── package-lock.json ├── package.json ├── public └── favicon.svg ├── src ├── content │ ├── config.ts │ └── docs │ │ ├── how-to-guides │ │ ├── debug.mdx │ │ ├── dynamically-load.mdx │ │ ├── handle-asynchronous-operations.mdx │ │ ├── handle-container-failures.mdx │ │ ├── share-data-between-containers.mdx │ │ ├── stages-required.mdx │ │ ├── use-with-react.mdx │ │ ├── use-with.mdx │ │ ├── visualize-the-system.mdx │ │ └── with-feature-toggle.mdx │ │ ├── index.mdx │ │ ├── reference │ │ ├── changelog.mdx │ │ ├── create-container.mdx │ │ └── glossary.mdx │ │ └── tutorials │ │ ├── basic-usage.mdx │ │ ├── dependencies.mdx │ │ ├── enable.mdx │ │ ├── getting-started.mdx │ │ ├── stages.mdx │ │ └── summary.mdx └── env.d.ts └── tsconfig.json /.clean-publish: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".editorconfig", 4 | ".gitignore", 5 | ".prettierrc", 6 | "lefthook.yml", 7 | "src", 8 | ".npmrc", 9 | "knip.json", 10 | "tsconfig.json", 11 | "Makefile" 12 | ], 13 | "fields": ["devDependencies", "scripts", "engines", "volta" ], 14 | "packageManager": "npm", 15 | "access": "public" 16 | } 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*] 5 | 6 | indent_style = space 7 | 8 | indent_size = 2 9 | 10 | end_of_line = lf 11 | 12 | charset = utf-8 13 | 14 | trim_trailing_whitespace = true 15 | 16 | insert_final_newline = true 17 | 18 | [Makefile] 19 | indent_style = tab 20 | -------------------------------------------------------------------------------- /.github/workflows/deploy-site.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout your repository using git 18 | uses: actions/checkout@v4 19 | - name: Install, build, and upload your site 20 | uses: withastro/action@v3 21 | with: 22 | path: ./website 23 | node-version: 22.9.0 24 | package-manager: pnpm@latest 25 | 26 | deploy: 27 | needs: build 28 | runs-on: ubuntu-latest 29 | environment: 30 | name: github-pages 31 | url: ${{ steps.deployment.outputs.page_url }} 32 | steps: 33 | - name: Deploy to GitHub Pages 34 | id: deployment 35 | uses: actions/deploy-pages@v4 36 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Install and Test 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths-ignore: 7 | - '**/*.md' 8 | - '**/*.mdx' 9 | - './website/**/*.*' 10 | pull_request: 11 | branches: [main] 12 | paths-ignore: 13 | - '**/*.md' 14 | - '**/*.mdx' 15 | - './website/**/*.*' 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up Node.js 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 22.9.0 29 | 30 | - name: Install dependencies 31 | run: npm ci 32 | 33 | - name: Prepublish 34 | run: make prepublish 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | dist/ 4 | .cache/ 5 | build/ 6 | .DS_Store 7 | coverage/ 8 | **/*.zip 9 | .vscode 10 | .swc/ 11 | .nx 12 | tsconfig.vitest-temp.json 13 | ./sandbox.ts 14 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier-plugin-organize-imports"], 3 | "printWidth": 120, 4 | "parser": "typescript", 5 | "useTabs": false, 6 | "singleQuote": true, 7 | "jsxSingleQuote": true, 8 | "experimentalTernaries": true, 9 | "trailingComma": "all", 10 | "overrides": [{ 11 | "files": ["*.json"], 12 | "options": { 13 | "parser": "json" 14 | } 15 | }, { 16 | "files": ["*.yml"], 17 | "options": { 18 | "parser": "yaml" 19 | } 20 | }, { 21 | "files": ["*.mdx", "*.md"], 22 | "options": { 23 | "parser": "markdown" 24 | } 25 | }], 26 | "organizeImportsSkipDestructiveCodeActions": true 27 | } 28 | -------------------------------------------------------------------------------- /.size-limit.json: -------------------------------------------------------------------------------- 1 | [{ "path": "dist/index.js", "limit": "4 kB" }] 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 4 | and this project adheres to [Semantic Versioning](http://semver.org). 5 | 6 | [Here](https://grlt-hub.github.io/app-compose/reference/changelog/) 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 grlt-hub 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | rm -rf ./dist && npx tsc --noEmit --project ./tsconfig.json && npx tsup src/index.ts --minify terser --format esm --dts 3 | 4 | test: 5 | npx vitest --coverage 6 | 7 | lint: 8 | npx knip && npx size-limit 9 | 10 | prepublish: 11 | make build && make lint && make test 12 | 13 | publish: 14 | npm i && make prepublish && npx clean-publish 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
10 | Compose modules into apps. 11 |
12 |
15 | 16 |
17 | Documentation | Get involved! 18 |
19 | 20 |(params: P) => { 53 | validateContainerId(params); 54 | validateDomainName(params); 55 | validateDepsIntersection(params); 56 | 57 | return params as InferValidParams
;
58 | };
59 |
60 | export { validate, type ContainerDomainNameEmptyStringError, type ContainerIdEmptyStringError };
61 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { compose } from './compose';
2 | export { createContainer } from './createContainer';
3 |
--------------------------------------------------------------------------------
/src/shared/colors.ts:
--------------------------------------------------------------------------------
1 | /* v8 ignore start */
2 |
3 | const isNode = typeof process !== 'undefined' && process.versions && process.versions.node;
4 |
5 | const colors = {
6 | bgGreen: (x: string) => (isNode ? `\x1b[42m${x}\x1b[49m` : x),
7 | magenta: (x: string) => (isNode ? `\x1b[35m${x}\x1b[39m` : x),
8 | yellow: (x: string) => (isNode ? `\x1b[33m${x}\x1b[39m` : x),
9 | };
10 |
11 | export { colors };
12 |
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | import type { AnyContainer, ContainerId } from '@createContainer';
2 |
3 | const LIBRARY_NAME = '[app-compose]';
4 | type NonEmptyTuple
98 | Try it
99 |
100 | ## Understanding Why a Container Was Started or Skipped
101 |
102 | For this, you can use the diff command, which shows which containers were started, skipped, or added automatically.
103 |
104 | ```ts {58}
105 | const hireChef = () => ({
106 | hasBreak: false,
107 | makePizza: (_: { ingredients: string[] }) => 'pizza',
108 | });
109 | const orderIngredients = () => ['dough', 'sauce', 'cheese'];
110 |
111 | const chef = createContainer({
112 | id: 'John Doe',
113 | domain: 'italian-chef',
114 | start: async () => {
115 | const hiredChef = await hireChef();
116 |
117 | return { api: hiredChef };
118 | },
119 | });
120 |
121 | const ingredients = createContainer({
122 | id: 'ingredients',
123 | domain: 'shop',
124 |
125 | dependencies: [chef],
126 | enable: (api) => api['John Doe'].hasBreak === false,
127 | start: async () => {
128 | const orderedIngredients = await orderIngredients();
129 |
130 | return { api: { orderedIngredients } };
131 | },
132 | });
133 |
134 | const olives = createContainer({
135 | id: 'olives',
136 | domain: 'ingredients',
137 | start: () => ({ api: { value: 'olives' } }),
138 | });
139 |
140 | const pizza = createContainer({
141 | id: 'pizza',
142 | domain: 'dish',
143 | dependencies: [chef, ingredients],
144 | optionalDependencies: [olives],
145 | start: (api) => {
146 | const pepperoniPizza = api['John Doe'].makePizza({
147 | ingredients: api.ingredients.orderedIngredients,
148 | });
149 |
150 | return { api: { pizza: pepperoniPizza } };
151 | },
152 | });
153 |
154 | const cmd = await compose({
155 | stages: [
156 | ['prepare', [ingredients]],
157 | ['cooking', [pizza]],
158 | ],
159 | required: 'all',
160 | });
161 |
162 | await cmd.diff();
163 | ```
164 |
165 | When you run this code, you should see the following highlighted output in the console:
166 |
167 | ```sh
168 | [app-compose] | diff command
169 | All skipped containers are optional. If they are expected to work,
170 | please include them in the list when calling `compose` function
171 |
172 | Stages:
173 | - prepare:
174 | expected: [ ingredients ]
175 | received: [ ingredients, John Doe ]
176 | - cooking:
177 | expected: [ pizza ]
178 | received: [ pizza ]
179 | skipped:
180 | - olives: [pizza]
181 | ```
182 |
183 |
184 | Try it
185 |
--------------------------------------------------------------------------------
/website/src/content/docs/how-to-guides/dynamically-load.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Dynamically load code for a container
3 | sidebar:
4 | order: 8
5 | ---
6 |
7 | In this section, you will learn how to dynamically load code for a container in `app-compose`.
8 | This is useful when you want to load a container only when it’s needed, which can improve app performance.
9 |
10 | ### Example
11 |
12 | ```ts title="featureModuleContainer.ts" {5,8}
13 | const featureModuleContainer = createContainer({
14 | id: 'featureModule',
15 | domain: 'features',
16 | start: async () => {
17 | const { FeatureComponent } = await import('./FeatureComponent');
18 | return { api: { ui: FeatureComponent } };
19 | },
20 | enable: () => process.env.FEATURE_MODULE_ENABLED === 'true',
21 | });
22 | ```
23 |
--------------------------------------------------------------------------------
/website/src/content/docs/how-to-guides/handle-asynchronous-operations.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Handle Asynchronous Operations
3 | sidebar:
4 | order: 1
5 | ---
6 |
7 | In this section, you will learn how to work with asynchronous operations in `app-compose`.
8 |
9 | Both `enable` and `start` can be async functions, which is useful when you need to perform operations like fetching data or checking system readiness.
10 |
11 | ## Example
12 |
13 | ```tsx {14-16}
14 | const isChefAvailable = async () => {
15 | await new Promise((resolve) => setTimeout(resolve, 1000));
16 | return true;
17 | };
18 |
19 | const sharpenKnives = async () => {
20 | await new Promise((resolve) => setTimeout(resolve, 2000));
21 | return { knives: ['little', 'big'] };
22 | };
23 |
24 | const chef = createContainer({
25 | id: 'John Doe',
26 | domain: 'italian-chef',
27 | enable: isChefAvailable,
28 | start: async () => {
29 | const data = await sharpenKnives();
30 | return { api: data };
31 | },
32 | });
33 | ```
34 |
--------------------------------------------------------------------------------
/website/src/content/docs/how-to-guides/handle-container-failures.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Handle container failures
3 | sidebar:
4 | order: 5
5 | ---
6 |
7 | In this section, you will learn how to handle container failures in `app-compose`.
8 |
9 | When a container fails to start, you can detect this and take action.
10 |
11 | ### Example
12 |
13 | ```ts {21}
14 | const unstableService = createContainer({
15 | id: 'unstable-service',
16 | domain: 'backend',
17 | start: () => {
18 | throw new Error('Service failed to start');
19 | },
20 | });
21 |
22 | const myFeature = createContainer({
23 | id: 'feature',
24 | domain: 'frontend',
25 | dependencies: [unstableService],
26 | start: () => ({ api: {} }),
27 | });
28 |
29 | const { up } = await compose({
30 | stages: [['stage-name', [myFeature]]],
31 | });
32 |
33 | await up({
34 | onContainerFail: ({ container, error, stageId }) => {
35 | // for example, sending the error
36 | // to an error tracking service.
37 | console.log({
38 | stageId,
39 | container,
40 | msg: error.message,
41 | });
42 | },
43 | });
44 | ```
45 |
46 | When you run this code, you should see the following output in the console:
47 |
48 | ```json
49 | {
50 | "stageId": "stage-name",
51 | "container": {
52 | "id": "unstable-service",
53 | "domain": "backend"
54 | },
55 | "msg": "Service failed to start"
56 | }
57 | ```
58 |
59 |
60 | Try it
61 |
--------------------------------------------------------------------------------
/website/src/content/docs/how-to-guides/share-data-between-containers.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Share Data Between Containers
3 | sidebar:
4 | order: 2
5 | ---
6 |
7 | In this section, you will learn how to share data between containers in `app-compose`.
8 |
9 | Both enable and start can receive data from other containers. This is useful when one container depends on information from another.
10 |
11 | ## Example
12 |
13 | ```tsx {11,13}
14 | const ingredients = createContainer({
15 | id: 'pizza-base',
16 | domain: 'kitchen',
17 | start: () => ({ api: { available: ['dough', 'sauce', 'cheese'] } }),
18 | });
19 |
20 | const chef = createContainer({
21 | id: 'John Doe',
22 | domain: 'italian-chef',
23 | dependencies: [ingredients],
24 | enable: (api) => api.ingredients.available.includes('cheese'),
25 | start: (api) => {
26 | console.log(`Making pizza with: ${api.ingredients.available.join(', ')}`);
27 |
28 | return { api: { dish: 'pizza' } };
29 | },
30 | });
31 | ```
32 |
--------------------------------------------------------------------------------
/website/src/content/docs/how-to-guides/stages-required.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Define which containers are critical for app startup
3 | sidebar:
4 | order: 7
5 | ---
6 |
7 | In this section, you will learn how to define which containers are critical for your app to start successfully.
8 | You can use the `required` option in `compose.up` to control the startup process and handle critical parts of your app effectively.
9 |
10 | ## Values
11 |
12 | - `required: undefined`: The app will start even if some containers do not work. This is the **default** value.
13 | - `required: 'all'`: All containers must start. If one container fails, up will stop and reject an error.
14 | - `required: [container1, container2]`: Only the listed containers must start successfully. If one of them fails, up will reject.
15 | - `required: [container1, [container2, container3]`: Nested lists mean that at least one container from the group must start.
16 | In this example, `container1` and either `container2` or `container3` must start successfully.
17 |
18 | ### Example
19 |
20 | ```tsx {6}
21 | const cmd = await compose({
22 | stages: [
23 | ['prepare', [chef, ingredients]],
24 | ['cooking', [pizza]],
25 | ],
26 | required: 'all',
27 | });
28 | ```
29 |
--------------------------------------------------------------------------------
/website/src/content/docs/how-to-guides/use-with-react.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Use with React
3 | sidebar:
4 | order: 3
5 | ---
6 |
7 | This page shows how to integrate `compose.up` with React for dynamic rendering of components managed by containers.
8 |
9 | ## Example
10 |
11 | ```tsx
12 | const Logo = () => ;
13 |
14 | const logoContainer = createContainer({
15 | id: 'logo',
16 | domain: 'assets',
17 | start: () => ({ api: { ui: Logo } }),
18 | enable: () => true,
19 | });
20 |
21 | const layoutContainer = createContainer({
22 | id: 'layout',
23 | domain: 'layouts',
24 | optionalDependencies: [logoContainer],
25 | start: (apis) => {
26 | const Layout = () => (apis.logo ?
no logo
);
27 |
28 | createRoot(document.getElementById('root')!).render(
29 | Hello
31 |
47 | Try it
48 |
49 | ## Tip
50 |
51 | This method works well for simple pages with few components. But if your app has many components inside each other, you might need more flexibility.
52 |
53 | For this, you can use slots. React doesn’t have slots by default, but you can use them with the [@grlt-hub/react-slots](https://github.com/grlt-hub/react-slots) package.
54 |
--------------------------------------------------------------------------------
/website/src/content/docs/how-to-guides/use-with.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Use with...
3 | sidebar:
4 | order: 4
5 | ---
6 |
7 | Unfortunately, examples are not available for all libraries.
8 |
9 | If you have successfully used `app-compose` with any of these frameworks, please help the project by [contributing](https://github.com/grlt-hub/app-compose) documentation.
10 |
--------------------------------------------------------------------------------
/website/src/content/docs/how-to-guides/visualize-the-system.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Visualize the System
3 | sidebar:
4 | order: 9
5 | ---
6 |
7 | In this section, you will learn how to visualize the system of containers in `app-compose`.
8 | Visualization helps you see how containers are connected, their dependencies, and their current statuses.
9 |
10 | You can use the `graph` command to generate a visual representation of your container structure. This makes it easier to understand complex systems and debug issues.
11 |
12 | ## Example
13 |
14 | ```ts {14}
15 | import { createContainer, compose } from '@grlt-hub/app-compose';
16 |
17 | const start = () => ({ api: null });
18 |
19 | const a = createContainer({ id: 'a', domain: 'a', start });
20 | const b = createContainer({ id: 'b', domain: 'b', dependencies: [a], start });
21 | const c = createContainer({ id: 'c', domain: 'c', optionalDependencies: [b], start });
22 | const d = createContainer({ id: 'd', domain: 'd', dependencies: [c], optionalDependencies: [b], start });
23 |
24 | const cmd = await compose({
25 | stages: [['_', [a, b, c, d]]],
26 | });
27 |
28 | const { graph, dependsOn, requiredBy } = await cmd.graph();
29 |
30 | console.log(JSON.stringify(graph, undefined, 2));
31 | ```
32 |
33 | The `cmd.graph` command returns an object with:
34 |
35 | - `graph` — the visual representation of the system.
36 | - `dependsOn`(Container[]) — shows which containers the specified container depends on.
37 | - `requiredBy`(Container[]) — shows which containers require the specified container to work.
38 |
39 | The output provides a detailed breakdown of each container:
40 |
41 | - **Direct Dependencies**:
42 | - **Strict**: Lists containers that are `strict` dependencies of the current container.
43 | - **Optional**: Lists containers that are `optional` dependencies of the current container.
44 | - **Transitive Dependencies**:
45 | - **Strict**: Lists containers that are `strict` dependencies, inherited through a chain of dependencies.
46 | - **Optional**: Lists containers that are `optional` dependencies, inherited through a chain of dependencies.
47 | - **Transitive Dependency Paths**:
48 | - Each transitive dependency includes a path that describes how the dependency is reached, which is helpful for tracing and debugging.
49 |
50 | ```json
51 | {
52 | "a": {
53 | "domain": "a",
54 | "dependencies": [],
55 | "optionalDependencies": [],
56 | "transitive": {
57 | "dependencies": [],
58 | "optionalDependencies": []
59 | }
60 | },
61 | "b": {
62 | "domain": "b",
63 | "dependencies": ["a"],
64 | "optionalDependencies": [],
65 | "transitive": {
66 | "dependencies": [],
67 | "optionalDependencies": []
68 | }
69 | },
70 | "c": {
71 | "domain": "c",
72 | "dependencies": [],
73 | "optionalDependencies": ["b"],
74 | "transitive": {
75 | "dependencies": [],
76 | "optionalDependencies": [
77 | {
78 | "id": "a",
79 | "path": "c -> b -> a"
80 | }
81 | ]
82 | }
83 | },
84 | "d": {
85 | "domain": "d",
86 | "dependencies": ["c"],
87 | "optionalDependencies": ["b"],
88 | "transitive": {
89 | "dependencies": [],
90 | "optionalDependencies": [
91 | {
92 | "id": "a",
93 | "path": "d -> b -> a"
94 | }
95 | ]
96 | }
97 | }
98 | }
99 | ```
100 |
101 |
102 | Try it
103 |
104 | ## Grouping by Domain
105 |
106 | If you want to collapse the view to show only domains, you can pass an option:
107 |
108 | ```ts
109 | const { graph, dependsOn, requiredBy } = await cmd.graph({ view: 'domains' });
110 | ```
111 |
112 | ## When to Use `compose.graph`
113 |
114 | - **Debugging**: Use the graph output to identify and resolve potential issues, such as missing or transitive (hidden) dependencies.
115 | - **Optimizing Architecture**: Analyze the dependency structure to refactor and optimize your application's architecture.
116 | - **Documenting Dependencies**: Generate a visual representation to document the architecture for your team.
117 |
--------------------------------------------------------------------------------
/website/src/content/docs/how-to-guides/with-feature-toggle.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Work with feature toggles
3 | sidebar:
4 | order: 0
5 | ---
6 |
7 | In this section, you will learn how to use feature toggles with `app-compose`.
8 | A feature toggle is a way to turn features on or off without changing the code.
9 |
10 | With `app-compose`, you can control when a container starts based on the feature toggle.
11 | You will see how to use the enable option to start or skip containers depending on the toggle.
12 |
13 | This helps you manage features easily, test new things, and turn features on or off when needed.
14 |
15 | ## Example
16 |
17 | ```tsx {1,7}
18 | const isChefAvailable = () => false;
19 |
20 | const chef = createContainer({
21 | id: 'John Doe',
22 | domain: 'italian-chef',
23 | start: () => ({ api: null }),
24 | enable: isChefAvailable,
25 | });
26 | ```
27 |
--------------------------------------------------------------------------------
/website/src/content/docs/index.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Introduction
3 | description: Compose modules into apps.
4 | ---
5 |
6 | ## What is it?
7 |
8 | `app-compose` is a library for module-based applications.
9 | It helps developers easily connect different parts of an application — features, entities, services, and so on — so they work together as a single system.
10 |
11 | With `app-compose`, you can:
12 |
13 | - Simplify the management of complex dependencies.
14 | - Control the order in which modules run.
15 | - Intuitively enable or disable parts of the application.
16 | - Clearly visualize the parts of the application and their connections.
17 |
18 | Instead of manually managing the chaos of modules, `app-compose` turns them into a well-organized and scalable application.
19 |
20 | ## Cooking Up Your Application
21 |
22 | An application is like a dish: a collection of features, entities, and services. But by themselves, they don’t make an application.
23 | To bring everything to life, you need to combine them properly: at the right time, in the right order, and without anything extra.
24 | One misstep, and instead of a pizza, you might end up with a cake.
25 |
26 | If you’re unsure how to connect modules into a single system, [app-compose](https://grlt-hub.github.io/app-compose/) can simplify the process for you.
27 |
28 | ### Example
29 |
30 | ```ts
31 | import { createContainer, compose } from '@grlt-hub/app-compose';
32 |
33 | // Imagine we are cooking a dish in our restaurant kitchen.
34 | // There are three steps:
35 | // 1. hire the chef
36 | // 2. order the ingredients,
37 | // 3. and cook the pizza.
38 |
39 | // First: prepare the "chef"
40 | // it’s like hiring the chef to start cooking.
41 | const chef = createContainer({
42 | // The name of our chef.
43 | id: 'John Doe',
44 | // This chef specializes in Italian cuisine.
45 | domain: 'italian-chef',
46 | start: async () => {
47 | // For example, we are hiring a chef.
48 | const hiredChef = await hireChef();
49 |
50 | // We return our chef.
51 | return { api: hiredChef };
52 | },
53 | });
54 |
55 | // Second: if the chef is hired,
56 | // we need to order the ingredients.
57 | const ingredients = createContainer({
58 | id: 'ingredients',
59 | domain: 'shop',
60 | // The ingredients ordering depends on the chef.
61 | dependencies: [chef],
62 | // If the chef is on break,
63 | // we can't proceed with the order.
64 | enable: (api) => api['John Doe'].hasBreak === false,
65 | start: async (api) => {
66 | // We order the ingredients.
67 | const orderedIngredients = await orderIngredients();
68 |
69 | // We return the ordered ingredients.
70 | return { api: { orderedIngredients } };
71 | },
72 | });
73 |
74 | // Third: we make the pizza.
75 | const pizza = createContainer({
76 | id: 'pizza',
77 | domain: 'dish',
78 | dependencies: [chef, ingredients],
79 | start: (api) => {
80 | // The chef uses the ingredients
81 | // to make the pizza.
82 | const pepperoniPizza = api['John Doe'].makePizza({
83 | ingredients: api.ingredients.orderedIngredients,
84 | });
85 |
86 | // The pizza is ready!
87 | return { api: pepperoniPizza };
88 | },
89 | });
90 |
91 | // Now the stages: we split the process into steps.
92 | // 1: "prepare" — hiring the chef and ordering the ingredients.
93 | // 2: "cooking" — making the pizza.
94 | const cmd = await compose({
95 | stages: [
96 | ['prepare', [chef, ingredients]],
97 | ['cooking', [pizza]],
98 | ],
99 | // We require everything to be ready.
100 | required: 'all',
101 | });
102 |
103 | // The cooking process has started!
104 | await cmd.up();
105 | ```
106 |
107 | #### Example Status Flow
108 |
109 | Here’s how the statuses change during the cooking process:
110 |
111 | 1. **Initial state**:
112 |
113 | - `chef: 'idle', ingredients: 'idle'` — Everything is waiting.
114 | - `chef: 'pending', ingredients: 'idle'` — The chef is on the way to the kitchen.
115 |
116 | 2. **If the chef is ready to work**:
117 |
118 | - `chef: 'done', ingredients: 'pending'` — Ordering the ingredients.
119 | - `chef: 'done', ingredients: 'done', pizza: 'idle'` — All ingredients have been delivered.
120 | - `chef: 'done', ingredients: 'done', pizza: 'pending'` — Starting to make the pizza.
121 | - `chef: 'done', ingredients: 'done', pizza: 'done'` — The pizza is ready!
122 |
123 | 3. **If the chef is here, but taking a break**:
124 |
125 | - `chef: 'done', ingredients: 'off', pizza: 'off'` — Cooking is canceled.
126 |
127 | ## Strengths of the Library
128 |
129 | - Automatically resolves dependencies, removing the need to manually specify all containers.
130 | - Simplifies working with feature-toggles by eliminating excessive `if/else` logic for disabled functionality.
131 | - Allows you to define which parts of the application to run and in what order, prioritizing more important and less important dependencies.
132 | - Offers the ability to visualize the system composed of containers effectively (including transitive dependencies and their paths).
133 | - Provides a simple and intuitive developer experience (DX).
134 | - Ensures high performance, suitable for scalable applications.
135 | - Includes debugging tools to facilitate the development process.
136 | - Covered by 100% tests, including type tests.
137 |
138 | ## What app-compose is NOT
139 |
140 | - It does not tell you how to build a module. You choose how your modules work. app-compose only helps you put them together in one app.
141 | - It does not manage data or state. If you need state (like Effector or Redux), you add it inside your modules. app-compose only starts them.
142 |
--------------------------------------------------------------------------------
/website/src/content/docs/reference/changelog.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Changelog
3 | sidebar:
4 | order: 4
5 | ---
6 |
7 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
8 | and this project adheres to [Semantic Versioning](http://semver.org).
9 |
10 | ## 2.0.2
11 |
12 | ### Changed
13 |
14 | - reduce bundle size
15 |
16 | ## 2.0.1
17 |
18 | ### Fixed
19 |
20 | - `graph` return value
21 |
22 | ## 2.0.0
23 |
24 | Vault
25 |
26 | ### Added
27 |
28 | - support for managing container execution order using `stages`.
29 | - `diff` command to display which containers were automatically added and which ones were skipped.
30 | - `domain` a required parameter in `createContainer`.
31 | - `view: domain` to visualize the entire application graph at the domain level.
32 | - `graph.dependsOn` command to quickly identify which containers a given container depends on, including transitive dependencies.
33 | - `graph.requiredBy` command to quickly identify which containers depend on a given container, including transitive dependencies.
34 | - the second argument of the `start` and `enable` functions contains information about whether the parent containers have been resolved.
35 |
36 | ### Changed
37 |
38 | - all parent container APIs are wrapped into the first parameter in `start` and `enable` functions.
39 | - automatic resolving of strict dependencies is enabled by default.
40 | - renamed `dependsOn` to `dependencies`.
41 | - renamed `optionalDependsOn` to `optionalDependencies`.
42 | - renamed `onFail` to `onContainerFail`.
43 | - the message format for the `debug: true` option.
44 |
45 | ## 1.4.0
46 |
47 | ### Added
48 |
49 | - `onFail` option to `compose.up` config for handling container failures, allowing centralized error tracking and custom recovery actions.
50 | [How to](https://grlt-hub.github.io/app-compose/how-to-guides/handle-container-failures/)
51 |
52 | ## 1.3.0
53 |
54 | ### Added
55 |
56 | ```ts
57 | autoResolveDeps?: {
58 | strict: true;
59 | optional?: boolean;
60 | };
61 | ```
62 |
63 | in the `compose.up` and `compose.graph` configs. `autoResolveDeps` allows automatic resolution of container dependencies (both strict and optional) without the need to manually pass them to `compose.up` and `compose.graph`.
64 |
65 | ## 1.2.0
66 |
67 | ### Added
68 |
69 | - `apis: boolean` in the `compose.up` config for accessing your containers' APIs after execution.
70 |
71 | ## 1.1.0
72 |
73 | ### Added
74 |
75 | - [the ability to visualize](https://grlt-hub.github.io/app-compose/how-to-guides/visualize-the-system/) the system composed of containers effectively (including transitive dependencies and their paths)
76 |
77 | ## 1.0.0
78 |
79 | ### Added
80 |
81 | - `createContainer` fn
82 | - `compose.up` fn
83 |
--------------------------------------------------------------------------------
/website/src/content/docs/reference/create-container.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: createContainer
3 | sidebar:
4 | order: 1
5 | ---
6 |
7 | `createContainer` is a function that creates and returns a container to manage modules in the application. Containers specify dependencies, define initialization conditions, and control the order of module startup.
8 |
9 | ## Syntax
10 |
11 | ```ts
12 | createContainer(config: ContainerConfig): Container
13 | ```
14 |
15 | ## Parameters
16 |
17 | - _config_: `ContainerConfig` — configuration object for the container
18 |
19 | - **id** (`string`) - Required. Unique identifier of the container.
20 | - **domain** (`string`) - Required. A category for a container.
21 | - **dependencies** (`Container[]`) - Optional. List of strict dependencies. These containers must be in the `done` status for the current container to transition to `pending`.
22 | - **optionalDependencies** (`Container[]`) - Optional. List of optional dependencies. These containers can be in the `done`, `fail`, or `off` status for the current container to activate.
23 | - **start** (`(api?: DepsApi & OptionalDepsApi, enabled: {keyof api: boolean}) => { api: {} }`) - Required. Initialization function of the container, invoked upon activation.
24 | - Accepts:
25 | - api (optional) — an object with APIs from required and optional dependencies.
26 | - enabled (optional) — an object showing which dependencies are enabled (true) or not (false).
27 | - Returns: an object with an api property, which contains data or methods that other containers can use.
28 | - **enable** (`() => boolean | Promise
24 | They are required—if one is missing, your container will not start.
25 |
26 | ### optionalDependencies
27 |
28 | optionalDependencies are containers that the container can use if they are available.
29 | They are not required, so your container can still run without them.
30 |
31 | ### api
32 |
33 | The object returned by container start. It can have data or methods that other containers use.
34 | An object like `{ api: {...} | null }`
35 |
36 | ### start
37 |
38 | The start function runs when the container begins.
39 | It can use the APIs and statuses of container dependencies.
40 |
41 | ### enable
42 |
43 | enable is a function that decides if the container can start.
44 | It can use the APIs and statuses of container dependencies.
45 |
46 | ## Compose
47 |
48 | compose is a function that collects all containers and prepares them to run.
49 | It returns an object with methods like up for starting the containers.
50 |
51 | ### stages
52 |
53 | Stages are steps for starting containers in priority.
54 | Each stage has a name and a list of containers.
55 | The containers in the first stage start first, then the next stage, and so on.
56 |
57 | ### up
58 |
59 | A command that starts all containers in the correct order.
60 |
61 | ### graph
62 |
63 | A command that displays how containers connect to each other.
64 |
65 | ### diff
66 |
67 | A command that shows which containers started and which were skipped or added automatically.
68 |
--------------------------------------------------------------------------------
/website/src/content/docs/tutorials/basic-usage.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Basic Usage
3 | sidebar:
4 | order: 1
5 | ---
6 |
7 | In this section, we will create a simple container using `createContainer` and run it with `compose.up`. This example will show how to start using the library and understand its basic ideas.
8 |
9 | The container we create will have an identifier, domain, and a start function that runs when the container starts. We will also see how `compose.up` connects containers and controls their execution.
10 |
11 | ## Analogy from Cooking
12 |
13 | Imagine you are in a kitchen. You have a chef, ingredients, and a pot. Each of them is a container.
14 |
15 | A chef alone can’t cook without ingredients. Ingredients won’t turn into a meal without a pot.
16 |
17 | Containers don’t do much on their own, but when they work together, you get a finished meal. `compose.up` is the one that organizes the process, makes sure everything happens in the right order, and starts each part at the right time.
18 |
19 | ## Example
20 |
21 | ```ts
22 | import { createContainer, compose } from '@grlt-hub/app-compose';
23 |
24 | const hireChef = () => null;
25 |
26 | const chef = createContainer({
27 | // The name of our chef.
28 | id: 'John Doe',
29 | // This chef specializes in Italian cuisine.
30 | domain: 'italian-chef',
31 | start: async () => {
32 | // For example, we are hiring a chef.
33 | const hiredChef = await hireChef();
34 |
35 | console.log('The chef is already on the way!');
36 |
37 | // Every start function should return an object like this:
38 | // { api: object | null }
39 | return { api: hiredChef };
40 | },
41 | });
42 |
43 | const { up } = await compose({
44 | stages: [['prepare', [chef]]],
45 | });
46 | ```
47 |
48 | When you run this code, you should see the following output in the console:
49 |
50 | ```sh
51 | > npm start
52 | The chef is already on the way!
53 | ```
54 |
55 |
56 | Try it
57 |
58 | ## What’s Next?
59 |
60 | In this example, we created a simple container that works alone. But in real applications, containers often depend on other containers to do their job.
61 |
62 | Next, we will learn about `dependencies` and `optionalDependencies`. You’ll see how to connect containers and build flexible applications where different parts work together.
63 |
--------------------------------------------------------------------------------
/website/src/content/docs/tutorials/dependencies.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Dependencies
3 | sidebar:
4 | order: 2
5 | ---
6 |
7 | In real applications, containers often need to work together. Some containers depend on others to perform their tasks. For this, we use `dependencies` and `optionalDependencies`.
8 |
9 | - **dependencies** are containers that must be available for another container to start. If such a dependency is missing or not working, the container won’t start.
10 |
11 | - **optionalDependencies** are containers that can be used if available, but their absence won’t prevent the container from starting.
12 |
13 | In this section, we’ll show how to connect containers using `dependencies` and `optionalDependencies`. You’ll see how one container can use data or functionality from another.
14 |
15 | ## Analogy from Cooking
16 |
17 | Imagine you are making a pizza.
18 |
19 | - You need dough, sauce, and cheese — without them, you can’t make a pizza. These are your required dependencies (`dependencies`).
20 | - But olives are an optional dependency (`optionalDependencies`). They add extra flavor, but if you don’t have them, you’ll still have a delicious pizza.
21 |
22 | It’s the same with containers: some things are needed to make them work, and others just make them better.
23 |
24 | ## Example
25 |
26 | ```ts
27 | import { compose, createContainer } from '@grlt-hub/app-compose';
28 |
29 | const dough = createContainer({
30 | id: 'dough',
31 | domain: 'ingredients',
32 | start: () => ({ api: { value: 'dough' } }),
33 | });
34 |
35 | const sauce = createContainer({
36 | id: 'sauce',
37 | domain: 'ingredients',
38 | start: () => ({ api: { value: 'sauce' } }),
39 | });
40 |
41 | const cheese = createContainer({
42 | id: 'cheese',
43 | domain: 'ingredients',
44 | start: () => ({ api: { value: 'cheese' } }),
45 | });
46 |
47 | const olives = createContainer({
48 | id: 'olives',
49 | domain: 'ingredients',
50 | start: () => ({ api: { value: 'olives' } }),
51 | });
52 |
53 | const pizza = createContainer({
54 | id: 'pizza',
55 | domain: 'dish',
56 |
57 | // Required dependencies: pizza cannot be made without dough, sauce, and cheese
58 | dependencies: [dough, sauce, cheese],
59 |
60 | // Optional dependency: olives, but pizza can be made without them
61 | optionalDependencies: [olives],
62 |
63 | // Start function: combines all available ingredients into a pizza
64 | start: (api) => {
65 | const ingredients = Object.values(api).map((x) => x.value);
66 |
67 | console.log(`${ingredients.join(' + ')} = pizza`);
68 |
69 | return { api: { data: 'pizza' } };
70 | },
71 | });
72 |
73 | const { up } = await compose({
74 | stages: [['prepare', [pizza]]],
75 | });
76 |
77 | up();
78 | ```
79 |
80 | When you run this code, you should see the following output in the console:
81 |
82 | ```sh
83 | > npm start
84 | dough + sauce + cheese = pizza
85 | ```
86 |
87 |
88 | Try it
89 |
90 | ## Why Olives Are Missing?
91 |
92 | In the last example, olives are not in the pizza because they are optional. We did not add them, so the pizza has no olives.
93 |
94 | But why did dough, sauce, and cheese work even though we didn’t add them directly?
95 | That’s because they are required dependencies. When a container depends on others, `compose.up` _automatically includes required dependencies_, even if they are not specified.
96 |
97 | Now let’s see how to include olives.
98 |
99 | ## Including Olives
100 |
101 | To include olives, you just need to add them to `compose.up`.
102 |
103 | ```diff
104 | const { up } = await compose({
105 | - stages: [['prepare', [pizza]]],
106 | + stages: [['prepare', [pizza, olives]]],
107 | });
108 | ```
109 |
110 | When you run this code, you should see the following output in the console:
111 |
112 | ```sh
113 | > npm start
114 | olives + dough + sauce + cheese = pizza
115 | ```
116 |
117 |
118 | Try it
119 |
120 | ## What’s Next?
121 |
122 | So far, we’ve learned how to connect containers using dependencies and optional dependencies. This makes sure containers start when they need other containers.
123 |
124 | But sometimes this is not enough.
125 | Imagine you’re making a pizza. You have dough and sauce, and you usually add cheese. But today, you look in the fridge and see — there is no cheese.
126 |
127 | This is where `enable` helps. It lets you choose if a container should start, like checking if you have cheese before making the pizza.
128 |
--------------------------------------------------------------------------------
/website/src/content/docs/tutorials/enable.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Enable
3 | sidebar:
4 | order: 3
5 | ---
6 |
7 | In this article, you’ll learn how to control when containers start using the `enable` option. While dependencies ensure that containers start when the required parts are ready, sometimes that’s not enough.
8 |
9 | The `enable` option allows you to set custom conditions that decide if a container should start. This is useful when you need more control, like starting a container only when certain data is available or a specific state is active.
10 |
11 | You’ll also see how `enable` can be used with **feature toggles** to turn features on or off in your app based on specific conditions.
12 |
13 | ## Analogy from Cooking
14 |
15 | Imagine you have dough and sauce, and you usually add cheese. But before you start making a pizza, you check if you have cheese.
16 |
17 | If there’s no cheese, you don’t start.
18 |
19 | This is how `enable` works. It helps you decide if you should start, even when everything else is ready.
20 |
21 | ## Example
22 |
23 | ```tsx
24 | import { compose, createContainer } from '@grlt-hub/app-compose';
25 |
26 | const hasCheese = () => false;
27 |
28 | const dough = createContainer({
29 | id: 'dough',
30 | domain: 'ingredients',
31 | start: () => ({ api: { value: 'dough' } }),
32 | });
33 |
34 | const sauce = createContainer({
35 | id: 'sauce',
36 | domain: 'ingredients',
37 | start: () => ({ api: { value: 'sauce' } }),
38 | });
39 |
40 | const cheese = createContainer({
41 | id: 'cheese',
42 | domain: 'ingredients',
43 | start: () => ({ api: { value: 'cheese' } }),
44 | // The container will start
45 | // only if hasCheese() returns true
46 | enable: hasCheese,
47 | });
48 |
49 | const pizza = createContainer({
50 | id: 'pizza',
51 | domain: 'dish',
52 | dependencies: [dough, sauce, cheese],
53 | start: () => {
54 | return { api: { data: 'pizza' } };
55 | },
56 | });
57 |
58 | const { up } = await compose({
59 | stages: [['prepare', [pizza]]],
60 | });
61 |
62 | const result = await up();
63 |
64 | console.log(JSON.stringify(result, undefined, 2));
65 | ```
66 |
67 | When you run this code, you should see the following output in the console:
68 |
69 | ```json
70 | {
71 | "allDone": false,
72 | "stages": {
73 | "prepare": {
74 | "allDone": false,
75 | "containerStatuses": {
76 | // because one of its required dependencies (cheese) is OFF
77 | "pizza": "off",
78 |
79 | "dough": "done",
80 | "sauce": "done",
81 |
82 | // because the enable condition returned false
83 | "cheese": "off"
84 | }
85 | }
86 | }
87 | }
88 | ```
89 |
90 | The `enable` function should always return a boolean. If it’s not provided, it returns **true** by default.
91 |
92 | Try modifying `hasCheese` and see what happens.
93 |
94 |
95 | Try it
96 |
97 | ## What’s Next?
98 |
99 | We’ve learned how to control when containers start using enable. This helps start containers only when needed.
100 |
101 | But what if you want to control the priority of starting containers?
102 | Sometimes, one container should start before another, even if they are not connected.
103 |
104 | This is where `stages` help. They let you group containers and choose the order they start.
105 |
--------------------------------------------------------------------------------
/website/src/content/docs/tutorials/getting-started.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Getting Started
3 | sidebar:
4 | order: 0
5 | ---
6 |
7 | import { Tabs, TabItem } from '@astrojs/starlight/components';
8 |
9 | ## Installation
10 |
11 | Note: Effector version 23 is a peerDependency of the library.
12 |
13 |
10 | In real applications, some things are more important, and some are less important.
11 |
12 | It’s a good idea to **load and start important things first**, and then start the less important ones.
13 | This helps your app start in the right priority and feel better for the user.
14 |
15 | Each stage has a name and a list of containers to start. The stages run in order, so you can decide which containers start first and which start next.
16 |
17 | ## Analogy from Cooking
18 |
19 | Imagine you’re preparing a meal. You need to make **the main course** and **dessert**.
20 |
21 | It’s important to start with the main course because it takes more time to cook and people are usually hungry for it first.
22 | Once the main course is ready, you can start making the dessert.
23 |
24 | ## Example
25 |
26 | ```tsx
27 | import { compose, createContainer } from '@grlt-hub/app-compose';
28 |
29 | const mainCource = createContainer({
30 | id: 'pizza',
31 | domain: 'dish',
32 | start: () => {
33 | console.log('pizza is ready');
34 |
35 | return { api: { value: 'pizza' } };
36 | },
37 | });
38 |
39 | const dessert = createContainer({
40 | id: 'dessert',
41 | domain: 'dish',
42 | start: () => {
43 | console.log('dessert is ready');
44 |
45 | return { api: { value: 'dessert' } };
46 | },
47 | });
48 |
49 | const { up } = await compose({
50 | stages: [
51 | // This stage runs first.
52 | // The main course (pizza) will start here.
53 | ['first', [mainCource]],
54 | // This stage runs after the first.
55 | // The dessert will start only when the first stage is done.
56 | ['then', [dessert]],
57 | ],
58 | });
59 |
60 | up();
61 | ```
62 |
63 | When you run this code, you should see the following output in the console:
64 |
65 | ```sh
66 | > npm start
67 | pizza is ready
68 | dessert is ready
69 | ```
70 |
71 |
72 | Try it
73 |
--------------------------------------------------------------------------------
/website/src/content/docs/tutorials/summary.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Summary
3 | ---
4 |
5 | We have learned the basics of `app-compose`:
6 |
7 | - Containers and how to create them.
8 | - Dependencies and optional dependencies to connect containers.
9 | - `enable` to control when a container should start.
10 | - Stages to manage the order of container startup.
11 |
12 | With these tools, you can build flexible and organized applications. Next, you can explore advanced features and best practices to make your project even stronger.
13 |
--------------------------------------------------------------------------------
/website/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///