libmodulor 0.21.0 → 0.23.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.
Files changed (86) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -1
  3. package/dist/esm/apps/Helper/src/lib/project.js +7 -7
  4. package/dist/esm/apps/Helper/src/ucds/CreateProjectUCD.d.ts +1 -0
  5. package/dist/esm/apps/Helper/src/ucds/CreateProjectUCD.js +20 -13
  6. package/dist/esm/dt/DataType.d.ts +2 -1
  7. package/dist/esm/dt/DataTypes.js +1 -0
  8. package/dist/esm/dt/final/TTransportType.d.ts +8 -0
  9. package/dist/esm/dt/final/TTransportType.js +16 -0
  10. package/dist/esm/dt/index.d.ts +1 -0
  11. package/dist/esm/dt/index.js +1 -0
  12. package/dist/esm/error/funcs.d.ts +2 -0
  13. package/dist/esm/error/funcs.js +19 -0
  14. package/dist/esm/error/index.d.ts +1 -0
  15. package/dist/esm/error/index.js +1 -0
  16. package/dist/esm/index.d.ts +2 -0
  17. package/dist/esm/index.js +2 -0
  18. package/dist/esm/index.react.d.ts +1 -1
  19. package/dist/esm/index.react.js +1 -1
  20. package/dist/esm/std/HTTPAPICallExecutor.d.ts +16 -7
  21. package/dist/esm/std/HTTPAPICaller.d.ts +8 -1
  22. package/dist/esm/std/JWTManager.d.ts +16 -1
  23. package/dist/esm/std/LLMManager.d.ts +20 -3
  24. package/dist/esm/std/impl/FakeClockManager.d.ts +6 -0
  25. package/dist/esm/std/impl/FakeClockManager.js +19 -0
  26. package/dist/esm/std/impl/FakeHTTPAPICallExecutor.js +19 -18
  27. package/dist/esm/std/impl/FakeLLMManager.d.ts +4 -0
  28. package/dist/esm/std/impl/FakeLLMManager.js +40 -0
  29. package/dist/esm/std/impl/JoseJWTManager.d.ts +3 -2
  30. package/dist/esm/std/impl/JoseJWTManager.js +4 -0
  31. package/dist/esm/std/impl/MistralAILLMManager.js +12 -0
  32. package/dist/esm/std/impl/OllamaLLMManager.d.ts +7 -2
  33. package/dist/esm/std/impl/OllamaLLMManager.js +32 -5
  34. package/dist/esm/std/impl/OpenAILLMManager.js +9 -0
  35. package/dist/esm/std/impl/SimpleHTTPAPICaller.d.ts +7 -3
  36. package/dist/esm/std/impl/SimpleHTTPAPICaller.js +68 -15
  37. package/dist/esm/target/lib/cli/CommandExecutor.js +9 -1
  38. package/dist/esm/target/lib/client/consts.js +1 -0
  39. package/dist/esm/target/lib/react/UCPanel.d.ts +11 -10
  40. package/dist/esm/target/lib/react/UCPanel.js +2 -2
  41. package/dist/esm/target/lib/react/useUC.js +11 -1
  42. package/dist/esm/target/lib/server/ServerManager.d.ts +1 -0
  43. package/dist/esm/target/lib/server/ServerRequestHandler.d.ts +3 -2
  44. package/dist/esm/target/lib/server/ServerRequestHandler.js +2 -2
  45. package/dist/esm/target/lib/server/consts.js +1 -0
  46. package/dist/esm/target/lib/server-express/funcs.js +53 -1
  47. package/dist/esm/target/lib/server-hono/funcs.js +65 -2
  48. package/dist/esm/target/lib/server-node/funcs.d.ts +2 -2
  49. package/dist/esm/target/lib/server-node/funcs.js +11 -1
  50. package/dist/esm/target/lib/server-node/types.d.ts +1 -0
  51. package/dist/esm/target/node-express-server/NodeExpressServerManager.d.ts +2 -2
  52. package/dist/esm/target/node-express-server/NodeExpressServerManager.js +2 -1
  53. package/dist/esm/target/node-hono-server/NodeHonoServerManager.d.ts +2 -2
  54. package/dist/esm/target/node-hono-server/NodeHonoServerManager.js +2 -1
  55. package/dist/esm/testing/impl/newNodeAppTester.js +3 -2
  56. package/dist/esm/testing/workers/UCExecutor.js +39 -1
  57. package/dist/esm/uc/ext.d.ts +7 -1
  58. package/dist/esm/uc/impl/HTTPUCTransporter.d.ts +2 -2
  59. package/dist/esm/uc/impl/HTTPUCTransporter.js +3 -1
  60. package/dist/esm/uc/impl/SimpleUCManager.d.ts +3 -3
  61. package/dist/esm/uc/impl/SimpleUCManager.js +23 -4
  62. package/dist/esm/uc/lifecycle/client/SendClientMain.d.ts +1 -1
  63. package/dist/esm/uc/lifecycle/client/SendClientMain.js +5 -2
  64. package/dist/esm/uc/main.d.ts +8 -1
  65. package/dist/esm/uc/manager.d.ts +11 -2
  66. package/dist/esm/uc/output.d.ts +1 -0
  67. package/dist/esm/uc/output.js +10 -1
  68. package/dist/esm/uc/transporter.d.ts +7 -1
  69. package/dist/esm/utils/async/types.d.ts +2 -0
  70. package/dist/esm/utils/async/types.js +1 -0
  71. package/dist/esm/utils/http/NDJSONStreamManager.d.ts +13 -0
  72. package/dist/esm/utils/http/NDJSONStreamManager.js +45 -0
  73. package/dist/esm/utils/http/SSEStreamManager.d.ts +13 -0
  74. package/dist/esm/utils/http/SSEStreamManager.js +60 -0
  75. package/dist/esm/utils/http/nd-json.d.ts +1 -0
  76. package/dist/esm/utils/http/nd-json.js +2 -0
  77. package/dist/esm/utils/http/sse.d.ts +14 -0
  78. package/dist/esm/utils/http/sse.js +24 -0
  79. package/dist/esm/utils/http/status.d.ts +4 -0
  80. package/dist/esm/utils/http/status.js +9 -0
  81. package/dist/esm/utils/index.d.ts +6 -0
  82. package/dist/esm/utils/index.js +4 -0
  83. package/dist/esm/utils/streams/types.d.ts +17 -0
  84. package/dist/esm/utils/streams/types.js +1 -0
  85. package/package.json +15 -15
  86. package/pnpm-workspace.yaml +1 -0
@@ -36,6 +36,9 @@ let MistralAILLMManager = class MistralAILLMManager {
36
36
  if ('message' in error) {
37
37
  return error.message;
38
38
  }
39
+ if (typeof error.detail === 'string') {
40
+ return error.detail;
41
+ }
39
42
  return error.detail.map((d) => d.msg).join('\n');
40
43
  },
41
44
  method: 'POST',
@@ -43,6 +46,15 @@ let MistralAILLMManager = class MistralAILLMManager {
43
46
  builder: async () => req,
44
47
  envelope: 'json',
45
48
  },
49
+ stream: {
50
+ onData: (res) => {
51
+ opts?.stream?.onData?.(res);
52
+ // Beware : this won't work if/when we accept multiple choices in the request
53
+ if (res.choices[0]?.finish_reason) {
54
+ opts?.stream?.onDone();
55
+ }
56
+ },
57
+ },
46
58
  urlBuilder: async () => `${MistralAILLMManager_1.BASE_URL}/chat/completions`,
47
59
  });
48
60
  }
@@ -1,7 +1,11 @@
1
1
  import type { URL } from '../../dt/index.js';
2
2
  import type { HTTPAPICaller } from '../HTTPAPICaller.js';
3
- import type { LLMManager, LLMManagerSendReq, LLMManagerSendRes } from '../LLMManager.js';
3
+ import type { LLMManager, LLMManagerSendOpts, LLMManagerSendReq, LLMManagerSendRes } from '../LLMManager.js';
4
4
  import type { Configurable, Settings, SettingsManager } from '../SettingsManager.js';
5
+ interface OllamaGenerateRes {
6
+ done: boolean;
7
+ response: string;
8
+ }
5
9
  /**
6
10
  * Unlike the "commercial" APIs, Ollama does not secure the API with an API key
7
11
  * @see https://github.com/ollama/ollama/issues/849
@@ -15,6 +19,7 @@ export declare class OllamaLLMManager implements Configurable<S>, LLMManager {
15
19
  private settingsManager;
16
20
  constructor(httpAPICaller: HTTPAPICaller, settingsManager: SettingsManager<S>);
17
21
  s(): OllamaLLMManagerSettings;
18
- send(req: LLMManagerSendReq): Promise<LLMManagerSendRes>;
22
+ send(req: LLMManagerSendReq, opts?: LLMManagerSendOpts): Promise<LLMManagerSendRes>;
23
+ toRes(stream: LLMManagerSendReq['stream'], res: OllamaGenerateRes): LLMManagerSendRes;
19
24
  }
20
25
  export {};
@@ -24,7 +24,7 @@ let OllamaLLMManager = class OllamaLLMManager {
24
24
  oll_base_url: this.settingsManager.get()('oll_base_url'),
25
25
  };
26
26
  }
27
- async send(req) {
27
+ async send(req, opts) {
28
28
  const firstMessage = req.messages[0];
29
29
  if (!firstMessage) {
30
30
  throw new IllegalArgumentError('Please provide at least one message');
@@ -32,20 +32,47 @@ let OllamaLLMManager = class OllamaLLMManager {
32
32
  return await this.httpAPICaller.exec({
33
33
  errBuilder: async (error) => error.error,
34
34
  method: 'POST',
35
- outputBuilder: async (res) => ({
36
- choices: [{ message: { content: res.response } }],
37
- }),
35
+ outputBuilder: async (res) => this.toRes(req.stream, res),
38
36
  req: {
39
37
  builder: async () => ({
40
38
  model: req.model,
41
39
  prompt: firstMessage.content,
42
- stream: false,
40
+ stream: req.stream ?? false,
43
41
  }),
44
42
  envelope: 'json',
45
43
  },
44
+ stream: {
45
+ onData: (res) => {
46
+ opts?.stream?.onData?.(res);
47
+ // Beware : this won't work if/when we accept multiple choices in the request
48
+ if (res.choices[0]?.finish_reason) {
49
+ opts?.stream?.onDone();
50
+ }
51
+ },
52
+ },
46
53
  urlBuilder: async () => `${this.s().oll_base_url}/api/generate`,
47
54
  });
48
55
  }
56
+ toRes(stream, res) {
57
+ if (stream) {
58
+ return {
59
+ choices: [
60
+ {
61
+ delta: { content: res.response },
62
+ finish_reason: res.done ? 'stop' : null,
63
+ },
64
+ ],
65
+ };
66
+ }
67
+ return {
68
+ choices: [
69
+ {
70
+ finish_reason: res.done ? 'stop' : null,
71
+ message: { content: res.response },
72
+ },
73
+ ],
74
+ };
75
+ }
49
76
  };
50
77
  OllamaLLMManager = __decorate([
51
78
  injectable(),
@@ -38,6 +38,15 @@ let OpenAILLMManager = class OpenAILLMManager {
38
38
  builder: async () => req,
39
39
  envelope: 'json',
40
40
  },
41
+ stream: {
42
+ onData: (res) => {
43
+ opts?.stream?.onData?.(res);
44
+ // Beware : this won't work if/when we accept multiple choices in the request
45
+ if (res.choices[0]?.finish_reason) {
46
+ opts?.stream?.onDone();
47
+ }
48
+ },
49
+ },
41
50
  urlBuilder: async () => `${OpenAILLMManager_1.BASE_URL}/chat/completions`,
42
51
  });
43
52
  }
@@ -1,19 +1,23 @@
1
- import { HTTPRequestBuilder } from '../../utils/index.js';
1
+ import { HTTPRequestBuilder, NDJSONStreamManager, SSEStreamManager } from '../../utils/index.js';
2
2
  import type { BufferManager } from '../BufferManager.js';
3
3
  import type { HTTPAPICallExecutor, HTTPAPICallExecutorAgentBuilder } from '../HTTPAPICallExecutor.js';
4
4
  import type { HTTPAPICaller, HTTPAPICallerInput } from '../HTTPAPICaller.js';
5
5
  import type { Logger } from '../Logger.js';
6
6
  import type { XMLManager } from '../XMLManager.js';
7
+ export declare const ERR_STREAM_UNAVAILABLE = "The internal HTTP impl (fetch ?) does not implement streaming (React Native ?)";
7
8
  export declare class SimpleHTTPAPICaller implements HTTPAPICaller {
8
9
  private bufferManager;
9
10
  private httpAPICallExecutor;
10
11
  private httpAPICallExecutorAgentBuilder;
11
12
  private httpRequestBuilder;
12
13
  private logger;
14
+ private ndJSONStreamManager;
15
+ private sseStreamManager;
13
16
  private xmlManager;
14
- constructor(bufferManager: BufferManager, httpAPICallExecutor: HTTPAPICallExecutor, httpAPICallExecutorAgentBuilder: HTTPAPICallExecutorAgentBuilder, httpRequestBuilder: HTTPRequestBuilder, logger: Logger, xmlManager: XMLManager);
15
- exec<AH extends object | undefined, Req extends object, ResBad, ResGood, O>({ additionalHeadersBuilder, authorizationHeader, basicAuth, contentType, errBuilder, method, opts, outputBuilder, req, urlBuilder, unknownErrorMessage, }: HTTPAPICallerInput<AH, Req, ResBad, ResGood, O>): Promise<O>;
17
+ constructor(bufferManager: BufferManager, httpAPICallExecutor: HTTPAPICallExecutor, httpAPICallExecutorAgentBuilder: HTTPAPICallExecutorAgentBuilder, httpRequestBuilder: HTTPRequestBuilder, logger: Logger, ndJSONStreamManager: NDJSONStreamManager, sseStreamManager: SSEStreamManager, xmlManager: XMLManager);
18
+ exec<AH extends object | undefined, Req extends object, ResBad, ResGood, O>({ additionalHeadersBuilder, authorizationHeader, basicAuth, contentType, errBuilder, method, opts, outputBuilder, registerAbort, req, stream, urlBuilder, unknownErrorMessage, }: HTTPAPICallerInput<AH, Req, ResBad, ResGood, O>): Promise<O>;
16
19
  private computeHeaders;
17
20
  private processResBad;
18
21
  private processResGood;
22
+ private throwError;
19
23
  }
@@ -12,23 +12,28 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  };
13
13
  import { inject, injectable } from 'inversify';
14
14
  import { CustomError, IllegalArgumentError } from '../../error/index.js';
15
- import { HTTPRequestBuilder } from '../../utils/index.js';
15
+ import { HTTPRequestBuilder, isClientError, NDJSONStreamManager, SSEStreamManager, } from '../../utils/index.js';
16
+ export const ERR_STREAM_UNAVAILABLE = 'The internal HTTP impl (fetch ?) does not implement streaming (React Native ?)';
16
17
  let SimpleHTTPAPICaller = class SimpleHTTPAPICaller {
17
18
  bufferManager;
18
19
  httpAPICallExecutor;
19
20
  httpAPICallExecutorAgentBuilder;
20
21
  httpRequestBuilder;
21
22
  logger;
23
+ ndJSONStreamManager;
24
+ sseStreamManager;
22
25
  xmlManager;
23
- constructor(bufferManager, httpAPICallExecutor, httpAPICallExecutorAgentBuilder, httpRequestBuilder, logger, xmlManager) {
26
+ constructor(bufferManager, httpAPICallExecutor, httpAPICallExecutorAgentBuilder, httpRequestBuilder, logger, ndJSONStreamManager, sseStreamManager, xmlManager) {
24
27
  this.bufferManager = bufferManager;
25
28
  this.httpAPICallExecutor = httpAPICallExecutor;
26
29
  this.httpAPICallExecutorAgentBuilder = httpAPICallExecutorAgentBuilder;
27
30
  this.httpRequestBuilder = httpRequestBuilder;
28
31
  this.logger = logger;
32
+ this.ndJSONStreamManager = ndJSONStreamManager;
33
+ this.sseStreamManager = sseStreamManager;
29
34
  this.xmlManager = xmlManager;
30
35
  }
31
- async exec({ additionalHeadersBuilder, authorizationHeader, basicAuth, contentType = 'application/json', errBuilder, method, opts, outputBuilder, req, urlBuilder, unknownErrorMessage = CustomError.ERROR_UNKNOWN, }) {
36
+ async exec({ additionalHeadersBuilder, authorizationHeader, basicAuth, contentType = 'application/json', errBuilder, method, opts, outputBuilder, registerAbort, req, stream, urlBuilder, unknownErrorMessage = CustomError.ERROR_UNKNOWN, }) {
32
37
  const baseURL = await urlBuilder();
33
38
  const data = (await req?.builder?.()) || {};
34
39
  const { body, url } = await this.httpRequestBuilder.exec({
@@ -46,11 +51,16 @@ let SimpleHTTPAPICaller = class SimpleHTTPAPICaller {
46
51
  const agent = this.httpAPICallExecutorAgentBuilder.exec({
47
52
  url: new URL(url),
48
53
  });
54
+ const abortController = new AbortController();
55
+ registerAbort?.(() => {
56
+ abortController.abort();
57
+ });
49
58
  const response = await this.httpAPICallExecutor.fn()(url, {
50
59
  agent,
51
60
  body,
52
61
  headers: reqHeaders,
53
62
  method,
63
+ signal: abortController.signal,
54
64
  });
55
65
  const { headers, status } = response;
56
66
  this.logger.trace('HTTPAPICaller', {
@@ -66,24 +76,24 @@ let SimpleHTTPAPICaller = class SimpleHTTPAPICaller {
66
76
  }
67
77
  // Using .startsWith instead of === because the value can look like this 'application/json; charset=utf-8'
68
78
  const responseContentType = headers.get('Content-Type');
69
- const isJSON = responseContentType?.startsWith('application/json');
70
79
  const isFormURLEncoded = responseContentType?.startsWith('application/x-www-form-urlencoded');
80
+ const isJSON = responseContentType?.startsWith('application/json');
81
+ const isNDJSON = responseContentType?.startsWith('application/x-ndjson');
82
+ const isSSE = responseContentType?.startsWith('text/event-stream');
71
83
  const isXML = responseContentType?.startsWith('text/xml');
72
84
  this.logger.trace('HTTPAPICaller', {
73
85
  isFormURLEncoded,
74
86
  isJSON,
87
+ isNDJSON,
88
+ isSSE,
75
89
  isXML,
76
90
  });
77
91
  const { ok, redirected } = response;
78
92
  if (ok || redirected) {
79
- return this.processResGood({ opts, outputBuilder }, isFormURLEncoded, isJSON, isXML, response);
80
- }
81
- const errMsg = await this.processResBad({ errBuilder, opts }, isJSON, isXML, response);
82
- const message = errMsg ?? unknownErrorMessage;
83
- if (status < 500) {
84
- throw new IllegalArgumentError(message);
93
+ return this.processResGood({ opts, outputBuilder, stream }, abortController, isFormURLEncoded, isJSON, isNDJSON, isSSE, isXML, response);
85
94
  }
86
- throw new Error(message);
95
+ const message = await this.processResBad({ errBuilder, opts }, isJSON, isXML, response);
96
+ this.throwError(message ?? unknownErrorMessage, status);
87
97
  }
88
98
  async computeHeaders({ additionalHeadersBuilder, authorizationHeader, basicAuth, contentType = 'application/json', req, }) {
89
99
  const headers = {};
@@ -163,9 +173,43 @@ let SimpleHTTPAPICaller = class SimpleHTTPAPICaller {
163
173
  return JSON.stringify(error);
164
174
  }
165
175
  }
166
- async processResGood({ opts, outputBuilder, }, isFormURLEncoded, isJSON, isXML, response) {
176
+ async processResGood({ opts, outputBuilder, stream, }, abortController, isFormURLEncoded, isJSON, isNDJSON, isSSE, isXML, response) {
167
177
  let payload;
168
- if (isJSON) {
178
+ if (isNDJSON && stream) {
179
+ if (!response.body) {
180
+ throw new Error(ERR_STREAM_UNAVAILABLE);
181
+ }
182
+ await this.ndJSONStreamManager.exec({
183
+ abortController,
184
+ onData: async (data) => {
185
+ if (outputBuilder) {
186
+ stream.onData(await outputBuilder(data));
187
+ }
188
+ else {
189
+ stream.onData(data);
190
+ }
191
+ },
192
+ reader: response.body.getReader(),
193
+ });
194
+ }
195
+ else if (isSSE && stream) {
196
+ if (!response.body) {
197
+ throw new Error(ERR_STREAM_UNAVAILABLE);
198
+ }
199
+ await this.sseStreamManager.exec({
200
+ abortController,
201
+ onData: async (data) => {
202
+ if (outputBuilder) {
203
+ stream.onData(await outputBuilder(data));
204
+ }
205
+ else {
206
+ stream.onData(data);
207
+ }
208
+ },
209
+ reader: response.body.getReader(),
210
+ });
211
+ }
212
+ else if (isJSON) {
169
213
  payload = await response.json();
170
214
  }
171
215
  else {
@@ -196,6 +240,12 @@ let SimpleHTTPAPICaller = class SimpleHTTPAPICaller {
196
240
  }
197
241
  return payload;
198
242
  }
243
+ throwError(message, status) {
244
+ if (isClientError(status)) {
245
+ throw new IllegalArgumentError(message);
246
+ }
247
+ throw new Error(message);
248
+ }
199
249
  };
200
250
  SimpleHTTPAPICaller = __decorate([
201
251
  injectable(),
@@ -204,7 +254,10 @@ SimpleHTTPAPICaller = __decorate([
204
254
  __param(2, inject('HTTPAPICallExecutorAgentBuilder')),
205
255
  __param(3, inject(HTTPRequestBuilder)),
206
256
  __param(4, inject('Logger')),
207
- __param(5, inject('XMLManager')),
208
- __metadata("design:paramtypes", [Object, Object, Object, HTTPRequestBuilder, Object, Object])
257
+ __param(5, inject(NDJSONStreamManager)),
258
+ __param(6, inject(SSEStreamManager)),
259
+ __param(7, inject('XMLManager')),
260
+ __metadata("design:paramtypes", [Object, Object, Object, HTTPRequestBuilder, Object, NDJSONStreamManager,
261
+ SSEStreamManager, Object])
209
262
  ], SimpleHTTPAPICaller);
210
263
  export { SimpleHTTPAPICaller };
@@ -41,7 +41,15 @@ let CommandExecutor = class CommandExecutor {
41
41
  if (!confirmed) {
42
42
  return;
43
43
  }
44
- const ucor = await this.ucManager.execClient(uc);
44
+ const ucor = await this.ucManager.execClient(uc, {
45
+ stream: {
46
+ onClose: async () => { },
47
+ onData: async (ucor) => {
48
+ print(JSON.stringify(ucor.output()));
49
+ },
50
+ onDone: async () => { },
51
+ },
52
+ });
45
53
  const output = ucor.output();
46
54
  if (output) {
47
55
  print(JSON.stringify(output));
@@ -2,6 +2,7 @@
2
2
  * @see TARGET_DEFAULT_SERVER_MANAGER_SETTINGS
3
3
  */
4
4
  export const TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS = {
5
+ server_cookies_name_auth: 'auth',
5
6
  server_public_api_key: 'PublicApiKeyToBeChangedWhenDeploying',
6
7
  server_public_api_key_header_name: 'X-API-Key',
7
8
  server_public_url: 'http://localhost:7443',
@@ -1,20 +1,21 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { UIntDuration } from '../../../dt/index.js';
3
- import { type UCInput, type UCOPIBase } from '../../../uc/index.js';
3
+ import { type UCInput, type UCOPIBase, type UCOutputReader } from '../../../uc/index.js';
4
+ import { type StreamConfig } from '../../../utils/index.js';
4
5
  import type { RenderUCForm } from './form.js';
5
6
  import type { RenderUCAutoExecLoader } from './loader.js';
6
7
  import type { UCPanelCtx, UCPanelOnDone, UCPanelOnError, UCPanelOnInit, UCPanelOnStartSubmitting } from './panel.js';
7
8
  import type { RenderUCExecTouchable } from './touchable.js';
8
- type Props<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = Pick<UCPanelCtx<I, OPI0, OPI1>, 'clearAfterExec' | 'uc'> & {
9
- autoExec?: boolean;
10
- onDone?: UCPanelOnDone<I, OPI0, OPI1>;
11
- onInit?: UCPanelOnInit<I, OPI0, OPI1>;
12
- onError?: UCPanelOnError;
13
- onStartSubmitting?: UCPanelOnStartSubmitting;
9
+ export type Props<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> = Pick<UCPanelCtx<I, OPI0, OPI1>, 'clearAfterExec' | 'uc'> & {
10
+ autoExec?: boolean | undefined;
11
+ onDone?: UCPanelOnDone<I, OPI0, OPI1> | undefined;
12
+ onInit?: UCPanelOnInit<I, OPI0, OPI1> | undefined;
13
+ onError?: UCPanelOnError | undefined;
14
+ onStartSubmitting?: UCPanelOnStartSubmitting | undefined;
14
15
  renderAutoExecLoader: RenderUCAutoExecLoader;
15
16
  renderExecTouchable: RenderUCExecTouchable<I, OPI0, OPI1>;
16
17
  renderForm: RenderUCForm<I, OPI0, OPI1>;
17
- sleepInMs?: UIntDuration;
18
+ sleepInMs?: UIntDuration | undefined;
19
+ stream?: StreamConfig<UCOutputReader<I, OPI0, OPI1>> | undefined;
18
20
  };
19
- export declare function UCPanel<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ autoExec, clearAfterExec, onDone, onError, onInit, onStartSubmitting, renderAutoExecLoader, renderForm, renderExecTouchable, sleepInMs, uc, }: Props<I, OPI0, OPI1>): ReactElement;
20
- export {};
21
+ export declare function UCPanel<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ autoExec, clearAfterExec, onDone, onError, onInit, onStartSubmitting, renderAutoExecLoader, renderForm, renderExecTouchable, sleepInMs, stream, uc, }: Props<I, OPI0, OPI1>): ReactElement;
@@ -5,12 +5,12 @@ import { sleep } from '../../../utils/index.js';
5
5
  import { useDIContext } from './DIContextProvider.js';
6
6
  import { UCContainer } from './UCContainer.js';
7
7
  import { useAction } from './useAction.js';
8
- export function UCPanel({ autoExec = false, clearAfterExec = true, onDone, onError, onInit, onStartSubmitting, renderAutoExecLoader, renderForm, renderExecTouchable, sleepInMs, uc, }) {
8
+ export function UCPanel({ autoExec = false, clearAfterExec = true, onDone, onError, onInit, onStartSubmitting, renderAutoExecLoader, renderForm, renderExecTouchable, sleepInMs, stream, uc, }) {
9
9
  const { container } = useDIContext();
10
10
  const [ucManager] = useState(container.get('UCManager'));
11
11
  const { exec, execState } = useAction({
12
12
  action: async () => {
13
- const ucor = await ucManager.execClient(uc);
13
+ const ucor = await ucManager.execClient(uc, { stream });
14
14
  await onDone?.(ucor);
15
15
  clear();
16
16
  },
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useEffect, useState } from 'react';
2
2
  import { UC, } from '../../../uc/index.js';
3
3
  /**
4
4
  * This hook provides utilities to init a use case and perform actions on it in a React way
@@ -21,6 +21,16 @@ export function useUC(appManifest, def, auth, opts) {
21
21
  }
22
22
  return v;
23
23
  });
24
+ // biome-ignore lint/correctness/useExhaustiveDependencies(appManifest): avoid infinite re-rendering
25
+ // biome-ignore lint/correctness/useExhaustiveDependencies(def): avoid infinite re-rendering
26
+ // biome-ignore lint/correctness/useExhaustiveDependencies(opts?.fillWith): avoid infinite re-rendering
27
+ useEffect(() => {
28
+ const v = new UC(appManifest, def, auth);
29
+ if (opts?.fillWith) {
30
+ v.fill(opts?.fillWith);
31
+ }
32
+ setUC(v);
33
+ }, [auth]);
24
34
  /**
25
35
  * Get a new `UC` based on the initial one
26
36
  * @param i
@@ -27,6 +27,7 @@ export interface ServerManagerSettings extends ServerManagerAuthSettings, Settin
27
27
  server_ssl_fullchain_path: FilePath | null;
28
28
  server_ssl_key_path: FilePath | null;
29
29
  server_static_dir_path: DirPath | null;
30
+ server_stop_mode: 'aggressive' | 'patient';
30
31
  server_tmp_path: FilePath;
31
32
  }
32
33
  export interface ServerManager extends Initializable {
@@ -1,7 +1,7 @@
1
1
  import type { AppManifest } from '../../../app/index.js';
2
2
  import type { HTTPMethod, HTTPStatusNumber, URL, URLPath } from '../../../dt/index.js';
3
3
  import type { SettingsManager, Worker } from '../../../std/index.js';
4
- import { UCBuilder, type UCDef, type UCInput, type UCManager, type UCOPIBase, type UCOutput } from '../../../uc/index.js';
4
+ import { UCBuilder, type UCDef, type UCInput, type UCManager, type UCManagerExecServerOpts, type UCOPIBase, type UCOutput } from '../../../uc/index.js';
5
5
  import type { HTTPDataEnvelope, HTTPReqData } from '../../../utils/index.js';
6
6
  import { AuthCookieCreator, type Output as AuthCookieCreatorOutput } from './AuthCookieCreator.js';
7
7
  import { AuthenticationChecker } from './AuthenticationChecker.js';
@@ -29,6 +29,7 @@ export interface ServerRequestHandlerRes {
29
29
  interface Input<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined> {
30
30
  appManifest: AppManifest;
31
31
  envelope: HTTPDataEnvelope;
32
+ execOpts?: UCManagerExecServerOpts<OPI0, OPI1> | undefined;
32
33
  req: ServerRequestHandlerReq;
33
34
  res: ServerRequestHandlerRes;
34
35
  ucd: UCDef<I, OPI0, OPI1>;
@@ -61,7 +62,7 @@ export declare class ServerRequestHandler implements Worker<Input, Promise<Outpu
61
62
  private static X_FORWARDED_PROTO_HEADER_NAME;
62
63
  constructor(authCookieCreator: AuthCookieCreator, authenticationChecker: AuthenticationChecker, customerFacingErrorBuilder: CustomerFacingErrorBuilder, publicApiKeyChecker: PublicApiKeyChecker, requestChecker: RequestChecker, requestLogger: RequestLogger, settingsManager: SettingsManager<S>, ucBuilder: UCBuilder);
63
64
  s(): S;
64
- exec<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ appManifest, envelope, req, res, ucd, ucManager, }: Input<I, OPI0, OPI1>): Promise<Output<OPI0, OPI1>>;
65
+ exec<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ appManifest, envelope, execOpts, req, res, ucd, ucManager, }: Input<I, OPI0, OPI1>): Promise<Output<OPI0, OPI1>>;
65
66
  private fill;
66
67
  private applySideEffects;
67
68
  private applyClearAuthSideEffect;
@@ -47,7 +47,7 @@ let ServerRequestHandler = class ServerRequestHandler {
47
47
  server_public_api_key_header_name: this.settingsManager.get()('server_public_api_key_header_name'),
48
48
  };
49
49
  }
50
- async exec({ appManifest, envelope, req, res, ucd, ucManager, }) {
50
+ async exec({ appManifest, envelope, execOpts, req, res, ucd, ucManager, }) {
51
51
  try {
52
52
  const { bodyRaw, cookie, header, method, secure, url } = req;
53
53
  this.requestLogger.exec({
@@ -79,7 +79,7 @@ let ServerRequestHandler = class ServerRequestHandler {
79
79
  uc.auth = auth;
80
80
  }
81
81
  await this.fill(req, envelope, uc);
82
- const output = await ucManager.execServer(uc);
82
+ const output = await ucManager.execServer(uc, execOpts);
83
83
  const { status } = await this.applySideEffects(res, ucd, output);
84
84
  if (status !== undefined) {
85
85
  return {
@@ -19,5 +19,6 @@ export const TARGET_DEFAULT_SERVER_MANAGER_SETTINGS = {
19
19
  server_ssl_fullchain_path: null,
20
20
  server_ssl_key_path: null,
21
21
  server_static_dir_path: null,
22
+ server_stop_mode: 'patient',
22
23
  server_tmp_path: 'tmp',
23
24
  };
@@ -1,12 +1,49 @@
1
1
  import cookieParser from 'cookie-parser';
2
2
  import express, {} from 'express';
3
3
  import fileUpload from 'express-fileupload';
4
+ import { fmtSingleDataMsg, fmtSSEError, isError, SSE_HEADERS, } from '../../../utils/index.js';
4
5
  export function buildHandler(appManifest, ucd, contract, serverRequestHandler, ucManager) {
5
6
  const { envelope } = contract;
6
7
  const handler = async (req, res) => {
8
+ const transportType = ucd.ext?.http?.transportType ?? 'standard';
9
+ let execOpts;
10
+ switch (transportType) {
11
+ case 'standard':
12
+ // Nothing to do
13
+ break;
14
+ case 'stream': {
15
+ execOpts = {
16
+ stream: {
17
+ onClose: async () => {
18
+ throw new Error('execOpts.stream.onClose needs to be set in the UC ServerMain');
19
+ },
20
+ onData: async (output) => {
21
+ if (!output) {
22
+ return;
23
+ }
24
+ res.write(fmtSingleDataMsg(output));
25
+ },
26
+ onDone: async () => {
27
+ res.end();
28
+ },
29
+ },
30
+ };
31
+ for (const [k, v] of SSE_HEADERS) {
32
+ res.setHeader(k, v);
33
+ }
34
+ res.flushHeaders();
35
+ res.on('close', async () => {
36
+ await execOpts?.stream?.onClose();
37
+ });
38
+ break;
39
+ }
40
+ default:
41
+ ((_) => { })(transportType);
42
+ }
7
43
  const { body, status } = await serverRequestHandler.exec({
8
44
  appManifest,
9
45
  envelope,
46
+ execOpts,
10
47
  req: toReq(req),
11
48
  res: toRes(res),
12
49
  ucd,
@@ -16,7 +53,22 @@ export function buildHandler(appManifest, ucd, contract, serverRequestHandler, u
16
53
  res.status(status).send();
17
54
  return;
18
55
  }
19
- res.status(status).send(body);
56
+ switch (transportType) {
57
+ case 'standard':
58
+ res.status(status).send(body);
59
+ return;
60
+ case 'stream': {
61
+ if (isError(status)) {
62
+ res.write(fmtSSEError({
63
+ message: body.message,
64
+ status,
65
+ }));
66
+ }
67
+ return;
68
+ }
69
+ default:
70
+ ((_) => { })(transportType);
71
+ }
20
72
  };
21
73
  return handler;
22
74
  }
@@ -3,14 +3,60 @@ import { deleteCookie, getCookie, setCookie } from 'hono/cookie';
3
3
  import { logger } from 'hono/logger';
4
4
  import { secureHeaders } from 'hono/secure-headers';
5
5
  import { NotFoundError } from '../../../error/index.js';
6
- import { fromFormData } from '../../../utils/index.js';
6
+ import { fmtSingleDataMsg, fmtSSEError, fromFormData, isError, SSE_HEADERS, } from '../../../utils/index.js';
7
7
  export function buildHandler(appManifest, ucd, contract, serverRequestHandler, ucManager, beforeExec) {
8
8
  const { envelope } = contract;
9
9
  const handler = async (c) => {
10
10
  await beforeExec?.(c);
11
+ const transportType = ucd.ext?.http?.transportType ?? 'standard';
12
+ let execOpts;
13
+ let stream;
14
+ let controller;
15
+ switch (transportType) {
16
+ case 'standard':
17
+ // Nothing to do
18
+ break;
19
+ case 'stream': {
20
+ stream = new ReadableStream({
21
+ start: (ctrl) => {
22
+ controller = ctrl;
23
+ let closed = false;
24
+ const close = () => {
25
+ if (closed) {
26
+ return;
27
+ }
28
+ ctrl.close();
29
+ closed = true;
30
+ };
31
+ execOpts = {
32
+ stream: {
33
+ onClose: async () => { },
34
+ onData: async (output) => {
35
+ if (!output || closed) {
36
+ return;
37
+ }
38
+ ctrl.enqueue(fmtSingleDataMsg(output));
39
+ },
40
+ onDone: async () => {
41
+ close();
42
+ },
43
+ },
44
+ };
45
+ c.req.raw.signal.addEventListener('abort', async () => {
46
+ close();
47
+ await execOpts?.stream?.onClose();
48
+ });
49
+ },
50
+ });
51
+ break;
52
+ }
53
+ default:
54
+ ((_) => { })(transportType);
55
+ }
11
56
  const { body, status } = await serverRequestHandler.exec({
12
57
  appManifest,
13
58
  envelope,
59
+ execOpts,
14
60
  req: toReq(c),
15
61
  res: toRes(c),
16
62
  ucd,
@@ -19,6 +65,23 @@ export function buildHandler(appManifest, ucd, contract, serverRequestHandler, u
19
65
  if (!body) {
20
66
  return c.newResponse(null, status);
21
67
  }
68
+ switch (transportType) {
69
+ case 'standard':
70
+ return c.json(body, status);
71
+ case 'stream': {
72
+ if (isError(status)) {
73
+ controller?.enqueue(fmtSSEError({
74
+ message: body.message,
75
+ status,
76
+ }));
77
+ }
78
+ return new Response(stream, {
79
+ headers: SSE_HEADERS,
80
+ });
81
+ }
82
+ default:
83
+ ((_) => { })(transportType);
84
+ }
22
85
  return c.json(body, status);
23
86
  };
24
87
  return handler;
@@ -50,7 +113,7 @@ export function toReq(c) {
50
113
  return {
51
114
  bodyFromFormData: async () => fromFormData(await c.req.formData()),
52
115
  bodyFromJSON: () => c.req.json(),
53
- bodyFromQueryParams: async () => c.req.queries(),
116
+ bodyFromQueryParams: async () => c.req.query(),
54
117
  bodyRaw: c.req.raw,
55
118
  cookie: async (name) => getCookie(c, name),
56
119
  header: async (name) => c.req.header(name),
@@ -1,5 +1,5 @@
1
1
  import type { Logger, SettingsManager } from '../../../std/index.js';
2
2
  import type { EntrypointsBuilder } from '../server/EntrypointsBuilder.js';
3
- import type { ListenSettings, Server } from './types.js';
3
+ import type { ListenSettings, Server, StopSettings } from './types.js';
4
4
  export declare function listen(server: Server, entrypointsBuilder: EntrypointsBuilder, logger: Logger, settingsManager: SettingsManager<ListenSettings>): void;
5
- export declare function stop(server: Server): Promise<void>;
5
+ export declare function stop(server: Server, settingsManager: SettingsManager<StopSettings>): Promise<void>;