libmodulor 0.13.1 → 0.14.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.14.0 (2025-05-24)
4
+
5
+ - Introduce `useAction` for react targets : it's a use case agnostic way of invoking an action
6
+
3
7
  ## v0.13.1 (2025-05-02)
4
8
 
5
9
  **Fixed**
package/README.md CHANGED
@@ -17,4 +17,4 @@ If you think you can help in any way, feel free to contact me (cf. `author` in `
17
17
 
18
18
  ## ⚖️ License
19
19
 
20
- [LGPL-3.0](https://github.com/c100k/libmodulor/blob/v0.13.1/LICENSE)
20
+ [LGPL-3.0](https://github.com/c100k/libmodulor/blob/v0.14.0/LICENSE)
@@ -42,18 +42,18 @@ export const PACKAGE_JSON = (name) => `{
42
42
  },
43
43
  "devDependencies": {
44
44
  "@biomejs/biome": "^1.9.4",
45
- "@types/node": "^22.15.2",
46
- "@vitest/coverage-v8": "^3.1.2",
45
+ "@types/node": "^22.15.19",
46
+ "@vitest/coverage-v8": "^3.1.3",
47
47
  "buffer": "^6.0.3",
48
48
  "cookie-parser": "^1.4.7",
49
49
  "express": "^5.1.0",
50
50
  "express-fileupload": "^1.5.1",
51
51
  "fast-check": "^4.1.1",
52
52
  "helmet": "^8.1.0",
53
- "jose": "^6.0.10",
53
+ "jose": "^6.0.11",
54
54
  "typescript": "^5.8.3",
55
- "vite": "^6.3.3",
56
- "vitest": "^3.1.2"
55
+ "vite": "^6.3.5",
56
+ "vitest": "^3.1.3"
57
57
  }
58
58
  }
59
59
  `;
@@ -6,5 +6,6 @@ export { UCContainer } from './target/lib/react/UCContainer.js';
6
6
  export { UCEntrypoint } from './target/lib/react/UCEntrypoint.js';
7
7
  export { type Props as UCOutputFieldValueFragmentProps, UCOutputFieldValueFragment, } from './target/lib/react/UCOutputFieldValueFragment.js';
8
8
  export { UCPanel } from './target/lib/react/UCPanel.js';
9
+ export { type UseActionOpts, useAction, } from './target/lib/react/useAction.js';
9
10
  export { type CloneFunc, type DivertFunc, type RefillFunc, useUC, } from './target/lib/react/useUC.js';
10
11
  export { type AppendFunc, type RemoveFunc, type UpdateFunc, useUCOR, } from './target/lib/react/useUCOR.js';
@@ -3,5 +3,6 @@ export { UCContainer } from './target/lib/react/UCContainer.js';
3
3
  export { UCEntrypoint } from './target/lib/react/UCEntrypoint.js';
4
4
  export { UCOutputFieldValueFragment, } from './target/lib/react/UCOutputFieldValueFragment.js';
5
5
  export { UCPanel } from './target/lib/react/UCPanel.js';
6
+ export { useAction, } from './target/lib/react/useAction.js';
6
7
  export { useUC, } from './target/lib/react/useUC.js';
7
8
  export { useUCOR, } from './target/lib/react/useUCOR.js';
@@ -1,27 +1,31 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useState } from 'react';
2
2
  import { ucIsDisabled, ucIsLoading, } from '../../../uc/index.js';
3
3
  import { sleep } from '../../../utils/index.js';
4
4
  import { useDIContext } from './DIContextProvider.js';
5
5
  import { UCContainer } from './UCContainer.js';
6
+ import { useAction } from './useAction.js';
6
7
  export function UCPanel({ autoExec = false, clearAfterExec = true, onDone, onError, onInit, onStartSubmitting, renderAutoExecLoader, renderForm, renderExecTouchable, sleepInMs, uc, }) {
7
8
  const { container } = useDIContext();
8
9
  const [ucManager] = useState(container.get('UCManager'));
9
- const [execState, setExecState] = useState(onInit ? 'initializing' : 'idle');
10
- useEffect(() => {
11
- if (!autoExec) {
12
- return;
13
- }
14
- onSubmit();
15
- }, [autoExec]);
16
- useEffect(() => {
17
- if (execState !== 'initializing') {
18
- return;
19
- }
20
- (async () => {
21
- await onInit?.(uc);
22
- setExecState('idle');
23
- })();
24
- }, [execState, onInit, uc]);
10
+ const { exec, execState } = useAction({
11
+ action: async () => {
12
+ const ucor = await ucManager.execClient(uc);
13
+ await onDone?.(ucor);
14
+ clear();
15
+ },
16
+ autoExec,
17
+ confirm: async () => await ucManager.confirmClient(uc),
18
+ onError,
19
+ onInit: async () => await onInit?.(uc),
20
+ onStart: async () => {
21
+ // Is some targets, the confirmClient blocks the main thread (e.g. window.confirm()).
22
+ // This leads to the state set above not being updated.
23
+ // This is a "hacky" workaroud to let React re-render the control with 'submitting' state before
24
+ await sleep(100);
25
+ await onStartSubmitting?.();
26
+ },
27
+ sleepInMs,
28
+ });
25
29
  const onChange = (f, op, v) => {
26
30
  f.setValue(op, v);
27
31
  };
@@ -31,38 +35,6 @@ export function UCPanel({ autoExec = false, clearAfterExec = true, onDone, onErr
31
35
  }
32
36
  uc.clear();
33
37
  };
34
- const onSubmit = async () => {
35
- setExecState('submitting');
36
- // Is some targets, the confirmClient blocks the main thread (e.g. window.confirm()).
37
- // This leads to the state set above not being updated.
38
- // This is a "hacky" workaroud to let React re-render the control with 'submitting' state before
39
- await sleep(100);
40
- await onStartSubmitting?.();
41
- const confirmed = await ucManager.confirmClient(uc);
42
- if (!confirmed) {
43
- setExecState('idle');
44
- return false;
45
- }
46
- if (sleepInMs !== undefined) {
47
- await sleep(sleepInMs);
48
- }
49
- try {
50
- const ucor = await ucManager.execClient(uc);
51
- await onDone?.(ucor);
52
- clear();
53
- return true;
54
- }
55
- catch (err) {
56
- if (!onError) {
57
- throw err;
58
- }
59
- onError(err);
60
- return false;
61
- }
62
- finally {
63
- setExecState('idle');
64
- }
65
- };
66
38
  const disabled = ucIsDisabled(execState);
67
39
  const loading = ucIsLoading(execState);
68
40
  // TODO : Keep these as a state to avoid recomputation
@@ -79,12 +51,12 @@ export function UCPanel({ autoExec = false, clearAfterExec = true, onDone, onErr
79
51
  !needsInputFilling &&
80
52
  renderExecTouchable({
81
53
  ...ctx,
82
- onSubmit,
54
+ onSubmit: exec,
83
55
  }),
84
56
  needsInputFilling &&
85
57
  renderForm({
86
58
  ...ctx,
87
59
  onChange,
88
- onSubmit,
60
+ onSubmit: exec,
89
61
  })))));
90
62
  }
@@ -1,9 +1,10 @@
1
1
  import type { UC, UCExecState, UCInput, UCOPIBase, UCOutputReader } from '../../../uc/index.js';
2
+ import type { UseActionExec, UseActionOnError } from './useAction.js';
2
3
  export type UCPanelOnDone<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = (ucor: UCOutputReader<I, OPI0, OPI1>) => Promise<void>;
3
- export type UCPanelOnError = (err: Error) => Promise<void>;
4
+ export type UCPanelOnError = UseActionOnError;
4
5
  export type UCPanelOnInit<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = (uc: UC<I, OPI0, OPI1>) => Promise<void>;
5
6
  export type UCPanelOnStartSubmitting = () => Promise<void>;
6
- export type UCPanelOnSubmit = () => Promise<boolean>;
7
+ export type UCPanelOnSubmit = UseActionExec;
7
8
  export interface UCPanelState {
8
9
  clearAfterExec?: boolean;
9
10
  disabled: boolean;
@@ -0,0 +1,24 @@
1
+ import type { ErrorMessage, UIntDuration } from '../../../dt/index.js';
2
+ import type { UCExecRes, UCExecState } from '../../../uc/exec.js';
3
+ export type UseActionAction = () => Promise<void>;
4
+ export type UseActionConfirm = () => Promise<boolean>;
5
+ export type UseActionExec = () => Promise<UCExecRes>;
6
+ export type UseActionOnError = (err: Error) => Promise<void>;
7
+ export type UseActionOnInit = () => Promise<void>;
8
+ export type UseActionOnStart = () => Promise<void>;
9
+ export interface UseActionOpts {
10
+ action: UseActionAction;
11
+ autoExec?: boolean;
12
+ confirm?: UseActionConfirm;
13
+ onError?: UseActionOnError | undefined;
14
+ onInit?: UseActionOnInit;
15
+ onStart?: UseActionOnStart;
16
+ sleepInMs?: UIntDuration | undefined;
17
+ }
18
+ export interface UseActionRes {
19
+ errMsg: ErrorMessage | null;
20
+ exec: UseActionExec;
21
+ execRes: UCExecRes | null;
22
+ execState: UCExecState;
23
+ }
24
+ export declare function useAction({ action, autoExec, confirm, onError, onInit, onStart, sleepInMs, }: UseActionOpts): UseActionRes;
@@ -0,0 +1,57 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { sleep } from '../../../utils/index.js';
3
+ export function useAction({ action, autoExec = false, confirm, onError, onInit, onStart, sleepInMs, }) {
4
+ const [errMsg, setErrMsg] = useState(null);
5
+ const [execRes, setExecRes] = useState(null);
6
+ const [execState, setExecState] = useState(onInit ? 'initializing' : 'idle');
7
+ useEffect(() => {
8
+ if (execState !== 'initializing') {
9
+ return;
10
+ }
11
+ (async () => {
12
+ await onInit?.();
13
+ setExecState('idle');
14
+ })();
15
+ }, [execState, onInit]);
16
+ const exec = async () => {
17
+ setErrMsg(null);
18
+ setExecRes(null);
19
+ setExecState('submitting');
20
+ await onStart?.();
21
+ const confirmed = confirm ? await confirm?.() : true;
22
+ if (!confirmed) {
23
+ setExecState('idle');
24
+ setExecRes('aborted');
25
+ return 'aborted';
26
+ }
27
+ if (sleepInMs !== undefined) {
28
+ await sleep(sleepInMs);
29
+ }
30
+ try {
31
+ await action();
32
+ setExecRes('succeeded');
33
+ return 'succeeded';
34
+ }
35
+ catch (err) {
36
+ setExecRes('failed');
37
+ if (onError) {
38
+ await onError?.(err);
39
+ }
40
+ else {
41
+ setErrMsg(err.message);
42
+ }
43
+ return 'failed';
44
+ }
45
+ finally {
46
+ setExecState('idle');
47
+ }
48
+ };
49
+ // biome-ignore lint/correctness/useExhaustiveDependencies(exec): it complains if I add it AND if I don't add it !
50
+ useEffect(() => {
51
+ if (!autoExec) {
52
+ return;
53
+ }
54
+ exec();
55
+ }, [autoExec]);
56
+ return { errMsg, exec, execRes, execState };
57
+ }
@@ -14,6 +14,14 @@ export declare enum UCExecMode {
14
14
  */
15
15
  USER = "user"
16
16
  }
17
+ /**
18
+ * Result of execution of a use case
19
+ *
20
+ * - `aborted` : The user aborted the exec (e.g. by not confirming)
21
+ * - `failed` : The exec failed
22
+ * - `succeeded` : The exec succeeded
23
+ */
24
+ export type UCExecRes = 'aborted' | 'failed' | 'succeeded';
17
25
  /**
18
26
  * State of execution of a use case
19
27
  *
@@ -44,3 +52,10 @@ export declare function ucIsDisabled(execState: UCExecState): boolean;
44
52
  * @returns
45
53
  */
46
54
  export declare function ucIsLoading(execState: UCExecState): boolean;
55
+ /**
56
+ * Check whether the execution corresponds to an "error" result
57
+ *
58
+ * @param execRes
59
+ * @returns
60
+ */
61
+ export declare function ucIsOnErr(execRes: UCExecRes | null): boolean;
@@ -38,3 +38,12 @@ export function ucIsDisabled(execState) {
38
38
  export function ucIsLoading(execState) {
39
39
  return execState === 'submitting';
40
40
  }
41
+ /**
42
+ * Check whether the execution corresponds to an "error" result
43
+ *
44
+ * @param execRes
45
+ * @returns
46
+ */
47
+ export function ucIsOnErr(execRes) {
48
+ return execRes === 'failed';
49
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "libmodulor",
3
3
  "description": "A TypeScript library to create platform-agnostic applications",
4
- "version": "0.13.1",
4
+ "version": "0.14.0",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "Chafik H'nini <chafik.hnini@gmail.com>",
7
7
  "homepage": "https://libmodulor.c100k.eu",
@@ -82,7 +82,7 @@
82
82
  },
83
83
  "peerDependencies": {
84
84
  "@hono/node-server": "^1.14.1",
85
- "@modelcontextprotocol/sdk": "^1.10.2",
85
+ "@modelcontextprotocol/sdk": "^1.11.4",
86
86
  "@stricli/core": "^1.1.2",
87
87
  "buffer": "^6.0.3",
88
88
  "cookie-parser": "^1.4.7",
@@ -90,20 +90,20 @@
90
90
  "express-fileupload": "^1.5.1",
91
91
  "fast-check": "^4.1.1",
92
92
  "helmet": "^8.1.0",
93
- "hono": "^4.7.7",
93
+ "hono": "^4.7.10",
94
94
  "inversify": "^7.5.1",
95
- "jose": "^6.0.10",
95
+ "jose": "^6.0.11",
96
96
  "knex": "^3.1.0",
97
- "next": "^15.3.1",
98
- "pg": "^8.15.6",
97
+ "next": "^15.3.2",
98
+ "pg": "^8.16.0",
99
99
  "react": "^19.1.0",
100
100
  "react-dom": "^19.1.0",
101
- "react-native": "^0.79.1",
101
+ "react-native": "^0.79.2",
102
102
  "reflect-metadata": "^0.2.2",
103
103
  "sqlite3": "^5.1.7",
104
104
  "typescript": "^5.8.3",
105
- "vite": "^6.3.3",
106
- "vitest": "^3.1.2"
105
+ "vite": "^6.3.5",
106
+ "vitest": "^3.1.3"
107
107
  },
108
108
  "peerDependenciesMeta": {
109
109
  "@hono/node-server": {