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.
@@ -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 orders by yourself.
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](./docs/getting-started/001_Create_the_project.md)
205
- 1. [Create the App](./docs/getting-started/002_Create_the_App.md)
206
- 1. [Create the UseCase](./docs/getting-started/003_Create_the_UseCase.md)
207
- 1. [Test the App](./docs/getting-started/004_Test_the_App.md)
208
- 1. [Create the Product](./docs/getting-started/005_Create_the_Product.md)
209
- 1. [Create the server Target](./docs/getting-started/006_Create_the_server_Target.md)
210
- 1. [Create the web Target](./docs/getting-started/007_Create_the_web_Target.md)
211
- 1. [Switch to a persistent data storage](./docs/getting-started/008_Switch_to_a_persistent_data_storage.md)
212
- 1. [Define wording for humans](./docs/getting-started/009_Define_wording_for_humans.md)
213
- 1. [Create the cli Target](./docs/getting-started/010_Create_the_cli_Target.md)
214
- 1. [Create the mcp-server Target](./docs/getting-started/011_Create_the_mcp_server_Target.md)
215
- 1. [Summary](./docs/getting-started/012_Summary.md)
216
-
217
- Let's go with the first step : [Create the project](./docs/getting-started/001_Create_the_project.md).
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));
@@ -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
  }
@@ -69,13 +69,13 @@ src/apps/**/test/reports
69
69
  },
70
70
  "dependencies": {
71
71
  "inversify": "^6.2.1",
72
- "libmodulor": "^0.2.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.2",
78
- "@vitest/coverage-v8": "^2.1.1",
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.2",
87
- "vite": "^6.0.5",
88
- "vitest": "^2.1.8"
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` and we could create fine-grained components. Everybody does that, right ?
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 { KnexUCDataStore } from 'libmodulor/uc-data-store/knex';
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 you with your favorite DB editor (e.g. TablePlus, DBeaver...).
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
- 'Must be 2 uppercase letters, followed by 9 alphanumeric characters and 1 digit',
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) CLI program.
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 BuyAsset
86
+ yarn run:cli Trading_BuyAsset
87
87
  # ❌ ISIN must be filled
88
- yarn run:cli BuyAsset --isin US02079K3059 --limit 123.5 --qty 150
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 you with your favorite DB editor (e.g. TablePlus, DBeaver...).
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) server.
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 you with your favorite DB editor (e.g. TablePlus, DBeaver...).
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.2.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",