@codenotch/codenotch.react 2.0.0 → 2.0.1
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/dist/index.d.ts +2 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -116
- package/dist/models/Codenotch.d.ts +8 -129
- package/dist/models/Codenotch.d.ts.map +1 -1
- package/package.json +3 -4
- package/src/index.ts +4 -136
- package/src/models/Codenotch.ts +8 -146
- package/README.md +0 -183
- package/src/core/ProcessUtils.ts +0 -86
- package/src/core/SignalR.ts +0 -375
package/src/index.ts
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { createElement, useSyncExternalStore, type ComponentType, type ComponentProps, type FunctionComponent, type ReactNode, type RefAttributes } from "react";
|
|
2
2
|
import { flushSync } from "react-dom";
|
|
3
3
|
import { createRoot, Root } from "react-dom/client";
|
|
4
|
-
import { SignalR } from "./core/SignalR";
|
|
5
|
-
import ProcessUtils from "./core/ProcessUtils";
|
|
6
4
|
import { IDisposable } from "./models/Misc";
|
|
7
5
|
import { ICodenotchApi, ICodenotchDialog, ICodenotchEnv, IProcessResult, WithCodenotchProps } from "./models/Codenotch";
|
|
8
6
|
import { v4 } from "uuid";
|
|
9
7
|
import CodeEditor from "./components/CodeEditor";
|
|
10
8
|
|
|
11
|
-
var signalR: SignalR | undefined = undefined;
|
|
12
|
-
|
|
13
9
|
/**
|
|
14
10
|
* The Codenotch runtime environment, populated by {@link init}.
|
|
15
11
|
* Prefer reading it through `useCodenotch().env`.
|
|
@@ -51,7 +47,6 @@ function subscribe(listener: () => void): () => void {
|
|
|
51
47
|
* any other source). It must be called before `useCodenotch()` is used.
|
|
52
48
|
*
|
|
53
49
|
* Recognized key formats:
|
|
54
|
-
* - `i18n.<key>.<language>` — a translation, stored in `env.i18n[language][key]`.
|
|
55
50
|
* The first language found becomes the current language if none is set.
|
|
56
51
|
* - `GLOBAL.<name>` — JSON value assigned to `window[name]` (or `globalThis`).
|
|
57
52
|
* - `appManifest` / `projectManifest` — parsed as JSON into the environment.
|
|
@@ -66,9 +61,7 @@ function subscribe(listener: () => void): () => void {
|
|
|
66
61
|
* init({
|
|
67
62
|
* clusterUrl: 'https://cluster.example.com',
|
|
68
63
|
* serviceName: 'myproject',
|
|
69
|
-
* tenantName: 'acme'
|
|
70
|
-
* 'i18n.welcome.en': 'Welcome',
|
|
71
|
-
* 'i18n.welcome.fr': 'Bienvenue'
|
|
64
|
+
* tenantName: 'acme'
|
|
72
65
|
* });
|
|
73
66
|
*/
|
|
74
67
|
function init(envVariables: any): void {
|
|
@@ -77,8 +70,6 @@ function init(envVariables: any): void {
|
|
|
77
70
|
// Ensure envVariables is an object
|
|
78
71
|
if (typeof envVariables === 'object' && envVariables !== null) {
|
|
79
72
|
|
|
80
|
-
let i18nLanguages = new Set<string>();
|
|
81
|
-
|
|
82
73
|
Object.keys(envVariables).forEach((key) => {
|
|
83
74
|
try {
|
|
84
75
|
if (key.startsWith('GLOBAL.')) {
|
|
@@ -89,23 +80,6 @@ function init(envVariables: any): void {
|
|
|
89
80
|
console.log(`Codenotch global variable ${globalKey} set!`);
|
|
90
81
|
return;
|
|
91
82
|
}
|
|
92
|
-
else if (key.startsWith("i18n.")) {
|
|
93
|
-
// Special case
|
|
94
|
-
let parts = key.split("."); // i18n.someKey.fr
|
|
95
|
-
let language = parts[parts.length - 1]; // fr
|
|
96
|
-
let keyName = parts.slice(1, parts.length - 1).join("."); // someKey
|
|
97
|
-
i18nLanguages.add(language);
|
|
98
|
-
|
|
99
|
-
let value = `${envVariables[key]}`.trim();
|
|
100
|
-
if (value.startsWith('"') && value.endsWith('"')) {
|
|
101
|
-
// Parse like json
|
|
102
|
-
value = JSON.parse(value);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
env.i18n = env.i18n ?? {};
|
|
106
|
-
env.i18n[language] = env.i18n[language] ?? {};
|
|
107
|
-
env.i18n[language][keyName] = value;
|
|
108
|
-
}
|
|
109
83
|
else {
|
|
110
84
|
(env as any)[key] = envVariables[key];
|
|
111
85
|
|
|
@@ -126,20 +100,6 @@ function init(envVariables: any): void {
|
|
|
126
100
|
console.error(`Error processing Codenotch environment variable ${key}:`, error);
|
|
127
101
|
}
|
|
128
102
|
});
|
|
129
|
-
|
|
130
|
-
if (env.language === undefined) {
|
|
131
|
-
// If current language is not defined, we set it to the first language found in i18n
|
|
132
|
-
if (i18nLanguages.size > 0) {
|
|
133
|
-
env.language = Array.from(i18nLanguages)[0];
|
|
134
|
-
console.log(`Codenotch current language set to ${env.language}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Instantiate SignalR for further use in the API
|
|
141
|
-
if (env.clusterUrl && env.serviceName) {
|
|
142
|
-
signalR = new SignalR(env.clusterUrl, env.serviceName, false, env.accessToken);
|
|
143
103
|
}
|
|
144
104
|
|
|
145
105
|
// Get theme like AEC does
|
|
@@ -217,57 +177,8 @@ function createApi(): ICodenotchApi {
|
|
|
217
177
|
|
|
218
178
|
return await response.json();
|
|
219
179
|
},
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (env.i18n === undefined || Object.keys(env.i18n).length === 0) {
|
|
223
|
-
console.warn("Codenotch i18n is not defined. Please set it in the Codenotch configuration.");
|
|
224
|
-
return key;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
let language = env.language ?? "en";
|
|
228
|
-
let isLanguageExists = env.i18n[language] !== undefined;
|
|
229
|
-
if (isLanguageExists === false) {
|
|
230
|
-
let fallbackLanguage = Object.keys(env.i18n)[0];
|
|
231
|
-
console.warn(`Codenotch i18n language ${language} is not defined. Falling back to ${fallbackLanguage}. Please set the correct language in the Codenotch configuration.`);
|
|
232
|
-
language = fallbackLanguage;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
let dico = env.i18n[language];
|
|
236
|
-
let value = dico[key];
|
|
237
|
-
|
|
238
|
-
if (typeof value !== "string") {
|
|
239
|
-
console.warn(`Codenotch i18n key ${key} is not defined for language ${language}. Please set it in the Codenotch configuration.`);
|
|
240
|
-
return key;
|
|
241
|
-
}
|
|
242
|
-
args = args ?? [];
|
|
243
|
-
|
|
244
|
-
let result = value.replace(/{([\d ]*)}/g, (match, p1) => {
|
|
245
|
-
let argValue = args[p1.trim()];
|
|
246
|
-
// Instead of returning 'undefined', returns an empty string
|
|
247
|
-
return argValue !== undefined ? argValue : "";
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
return result;
|
|
251
|
-
}
|
|
252
|
-
catch (error) {
|
|
253
|
-
console.error("Error in i18n function:", error);
|
|
254
|
-
return key;
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
startProcess: async (processName: any, startNodeId: any, inputs: any): Promise<IProcessResult> => {
|
|
258
|
-
if (env.clusterUrl === undefined) throw new Error(MISSING_CLUSTER_URL_ERROR);
|
|
259
|
-
if (env.serviceName === undefined) throw new Error(MISSING_SERVICE_NAME_ERROR);
|
|
260
|
-
if (env.tenantName === undefined) throw new Error(MISSING_TENANT_NAME_ERROR);
|
|
261
|
-
if (signalR === undefined) throw new Error("SignalR not available. Cannot start process.");
|
|
262
|
-
return ProcessUtils.startProcess((processInstanceId, callbacks) => signalR!.subscribeToProcessInstance(processInstanceId, callbacks),
|
|
263
|
-
env.clusterUrl,
|
|
264
|
-
env.tenantName,
|
|
265
|
-
env.serviceName,
|
|
266
|
-
processName,
|
|
267
|
-
inputs,
|
|
268
|
-
undefined,
|
|
269
|
-
startNodeId ?? 'start',
|
|
270
|
-
env.accessToken);
|
|
180
|
+
startProcess: async <TProcess, TInput, TOutput>(process: TProcess, inputs: TInput): Promise<IProcessResult<TOutput>> => {
|
|
181
|
+
throw new Error('Not implemented.');
|
|
271
182
|
},
|
|
272
183
|
getUrlParams: () => {
|
|
273
184
|
let result: { [key: string]: string } = {};
|
|
@@ -345,21 +256,6 @@ function createApi(): ICodenotchApi {
|
|
|
345
256
|
|
|
346
257
|
return result;
|
|
347
258
|
},
|
|
348
|
-
listenSignal: async (signalId: string, callback: (data: any) => void): Promise<IDisposable> => {
|
|
349
|
-
|
|
350
|
-
if (!signalR) {
|
|
351
|
-
throw new Error("SignalR not available. Cannot listen to signal.");
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
let unsubscribeFunc = await signalR.subscribeToSignal(signalId, callback);
|
|
355
|
-
|
|
356
|
-
return {
|
|
357
|
-
dispose: () => {
|
|
358
|
-
unsubscribeFunc();
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
},
|
|
363
259
|
setTheme: (theme: 'light' | 'dark') => {
|
|
364
260
|
if (env.theme === theme) return;
|
|
365
261
|
if (env.theme) document.documentElement.classList.remove(env.theme);
|
|
@@ -427,7 +323,7 @@ function getCodenotch(): ICodenotchApi {
|
|
|
427
323
|
*
|
|
428
324
|
* The reference is stable across renders and only changes when the environment
|
|
429
325
|
* changes (`setLanguage`, `setTheme`, `init`), in which case every component
|
|
430
|
-
* using the hook re-renders
|
|
326
|
+
* using the hook re-renders output follows the current
|
|
431
327
|
* language automatically, and the object is safe to use in `useMemo` /
|
|
432
328
|
* `useEffect` dependency arrays.
|
|
433
329
|
*
|
|
@@ -435,15 +331,6 @@ function getCodenotch(): ICodenotchApi {
|
|
|
435
331
|
* component or a custom hook). Outside of components — handlers defined in
|
|
436
332
|
* plain modules, services, class components — use {@link getCodenotch} or
|
|
437
333
|
* {@link withCodenotch} instead.
|
|
438
|
-
*
|
|
439
|
-
* @returns The Codenotch API: BPMN processes, CNQL queries, i18n, signals, theme…
|
|
440
|
-
* @example
|
|
441
|
-
* import { useCodenotch } from 'codenotch-react';
|
|
442
|
-
*
|
|
443
|
-
* const MyApp = () => {
|
|
444
|
-
* const cn = useCodenotch();
|
|
445
|
-
* return <h1>{cn.i18n('welcome')}</h1>;
|
|
446
|
-
* };
|
|
447
334
|
*/
|
|
448
335
|
function useCodenotch(): ICodenotchApi {
|
|
449
336
|
return useSyncExternalStore(subscribe, getCodenotch, getCodenotch);
|
|
@@ -485,21 +372,6 @@ type ComponentInstance<C> = C extends new (...args: any[]) => infer I ? I : neve
|
|
|
485
372
|
*
|
|
486
373
|
* @param Component A component whose props extend {@link WithCodenotchProps}.
|
|
487
374
|
* @returns A component with the same props minus `cn`.
|
|
488
|
-
* @example
|
|
489
|
-
* import { withCodenotch, WithCodenotchProps } from 'codenotch-react';
|
|
490
|
-
*
|
|
491
|
-
* interface Props extends WithCodenotchProps {
|
|
492
|
-
* userId: string;
|
|
493
|
-
* }
|
|
494
|
-
*
|
|
495
|
-
* class TodoList extends React.Component<Props> {
|
|
496
|
-
* render() {
|
|
497
|
-
* return <h1>{this.props.cn.i18n('todos.title')}</h1>;
|
|
498
|
-
* }
|
|
499
|
-
* }
|
|
500
|
-
*
|
|
501
|
-
* export default withCodenotch(TodoList);
|
|
502
|
-
* // <TodoList userId="42" /> — `cn` is injected
|
|
503
375
|
*/
|
|
504
376
|
function withCodenotch<C extends ComponentType<any>>(
|
|
505
377
|
Component: C
|
|
@@ -523,10 +395,6 @@ export {
|
|
|
523
395
|
CodeEditor
|
|
524
396
|
};
|
|
525
397
|
|
|
526
|
-
// Public models. Re-exported from the package root so that the typings
|
|
527
|
-
// generated by the Codenotch IDE (typings/i18n.d.ts and typings/process.d.ts,
|
|
528
|
-
// which augment the 'codenotch-react' module) merge with the real
|
|
529
|
-
// TranslationRegistry / ProcessRegistry declarations.
|
|
530
398
|
export * from "./models/Codenotch";
|
|
531
399
|
export * from "./models/Misc";
|
|
532
400
|
export * from "@codenotch/codenotch.core";
|
package/src/models/Codenotch.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { IAppManifest, ICodenotchProjectManifest } from "@codenotch/codenotch.core";
|
|
3
3
|
import { ICodenotchSignal, IDisposable } from "./Misc";
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -21,9 +21,7 @@ export interface ICodenotchEnv {
|
|
|
21
21
|
accessToken?: string;
|
|
22
22
|
/** Base URL of the current application. */
|
|
23
23
|
baseUrl?: string;
|
|
24
|
-
/**
|
|
25
|
-
i18n?: { [language: string]: { [key: string]: string } };
|
|
26
|
-
/** Current language code (e.g. `"en"`). Defaults to the first language found in {@link i18n}. */
|
|
24
|
+
/** Current language code (e.g. `"en"`). */
|
|
27
25
|
language?: string;
|
|
28
26
|
/** Current UI theme. Resolved from the `_theme` URL parameter, or `prefers-color-scheme` as a fallback. */
|
|
29
27
|
theme?: 'light' | 'dark';
|
|
@@ -31,94 +29,12 @@ export interface ICodenotchEnv {
|
|
|
31
29
|
/** Manifest of the current application (the `<AppName>.manifest.json` file next to the app). */
|
|
32
30
|
appManifest?: IAppManifest;
|
|
33
31
|
/** Manifest of the Codenotch project (the project's `manifest.json` file). */
|
|
34
|
-
projectManifest?:
|
|
32
|
+
projectManifest?: ICodenotchProjectManifest;
|
|
35
33
|
|
|
36
34
|
/** Any additional environment variable passed to `init()`. */
|
|
37
35
|
[key: string]: any; // Allow additional properties
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Registry of the project's BPMN processes, used to type {@link ICodenotchApi.startProcess}.
|
|
43
|
-
*
|
|
44
|
-
* Empty by default: the Codenotch IDE fills it through declaration merging in the
|
|
45
|
-
* auto-generated `typings/process.d.ts` file of each project, with one entry per
|
|
46
|
-
* BPMN process. Each entry maps the process name to its start events (`nodes`,
|
|
47
|
-
* keyed by start node id, valued with the start event's input parameters) and to
|
|
48
|
-
* the `output` of its end event.
|
|
49
|
-
*
|
|
50
|
-
* When the registry has not been augmented (project built without the IDE
|
|
51
|
-
* typings), `startProcess` falls back to plain string names and untyped inputs,
|
|
52
|
-
* so the code still compiles.
|
|
53
|
-
*
|
|
54
|
-
* @example
|
|
55
|
-
* // typings/process.d.ts (auto-generated by the Codenotch IDE)
|
|
56
|
-
* import 'codenotch-react';
|
|
57
|
-
* declare module 'codenotch-react' {
|
|
58
|
-
* interface ProcessRegistry {
|
|
59
|
-
* 'getTodos': {
|
|
60
|
-
* nodes: { 'start': { UserId: string } };
|
|
61
|
-
* output: { todos: any[] };
|
|
62
|
-
* };
|
|
63
|
-
* }
|
|
64
|
-
* }
|
|
65
|
-
*/
|
|
66
|
-
export interface ProcessRegistry {
|
|
67
|
-
// Extended by codenotch IDE
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Registry of the project's translation keys, used to type {@link ICodenotchApi.i18n}.
|
|
72
|
-
*
|
|
73
|
-
* Empty by default: the Codenotch IDE fills it through declaration merging in the
|
|
74
|
-
* auto-generated `typings/i18n.d.ts` file of each project, from the project's
|
|
75
|
-
* `.i18n.csv` files. Each entry maps a translation key to the tuple of arguments
|
|
76
|
-
* expected by its `{0}`, `{1}`, … placeholders.
|
|
77
|
-
*
|
|
78
|
-
* When the registry has not been augmented (project built without the IDE
|
|
79
|
-
* typings), `i18n` falls back to plain string keys and untyped arguments,
|
|
80
|
-
* so the code still compiles.
|
|
81
|
-
*
|
|
82
|
-
* @example
|
|
83
|
-
* // typings/i18n.d.ts (auto-generated by the Codenotch IDE)
|
|
84
|
-
* import 'codenotch-react';
|
|
85
|
-
* declare module 'codenotch-react' {
|
|
86
|
-
* interface TranslationRegistry {
|
|
87
|
-
* 'welcome': [];
|
|
88
|
-
* 'greeting': [arg1: any, arg2: any];
|
|
89
|
-
* }
|
|
90
|
-
* }
|
|
91
|
-
*/
|
|
92
|
-
export interface TranslationRegistry {
|
|
93
|
-
// Extended by codenotch IDE
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// When the codenotch IDE has not augmented the registries, `keyof Registry` is `never`
|
|
97
|
-
// and every call would fail to compile. These helpers fall back to plain string keys
|
|
98
|
-
// and untyped arguments so the API stays usable without code generation.
|
|
99
|
-
|
|
100
|
-
/** A BPMN process name: a key of {@link ProcessRegistry}, or any string when the registry is empty. */
|
|
101
|
-
export type ProcessName = keyof ProcessRegistry extends never ? string : keyof ProcessRegistry;
|
|
102
|
-
|
|
103
|
-
/** The start nodes of process `P`: its `nodes` map from {@link ProcessRegistry}, or an open map when unknown. */
|
|
104
|
-
export type ProcessNodes<P> = P extends keyof ProcessRegistry
|
|
105
|
-
? ProcessRegistry[P] extends { nodes: infer N } ? N : { [nodeId: string]: any }
|
|
106
|
-
: { [nodeId: string]: any };
|
|
107
|
-
|
|
108
|
-
/** The end-event output of process `P` from {@link ProcessRegistry}, or `any` when unknown. */
|
|
109
|
-
export type ProcessOutput<P> = P extends keyof ProcessRegistry
|
|
110
|
-
? ProcessRegistry[P] extends { output: infer O } ? O : any
|
|
111
|
-
: any;
|
|
112
|
-
|
|
113
|
-
/** A translation key: a key of {@link TranslationRegistry}, or any string when the registry is empty. */
|
|
114
|
-
export type TranslationKey = keyof TranslationRegistry extends never ? string : keyof TranslationRegistry;
|
|
115
|
-
|
|
116
|
-
/** The placeholder arguments of translation key `K` from {@link TranslationRegistry}, or `any[]` when unknown. */
|
|
117
|
-
export type TranslationArgs<K> = K extends keyof TranslationRegistry
|
|
118
|
-
? TranslationRegistry[K] extends any[] ? TranslationRegistry[K] : any[]
|
|
119
|
-
: any[];
|
|
120
|
-
|
|
121
|
-
|
|
122
38
|
/**
|
|
123
39
|
* Result of a BPMN process execution started with {@link ICodenotchApi.startProcess}.
|
|
124
40
|
*/
|
|
@@ -142,56 +58,17 @@ export interface IProcessResult<T = any> {
|
|
|
142
58
|
* plain `getCodenotch()` function anywhere else (handlers, modules, services).
|
|
143
59
|
*
|
|
144
60
|
* It is the bridge between a React app and the Codenotch runtime: it starts
|
|
145
|
-
* server-side BPMN processes, runs CNQL queries,
|
|
61
|
+
* server-side BPMN processes, runs CNQL queries,
|
|
146
62
|
* listens to real-time signals and manages theme/language.
|
|
147
|
-
*
|
|
148
|
-
* @example
|
|
149
|
-
* import { useCodenotch } from 'codenotch-react';
|
|
150
|
-
*
|
|
151
|
-
* const cn = useCodenotch();
|
|
152
|
-
* const title = cn.i18n('welcome');
|
|
153
|
-
* const result = await cn.startProcess('getTodos', 'start', { UserId: '42' });
|
|
154
63
|
*/
|
|
155
64
|
export interface ICodenotchApi {
|
|
156
65
|
|
|
157
66
|
/**
|
|
158
67
|
* Start a server-side BPMN process and wait for its completion.
|
|
159
|
-
*
|
|
160
|
-
* @param processName Name of the process — the `.bpmn` file name without extension (e.g. `processes/getTodos.bpmn` → `'getTodos'`).
|
|
161
|
-
* @param startNodeId Id of the start event to trigger (conventionally `'start'`).
|
|
162
|
-
* @param inputs Input parameters declared by that start event.
|
|
163
|
-
* @returns The process result; check `isError` before using `output` (the parameters of the end event reached).
|
|
164
|
-
* @example
|
|
165
|
-
* const result = await cn.startProcess('getTodos', 'start', { UserId: userId });
|
|
166
|
-
* if (!result.isError) {
|
|
167
|
-
* setTodos(result.output.todos);
|
|
168
|
-
* }
|
|
169
|
-
*/
|
|
170
|
-
startProcess<P extends ProcessName, N extends keyof ProcessNodes<P>>(
|
|
171
|
-
processName: P,
|
|
172
|
-
startNodeId: N,
|
|
173
|
-
inputs: ProcessNodes<P>[N]
|
|
174
|
-
): Promise<IProcessResult<ProcessOutput<P>>>;
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Translate a key for the current language ({@link ICodenotchEnv.language}).
|
|
178
|
-
*
|
|
179
|
-
* Placeholders `{0}`, `{1}`, … in the translated text are replaced by `args`.
|
|
180
|
-
* Never throws: if the key or the language is missing, a warning is logged
|
|
181
|
-
* and the key itself is returned.
|
|
182
|
-
*
|
|
183
|
-
* @param key Translation key, as defined in the project's `.i18n.csv` files.
|
|
184
|
-
* @param args Values for the `{0}`, `{1}`, … placeholders of the translation.
|
|
185
|
-
* @example
|
|
186
|
-
* cn.i18n('welcome'); // "Bienvenue"
|
|
187
|
-
* cn.i18n('greeting', 'Fabian', 3); // "Bonjour Fabian, 3 messages" (from "Bonjour {0}, {1} messages")
|
|
188
68
|
*/
|
|
189
|
-
|
|
190
|
-
key: K,
|
|
191
|
-
...args: TranslationArgs<K>
|
|
192
|
-
): string;
|
|
69
|
+
startProcess<TProcess, TInput, TOutput>(process: TProcess, inputs: TInput): Promise<IProcessResult<TOutput>>;
|
|
193
70
|
|
|
194
|
-
/** Runtime environment (cluster, service, language, theme
|
|
71
|
+
/** Runtime environment (cluster, service, language, theme dictionaries, manifests…). Populated by `init()`. */
|
|
195
72
|
readonly env: ICodenotchEnv;
|
|
196
73
|
|
|
197
74
|
/**
|
|
@@ -243,26 +120,11 @@ export interface ICodenotchApi {
|
|
|
243
120
|
*/
|
|
244
121
|
readonly getProjectFileUrl: (relativePath: string) => string;
|
|
245
122
|
|
|
246
|
-
/**
|
|
247
|
-
* Subscribe to a real-time signal emitted by the project's BPMN processes (via SignalR).
|
|
248
|
-
*
|
|
249
|
-
* @param signalId Id of the signal, as declared in the BPMN process.
|
|
250
|
-
* @param callback Called each time the signal is received, with `{ signalId, data }`.
|
|
251
|
-
* @returns A disposable — call `dispose()` to unsubscribe (e.g. in a `useEffect` cleanup).
|
|
252
|
-
* @example
|
|
253
|
-
* useEffect(() => {
|
|
254
|
-
* let sub: IDisposable | undefined;
|
|
255
|
-
* cn.listenSignal('todosChanged', () => refresh()).then(s => sub = s);
|
|
256
|
-
* return () => sub?.dispose();
|
|
257
|
-
* }, []);
|
|
258
|
-
*/
|
|
259
|
-
readonly listenSignal: (signalId: string, callback: (data: ICodenotchSignal) => void) => Promise<IDisposable>;
|
|
260
|
-
|
|
261
123
|
/** Set the UI theme. Also toggles the `light`/`dark` class on `<html>` (Tailwind `dark:` variants). */
|
|
262
124
|
readonly setTheme: (theme: 'light' | 'dark') => void;
|
|
263
125
|
/** Current UI theme, if resolved. */
|
|
264
126
|
readonly getTheme: () => 'light' | 'dark' | undefined;
|
|
265
|
-
/** Set the current language
|
|
127
|
+
/** Set the current language. Must be one of the project's languages. */
|
|
266
128
|
readonly setLanguage: (lang: string) => void;
|
|
267
129
|
/** Current language code, if any. */
|
|
268
130
|
readonly getLanguage: () => string | undefined;
|
|
@@ -273,7 +135,7 @@ export interface ICodenotchApi {
|
|
|
273
135
|
/** Generate a random UUID v4. */
|
|
274
136
|
readonly uuid: () => string;
|
|
275
137
|
/** Manifest of the Codenotch project. Throws if not available in the environment. */
|
|
276
|
-
readonly getProjectManifest: () =>
|
|
138
|
+
readonly getProjectManifest: () => ICodenotchProjectManifest;
|
|
277
139
|
/** Manifest of the current application, if any. */
|
|
278
140
|
readonly getAppManifest: () => IAppManifest | undefined;
|
|
279
141
|
}
|
package/README.md
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# codenotch-react
|
|
2
|
-
|
|
3
|
-
React bindings for [Codenotch](https://codenotch.com) applications.
|
|
4
|
-
|
|
5
|
-
Codenotch is a full-stack development platform: a project combines server-side **BPMN processes**, **SQL tables** / **NoSQL documents**, **i18n** translations and **React** apps. This package is the client-side bridge between those React apps and the Codenotch runtime: it lets a component start BPMN processes, run CNQL queries, translate i18n keys, listen to real-time signals, and manage theme/language.
|
|
6
|
-
|
|
7
|
-
> **Requirements** — Codenotch apps run on **React 19** (`react@^19`, `react-dom@^19`). The package uses the automatic JSX runtime and `createRoot`; all modern React APIs (hooks, `useId`, `use`, Actions…) are available.
|
|
8
|
-
|
|
9
|
-
## Quick start
|
|
10
|
-
|
|
11
|
-
```tsx
|
|
12
|
-
import { useCodenotch } from 'codenotch-react';
|
|
13
|
-
|
|
14
|
-
const MyApp = () => {
|
|
15
|
-
const cn = useCodenotch();
|
|
16
|
-
|
|
17
|
-
return <div className="p-4">
|
|
18
|
-
<h1 className="text-xl font-bold">{cn.i18n('welcome')}</h1>
|
|
19
|
-
</div>;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export default MyApp;
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
`useCodenotch()` is a real React hook: the returned object is stable across renders and is replaced — re-rendering the component — whenever the environment changes (`setLanguage`, `setTheme`, `init`). Regular hook rules apply. The same API is available everywhere else through two non-hook entry points:
|
|
26
|
-
|
|
27
|
-
| Where | Use |
|
|
28
|
-
|-------|-----|
|
|
29
|
-
| Function component / custom hook | `const cn = useCodenotch();` — re-renders on language/theme change. |
|
|
30
|
-
| Class component | `export default withCodenotch(MyClass);` — injects `this.props.cn`, re-renders on change, forwards `ref`. |
|
|
31
|
-
| Event handler, plain module, service, anywhere | `getCodenotch()` — plain function, never stale, no re-render. |
|
|
32
|
-
| Manual subscription (rare) | `onCodenotchChange(listener)` → `IDisposable`. |
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
import React from 'react';
|
|
36
|
-
import { withCodenotch, WithCodenotchProps } from 'codenotch-react';
|
|
37
|
-
|
|
38
|
-
interface Props extends WithCodenotchProps {
|
|
39
|
-
userId: string;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
class TodoList extends React.Component<Props> {
|
|
43
|
-
render() {
|
|
44
|
-
return <h1>{this.props.cn.i18n('todos.title')}</h1>;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export default withCodenotch(TodoList); // <TodoList userId="42" /> — `cn` is injected
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
```ts
|
|
52
|
-
// services/todos.ts — outside React
|
|
53
|
-
import { getCodenotch } from 'codenotch-react';
|
|
54
|
-
|
|
55
|
-
export async function loadTodos(userId: string) {
|
|
56
|
-
const result = await getCodenotch().startProcess('getTodos', 'start', { UserId: userId });
|
|
57
|
-
return result.output.todos;
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The environment (cluster URL, service name, language, translations…) is set up by `init(envVariables)`, which the Codenotch runtime calls automatically from the page hosting the application. You only call `init` yourself in custom hosting scenarios.
|
|
62
|
-
|
|
63
|
-
## API overview
|
|
64
|
-
|
|
65
|
-
| Member | Description |
|
|
66
|
-
|--------|-------------|
|
|
67
|
-
| `cn.env` | Readonly environment: `clusterUrl`, `serviceName`, `tenantName`, `accessToken`, `language`, `theme`, `i18n`, `appManifest`, `projectManifest`. |
|
|
68
|
-
| `cn.i18n(key, ...args)` | Translate a key for the current language, filling `{0}`, `{1}`, … placeholders. |
|
|
69
|
-
| `cn.startProcess(name, startNodeId, inputs)` | Start a server-side BPMN process and await its result. |
|
|
70
|
-
| `cn.requestCnql(cnql, verbose?)` | Run a CNQL query (XML, SELECT-only) against the project's tables. |
|
|
71
|
-
| `cn.listenSignal(signalId, callback)` | Subscribe to real-time signals emitted by BPMN processes. |
|
|
72
|
-
| `cn.showDialog(node)` | Render a React node in a fullscreen modal dialog (own React root, unmounted on `close()`). |
|
|
73
|
-
| `cn.setTheme(t)` / `cn.getTheme()` | `'light' \| 'dark'`; `setTheme` also toggles the class on `<html>` (Tailwind `dark:`). |
|
|
74
|
-
| `cn.setLanguage(l)` / `cn.getLanguage()` / `cn.getLanguages()` | Current language and the languages declared in `manifest.json`. |
|
|
75
|
-
| `cn.getProjectFile(path)` / `cn.getProjectFileUrl(path)` | Read/link a file of the deployed project. |
|
|
76
|
-
| `cn.getUrlParams()` | Query-string parameters of the current URL as a plain object. |
|
|
77
|
-
| `cn.uuid()` | Random UUID v4. |
|
|
78
|
-
| `cn.getProjectManifest()` / `cn.getAppManifest()` | Project / application manifests. |
|
|
79
|
-
|
|
80
|
-
Full signatures and JSDoc live in `dist/index.d.ts` (sources in `src/`).
|
|
81
|
-
|
|
82
|
-
## Calling a BPMN process
|
|
83
|
-
|
|
84
|
-
`processName` is the `.bpmn` file name without extension; `startNodeId` is the id of the start event to trigger (conventionally `'start'`); `inputs` are the input parameters declared by that start event. The result's `output` contains the parameters of the end event reached.
|
|
85
|
-
|
|
86
|
-
```tsx
|
|
87
|
-
const result = await cn.startProcess('getTodos', 'start', { UserId: userId });
|
|
88
|
-
|
|
89
|
-
if (!result.isError) {
|
|
90
|
-
setTodos(result.output.todos);
|
|
91
|
-
} else {
|
|
92
|
-
console.error(result.errorMessage);
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Querying tables with CNQL
|
|
97
|
-
|
|
98
|
-
CNQL is Codenotch's XML query language over the project's SQL tables (SELECT only — writes go through BPMN processes). The root `xmlns` must be the project's `serviceName`; each queried table's `Ref` attribute names its result set:
|
|
99
|
-
|
|
100
|
-
```tsx
|
|
101
|
-
const data = await cn.requestCnql(`
|
|
102
|
-
<CNQL xmlns="myproject" PageSize="10" PageIndex="0">
|
|
103
|
-
<Users Ref="results">
|
|
104
|
-
<Id />
|
|
105
|
-
<Email />
|
|
106
|
-
<IsAdmin Equal="true" />
|
|
107
|
-
</Users>
|
|
108
|
-
</CNQL>`);
|
|
109
|
-
|
|
110
|
-
console.log(data.results); // [{ Id: '...', Email: '...' }, ...]
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## Translations (i18n)
|
|
114
|
-
|
|
115
|
-
Translations come from the project's `.i18n.csv` files and are injected into `cn.env.i18n` at startup. `cn.i18n(key, ...args)` translates for the current language and replaces `{0}`, `{1}`, … placeholders; it never throws — an unknown key is returned as-is (with a console warning).
|
|
116
|
-
|
|
117
|
-
```tsx
|
|
118
|
-
cn.i18n('welcome'); // "Bienvenue"
|
|
119
|
-
cn.i18n('greeting', 'Ada', 3); // "Bonjour Ada, 3 messages" (from "Bonjour {0}, {1} messages")
|
|
120
|
-
cn.setLanguage('en'); // switch language at runtime — every component using
|
|
121
|
-
// useCodenotch() / withCodenotch() re-renders
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
## Real-time signals
|
|
125
|
-
|
|
126
|
-
BPMN processes can broadcast signals; subscribe from the UI with `listenSignal`:
|
|
127
|
-
|
|
128
|
-
```tsx
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
let sub: IDisposable | undefined;
|
|
131
|
-
let cancelled = false;
|
|
132
|
-
getCodenotch().listenSignal('todosChanged', (signal) => refresh(signal.data))
|
|
133
|
-
.then(s => { if (cancelled) s.dispose(); else sub = s; });
|
|
134
|
-
return () => { cancelled = true; sub?.dispose(); };
|
|
135
|
-
}, []);
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Two details: the `cancelled` flag matters under React 19's StrictMode, which mounts / unmounts / remounts effects in development — without it the first subscription would leak when the cleanup runs before the promise resolves. And the effect uses `getCodenotch()` rather than the `cn` from `useCodenotch()`: the hook's object changes on every language/theme change, so listing it in the dependency array would re-subscribe on each change, while omitting it trips `react-hooks/exhaustive-deps`. `getCodenotch()` sidesteps both.
|
|
139
|
-
|
|
140
|
-
## Dialogs
|
|
141
|
-
|
|
142
|
-
```tsx
|
|
143
|
-
const dialog = cn.showDialog(
|
|
144
|
-
<div className="bg-white dark:bg-gray-800 p-6 rounded shadow">
|
|
145
|
-
<p>{cn.i18n('confirm.message')}</p>
|
|
146
|
-
<button onClick={() => dialog.close()}>{cn.i18n('close')}</button>
|
|
147
|
-
</div>
|
|
148
|
-
);
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
The dialog stays open until you call `dialog.close()`, which unmounts the React tree and removes the `<dialog>` element. The content is rendered in its own React root, so it does not share context (providers) with the calling component.
|
|
152
|
-
|
|
153
|
-
## Type-safe processes and translation keys
|
|
154
|
-
|
|
155
|
-
`startProcess` and `i18n` are typed through two registries, `ProcessRegistry` and `TranslationRegistry`, that the **Codenotch IDE fills by declaration merging**: it generates `typings/process.d.ts` and `typings/i18n.d.ts` in each project from the `.bpmn` and `.i18n.csv` files (never edit those files — refresh the project instead). The generated files look like:
|
|
156
|
-
|
|
157
|
-
```ts
|
|
158
|
-
import 'codenotch-react';
|
|
159
|
-
|
|
160
|
-
declare module 'codenotch-react' {
|
|
161
|
-
interface TranslationRegistry {
|
|
162
|
-
'welcome': [];
|
|
163
|
-
'greeting': [arg1: any, arg2: any];
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
interface ProcessRegistry {
|
|
167
|
-
'getTodos': {
|
|
168
|
-
nodes: { 'start': { UserId: string } };
|
|
169
|
-
output: { todos: any[] };
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
With these in the compilation, keys, inputs and outputs are strictly checked and autocompleted. **Without them** (project compiled outside the IDE), the registries are empty and both methods gracefully fall back to plain `string` keys and untyped arguments — the code still compiles.
|
|
176
|
-
|
|
177
|
-
## Notes for AI assistants
|
|
178
|
-
|
|
179
|
-
- The complete typed API surface is in `dist/index.d.ts`; the readable implementation ships in `src/` (entry point `src/index.ts`).
|
|
180
|
-
- `useCodenotch()` is a real hook (hook rules apply; re-renders on language/theme change). In class components use `withCodenotch(Component)` and read `this.props.cn`; in event handlers, services, plain modules or effects use `getCodenotch()`. Never call `useCodenotch()` outside a function component / custom hook.
|
|
181
|
-
- The object returned by `useCodenotch()` changes when the environment changes: fine in `useMemo` deps, but in `useEffect` prefer `getCodenotch()` unless the effect should re-run on language/theme change.
|
|
182
|
-
- Never hand-edit a project's `typings/i18n.d.ts` / `typings/process.d.ts`: they are regenerated by the Codenotch IDE.
|
|
183
|
-
- Codenotch apps are React 19 + Tailwind CSS (automatic JSX runtime — no `import React` needed for JSX); CNQL is SELECT-only (writes go through BPMN processes via `startProcess`).
|