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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## v0.4.0 (2025-01-31)
|
|
4
|
+
|
|
5
|
+
**feat(target): introduce react-native-pure**
|
|
6
|
+
|
|
7
|
+
To help with the creation of specific targets, we've added a new one : `react-native-pure`. It's as simple as `react-web-pure`, with no specific UI style. It's a good starting point to take inspiration to create your own GUI target, with your own style.
|
|
8
|
+
|
|
9
|
+
**Misc**
|
|
10
|
+
|
|
11
|
+
- Introduced Guides in docs for more advanced scenarios (e.g. Create a target) (https://github.com/c100k/libmodulor/pull/10)
|
|
12
|
+
- Improved the docs for a better readability (https://github.com/c100k/libmodulor/pull/10)
|
|
13
|
+
|
|
3
14
|
## v0.3.0 (2025-01-23)
|
|
4
15
|
|
|
5
16
|
**feat(uc): introduce alternate mounting point**
|
package/README.md
CHANGED
|
@@ -25,193 +25,18 @@ Applications created with `libmodulor` have **6 main properties** :
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
## 📚 Philosophy
|
|
29
|
-
|
|
30
|
-
One might argue that, with so many "JS frameworks" on the market, there are already too many ways to build new applications today. And they would be right.
|
|
31
|
-
|
|
32
|
-
That's why the angle taken by `libmodulor` is different. Although opinionated about some things (see below), it is not, regarding the technical side. Instead, it focuses mainly on the "core" of your application.
|
|
33
|
-
|
|
34
|
-
Thus, you are free to use :
|
|
35
|
-
|
|
36
|
-
- the data store of your choice (PostgreSQL, MySQL, MariaDB, DynamoDB, SQLite, MongoDB...),
|
|
37
|
-
- the frontend framework of your choice (React, Svelte, Angular, Vue, Solid...),
|
|
38
|
-
- the server of your choice (Express, Fastify, Hono...),
|
|
39
|
-
- the meta framework of your choice (Next, Remix, Astro, Nuxt...),
|
|
40
|
-
- the runtime of your choice (Node, Deno, Bun...)
|
|
41
|
-
- the libraries of your choice (Lodash, React Query...)
|
|
42
|
-
- the tools of your choice (Biome, ESLint, Prettier...)
|
|
43
|
-
- the styling library of your choice for web (tailwind, shadcn, bootstrap, vanilla CSS...)
|
|
44
|
-
- the hosting of your choice (Cloud, IaaS, PaaS, On-Prem, RaspberryPi, your fridge...)
|
|
45
|
-
|
|
46
|
-
The main goal is to offer higher level primitives that make building business applications faster, without having to use a boilerplate or worse, no/low code, and thus, avoid vendor lock-in.
|
|
47
|
-
|
|
48
|
-
## 💡 How it works
|
|
49
|
-
|
|
50
|
-
The library defines a **4-layer architecture** composed of : `UseCase`, `App`, `Product`, `Target`.
|
|
51
|
-
|
|
52
|
-
```mermaid
|
|
53
|
-
block-beta
|
|
54
|
-
Target1:2
|
|
55
|
-
Target2:2
|
|
56
|
-
Target3:2
|
|
57
|
-
columns 6
|
|
58
|
-
Product1:6
|
|
59
|
-
App1:3
|
|
60
|
-
App2:3
|
|
61
|
-
UseCase1
|
|
62
|
-
UseCase2
|
|
63
|
-
UseCase3
|
|
64
|
-
UseCase4
|
|
65
|
-
UseCase6
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
_If you're not seeing the mermaid chart (e.g. on npm), head to GitHub._
|
|
69
|
-
|
|
70
|
-
### UseCase
|
|
71
|
-
|
|
72
|
-
A use case is the smallest unit. It defines the contract, mainly as an `Input` that goes into lifecycle methods (`client` and/or `server`) to finally give an `Output`. In the end, it constitutes a piece of business functionality.
|
|
73
|
-
|
|
74
|
-
Inspired by [UML's Use case diagram](https://en.wikipedia.org/wiki/Use_case_diagram) and [Event-driven architecture](https://en.wikipedia.org/wiki/Event-driven_architecture), schematically, it could be defined as follows :
|
|
75
|
-
|
|
76
|
-
```math
|
|
77
|
-
O = clientMain(serverMain(I))
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
_Examples : `SignIn`, `CreatePost`, `TransferAccount`, `InviteContacts`_...
|
|
81
|
-
|
|
82
|
-
Note how it always starts with a verb.
|
|
83
|
-
|
|
84
|
-
### App
|
|
85
|
-
|
|
86
|
-
An app is a logical group of use cases.
|
|
87
|
-
|
|
88
|
-
It's like a "module" (_whatever that means_), inspired by [Domain-driven design (DDD)](https://en.wikipedia.org/wiki/Domain-driven_design) bounded contexts.
|
|
89
|
-
|
|
90
|
-
_Examples : `Auth`, `Accounting`, `CMS`..._
|
|
91
|
-
|
|
92
|
-
### Product
|
|
93
|
-
|
|
94
|
-
A product is a logical group of apps that are assembled together.
|
|
95
|
-
|
|
96
|
-
Behind this barbaric definition, it's simply what end users know and use.
|
|
97
|
-
|
|
98
|
-
_Examples : `GitHub`, `Facebook`, `LinkedIn`, `Airbnb`..._
|
|
99
|
-
|
|
100
|
-
When defined correctly, apps are reusable across multiple products (e.g. `Auth`).
|
|
101
|
-
|
|
102
|
-
### Target
|
|
103
|
-
|
|
104
|
-
A target defines how a product is "exposed" to the end user. It's a combination of platform and runtime.
|
|
105
|
-
|
|
106
|
-
_Examples : `web-react`, `web-angular`, `server-node`, `cli-node`, `cli-stricli`..._
|
|
107
|
-
|
|
108
|
-
Note that it's the only place where the "infrastructure" choices are applied.
|
|
109
|
-
|
|
110
|
-
## 👀 At a glance
|
|
111
|
-
|
|
112
|
-
Here is what a typical use case looks like. For more details, please follow the Guide below.
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
import { UCInput, /* omitted for brevity */ } from 'libmodulor';
|
|
116
|
-
import { Manifest } from '../manifest.js';
|
|
117
|
-
import { SignInServerMain } from './SignInServerMain.js';
|
|
118
|
-
|
|
119
|
-
export interface SignInInput extends UCInput {
|
|
120
|
-
email: UCInputFieldValue<Email>;
|
|
121
|
-
password: UCInputFieldValue<Password>;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export interface SignInOPI0 extends UCOPIBase {
|
|
125
|
-
jwt: JWT;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export const SignInUCD: UCDef<SignInInput, SignInOPI0> = {
|
|
129
|
-
io: {
|
|
130
|
-
i: {
|
|
131
|
-
fields: {
|
|
132
|
-
email: {
|
|
133
|
-
type: new TEmail(),
|
|
134
|
-
},
|
|
135
|
-
password: {
|
|
136
|
-
type: new TPassword({ minLength: 10 }),
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
o: {
|
|
141
|
-
parts: {
|
|
142
|
-
_0: {
|
|
143
|
-
fields: {
|
|
144
|
-
jwt: {
|
|
145
|
-
type: new TJWT(),
|
|
146
|
-
},
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
lifecycle: {
|
|
153
|
-
client: {
|
|
154
|
-
main: SendClientMain,
|
|
155
|
-
policy: AnonymousUCPolicy,
|
|
156
|
-
},
|
|
157
|
-
server: {
|
|
158
|
-
main: SignInServerMain,
|
|
159
|
-
policy: AnonymousUCPolicy,
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
metadata: Manifest.ucReg.SignIn,
|
|
163
|
-
};
|
|
164
|
-
```
|
|
165
|
-
|
|
166
28
|
## 🚀 Getting Started
|
|
167
29
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
> [!NOTE]
|
|
171
|
-
> This Guide is voluntarily very verbose and not scripted so you can get a full overview of how things work. `npx` magic is good. But understanding what happens behind the scenes is good as well.
|
|
172
|
-
|
|
173
|
-
In this Guide, we'll init a repository (a repository can contain multiple apps and products) and create a real life application using the `libmodulor` primitives.
|
|
174
|
-
|
|
175
|
-
We'll build a small trading application. It will contain one `App` named `Trading`, which will contain one `UseCase` named `BuyAsset`. The `App` will be mounted in a `Product` called `SuperTrader` which will be exposed via a `server` `Target`, a `web` `Target`, a `cli` `Target` and finally, a `mcp-server` `Target`.
|
|
176
|
-
|
|
177
|
-
> [!NOTE]
|
|
178
|
-
> MCP stands for [Model Context Protocol](https://modelcontextprotocol.io) introduced recently by [@anthropics](https://github.com/anthropics).
|
|
179
|
-
|
|
180
|
-
If we adapt the abstract mermaid chart displayed above, concretely, it looks like this :
|
|
30
|
+
To get started, we recommend reading the [📖 Introduction](https://github.com/c100k/libmodulor/blob/v0.4.0/docs/Introduction.md) first. It will give you an overview of what `libmodulor` is and how it works.
|
|
181
31
|
|
|
182
|
-
|
|
183
|
-
block-beta
|
|
184
|
-
server
|
|
185
|
-
web
|
|
186
|
-
cli
|
|
187
|
-
mcp_server
|
|
188
|
-
columns 4
|
|
189
|
-
SuperTrader:4
|
|
190
|
-
Trading:2
|
|
191
|
-
Auth:2
|
|
192
|
-
BuyAsset
|
|
193
|
-
ListOrders
|
|
194
|
-
SignUp
|
|
195
|
-
SignIn
|
|
196
|
-
```
|
|
32
|
+
Then, you can follow the [🚀 Tutorial](https://github.com/c100k/libmodulor/blob/v0.4.0/docs/Tutorial.md) that will show you all the main notions by building something real.
|
|
197
33
|
|
|
198
|
-
|
|
34
|
+
Finally, for more advanced usages, go to the [📜 Guides](https://github.com/c100k/libmodulor/blob/v0.4.0/docs/Guides.md).
|
|
199
35
|
|
|
200
|
-
|
|
36
|
+
## 👨💻 Contribute
|
|
201
37
|
|
|
202
|
-
|
|
38
|
+
If you think you can help in any way, feel free to contact me (cf. `author` in `package.json`). I'd love to chat.
|
|
203
39
|
|
|
204
|
-
|
|
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)
|
|
40
|
+
## ⚖️ License
|
|
216
41
|
|
|
217
|
-
|
|
42
|
+
[LGPL-3.0](./LICENSE)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { UCAutoExecLoader } from './target/react-native-pure/UCAutoExecLoader.js';
|
|
2
|
+
export { UCEntrypointTouchable } from './target/react-native-pure/UCEntrypointTouchable.js';
|
|
3
|
+
export { UCExecTouchable } from './target/react-native-pure/UCExecTouchable.js';
|
|
4
|
+
export { UCForm } from './target/react-native-pure/UCForm.js';
|
|
5
|
+
export { UCFormField } from './target/react-native-pure/UCFormField.js';
|
|
6
|
+
export { UCFormFieldControl } from './target/react-native-pure/UCFormFieldControl.js';
|
|
7
|
+
export { UCFormFieldDesc } from './target/react-native-pure/UCFormFieldDesc.js';
|
|
8
|
+
export { UCFormFieldErr } from './target/react-native-pure/UCFormFieldErr.js';
|
|
9
|
+
export { UCFormFieldLabel } from './target/react-native-pure/UCFormFieldLabel.js';
|
|
10
|
+
export { UCFormSubmitControl } from './target/react-native-pure/UCFormSubmitControl.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { UCAutoExecLoader } from './target/react-native-pure/UCAutoExecLoader.js';
|
|
2
|
+
export { UCEntrypointTouchable } from './target/react-native-pure/UCEntrypointTouchable.js';
|
|
3
|
+
export { UCExecTouchable } from './target/react-native-pure/UCExecTouchable.js';
|
|
4
|
+
export { UCForm } from './target/react-native-pure/UCForm.js';
|
|
5
|
+
export { UCFormField } from './target/react-native-pure/UCFormField.js';
|
|
6
|
+
export { UCFormFieldControl } from './target/react-native-pure/UCFormFieldControl.js';
|
|
7
|
+
export { UCFormFieldDesc } from './target/react-native-pure/UCFormFieldDesc.js';
|
|
8
|
+
export { UCFormFieldErr } from './target/react-native-pure/UCFormFieldErr.js';
|
|
9
|
+
export { UCFormFieldLabel } from './target/react-native-pure/UCFormFieldLabel.js';
|
|
10
|
+
export { UCFormSubmitControl } from './target/react-native-pure/UCFormSubmitControl.js';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { URLPath } from '../../../dt/index.js';
|
|
2
2
|
import type { UC, UCInput, UCOPIBase } from '../../../uc/index.js';
|
|
3
|
+
export type UCEntrypointOnPress = () => Promise<void>;
|
|
3
4
|
export interface UCEntrypointCtx<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> {
|
|
5
|
+
onPress?: UCEntrypointOnPress;
|
|
4
6
|
path?: URLPath | undefined;
|
|
5
7
|
uc: UC<I, OPI0, OPI1>;
|
|
6
8
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TextInputProps } from 'react-native';
|
|
2
|
+
import { type DataType, type ErrorMessage } from '../../../dt/index.js';
|
|
3
|
+
import { type UCExecState, type UCInputField } from '../../../uc/index.js';
|
|
4
|
+
export interface RNInputDef {
|
|
5
|
+
internal?: undefined;
|
|
6
|
+
spec?: TextInputProps;
|
|
7
|
+
}
|
|
8
|
+
export declare function rnInputDef<T extends DataType>(field: UCInputField<T>, execState: UCExecState, _errMsg: ErrorMessage | null): RNInputDef;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { TString, } from '../../../dt/index.js';
|
|
2
|
+
import { ucIsDisabled, ucifHint, ucifId, } from '../../../uc/index.js';
|
|
3
|
+
export function rnInputDef(field, execState, _errMsg) {
|
|
4
|
+
const def = {
|
|
5
|
+
internal: undefined,
|
|
6
|
+
spec: {},
|
|
7
|
+
};
|
|
8
|
+
if (!def.spec) {
|
|
9
|
+
return def;
|
|
10
|
+
}
|
|
11
|
+
const { key, def: fDef } = field;
|
|
12
|
+
const { type: fType } = fDef;
|
|
13
|
+
def.spec.editable = !ucIsDisabled(execState);
|
|
14
|
+
def.spec.id = ucifId(key);
|
|
15
|
+
if (fType instanceof TString) {
|
|
16
|
+
const constraints = fType.getConstraints();
|
|
17
|
+
if (constraints) {
|
|
18
|
+
def.spec.maxLength = constraints.maxLength;
|
|
19
|
+
}
|
|
20
|
+
def.spec.multiline = fType.isPotentiallyLong();
|
|
21
|
+
}
|
|
22
|
+
def.spec.placeholder = ucifHint(fDef);
|
|
23
|
+
def.spec.inputMode = fType.rnInputMode();
|
|
24
|
+
def.spec.secureTextEntry = fType.isSensitive();
|
|
25
|
+
return def;
|
|
26
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { UCInput, UCOPIBase } from '../../uc/index.js';
|
|
3
|
+
import type { UCEntrypointTouchableProps } from '../lib/react/touchable.js';
|
|
4
|
+
export declare function UCEntrypointTouchable<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ onPress, wording, }: UCEntrypointTouchableProps<I, OPI0, OPI1>): ReactElement;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { UCInput, UCOPIBase } from '../../uc/index.js';
|
|
3
|
+
import type { UCExecTouchableProps } from '../lib/react/touchable.js';
|
|
4
|
+
export declare function UCExecTouchable<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ disabled, execState, onSubmit, uc, }: UCExecTouchableProps<I, OPI0, OPI1>): ReactElement;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React, {} from 'react';
|
|
2
|
+
import { Pressable, Text } from 'react-native';
|
|
3
|
+
import { useDIContext } from '../lib/react/DIContextProvider.js';
|
|
4
|
+
export function UCExecTouchable({ disabled, execState, onSubmit, uc, }) {
|
|
5
|
+
const { wordingManager } = useDIContext();
|
|
6
|
+
const label = wordingManager.ucISubmit(uc.def, execState);
|
|
7
|
+
return (React.createElement(Pressable, { disabled: disabled, onPress: onSubmit },
|
|
8
|
+
React.createElement(Text, null, label)));
|
|
9
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { UCInput, UCOPIBase } from '../../uc/index.js';
|
|
3
|
+
import type { UCFormProps } from '../lib/react/form.js';
|
|
4
|
+
export declare function UCForm<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ disabled, execState, onChange, onSubmit, uc, }: UCFormProps<I, OPI0, OPI1>): ReactElement;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, {} from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { UCFormField } from './UCFormField.js';
|
|
4
|
+
import { UCFormSubmitControl, } from './UCFormSubmitControl.js';
|
|
5
|
+
export function UCForm({ disabled, execState, onChange, onSubmit, uc, }) {
|
|
6
|
+
const onPress = async () => {
|
|
7
|
+
await onSubmit();
|
|
8
|
+
};
|
|
9
|
+
return (React.createElement(View, null,
|
|
10
|
+
uc.inputFieldsForForm().map((f) => (React.createElement(View, { key: f.key },
|
|
11
|
+
React.createElement(UCFormField, { disabled: disabled, execState: execState, field: f, onChange: onChange })))),
|
|
12
|
+
React.createElement(UCFormSubmitControl, { execState: execState, disabled: disabled, onPress: onPress, uc: uc })));
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { DataType } from '../../dt/index.js';
|
|
3
|
+
import { type Props as FormFieldControlProps } from './UCFormFieldControl.js';
|
|
4
|
+
import { type Props as FormFieldLabelProps } from './UCFormFieldLabel.js';
|
|
5
|
+
declare const ELEMENTS: readonly ["control", "desc", "err", "label"];
|
|
6
|
+
type Element = (typeof ELEMENTS)[number];
|
|
7
|
+
type Props<T extends DataType> = FormFieldControlProps<T> & FormFieldLabelProps<T> & {
|
|
8
|
+
only?: Element[];
|
|
9
|
+
};
|
|
10
|
+
export declare function UCFormField<T extends DataType>({ disabled, execState, field, onChange: onChangeBase, only, }: Props<T>): ReactElement;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useDIContext } from '../lib/react/DIContextProvider.js';
|
|
3
|
+
import { UCFormFieldControl, } from './UCFormFieldControl.js';
|
|
4
|
+
import { UCFormFieldDesc } from './UCFormFieldDesc.js';
|
|
5
|
+
import { UCFormFieldErr } from './UCFormFieldErr.js';
|
|
6
|
+
import { UCFormFieldLabel, } from './UCFormFieldLabel.js';
|
|
7
|
+
const ELEMENTS = ['control', 'desc', 'err', 'label'];
|
|
8
|
+
export function UCFormField({ disabled, execState, field, onChange: onChangeBase, only, }) {
|
|
9
|
+
const { i18nManager } = useDIContext();
|
|
10
|
+
const { type } = field.def;
|
|
11
|
+
const [errMsg, setErrMsg] = useState(null);
|
|
12
|
+
const elements = only ?? ELEMENTS;
|
|
13
|
+
const onChange = (f, op, v) => {
|
|
14
|
+
setErrMsg(null);
|
|
15
|
+
const vArr = Array.isArray(v) ? v : [v];
|
|
16
|
+
for (const vv of vArr) {
|
|
17
|
+
const validation = type.assign(vv).validate();
|
|
18
|
+
const violation = validation.get();
|
|
19
|
+
if (violation) {
|
|
20
|
+
const [key, expected] = violation;
|
|
21
|
+
setErrMsg(i18nManager.t(key, { vars: { expected } }));
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
onChangeBase(f, op, v);
|
|
26
|
+
};
|
|
27
|
+
return (React.createElement(React.Fragment, null,
|
|
28
|
+
elements.includes('label') && React.createElement(UCFormFieldLabel, { field: field }),
|
|
29
|
+
elements.includes('control') && (React.createElement(UCFormFieldControl, { disabled: disabled, execState: execState, field: field, onChange: onChange })),
|
|
30
|
+
elements.includes('err') && errMsg && (React.createElement(UCFormFieldErr, { errMsg: errMsg })),
|
|
31
|
+
elements.includes('desc') && React.createElement(UCFormFieldDesc, { field: field })));
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { DataType, ErrorMessage } from '../../dt/index.js';
|
|
3
|
+
import { type UCInputField } from '../../uc/index.js';
|
|
4
|
+
import type { UCFormFieldControlOnChange } from '../lib/react/form.js';
|
|
5
|
+
import type { UCPanelState } from '../lib/react/panel.js';
|
|
6
|
+
export type Props<T extends DataType> = UCPanelState & {
|
|
7
|
+
errMsg?: ErrorMessage | null;
|
|
8
|
+
field: UCInputField<T>;
|
|
9
|
+
onChange: UCFormFieldControlOnChange<T>;
|
|
10
|
+
};
|
|
11
|
+
export declare function UCFormFieldControl<T extends DataType>({ errMsg, execState, field, onChange: onChangeBase, }: Props<T>): ReactElement;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { TextInput } from 'react-native';
|
|
3
|
+
import { UCInputFieldChangeOperator, ucifRepeatability, } from '../../uc/index.js';
|
|
4
|
+
import { isBlank } from '../../utils/index.js';
|
|
5
|
+
import { rnInputDef } from '../lib/rn/input.js';
|
|
6
|
+
const MULTIPLE_VALUES_SEPARATOR = ',';
|
|
7
|
+
export function UCFormFieldControl({ errMsg = null, execState, field, onChange: onChangeBase, }) {
|
|
8
|
+
const [internalValue, setInternalValue] = useState(field.getValue());
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
setInternalValue(field.getValue());
|
|
11
|
+
}, [field.getValue()]);
|
|
12
|
+
const attrs = rnInputDef(field, execState, errMsg);
|
|
13
|
+
const onChangeText = (value) => {
|
|
14
|
+
const [isRepeatable] = ucifRepeatability(field.def);
|
|
15
|
+
if (isRepeatable && typeof value === 'string') {
|
|
16
|
+
const valueArr = value
|
|
17
|
+
.split(MULTIPLE_VALUES_SEPARATOR)
|
|
18
|
+
.map((v) => v.trim());
|
|
19
|
+
onChangeBase(field, UCInputFieldChangeOperator.SET, valueArr);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
onChangeBase(field, UCInputFieldChangeOperator.SET, value);
|
|
23
|
+
}
|
|
24
|
+
setInternalValue(value);
|
|
25
|
+
};
|
|
26
|
+
let valueAsString = '';
|
|
27
|
+
if (!isBlank(internalValue)) {
|
|
28
|
+
if (Array.isArray(internalValue)) {
|
|
29
|
+
valueAsString = internalValue.join(MULTIPLE_VALUES_SEPARATOR);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
valueAsString = internalValue.toString();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return (React.createElement(TextInput, { ...attrs.spec, onChangeText: onChangeText, value: valueAsString }));
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { DataType } from '../../dt/index.js';
|
|
3
|
+
import type { UCInputField } from '../../uc/index.js';
|
|
4
|
+
export interface Props<T extends DataType> {
|
|
5
|
+
field: UCInputField<T>;
|
|
6
|
+
}
|
|
7
|
+
export declare function UCFormFieldDesc<T extends DataType>({ field, }: Props<T>): ReactElement | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React, {} from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { useDIContext } from '../lib/react/DIContextProvider.js';
|
|
4
|
+
export function UCFormFieldDesc({ field, }) {
|
|
5
|
+
const { wordingManager } = useDIContext();
|
|
6
|
+
const { desc } = wordingManager.ucif(field);
|
|
7
|
+
if (!desc) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return React.createElement(Text, null, desc);
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { DataType } from '../../dt/index.js';
|
|
3
|
+
import type { UCInputField } from '../../uc/index.js';
|
|
4
|
+
export interface Props<T extends DataType> {
|
|
5
|
+
field: UCInputField<T>;
|
|
6
|
+
}
|
|
7
|
+
export declare function UCFormFieldLabel<T extends DataType>({ field, }: Props<T>): ReactElement;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React, {} from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import { useDIContext } from '../lib/react/DIContextProvider.js';
|
|
4
|
+
export function UCFormFieldLabel({ field, }) {
|
|
5
|
+
const { wordingManager } = useDIContext();
|
|
6
|
+
const { label } = wordingManager.ucif(field);
|
|
7
|
+
return React.createElement(Text, null, label);
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { UCInput, UCOPIBase } from '../../uc/index.js';
|
|
3
|
+
import type { UCPanelCtx } from '../lib/react/panel.js';
|
|
4
|
+
export type UCFormSubmitOnPress = () => Promise<void>;
|
|
5
|
+
type Props<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = UCPanelCtx<I, OPI0, OPI1> & {
|
|
6
|
+
onPress?: UCFormSubmitOnPress;
|
|
7
|
+
};
|
|
8
|
+
export declare function UCFormSubmitControl<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ execState, disabled, onPress, uc }: Props<I, OPI0, OPI1>): ReactElement;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React, {} from 'react';
|
|
2
|
+
import { Pressable, Text } from 'react-native';
|
|
3
|
+
import { useDIContext } from '../lib/react/DIContextProvider.js';
|
|
4
|
+
export function UCFormSubmitControl({ execState, disabled, onPress, uc }) {
|
|
5
|
+
const { wordingManager } = useDIContext();
|
|
6
|
+
return (React.createElement(Pressable, { onPress: onPress, disabled: disabled },
|
|
7
|
+
React.createElement(Text, null, wordingManager.ucISubmit(uc.def, execState))));
|
|
8
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type ReactElement } from 'react';
|
|
2
|
-
import type { UCEntrypointTouchableProps } from '../../index.react.js';
|
|
3
2
|
import type { UCInput, UCOPIBase } from '../../uc/index.js';
|
|
3
|
+
import type { UCEntrypointTouchableProps } from '../lib/react/touchable.js';
|
|
4
4
|
export declare function UCEntrypointTouchable<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ path, wording }: UCEntrypointTouchableProps<I, OPI0, OPI1>): ReactElement;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type ReactElement } from 'react';
|
|
2
|
-
import { type UCExecTouchableProps } from '../../index.react.js';
|
|
3
2
|
import type { UCInput, UCOPIBase } from '../../uc/index.js';
|
|
3
|
+
import type { UCExecTouchableProps } from '../lib/react/touchable.js';
|
|
4
4
|
export declare function UCExecTouchable<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ disabled, execState, onSubmit, uc, }: UCExecTouchableProps<I, OPI0, OPI1>): ReactElement;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, {} from 'react';
|
|
2
|
-
import { useDIContext } from '
|
|
2
|
+
import { useDIContext } from '../lib/react/DIContextProvider.js';
|
|
3
3
|
export function UCExecTouchable({ disabled, execState, onSubmit, uc, }) {
|
|
4
4
|
const { wordingManager } = useDIContext();
|
|
5
5
|
const label = wordingManager.ucISubmit(uc.def, execState);
|
|
@@ -8,4 +8,4 @@ export type Props<T extends DataType> = UCPanelState & {
|
|
|
8
8
|
field: UCInputField<T>;
|
|
9
9
|
onChange: UCFormFieldControlOnChange<T>;
|
|
10
10
|
};
|
|
11
|
-
export declare function UCFormFieldControl<T extends DataType>({
|
|
11
|
+
export declare function UCFormFieldControl<T extends DataType>({ errMsg, execState, field, onChange: onChangeBase, }: Props<T>): ReactElement;
|
|
@@ -4,7 +4,7 @@ import { htmlInputDef } from '../lib/web/input.js';
|
|
|
4
4
|
const CHECKED_FIELD_TYPES = ['checkbox', 'radio'];
|
|
5
5
|
const FILE_FIELD_TYPES = ['file'];
|
|
6
6
|
const MULTIPLE_VALUES_SEPARATOR = ',';
|
|
7
|
-
export function UCFormFieldControl({
|
|
7
|
+
export function UCFormFieldControl({ errMsg = null, execState, field, onChange: onChangeBase, }) {
|
|
8
8
|
const attrs = htmlInputDef(field, execState, errMsg);
|
|
9
9
|
const onChange = (e) => {
|
|
10
10
|
const target = e.currentTarget;
|
|
@@ -28,7 +28,7 @@ export function UCFormFieldControl({ disabled, errMsg = null, execState, field,
|
|
|
28
28
|
}
|
|
29
29
|
};
|
|
30
30
|
if (attrs.internal?.multiline) {
|
|
31
|
-
return
|
|
31
|
+
return React.createElement("textarea", { ...attrs.spec, onChange: onChange });
|
|
32
32
|
}
|
|
33
|
-
return React.createElement("input", { ...attrs.spec,
|
|
33
|
+
return React.createElement("input", { ...attrs.spec, onChange: onChange });
|
|
34
34
|
}
|
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.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.
|
|
59
|
-
"@stricli/core": "^1.1.
|
|
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.
|
|
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.
|
|
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
|