libmodulor 0.2.0 → 0.3.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/.github/ISSUE_TEMPLATE/generic-issue.md +16 -0
- package/CHANGELOG.md +20 -0
- package/README.md +15 -15
- package/dist/esm/target/node-express-server/NodeExpressServerManager.js +4 -1
- package/dist/esm/uc/ext.d.ts +3 -1
- package/dist/esm/uc/helpers/UCOutputReader.d.ts +1 -1
- package/dist/esm/uc/helpers/UCOutputReader.js +4 -2
- package/dist/esm/uc/output-part.d.ts +2 -1
- package/dist/esm/uc/utils/ucHTTPContract.d.ts +1 -0
- package/dist/esm/uc/utils/ucHTTPContract.js +5 -0
- package/docs/getting-started/001_Create_the_project.md +6 -6
- package/docs/getting-started/007_Create_the_web_Target.md +1 -1
- package/docs/getting-started/008_Switch_to_a_persistent_data_storage.md +5 -2
- package/docs/getting-started/009_Define_wording_for_humans.md +9 -9
- package/docs/getting-started/010_Create_the_cli_Target.md +4 -4
- package/docs/getting-started/011_Create_the_mcp_server_Target.md +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Generic Issue
|
|
3
|
+
about: Describe this issue template's purpose here.
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
<!-- State the problem clearly with as much details as you can. It can be a new feature request or a bug. Either way, there should be a clear problem -->
|
|
13
|
+
|
|
14
|
+
## Potential Solution(s)
|
|
15
|
+
|
|
16
|
+
<!-- If you have ideas, feel free to suggest them, we love it. We'll discuss the solutions and find the best one for everyone -->
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## v0.3.0 (2025-01-23)
|
|
4
|
+
|
|
5
|
+
**feat(uc): introduce alternate mounting point**
|
|
6
|
+
|
|
7
|
+
Added a new property `UcDef.ext.http.mountAlsoAt` to be able to define path aliases. See the comment below to understand why.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
/**
|
|
11
|
+
* The path on which the use case should also mounted at
|
|
12
|
+
*
|
|
13
|
+
* This is typically used when the mounting point is changed and you want to maintain a "legacy" endpoint for clients having
|
|
14
|
+
* a different release cycle than the server (e.g. a mobile app), who are still calling the old endpoint.
|
|
15
|
+
*/
|
|
16
|
+
mountAlsoAt?: UCHTTPMountingPoint[];
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**feat(uc): add id to fields in UCOutputReader**
|
|
20
|
+
|
|
21
|
+
The `UCOutputReader` automatically builds a fields list based on the `UCDef.io.o` `fields` and `order`. Although being a technical value, it's sometimes useful to get the `id` as a field as well as all the other fields explicitly defined. Hence the addition of `id` to the fields list.
|
|
22
|
+
|
|
3
23
|
## v0.2.0 (2025-01-20)
|
|
4
24
|
|
|
5
25
|
It's finally here ! Very first version of the library with all the primitives discussed in the documentation.
|
package/README.md
CHANGED
|
@@ -197,21 +197,21 @@ block-beta
|
|
|
197
197
|
|
|
198
198
|
_If you're not seeing the mermaid chart (e.g. on npm), head to GitHub._
|
|
199
199
|
|
|
200
|
-
Please note that we'll only develop one use case to keep the Guide straightforward but you'll quickly get the idea to develop all the
|
|
200
|
+
Please note that we'll only develop one use case to keep the Guide straightforward but you'll quickly get the idea to develop all the others by yourself.
|
|
201
201
|
|
|
202
202
|
Here are the steps we're going to follow. Don't worry. Even though it seems a lot, it will be super quick, efficient and straight to the point.
|
|
203
203
|
|
|
204
|
-
1. [Create the project](
|
|
205
|
-
1. [Create the App](
|
|
206
|
-
1. [Create the UseCase](
|
|
207
|
-
1. [Test the App](
|
|
208
|
-
1. [Create the Product](
|
|
209
|
-
1. [Create the server Target](
|
|
210
|
-
1. [Create the web Target](
|
|
211
|
-
1. [Switch to a persistent data storage](
|
|
212
|
-
1. [Define wording for humans](
|
|
213
|
-
1. [Create the cli Target](
|
|
214
|
-
1. [Create the mcp-server Target](
|
|
215
|
-
1. [Summary](
|
|
216
|
-
|
|
217
|
-
Let's go with the first step : [Create the project](
|
|
204
|
+
1. [Create the project](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/001_Create_the_project.md)
|
|
205
|
+
1. [Create the App](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/002_Create_the_App.md)
|
|
206
|
+
1. [Create the UseCase](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/003_Create_the_UseCase.md)
|
|
207
|
+
1. [Test the App](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/004_Test_the_App.md)
|
|
208
|
+
1. [Create the Product](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/005_Create_the_Product.md)
|
|
209
|
+
1. [Create the server Target](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/006_Create_the_server_Target.md)
|
|
210
|
+
1. [Create the web Target](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/007_Create_the_web_Target.md)
|
|
211
|
+
1. [Switch to a persistent data storage](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/008_Switch_to_a_persistent_data_storage.md)
|
|
212
|
+
1. [Define wording for humans](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/009_Define_wording_for_humans.md)
|
|
213
|
+
1. [Create the cli Target](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/010_Create_the_cli_Target.md)
|
|
214
|
+
1. [Create the mcp-server Target](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/011_Create_the_mcp_server_Target.md)
|
|
215
|
+
1. [Summary](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/012_Summary.md)
|
|
216
|
+
|
|
217
|
+
Let's go with the first step : [Create the project](https://github.com/c100k/libmodulor/blob/v0.3.0/docs/getting-started/001_Create_the_project.md).
|
|
@@ -92,7 +92,7 @@ let NodeExpressServerManager = class NodeExpressServerManager {
|
|
|
92
92
|
}
|
|
93
93
|
async mount(appManifest, ucd, contract) {
|
|
94
94
|
const { sec } = ucd;
|
|
95
|
-
const { envelope, method, path } = contract;
|
|
95
|
+
const { envelope, method, path, pathAliases } = contract;
|
|
96
96
|
const httpMethod = method.toLowerCase();
|
|
97
97
|
const handlers = [
|
|
98
98
|
this.publicApiKeyCheckerMB.exec({
|
|
@@ -107,6 +107,9 @@ let NodeExpressServerManager = class NodeExpressServerManager {
|
|
|
107
107
|
}),
|
|
108
108
|
];
|
|
109
109
|
this.runtime[httpMethod](path, handlers);
|
|
110
|
+
for (const pathAlias of pathAliases) {
|
|
111
|
+
this.runtime[httpMethod](pathAlias, handlers);
|
|
112
|
+
}
|
|
110
113
|
}
|
|
111
114
|
async mountStaticDir(dirPath) {
|
|
112
115
|
this.runtime.use(express.static(dirPath));
|
package/dist/esm/uc/ext.d.ts
CHANGED
|
@@ -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?:
|
|
12
|
+
mountAt?: UCHTTPMountingPoint;
|
|
13
|
+
mountAlsoAt?: UCHTTPMountingPoint[];
|
|
12
14
|
transform?: <T extends object>(output: UCOutput<OPI0, OPI1>) => T;
|
|
13
15
|
};
|
|
14
16
|
}
|
|
@@ -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
|
|
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:
|
|
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<
|
|
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
|
}
|
|
@@ -69,13 +69,13 @@ src/apps/**/test/reports
|
|
|
69
69
|
},
|
|
70
70
|
"dependencies": {
|
|
71
71
|
"inversify": "^6.2.1",
|
|
72
|
-
"libmodulor": "^0.
|
|
72
|
+
"libmodulor": "^0.3.0",
|
|
73
73
|
"reflect-metadata": "^0.2.2"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@biomejs/biome": "^1.9.4",
|
|
77
|
-
"@types/node": "^22.10.
|
|
78
|
-
"@vitest/coverage-v8": "^
|
|
77
|
+
"@types/node": "^22.10.7",
|
|
78
|
+
"@vitest/coverage-v8": "^3.0.2",
|
|
79
79
|
"buffer": "^6.0.3",
|
|
80
80
|
"cookie-parser": "^1.4.7",
|
|
81
81
|
"express": "^4.21.2",
|
|
@@ -83,9 +83,9 @@ src/apps/**/test/reports
|
|
|
83
83
|
"fast-check": "^3.23.2",
|
|
84
84
|
"helmet": "^8.0.0",
|
|
85
85
|
"jose": "^5.9.6",
|
|
86
|
-
"typescript": "^5.7.
|
|
87
|
-
"vite": "^6.0.
|
|
88
|
-
"vitest": "^
|
|
86
|
+
"typescript": "^5.7.3",
|
|
87
|
+
"vite": "^6.0.11",
|
|
88
|
+
"vitest": "^3.0.3"
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
```
|
|
@@ -99,7 +99,7 @@ Update `src/apps/Trading/index.ts` to expose the use case.
|
|
|
99
99
|
export { BuyAssetUCD } from './src/ucds/BuyAssetUCD.js';
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
Naturally, in real life scenarios, we would never have such a bloated `App.tsx
|
|
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
103
|
|
|
104
104
|
```tsx
|
|
105
105
|
import { type Logger, type ProductManifest, UCOutputReader } from 'libmodulor';
|
|
@@ -13,7 +13,10 @@ Update `src/products/SuperTrader/server/container.ts` to change the implementati
|
|
|
13
13
|
TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
|
|
14
14
|
+ type UCDataStore,
|
|
15
15
|
[...]
|
|
16
|
-
+import {
|
|
16
|
+
+import {
|
|
17
|
+
+ KnexUCDataStore,
|
|
18
|
+
+ type KnexUCDataStoreSettings,
|
|
19
|
+
+} from 'libmodulor/uc-data-store-knex';
|
|
17
20
|
[...]
|
|
18
21
|
+type S = KnexUCDataStoreSettings & ServerManagerSettings;
|
|
19
22
|
[...]
|
|
@@ -37,7 +40,7 @@ yarn build && yarn run:server
|
|
|
37
40
|
|
|
38
41
|
Fill and submit the use case multiple times.
|
|
39
42
|
|
|
40
|
-
Open the SQLite database with
|
|
43
|
+
Open the SQLite database with your favorite DB editor (e.g. TablePlus, DBeaver...).
|
|
41
44
|
|
|
42
45
|
```sh
|
|
43
46
|
open dist/products/SuperTrader/server/uc-data-store.sqlite
|
|
@@ -5,15 +5,15 @@ By default, the UI simply "humanizes" the use case field keys. It's fine for tec
|
|
|
5
5
|
Update `src/apps/Trading/src/i18n.ts` and add the following keys to `en`.
|
|
6
6
|
|
|
7
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
|
-
|
|
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
17
|
```
|
|
18
18
|
|
|
19
19
|
Update `src/products/SuperTrader/i18n.ts` and add the following keys to `en`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Create the cli Target
|
|
2
2
|
|
|
3
|
-
We'll use the pre-built [Node.js parseArgs](https://nodejs.org/api/util.html#utilparseargsconfig)
|
|
3
|
+
We'll use the pre-built [Node.js parseArgs](https://nodejs.org/api/util.html#utilparseargsconfig) `CLIManager`.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
mkdir src/products/SuperTrader/cli
|
|
@@ -83,13 +83,13 @@ yarn run:server
|
|
|
83
83
|
Execute the CLI in another terminal or tab.
|
|
84
84
|
|
|
85
85
|
```sh
|
|
86
|
-
yarn run:cli
|
|
86
|
+
yarn run:cli Trading_BuyAsset
|
|
87
87
|
# ❌ ISIN must be filled
|
|
88
|
-
yarn run:cli
|
|
88
|
+
yarn run:cli Trading_BuyAsset --isin US02079K3059 --limit 123.5 --qty 150
|
|
89
89
|
# ✅ {"parts":{"_0":{"items":[{"executedDirectly":false,"id":"da3dc295-6d7c-41b1-a00a-62683f3e6ab9"}],"total":1}}}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
Open the SQLite database with
|
|
92
|
+
Open the SQLite database with your favorite DB editor (e.g. TablePlus, DBeaver...).
|
|
93
93
|
|
|
94
94
|
```sh
|
|
95
95
|
open dist/products/SuperTrader/server/uc-data-store.sqlite
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Create the mcp-server Target
|
|
2
2
|
|
|
3
|
-
We'll use the pre-built local [stdio transport](https://modelcontextprotocol.io/docs/concepts/transports#standard-input-output-stdio)
|
|
3
|
+
We'll use the pre-built local [stdio transport](https://modelcontextprotocol.io/docs/concepts/transports#standard-input-output-stdio) `ServerManager`.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
6
|
yarn add "@modelcontextprotocol/sdk@^1.1.1"
|
|
@@ -144,7 +144,7 @@ And let the magic happens.
|
|
|
144
144
|
|
|
145
145
|
<img src="/docs/assets/trading-target-mcp-server.png" width="600px">
|
|
146
146
|
|
|
147
|
-
Open the SQLite database with
|
|
147
|
+
Open the SQLite database with your favorite DB editor (e.g. TablePlus, DBeaver...).
|
|
148
148
|
|
|
149
149
|
```sh
|
|
150
150
|
open dist/products/SuperTrader/server/uc-data-store.sqlite
|
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.
|
|
4
|
+
"version": "0.3.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",
|