libmodulor 0.3.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.
- package/CHANGELOG.md +11 -0
- package/README.md +7 -182
- package/dist/esm/index.react-native-pure.d.ts +10 -0
- package/dist/esm/index.react-native-pure.js +10 -0
- package/dist/esm/target/lib/react/entrypoint.d.ts +2 -0
- package/dist/esm/target/lib/rn/input.d.ts +8 -0
- package/dist/esm/target/lib/rn/input.js +26 -0
- package/dist/esm/target/react-native-pure/UCAutoExecLoader.d.ts +2 -0
- package/dist/esm/target/react-native-pure/UCAutoExecLoader.js +5 -0
- package/dist/esm/target/react-native-pure/UCEntrypointTouchable.d.ts +4 -0
- package/dist/esm/target/react-native-pure/UCEntrypointTouchable.js +6 -0
- package/dist/esm/target/react-native-pure/UCExecTouchable.d.ts +4 -0
- package/dist/esm/target/react-native-pure/UCExecTouchable.js +9 -0
- package/dist/esm/target/react-native-pure/UCForm.d.ts +4 -0
- package/dist/esm/target/react-native-pure/UCForm.js +13 -0
- package/dist/esm/target/react-native-pure/UCFormField.d.ts +11 -0
- package/dist/esm/target/react-native-pure/UCFormField.js +32 -0
- package/dist/esm/target/react-native-pure/UCFormFieldControl.d.ts +11 -0
- package/dist/esm/target/react-native-pure/UCFormFieldControl.js +36 -0
- package/dist/esm/target/react-native-pure/UCFormFieldDesc.d.ts +7 -0
- package/dist/esm/target/react-native-pure/UCFormFieldDesc.js +11 -0
- package/dist/esm/target/react-native-pure/UCFormFieldErr.d.ts +7 -0
- package/dist/esm/target/react-native-pure/UCFormFieldErr.js +5 -0
- package/dist/esm/target/react-native-pure/UCFormFieldLabel.d.ts +7 -0
- package/dist/esm/target/react-native-pure/UCFormFieldLabel.js +8 -0
- package/dist/esm/target/react-native-pure/UCFormSubmitControl.d.ts +9 -0
- package/dist/esm/target/react-native-pure/UCFormSubmitControl.js +8 -0
- package/dist/esm/target/react-web-pure/UCEntrypointTouchable.d.ts +1 -1
- package/dist/esm/target/react-web-pure/UCExecTouchable.d.ts +1 -1
- package/dist/esm/target/react-web-pure/UCExecTouchable.js +1 -1
- package/dist/esm/target/react-web-pure/UCFormFieldControl.d.ts +1 -1
- package/dist/esm/target/react-web-pure/UCFormFieldControl.js +3 -3
- package/package.json +12 -5
- package/docs/assets/trading-buy-asset-sequence-diagram.png +0 -0
- package/docs/assets/trading-target-mcp-server.png +0 -0
- package/docs/assets/trading-target-web-human.png +0 -0
- package/docs/assets/trading-target-web.png +0 -0
- package/docs/getting-started/001_Create_the_project.md +0 -168
- package/docs/getting-started/002_Create_the_App.md +0 -49
- package/docs/getting-started/003_Create_the_UseCase.md +0 -205
- package/docs/getting-started/004_Test_the_App.md +0 -114
- package/docs/getting-started/005_Create_the_Product.md +0 -46
- package/docs/getting-started/006_Create_the_server_Target.md +0 -130
- package/docs/getting-started/007_Create_the_web_Target.md +0 -262
- package/docs/getting-started/008_Switch_to_a_persistent_data_storage.md +0 -55
- package/docs/getting-started/009_Define_wording_for_humans.md +0 -42
- package/docs/getting-started/010_Create_the_cli_Target.md +0 -102
- package/docs/getting-started/011_Create_the_mcp_server_Target.md +0 -157
- package/docs/getting-started/012_Summary.md +0 -29
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
# Create the server Target
|
|
2
|
-
|
|
3
|
-
We'll use the pre-built [express](https://expressjs.com) `ServerManager`.
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
mkdir src/products/SuperTrader/server
|
|
7
|
-
touch src/products/SuperTrader/server/{container.ts,index.ts}
|
|
8
|
-
touch tsconfig.build.json
|
|
9
|
-
touch .env
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## container.ts
|
|
13
|
-
|
|
14
|
-
```typescript
|
|
15
|
-
import {
|
|
16
|
-
CONTAINER_OPTS,
|
|
17
|
-
EnvSettingsManager,
|
|
18
|
-
type ServerManager,
|
|
19
|
-
type ServerManagerSettings,
|
|
20
|
-
type SettingsManager,
|
|
21
|
-
TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
|
|
22
|
-
bindCommon,
|
|
23
|
-
bindProduct,
|
|
24
|
-
} from 'libmodulor';
|
|
25
|
-
import {
|
|
26
|
-
NodeExpressServerManager,
|
|
27
|
-
bindNodeCore,
|
|
28
|
-
bindServer,
|
|
29
|
-
} from 'libmodulor/node';
|
|
30
|
-
import { Container } from 'inversify';
|
|
31
|
-
|
|
32
|
-
import { I18n } from '../i18n.js';
|
|
33
|
-
import { Manifest } from '../manifest.js';
|
|
34
|
-
|
|
35
|
-
type S = ServerManagerSettings;
|
|
36
|
-
|
|
37
|
-
const container = new Container(CONTAINER_OPTS);
|
|
38
|
-
|
|
39
|
-
bindCommon<S>(container, () => ({
|
|
40
|
-
...TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
|
|
41
|
-
}));
|
|
42
|
-
bindNodeCore(container);
|
|
43
|
-
bindServer(container);
|
|
44
|
-
bindProduct(container, Manifest, I18n);
|
|
45
|
-
|
|
46
|
-
container.rebind<SettingsManager>('SettingsManager').to(EnvSettingsManager);
|
|
47
|
-
|
|
48
|
-
container.bind<ServerManager>('ServerManager').to(NodeExpressServerManager);
|
|
49
|
-
|
|
50
|
-
export default container;
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## index.ts
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
import {
|
|
57
|
-
APPS_ROOT_DIR_NAME,
|
|
58
|
-
type FSManager,
|
|
59
|
-
type I18nManager,
|
|
60
|
-
ServerBooter,
|
|
61
|
-
} from 'libmodulor';
|
|
62
|
-
|
|
63
|
-
import container from './container.js';
|
|
64
|
-
|
|
65
|
-
await container.get<I18nManager>('I18nManager').init();
|
|
66
|
-
|
|
67
|
-
await container.resolve(ServerBooter).exec({
|
|
68
|
-
appsRootPath: container
|
|
69
|
-
.get<FSManager>('FSManager')
|
|
70
|
-
.path('..', '..', '..', APPS_ROOT_DIR_NAME),
|
|
71
|
-
srcImporter: (path) => import(path),
|
|
72
|
-
});
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## tsconfig.build.json
|
|
76
|
-
|
|
77
|
-
```json
|
|
78
|
-
{
|
|
79
|
-
"extends": "./tsconfig.json",
|
|
80
|
-
"compilerOptions": {
|
|
81
|
-
"noEmit": false,
|
|
82
|
-
"outDir": "dist"
|
|
83
|
-
},
|
|
84
|
-
"include": ["src"]
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## .env
|
|
89
|
-
|
|
90
|
-
```properties
|
|
91
|
-
app_logger_level=trace # the default is 'debug'
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
> [!TIP]
|
|
95
|
-
> A setting named `my_setting` in the code can be overriden with an environment variable called `app_my_setting`.
|
|
96
|
-
|
|
97
|
-
## Build & Run
|
|
98
|
-
|
|
99
|
-
Update `package.json` to add new entries to the `scripts`.
|
|
100
|
-
|
|
101
|
-
```json
|
|
102
|
-
"build": "tsc --project tsconfig.build.json && cp .env dist/products/SuperTrader/server/.env",
|
|
103
|
-
"run:server": "cd dist/products/SuperTrader/server && node --env-file .env index.js",
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
```sh
|
|
107
|
-
yarn build && yarn run:server
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
Et voilà ! The server is running !
|
|
111
|
-
|
|
112
|
-
```sh
|
|
113
|
-
curl -X POST -H "Content-Type: application/json" http://localhost:7443/api/v1/Trading_BuyAsset
|
|
114
|
-
# ❌ {"message":"Invalid credentials"}
|
|
115
|
-
curl -X POST -H "Content-Type: application/json" -H "X-API-Key: PublicApiKeyToBeChangedWhenDeploying" http://localhost:7443/api/v1/Trading_BuyAsset
|
|
116
|
-
# ❌ {"message":"isin must be filled"}
|
|
117
|
-
curl -X POST -H "Content-Type: application/json" -H "X-API-Key: PublicApiKeyToBeChangedWhenDeploying" -d '{"isin":"US02079K3059","limit":123.5,"qty":150}' http://localhost:7443/api/v1/Trading_BuyAsset
|
|
118
|
-
# ✅ {"parts":{"_0":{"items":[{"executedDirectly":false,"id":"95dddca5-5e9d-48ac-a90c-71a58d4e8554"}],"total":1}}}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
As you can see, validation comes out of the box. Later we'll see how to add even more precise rules to the data types.
|
|
122
|
-
|
|
123
|
-
> [!NOTE]
|
|
124
|
-
> The `public_api_key` is just a first layer of security to "authenticate" the client apps calling the server. Hopefully this is not the only security mechanism because of course, this key must be present in clear client side (web, cli, curl...). We'll dive deeper in security when we study the policies.
|
|
125
|
-
|
|
126
|
-
```sh
|
|
127
|
-
yarn lint && git add . && git commit -m "feat: add the server target"
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Now that's done, let's [Create the web Target](./007_Create_the_web_Target.md).
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
# Create the web Target
|
|
2
|
-
|
|
3
|
-
We'll use the pre-built [React](https://react.dev) components to build a SPA (Single Page Application), bundled with [vite](https://vite.dev) and served with the server defined above.
|
|
4
|
-
|
|
5
|
-
> [!WARNING]
|
|
6
|
-
> For readers used to "beautiful" websites à la Linear, Vercel and related, your eyes will burn. You're going to discover the simple and pure CSS-less Web. The most beautiful one.
|
|
7
|
-
> Of course, feel free to add CSS if you want to. The main goal here is to focus on the essence of the UI and not the UI design.
|
|
8
|
-
|
|
9
|
-
```sh
|
|
10
|
-
yarn add --dev "@types/react@^18.3.17" "@types/react-dom@^18.3.5"
|
|
11
|
-
yarn add "react@^18.3.1" "react-dom@^18.3.1"
|
|
12
|
-
|
|
13
|
-
mkdir -p src/products/SuperTrader/web/components
|
|
14
|
-
touch src/products/SuperTrader/vite.config.web.ts
|
|
15
|
-
touch src/products/SuperTrader/web/{container.ts,index.html,index.tsx}
|
|
16
|
-
touch src/products/SuperTrader/web/components/App.tsx
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## vite.config.web.ts
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
import { join } from 'node:path';
|
|
23
|
-
|
|
24
|
-
import { StripUCDLifecycleServerPlugin } from 'libmodulor/vite';
|
|
25
|
-
import { defineConfig } from 'vite';
|
|
26
|
-
|
|
27
|
-
const base = process.cwd();
|
|
28
|
-
const root = join('src', 'products', 'SuperTrader', 'web');
|
|
29
|
-
const outDir = join(
|
|
30
|
-
base,
|
|
31
|
-
'dist',
|
|
32
|
-
'products',
|
|
33
|
-
'SuperTrader',
|
|
34
|
-
'server',
|
|
35
|
-
'public',
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
export default defineConfig({
|
|
39
|
-
build: {
|
|
40
|
-
emptyOutDir: true,
|
|
41
|
-
outDir,
|
|
42
|
-
},
|
|
43
|
-
plugins: [StripUCDLifecycleServerPlugin],
|
|
44
|
-
root,
|
|
45
|
-
});
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## container.ts
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
import {
|
|
52
|
-
CONTAINER_OPTS,
|
|
53
|
-
type ServerClientManagerSettings,
|
|
54
|
-
TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
|
|
55
|
-
bindCommon,
|
|
56
|
-
bindProduct,
|
|
57
|
-
} from 'libmodulor';
|
|
58
|
-
import { bindWeb } from 'libmodulor/web';
|
|
59
|
-
import { Container } from 'inversify';
|
|
60
|
-
|
|
61
|
-
import { I18n } from '../i18n.js';
|
|
62
|
-
import { Manifest } from '../manifest.js';
|
|
63
|
-
|
|
64
|
-
type S = ServerClientManagerSettings;
|
|
65
|
-
|
|
66
|
-
const container = new Container(CONTAINER_OPTS);
|
|
67
|
-
|
|
68
|
-
bindCommon<S>(container, () => ({
|
|
69
|
-
...TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
|
|
70
|
-
}));
|
|
71
|
-
bindWeb(container);
|
|
72
|
-
bindProduct(container, Manifest, I18n);
|
|
73
|
-
|
|
74
|
-
export default container;
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## index.html
|
|
78
|
-
|
|
79
|
-
```html
|
|
80
|
-
<!DOCTYPE html>
|
|
81
|
-
<html lang="en">
|
|
82
|
-
<head>
|
|
83
|
-
<meta charset="utf-8" />
|
|
84
|
-
<meta content="width=device-width, initial-scale=1" name="viewport"
|
|
85
|
-
/>
|
|
86
|
-
</head>
|
|
87
|
-
<body>
|
|
88
|
-
<div id="root"></div>
|
|
89
|
-
<script type="module" src="/index.tsx"></script>
|
|
90
|
-
</body>
|
|
91
|
-
</html>
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## App.tsx
|
|
95
|
-
|
|
96
|
-
Update `src/apps/Trading/index.ts` to expose the use case.
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
export { BuyAssetUCD } from './src/ucds/BuyAssetUCD.js';
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Naturally, in real life scenarios, we would never have such a bloated `App.tsx`. Instead, we would create fine-grained components. Everybody does that, right ? Right ?
|
|
103
|
-
|
|
104
|
-
```tsx
|
|
105
|
-
import { type Logger, type ProductManifest, UCOutputReader } from 'libmodulor';
|
|
106
|
-
import {
|
|
107
|
-
UCPanel,
|
|
108
|
-
type UCPanelOnError,
|
|
109
|
-
useDIContext,
|
|
110
|
-
useUC,
|
|
111
|
-
useUCOR,
|
|
112
|
-
} from 'libmodulor/react';
|
|
113
|
-
import {
|
|
114
|
-
UCAutoExecLoader,
|
|
115
|
-
UCExecTouchable,
|
|
116
|
-
UCForm,
|
|
117
|
-
} from 'libmodulor/react-web-pure';
|
|
118
|
-
import React, { useEffect, useState, type ReactElement } from 'react';
|
|
119
|
-
|
|
120
|
-
import { BuyAssetUCD, Manifest } from '../../../../apps/Trading/index.js';
|
|
121
|
-
|
|
122
|
-
export default function App(): ReactElement {
|
|
123
|
-
const { container, i18nManager, wordingManager } = useDIContext();
|
|
124
|
-
const [logger] = useState(container.get<Logger>('Logger'));
|
|
125
|
-
const [productManifest] = useState(
|
|
126
|
-
container.get<ProductManifest>('ProductManifest'),
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const [buyAssetUC] = useUC(Manifest, BuyAssetUCD, null);
|
|
130
|
-
const [buyAssetPart0, _buyAssetPart1, { append0 }] = useUCOR(
|
|
131
|
-
new UCOutputReader(BuyAssetUCD, undefined),
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const [loading, setLoading] = useState(true);
|
|
135
|
-
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
(async () => {
|
|
138
|
-
logger.debug('Initializing i18n');
|
|
139
|
-
await i18nManager.init();
|
|
140
|
-
logger.debug('Done initializing i18n');
|
|
141
|
-
setLoading(false);
|
|
142
|
-
})();
|
|
143
|
-
|
|
144
|
-
const { slogan } = wordingManager.p();
|
|
145
|
-
document.title = `${productManifest.name} : ${slogan}`;
|
|
146
|
-
}, [i18nManager, logger, productManifest, wordingManager]);
|
|
147
|
-
|
|
148
|
-
const onError: UCPanelOnError = async (err) => alert(err.message);
|
|
149
|
-
|
|
150
|
-
const { slogan } = wordingManager.p();
|
|
151
|
-
const { label } = wordingManager.uc(buyAssetUC.def);
|
|
152
|
-
const { label: idLabel } = wordingManager.ucof('id');
|
|
153
|
-
const { label: executedDirectlyLabel } =
|
|
154
|
-
wordingManager.ucof('executedDirectly');
|
|
155
|
-
|
|
156
|
-
return (
|
|
157
|
-
<div>
|
|
158
|
-
{loading && 'Loading...'}
|
|
159
|
-
|
|
160
|
-
{!loading && (
|
|
161
|
-
<>
|
|
162
|
-
<h1>
|
|
163
|
-
{productManifest.name} : {slogan}
|
|
164
|
-
</h1>
|
|
165
|
-
|
|
166
|
-
<h2>{label}</h2>
|
|
167
|
-
|
|
168
|
-
<UCPanel
|
|
169
|
-
clearAfterExec={false}
|
|
170
|
-
onDone={async (ucor) => append0(ucor)}
|
|
171
|
-
onError={onError}
|
|
172
|
-
renderAutoExecLoader={UCAutoExecLoader}
|
|
173
|
-
renderExecTouchable={UCExecTouchable}
|
|
174
|
-
renderForm={UCForm}
|
|
175
|
-
sleepInMs={200} // Fake delay to see submit wording changing
|
|
176
|
-
uc={buyAssetUC}
|
|
177
|
-
/>
|
|
178
|
-
|
|
179
|
-
<table>
|
|
180
|
-
<thead>
|
|
181
|
-
<tr>
|
|
182
|
-
<th>{idLabel}</th>
|
|
183
|
-
<th>{executedDirectlyLabel}</th>
|
|
184
|
-
</tr>
|
|
185
|
-
</thead>
|
|
186
|
-
<tbody>
|
|
187
|
-
{buyAssetPart0?.items.map((i) => (
|
|
188
|
-
<tr key={i.id}>
|
|
189
|
-
<td>{i.id}</td>
|
|
190
|
-
<td>{i.executedDirectly ? '✅' : '❌'}</td>
|
|
191
|
-
</tr>
|
|
192
|
-
))}
|
|
193
|
-
</tbody>
|
|
194
|
-
<tfoot>
|
|
195
|
-
<tr>
|
|
196
|
-
<th>{i18nManager.t('total')}</th>
|
|
197
|
-
<th>{buyAssetPart0?.pagination.total}</th>
|
|
198
|
-
</tr>
|
|
199
|
-
</tfoot>
|
|
200
|
-
</table>
|
|
201
|
-
</>
|
|
202
|
-
)}
|
|
203
|
-
</div>
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
## index.tsx
|
|
209
|
-
|
|
210
|
-
```typescript
|
|
211
|
-
import { DIContextProvider } from 'libmodulor/react';
|
|
212
|
-
import React, { StrictMode } from 'react';
|
|
213
|
-
import ReactDOM from 'react-dom/client';
|
|
214
|
-
|
|
215
|
-
import App from './components/App.js';
|
|
216
|
-
import container from './container.js';
|
|
217
|
-
|
|
218
|
-
const rootElt = document.getElementById('root');
|
|
219
|
-
if (!rootElt) {
|
|
220
|
-
throw new Error('Add a div#root in index.html');
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
ReactDOM.createRoot(rootElt).render(
|
|
224
|
-
<StrictMode>
|
|
225
|
-
<DIContextProvider container={container}>
|
|
226
|
-
<App />
|
|
227
|
-
</DIContextProvider>
|
|
228
|
-
</StrictMode>,
|
|
229
|
-
);
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
## Build & Run
|
|
233
|
-
|
|
234
|
-
Update `package.json` to add the `web` build to the `build` command.
|
|
235
|
-
|
|
236
|
-
```json
|
|
237
|
-
"build": "tsc --project tsconfig.build.json && cp .env dist/products/SuperTrader/server/.env && vite -c src/products/SuperTrader/vite.config.web.ts build",
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
Update `src/products/SuperTrader/server/container.ts` to mount the `public` directory.
|
|
241
|
-
|
|
242
|
-
```diff
|
|
243
|
-
...TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
|
|
244
|
-
+server_static_dir_path: 'public',
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
Press <kbd>ctrl</kbd> + <kbd>C</kbd> to stop the server (we'll setup hot reload later).
|
|
248
|
-
|
|
249
|
-
```sh
|
|
250
|
-
yarn build && yarn run:server
|
|
251
|
-
open http://localhost:7443
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
Et voilà ! The server is running ! Fill the form and see how it automatically submits to the server with client side and server side validation out of the box.
|
|
255
|
-
|
|
256
|
-
<img src="/docs/assets/trading-target-web.png" width="600px">
|
|
257
|
-
|
|
258
|
-
```sh
|
|
259
|
-
yarn lint && git add . && git commit -m "feat: add the web target"
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
Now that's done, let's [Switch to a persistent data storage](./008_Switch_to_a_persistent_data_storage.md).
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Switch to a persistent data storage
|
|
2
|
-
|
|
3
|
-
By default, the data is stored in memory on the server. Therefore, whenever we restart it, we lose everything. That is not very practical in real life scenarios. Let's use SQLite instead.
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
yarn add "knex@^3.1.0" "sqlite3@^5.1.7"
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
Update `src/products/SuperTrader/server/container.ts` to change the implementation.
|
|
10
|
-
|
|
11
|
-
```diff
|
|
12
|
-
[...]
|
|
13
|
-
TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
|
|
14
|
-
+ type UCDataStore,
|
|
15
|
-
[...]
|
|
16
|
-
+import {
|
|
17
|
-
+ KnexUCDataStore,
|
|
18
|
-
+ type KnexUCDataStoreSettings,
|
|
19
|
-
+} from 'libmodulor/uc-data-store-knex';
|
|
20
|
-
[...]
|
|
21
|
-
+type S = KnexUCDataStoreSettings & ServerManagerSettings;
|
|
22
|
-
[...]
|
|
23
|
-
...TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
|
|
24
|
-
+ knex_uc_data_store_conn_string: 'postgresql://toto',
|
|
25
|
-
+ knex_uc_data_store_file_path: 'uc-data-store.sqlite',
|
|
26
|
-
+ knex_uc_data_store_pool_max: 5,
|
|
27
|
-
+ knex_uc_data_store_pool_min: 0,
|
|
28
|
-
+ knex_uc_data_store_type: 'sqlite3',
|
|
29
|
-
server_static_dir_path: 'public',
|
|
30
|
-
[...]
|
|
31
|
-
container.rebind<SettingsManager>('SettingsManager').to(EnvSettingsManager);
|
|
32
|
-
+container.rebind<UCDataStore>('UCDataStore').to(KnexUCDataStore);
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Press <kbd>ctrl</kbd> + <kbd>C</kbd> to stop the server.
|
|
36
|
-
|
|
37
|
-
```sh
|
|
38
|
-
yarn build && yarn run:server
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Fill and submit the use case multiple times.
|
|
42
|
-
|
|
43
|
-
Open the SQLite database with your favorite DB editor (e.g. TablePlus, DBeaver...).
|
|
44
|
-
|
|
45
|
-
```sh
|
|
46
|
-
open dist/products/SuperTrader/server/uc-data-store.sqlite
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
You should see all your submissions stored in the database.
|
|
50
|
-
|
|
51
|
-
```sh
|
|
52
|
-
yarn lint && git add . && git commit -m "feat: persist data in SQLite"
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Now that's done, let's [Define wording for humans](./009_Define_wording_for_humans.md).
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# Define wording for humans
|
|
2
|
-
|
|
3
|
-
By default, the UI simply "humanizes" the use case field keys. It's fine for technical people, but not for humans.
|
|
4
|
-
|
|
5
|
-
Update `src/apps/Trading/src/i18n.ts` and add the following keys to `en`.
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
uc_BuyAsset_label: 'Buy an asset',
|
|
9
|
-
uc_BuyAsset_i_submit_idle: 'Send buy order',
|
|
10
|
-
uc_BuyAsset_i_submit_submitting: 'Sending',
|
|
11
|
-
ucif_isin_label: 'ISIN',
|
|
12
|
-
ucif_qty_label: 'Quantity',
|
|
13
|
-
ucof_executedDirectly_label: '🚀 Executed directly',
|
|
14
|
-
ucof_id_label: 'Identifier',
|
|
15
|
-
validation_format_ISIN:
|
|
16
|
-
'Must be 2 uppercase letters, followed by 9 alphanumeric characters and 1 digit',
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Update `src/products/SuperTrader/i18n.ts` and add the following keys to `en`.
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
total: 'Total',
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
> [!NOTE]
|
|
26
|
-
> We distinguish what's related to the app from what's related to the product. Usually, in the app's `i18n`, you'll add only translations following a certain convention like `dt_*` (data type choices), `err_*` (error messages), `uc_*` (use cases), `ucif_*` (use case input fields), `ucof_*` (use case output fields), `validation_*` (validation messages), etc.
|
|
27
|
-
|
|
28
|
-
Press <kbd>ctrl</kbd> + <kbd>C</kbd> to stop the server.
|
|
29
|
-
|
|
30
|
-
```sh
|
|
31
|
-
yarn build && yarn run:server
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Refresh the page. You should see a better wording. Try to type an invalid `ISIN` and see how the full validation message is displayed as well.
|
|
35
|
-
|
|
36
|
-
<img src="/docs/assets/trading-target-web-human.png" width="600px">
|
|
37
|
-
|
|
38
|
-
```sh
|
|
39
|
-
yarn lint && git add . && git commit -m "feat: define wording for humans"
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Now that's done, let's [Create the cli Target](./010_Create_the_cli_Target.md).
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# Create the cli Target
|
|
2
|
-
|
|
3
|
-
We'll use the pre-built [Node.js parseArgs](https://nodejs.org/api/util.html#utilparseargsconfig) `CLIManager`.
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
mkdir src/products/SuperTrader/cli
|
|
7
|
-
touch src/products/SuperTrader/cli/{container.ts,index.ts}
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
## container.ts
|
|
11
|
-
|
|
12
|
-
```typescript
|
|
13
|
-
import {
|
|
14
|
-
CONTAINER_OPTS,
|
|
15
|
-
type ServerClientManagerSettings,
|
|
16
|
-
TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
|
|
17
|
-
bindCommon,
|
|
18
|
-
bindProduct,
|
|
19
|
-
} from 'libmodulor';
|
|
20
|
-
import { bindNodeCLI, bindNodeCore } from 'libmodulor/node';
|
|
21
|
-
import { Container } from 'inversify';
|
|
22
|
-
|
|
23
|
-
import { I18n } from '../i18n.js';
|
|
24
|
-
import { Manifest } from '../manifest.js';
|
|
25
|
-
|
|
26
|
-
const container = new Container(CONTAINER_OPTS);
|
|
27
|
-
|
|
28
|
-
bindCommon<ServerClientManagerSettings>(container, () => ({
|
|
29
|
-
...TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
|
|
30
|
-
}));
|
|
31
|
-
bindNodeCore(container);
|
|
32
|
-
bindNodeCLI(container);
|
|
33
|
-
bindProduct(container, Manifest, I18n);
|
|
34
|
-
|
|
35
|
-
export default container;
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## index.ts
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import {
|
|
42
|
-
APPS_ROOT_DIR_NAME,
|
|
43
|
-
type FSManager,
|
|
44
|
-
type I18nManager,
|
|
45
|
-
} from 'libmodulor';
|
|
46
|
-
import { NodeCoreCLIManager } from 'libmodulor/node';
|
|
47
|
-
|
|
48
|
-
import container from './container.js';
|
|
49
|
-
|
|
50
|
-
await container.get<I18nManager>('I18nManager').init();
|
|
51
|
-
|
|
52
|
-
await container.resolve(NodeCoreCLIManager).handleCommand({
|
|
53
|
-
appsRootPath: container
|
|
54
|
-
.get<FSManager>('FSManager')
|
|
55
|
-
.path('..', '..', '..', APPS_ROOT_DIR_NAME),
|
|
56
|
-
srcImporter: (path) => import(path),
|
|
57
|
-
});
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Build & Run
|
|
61
|
-
|
|
62
|
-
Update `package.json` to add a new entry to the `scripts`.
|
|
63
|
-
|
|
64
|
-
```json
|
|
65
|
-
"run:cli": "cd dist/products/SuperTrader/cli && node index.js",
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
```sh
|
|
69
|
-
yarn build && yarn run:cli
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
You can see the CLI help appearing with the available commands.
|
|
73
|
-
|
|
74
|
-
> [!TIP]
|
|
75
|
-
> Update the app's `i18n.ts` to add `uc_BuyAsset_desc` and `ucif_isin_desc` to have a more detailed help section.
|
|
76
|
-
|
|
77
|
-
Start the server if it's not running.
|
|
78
|
-
|
|
79
|
-
```sh
|
|
80
|
-
yarn run:server
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Execute the CLI in another terminal or tab.
|
|
84
|
-
|
|
85
|
-
```sh
|
|
86
|
-
yarn run:cli Trading_BuyAsset
|
|
87
|
-
# ❌ ISIN must be filled
|
|
88
|
-
yarn run:cli Trading_BuyAsset --isin US02079K3059 --limit 123.5 --qty 150
|
|
89
|
-
# ✅ {"parts":{"_0":{"items":[{"executedDirectly":false,"id":"da3dc295-6d7c-41b1-a00a-62683f3e6ab9"}],"total":1}}}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Open the SQLite database with your favorite DB editor (e.g. TablePlus, DBeaver...).
|
|
93
|
-
|
|
94
|
-
```sh
|
|
95
|
-
open dist/products/SuperTrader/server/uc-data-store.sqlite
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
```sh
|
|
99
|
-
yarn lint && git add . && git commit -m "feat: add the cli target"
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Now that's done, let's [Create the mcp-server Target](./011_Create_the_mcp_server_Target.md).
|