libmodulor 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.github/ISSUE_TEMPLATE/generic-issue.md +16 -0
  2. package/CHANGELOG.md +31 -0
  3. package/README.md +7 -182
  4. package/dist/esm/index.react-native-pure.d.ts +10 -0
  5. package/dist/esm/index.react-native-pure.js +10 -0
  6. package/dist/esm/target/lib/react/entrypoint.d.ts +2 -0
  7. package/dist/esm/target/lib/rn/input.d.ts +8 -0
  8. package/dist/esm/target/lib/rn/input.js +26 -0
  9. package/dist/esm/target/node-express-server/NodeExpressServerManager.js +4 -1
  10. package/dist/esm/target/react-native-pure/UCAutoExecLoader.d.ts +2 -0
  11. package/dist/esm/target/react-native-pure/UCAutoExecLoader.js +5 -0
  12. package/dist/esm/target/react-native-pure/UCEntrypointTouchable.d.ts +4 -0
  13. package/dist/esm/target/react-native-pure/UCEntrypointTouchable.js +6 -0
  14. package/dist/esm/target/react-native-pure/UCExecTouchable.d.ts +4 -0
  15. package/dist/esm/target/react-native-pure/UCExecTouchable.js +9 -0
  16. package/dist/esm/target/react-native-pure/UCForm.d.ts +4 -0
  17. package/dist/esm/target/react-native-pure/UCForm.js +13 -0
  18. package/dist/esm/target/react-native-pure/UCFormField.d.ts +11 -0
  19. package/dist/esm/target/react-native-pure/UCFormField.js +32 -0
  20. package/dist/esm/target/react-native-pure/UCFormFieldControl.d.ts +11 -0
  21. package/dist/esm/target/react-native-pure/UCFormFieldControl.js +36 -0
  22. package/dist/esm/target/react-native-pure/UCFormFieldDesc.d.ts +7 -0
  23. package/dist/esm/target/react-native-pure/UCFormFieldDesc.js +11 -0
  24. package/dist/esm/target/react-native-pure/UCFormFieldErr.d.ts +7 -0
  25. package/dist/esm/target/react-native-pure/UCFormFieldErr.js +5 -0
  26. package/dist/esm/target/react-native-pure/UCFormFieldLabel.d.ts +7 -0
  27. package/dist/esm/target/react-native-pure/UCFormFieldLabel.js +8 -0
  28. package/dist/esm/target/react-native-pure/UCFormSubmitControl.d.ts +9 -0
  29. package/dist/esm/target/react-native-pure/UCFormSubmitControl.js +8 -0
  30. package/dist/esm/target/react-web-pure/UCEntrypointTouchable.d.ts +1 -1
  31. package/dist/esm/target/react-web-pure/UCExecTouchable.d.ts +1 -1
  32. package/dist/esm/target/react-web-pure/UCExecTouchable.js +1 -1
  33. package/dist/esm/target/react-web-pure/UCFormFieldControl.d.ts +1 -1
  34. package/dist/esm/target/react-web-pure/UCFormFieldControl.js +3 -3
  35. package/dist/esm/uc/ext.d.ts +3 -1
  36. package/dist/esm/uc/helpers/UCOutputReader.d.ts +1 -1
  37. package/dist/esm/uc/helpers/UCOutputReader.js +4 -2
  38. package/dist/esm/uc/output-part.d.ts +2 -1
  39. package/dist/esm/uc/utils/ucHTTPContract.d.ts +1 -0
  40. package/dist/esm/uc/utils/ucHTTPContract.js +5 -0
  41. package/package.json +12 -5
  42. package/docs/assets/trading-buy-asset-sequence-diagram.png +0 -0
  43. package/docs/assets/trading-target-mcp-server.png +0 -0
  44. package/docs/assets/trading-target-web-human.png +0 -0
  45. package/docs/assets/trading-target-web.png +0 -0
  46. package/docs/getting-started/001_Create_the_project.md +0 -168
  47. package/docs/getting-started/002_Create_the_App.md +0 -49
  48. package/docs/getting-started/003_Create_the_UseCase.md +0 -205
  49. package/docs/getting-started/004_Test_the_App.md +0 -114
  50. package/docs/getting-started/005_Create_the_Product.md +0 -46
  51. package/docs/getting-started/006_Create_the_server_Target.md +0 -130
  52. package/docs/getting-started/007_Create_the_web_Target.md +0 -262
  53. package/docs/getting-started/008_Switch_to_a_persistent_data_storage.md +0 -52
  54. package/docs/getting-started/009_Define_wording_for_humans.md +0 -42
  55. package/docs/getting-started/010_Create_the_cli_Target.md +0 -102
  56. package/docs/getting-started/011_Create_the_mcp_server_Target.md +0 -157
  57. package/docs/getting-started/012_Summary.md +0 -29
@@ -2,13 +2,15 @@ import type { HTTPMethod, URLPath } from '../dt/index.js';
2
2
  import type { UCOPIBase } from './opi.js';
3
3
  import type { UCOutput } from './output.js';
4
4
  import type { UCMountingPoint } from './utils/ucMountingPoint.js';
5
+ export type UCHTTPMountingPoint = `/${URLPath}`;
5
6
  export interface UCExt<OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> {
6
7
  cmd?: {
7
8
  mountAt?: UCMountingPoint;
8
9
  };
9
10
  http?: {
10
11
  method?: HTTPMethod;
11
- mountAt?: `/${URLPath}`;
12
+ mountAt?: UCHTTPMountingPoint;
13
+ mountAlsoAt?: UCHTTPMountingPoint[];
12
14
  transform?: <T extends object>(output: UCOutput<OPI0, OPI1>) => T;
13
15
  };
14
16
  }
@@ -1,4 +1,4 @@
1
- import type { DataType } from '../../dt/index.js';
1
+ import { type DataType } from '../../dt/index.js';
2
2
  import type { StringKeys } from '../../utils/index.js';
3
3
  import type { UC } from '../UC.js';
4
4
  import { UCOutputField } from '../UCOutputField.js';
@@ -1,3 +1,4 @@
1
+ import { TUUID } from '../../dt/index.js';
1
2
  import { UCOutputField } from '../UCOutputField.js';
2
3
  const ERR_NO_FIELD = (key) => `No field ${key} is defined for this use case`;
3
4
  const ERR_NO_IO_OUTPUT = 'No output is defined for this use case';
@@ -64,7 +65,8 @@ export class UCOutputReader {
64
65
  return undefined;
65
66
  }
66
67
  const { fields, layout, order, related } = partDef;
67
- const fieldKeys = order ?? Object.keys(fields);
68
+ const allFields = { id: { type: new TUUID() }, ...fields };
69
+ const allFieldsKeys = order ?? Object.keys(allFields);
68
70
  let items = [];
69
71
  let total = 0;
70
72
  const part = this._output?.parts[key];
@@ -73,7 +75,7 @@ export class UCOutputReader {
73
75
  total = part.total;
74
76
  }
75
77
  return {
76
- fields: fieldKeys.map((fieldKey) => new UCOutputField(fieldKey, fields[fieldKey])),
78
+ fields: allFieldsKeys.map((fieldKey) => new UCOutputField(fieldKey, allFields[fieldKey])),
77
79
  idx,
78
80
  items,
79
81
  key,
@@ -10,8 +10,9 @@ export interface UCOutputPart<OPI extends UCOPIBase> {
10
10
  pagination?: ListInput;
11
11
  total: UIntQuantity;
12
12
  }
13
+ export type UCOutputPartDefFields<OPI extends UCOPIBase> = Record<StringKeys<OPI>, UCOutputFieldDef<OPI, any>>;
13
14
  export interface UCOutputPartDef<OPI extends UCOPIBase> {
14
- fields: Omit<Record<StringKeys<OPI>, UCOutputFieldDef<OPI, any>>, 'id'>;
15
+ fields: Omit<UCOutputPartDefFields<OPI>, 'id'>;
15
16
  layout?: UCOPILayout<OPI>;
16
17
  order?: StringKeys<OPI>[];
17
18
  related?: {
@@ -10,5 +10,6 @@ export interface UCHTTPContract {
10
10
  method: HTTPMethod;
11
11
  mountingPoint: UCMountingPoint;
12
12
  path: URLPath;
13
+ pathAliases: URLPath[];
13
14
  }
14
15
  export declare function ucHTTPContract<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>, pathPrefix?: `/${URLPath}`): UCHTTPContract;
@@ -24,11 +24,16 @@ export function ucHTTPContract(uc, pathPrefix = '/api/v1') {
24
24
  }
25
25
  const mountingPoint = ucMountingPoint(uc);
26
26
  const path = ext?.http?.mountAt ?? `${pathPrefix}/${mountingPoint}`;
27
+ const pathAliases = [];
28
+ if (ext?.http?.mountAlsoAt) {
29
+ pathAliases.push(...ext.http.mountAlsoAt);
30
+ }
27
31
  return {
28
32
  contentType,
29
33
  envelope,
30
34
  method,
31
35
  mountingPoint,
32
36
  path,
37
+ pathAliases,
33
38
  };
34
39
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "libmodulor",
3
3
  "description": "An opinionated TypeScript library to create business oriented applications",
4
- "version": "0.2.0",
4
+ "version": "0.4.0",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "Chafik H'nini <chafik.hnini@gmail.com>",
7
7
  "homepage": "https://github.com/c100k/libmodulor#readme",
@@ -31,6 +31,9 @@
31
31
  "./node": {
32
32
  "import": "./dist/esm/index.node.js"
33
33
  },
34
+ "./react-native-pure": {
35
+ "import": "./dist/esm/index.react-native-pure.js"
36
+ },
34
37
  "./react-web-pure": {
35
38
  "import": "./dist/esm/index.react-web-pure.js"
36
39
  },
@@ -55,25 +58,26 @@
55
58
  },
56
59
  "peerDependencies": {
57
60
  "@hono/node-server": "^1.13.7",
58
- "@modelcontextprotocol/sdk": "^1.1.1",
59
- "@stricli/core": "^1.1.0",
61
+ "@modelcontextprotocol/sdk": "^1.4.1",
62
+ "@stricli/core": "^1.1.1",
60
63
  "buffer": "^6.0.3",
61
64
  "cookie-parser": "^1.4.7",
62
65
  "express": "^4.21.2",
63
66
  "express-fileupload": "^1.5.1",
64
67
  "fast-check": "^3.23.2",
65
68
  "helmet": "^8.0.0",
66
- "hono": "^4.6.16",
69
+ "hono": "^4.6.19",
67
70
  "inversify": "^6.2.1",
68
71
  "jose": "^5.9.6",
69
72
  "knex": "^3.1.0",
70
73
  "pg": "^8.13.1",
71
74
  "react": "^18.3.1",
72
75
  "react-dom": "^18.3.1",
76
+ "react-native": "^0.76.6",
73
77
  "reflect-metadata": "^0.2.2",
74
78
  "sqlite3": "^5.1.7",
75
79
  "typescript": "^5.7.3",
76
- "vite": "^6.0.7"
80
+ "vite": "^6.0.11"
77
81
  },
78
82
  "peerDependenciesMeta": {
79
83
  "@hono/node-server": {
@@ -103,6 +107,9 @@
103
107
  "react-dom": {
104
108
  "optional": true
105
109
  },
110
+ "react-native": {
111
+ "optional": true
112
+ },
106
113
  "sqlite3": {
107
114
  "optional": true
108
115
  },
Binary file
@@ -1,168 +0,0 @@
1
- # Create the project
2
-
3
- Assuming you have the following installed (otherwise, install them or adapt the commands) :
4
-
5
- - `node` >= 22
6
- - `yarn` >= 1.x
7
- - `wget` and `curl`
8
-
9
- If you're on macOS, for the `sed` commands, add a `''` after `-i` ([Explanation](https://stackoverflow.com/a/4247319/1259118)).
10
-
11
- ```sh
12
- # Create the directory
13
- mkdir libmodulor-tuto && cd libmodulor-tuto # Note how the repository is generic to contain multiple apps and products
14
-
15
- # Initialize git
16
- git init
17
-
18
- # Initialize config files
19
- touch .gitignore biome.json package.json README.md tsconfig.json vitest.config.ts
20
- ```
21
-
22
- ## .gitignore
23
-
24
- ```.gitignore
25
- coverage
26
- dist
27
- node_modules
28
- src/apps/**/test/reports
29
- .env
30
- ```
31
-
32
- ## biome.json
33
-
34
- ```json
35
- {
36
- "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
37
- "files": {
38
- "ignore": ["coverage", "dist", "node_modules"],
39
- "ignoreUnknown": true
40
- },
41
- "formatter": {
42
- "indentStyle": "space",
43
- "indentWidth": 4
44
- },
45
- "javascript": {
46
- "formatter": {
47
- "quoteStyle": "single"
48
- },
49
- "parser": {
50
- "unsafeParameterDecoratorsEnabled": true
51
- }
52
- }
53
- }
54
- ```
55
-
56
- ## package.json
57
-
58
- ```json
59
- {
60
- "name": "libmodulor-tuto",
61
- "version": "0.1.0",
62
- "author": "Chafik H'nini <chafik.hnini@gmail.com>",
63
- "type": "module",
64
- "private": true,
65
- "scripts": {
66
- "helper": "node ./node_modules/libmodulor/dist/esm/products/Helper/index.js",
67
- "lint": "biome check --write .",
68
- "test": "tsc && vitest run"
69
- },
70
- "dependencies": {
71
- "inversify": "^6.2.1",
72
- "libmodulor": "^0.2.0",
73
- "reflect-metadata": "^0.2.2"
74
- },
75
- "devDependencies": {
76
- "@biomejs/biome": "^1.9.4",
77
- "@types/node": "^22.10.2",
78
- "@vitest/coverage-v8": "^2.1.1",
79
- "buffer": "^6.0.3",
80
- "cookie-parser": "^1.4.7",
81
- "express": "^4.21.2",
82
- "express-fileupload": "^1.5.1",
83
- "fast-check": "^3.23.2",
84
- "helmet": "^8.0.0",
85
- "jose": "^5.9.6",
86
- "typescript": "^5.7.2",
87
- "vite": "^6.0.5",
88
- "vitest": "^2.1.8"
89
- }
90
- }
91
- ```
92
-
93
- ## README.md
94
-
95
- ```md
96
- # libmodulor-tuto
97
-
98
- 🚀🚀🚀
99
- ```
100
-
101
- ## tsconfig.json
102
-
103
- ```json
104
- {
105
- "compilerOptions": {
106
- "allowSyntheticDefaultImports": true,
107
- "declaration": true,
108
- "lib": ["dom", "esnext"],
109
- "module": "NodeNext",
110
- "moduleResolution": "NodeNext",
111
- "noEmit": true,
112
- "removeComments": true,
113
- "skipLibCheck": true,
114
- "sourceMap": true,
115
- "target": "ESNext",
116
-
117
- "strict": true,
118
- "allowUnreachableCode": false,
119
- "allowUnusedLabels": false,
120
- "exactOptionalPropertyTypes": true,
121
- "noFallthroughCasesInSwitch": true,
122
- "noPropertyAccessFromIndexSignature": true,
123
- "noImplicitOverride": true,
124
- "noImplicitReturns": true,
125
- "noUncheckedIndexedAccess": true,
126
- "noUnusedLocals": true,
127
- "noUnusedParameters": true,
128
- "verbatimModuleSyntax": true,
129
-
130
- "emitDecoratorMetadata": true,
131
- "experimentalDecorators": true,
132
-
133
- "jsx": "react"
134
- }
135
- }
136
- ```
137
-
138
- ## vitest.config.ts
139
-
140
- ```typescript
141
- import { defineConfig } from 'vitest/config';
142
-
143
- export default defineConfig({
144
- test: {
145
- coverage: {
146
- enabled: true,
147
- exclude: ['src/apps/**/test', 'src/**/*.test.ts'],
148
- include: ['src'],
149
- reporter: ['html', 'lcov', 'text'],
150
- },
151
- reporters: ['verbose'],
152
- },
153
- });
154
- ```
155
-
156
- ## Install
157
-
158
- ```sh
159
- yarn install
160
- ```
161
-
162
- ```sh
163
- yarn lint && git add . && git commit -m "chore: init source code"
164
- ```
165
-
166
- Optionally, you can create a remote repository (e.g. on GitHub) and push it.
167
-
168
- Now that's done, let's [Create the App](./002_Create_the_App.md).
@@ -1,49 +0,0 @@
1
- # Create the App
2
-
3
- An app is composed of three main files : `i18n.ts`, `manifest.ts` and `index.ts`.
4
-
5
- ```sh
6
- mkdir -p src/apps/Trading/src/ucds
7
- touch src/apps/Trading/src/{i18n.ts,manifest.ts}
8
- touch src/apps/Trading/index.ts
9
- ```
10
-
11
- > [!NOTE]
12
- > There is a lot of controversy about barrel files. In this specific context, they are useful to only expose the necessary things to the upper layers and keep the app isolated.
13
-
14
- ## i18n.ts
15
-
16
- ```typescript
17
- import type { AppI18n } from 'libmodulor';
18
-
19
- export const I18n: AppI18n = {
20
- en: {},
21
- };
22
- ```
23
-
24
- ## manifest.ts
25
-
26
- ```typescript
27
- import type { AppManifest } from 'libmodulor';
28
-
29
- export const Manifest = {
30
- languageCodes: ['en'],
31
- name: 'Trading',
32
- ucReg: {},
33
- } satisfies AppManifest;
34
- ```
35
-
36
- ## index.ts
37
-
38
- ```typescript
39
- // Expose only what's necessary
40
-
41
- export { I18n } from './src/i18n.js';
42
- export { Manifest } from './src/manifest.js';
43
- ```
44
-
45
- ```sh
46
- yarn lint && git add . && git commit -m "feat: add the app"
47
- ```
48
-
49
- Now that's done, let's [Create the UseCase](./003_Create_the_UseCase.md).
@@ -1,205 +0,0 @@
1
- # Create the UseCase
2
-
3
- > [!NOTE]
4
- > Starting now, you'll see `UC` or `uc` a lot. It's the abbreviation of `UseCase`. Acronyms are not good in codebases, except those that are commonly used ([debate](https://stackoverflow.com/questions/2236807/java-naming-convention-with-acronyms)). In any case, when you write `UseCase` hundreds of times, you're happy to be able to write `UC` instead. Thus, `UCD` stands for `Use Case Definition`, `UCIF` stands for `Use Case Input Field` and so on.
5
-
6
- The app manifest registers all the use cases metadata. Use cases can depend on each other as we'll see later, and this dependency must go through the manifest. Never directly.
7
-
8
- Update `manifest.ts` to register the new use case.
9
-
10
- ```typescript
11
- // ...
12
- ucReg: {
13
- BuyAsset: {
14
- action: 'Create',
15
- icon: 'plus',
16
- name: 'BuyAsset',
17
- },
18
- },
19
- // ...
20
- ```
21
-
22
- If you're using an IDE with auto-complete, you might have noticed the other properties like `beta`, `new`, `sensitive`. We'll come back to them later.
23
-
24
- ```sh
25
- mkdir src/apps/Trading/src/dt
26
- touch src/apps/Trading/src/dt/TISIN.ts
27
- touch src/apps/Trading/src/ucds/{BuyAssetServerMain.ts,BuyAssetUCD.ts}
28
- ```
29
-
30
- ## TISIN.ts
31
-
32
- An asset is usually identified by a unique code called [ISIN](https://www.isin.org). This is a typical business data type that has specific rules and that is not simply a `string`.
33
-
34
- ```typescript
35
- import { type TName, TString, type TStringConstraints } from 'libmodulor';
36
-
37
- export type ISIN = Capitalize<string>;
38
-
39
- export class TISIN extends TString<ISIN, 'ISIN'> {
40
- public static readonly FORMAT: RegExp = /^[A-Z]{2}[A-Z0-9]{9}[0-9]$/;
41
-
42
- constructor(constraints?: TStringConstraints) {
43
- super({
44
- ...constraints,
45
- format: { f: 'ISIN', regexp: TISIN.FORMAT },
46
- });
47
- }
48
-
49
- public override tName(): TName {
50
- return 'ISIN';
51
- }
52
-
53
- public override example(): ISIN {
54
- return 'US02079K3059';
55
- }
56
- }
57
- ```
58
-
59
- ## BuyAssetUCD.ts
60
-
61
- ```typescript
62
- import {
63
- type AggregateOPI0,
64
- type Amount,
65
- EverybodyUCPolicy,
66
- TAmount,
67
- TBoolean,
68
- TUIntQuantity,
69
- type UCDef,
70
- type UCInput,
71
- type UCInputFieldValue,
72
- type UCMain,
73
- type UCMainInput,
74
- type UCOutputOrNothing,
75
- type UCTransporter,
76
- type UIntQuantity,
77
- } from 'libmodulor';
78
- import { inject, injectable } from 'inversify';
79
-
80
- import { Manifest } from '../manifest.js';
81
-
82
- import { type ISIN, TISIN } from '../dt/TISIN.js';
83
- import { BuyAssetServerMain } from './BuyAssetServerMain.js';
84
-
85
- export interface BuyAssetInput extends UCInput {
86
- isin: UCInputFieldValue<ISIN>;
87
- limit: UCInputFieldValue<Amount>;
88
- qty: UCInputFieldValue<UIntQuantity>;
89
- }
90
-
91
- export interface BuyAssetOPI0 extends AggregateOPI0 {
92
- executedDirectly: boolean;
93
- }
94
-
95
- @injectable()
96
- class BuyAssetClientMain implements UCMain<BuyAssetInput, BuyAssetOPI0> {
97
- constructor(
98
- @inject('UCTransporter')
99
- private ucTransporter: UCTransporter,
100
- ) {}
101
-
102
- public async exec({
103
- uc,
104
- }: UCMainInput<BuyAssetInput, BuyAssetOPI0>): Promise<
105
- UCOutputOrNothing<BuyAssetOPI0>
106
- > {
107
- return this.ucTransporter.send(uc);
108
- }
109
- }
110
-
111
- export const BuyAssetUCD: UCDef<BuyAssetInput, BuyAssetOPI0> = {
112
- io: {
113
- i: {
114
- fields: {
115
- isin: {
116
- type: new TISIN(),
117
- },
118
- limit: {
119
- type: new TAmount('USD'),
120
- },
121
- qty: {
122
- type: new TUIntQuantity(),
123
- },
124
- },
125
- },
126
- o: {
127
- parts: {
128
- _0: {
129
- fields: {
130
- executedDirectly: {
131
- type: new TBoolean(),
132
- },
133
- },
134
- },
135
- },
136
- },
137
- },
138
- lifecycle: {
139
- client: {
140
- main: BuyAssetClientMain,
141
- policy: EverybodyUCPolicy,
142
- },
143
- server: {
144
- main: BuyAssetServerMain,
145
- policy: EverybodyUCPolicy,
146
- },
147
- },
148
- metadata: Manifest.ucReg.BuyAsset,
149
- };
150
- ```
151
-
152
- ## BuyAssetServerMain.ts
153
-
154
- ```typescript
155
- import {
156
- type UCMain,
157
- type UCMainInput,
158
- type UCManager,
159
- type UCOutput,
160
- UCOutputBuilder,
161
- } from 'libmodulor';
162
- import { inject, injectable } from 'inversify';
163
-
164
- import type { BuyAssetInput, BuyAssetOPI0 } from './BuyAssetUCD.js';
165
-
166
- @injectable()
167
- export class BuyAssetServerMain implements UCMain<BuyAssetInput, BuyAssetOPI0> {
168
- constructor(@inject('UCManager') private ucManager: UCManager) {}
169
-
170
- public async exec({
171
- uc,
172
- }: UCMainInput<BuyAssetInput, BuyAssetOPI0>): Promise<
173
- UCOutput<BuyAssetOPI0>
174
- > {
175
- // >=> Persist the order
176
- const { aggregateId } = await this.ucManager.persist(uc);
177
-
178
- // >=> TODO : Check the user has enough funds to place the order
179
-
180
- // >=> TODO : Send the order to a queue for processing
181
- const executedDirectly: BuyAssetOPI0['executedDirectly'] = false;
182
-
183
- return new UCOutputBuilder<BuyAssetOPI0>()
184
- .add({
185
- executedDirectly,
186
- id: aggregateId,
187
- })
188
- .get();
189
- }
190
- }
191
- ```
192
-
193
- For now, we won't detail all this code but take the time to read it and understand how it works. Hopefully it's clear enough and self-explanatory.
194
-
195
- > [!TIP]
196
- > Using a comment following the pattern `// >=> ` in `ClientMain` and `ServerMain` has a specific meaning as we'll see a little bit later.
197
-
198
- > [!NOTE]
199
- > Unlike `ClientMain`, `ServerMain` is put in another file for "historical" reasons, mainly for stripping and tree shaking reasons. More on this later.
200
-
201
- ```sh
202
- yarn lint && git add . && git commit -m "feat: add the use case"
203
- ```
204
-
205
- Now that's done, let's [Test the App](./004_Test_the_App.md).