libmodulor 0.21.0 → 0.22.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 (69) hide show
  1. package/CHANGELOG.md +13 -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/std/HTTPAPICallExecutor.d.ts +16 -7
  19. package/dist/esm/std/HTTPAPICaller.d.ts +8 -1
  20. package/dist/esm/std/LLMManager.d.ts +20 -3
  21. package/dist/esm/std/impl/FakeClockManager.d.ts +6 -0
  22. package/dist/esm/std/impl/FakeClockManager.js +19 -0
  23. package/dist/esm/std/impl/FakeHTTPAPICallExecutor.js +19 -18
  24. package/dist/esm/std/impl/FakeLLMManager.d.ts +4 -0
  25. package/dist/esm/std/impl/FakeLLMManager.js +40 -0
  26. package/dist/esm/std/impl/MistralAILLMManager.js +12 -0
  27. package/dist/esm/std/impl/OllamaLLMManager.d.ts +7 -2
  28. package/dist/esm/std/impl/OllamaLLMManager.js +32 -5
  29. package/dist/esm/std/impl/OpenAILLMManager.js +9 -0
  30. package/dist/esm/std/impl/SimpleHTTPAPICaller.d.ts +7 -3
  31. package/dist/esm/std/impl/SimpleHTTPAPICaller.js +66 -15
  32. package/dist/esm/target/lib/cli/CommandExecutor.js +9 -1
  33. package/dist/esm/target/lib/react/UCPanel.d.ts +4 -2
  34. package/dist/esm/target/lib/react/UCPanel.js +2 -2
  35. package/dist/esm/target/lib/server/ServerRequestHandler.d.ts +3 -2
  36. package/dist/esm/target/lib/server/ServerRequestHandler.js +2 -2
  37. package/dist/esm/target/lib/server-express/funcs.js +52 -1
  38. package/dist/esm/target/lib/server-hono/funcs.js +65 -2
  39. package/dist/esm/testing/workers/UCExecutor.js +39 -1
  40. package/dist/esm/uc/ext.d.ts +7 -1
  41. package/dist/esm/uc/impl/HTTPUCTransporter.d.ts +2 -2
  42. package/dist/esm/uc/impl/HTTPUCTransporter.js +3 -1
  43. package/dist/esm/uc/impl/SimpleUCManager.d.ts +3 -3
  44. package/dist/esm/uc/impl/SimpleUCManager.js +23 -4
  45. package/dist/esm/uc/lifecycle/client/SendClientMain.d.ts +1 -1
  46. package/dist/esm/uc/lifecycle/client/SendClientMain.js +5 -2
  47. package/dist/esm/uc/main.d.ts +8 -1
  48. package/dist/esm/uc/manager.d.ts +11 -2
  49. package/dist/esm/uc/output.d.ts +1 -0
  50. package/dist/esm/uc/output.js +10 -1
  51. package/dist/esm/uc/transporter.d.ts +7 -1
  52. package/dist/esm/utils/async/types.d.ts +2 -0
  53. package/dist/esm/utils/async/types.js +1 -0
  54. package/dist/esm/utils/http/NDJSONStreamManager.d.ts +12 -0
  55. package/dist/esm/utils/http/NDJSONStreamManager.js +42 -0
  56. package/dist/esm/utils/http/SSEStreamManager.d.ts +12 -0
  57. package/dist/esm/utils/http/SSEStreamManager.js +57 -0
  58. package/dist/esm/utils/http/nd-json.d.ts +1 -0
  59. package/dist/esm/utils/http/nd-json.js +2 -0
  60. package/dist/esm/utils/http/sse.d.ts +14 -0
  61. package/dist/esm/utils/http/sse.js +24 -0
  62. package/dist/esm/utils/http/status.d.ts +4 -0
  63. package/dist/esm/utils/http/status.js +9 -0
  64. package/dist/esm/utils/index.d.ts +6 -0
  65. package/dist/esm/utils/index.js +4 -0
  66. package/dist/esm/utils/streams/types.d.ts +17 -0
  67. package/dist/esm/utils/streams/types.js +1 -0
  68. package/package.json +15 -15
  69. package/pnpm-workspace.yaml +1 -0
@@ -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 }, 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,41 @@ 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, }, 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
+ onData: async (data) => {
184
+ if (outputBuilder) {
185
+ stream.onData(await outputBuilder(data));
186
+ }
187
+ else {
188
+ stream.onData(data);
189
+ }
190
+ },
191
+ reader: response.body.getReader(),
192
+ });
193
+ }
194
+ else if (isSSE && stream) {
195
+ if (!response.body) {
196
+ throw new Error(ERR_STREAM_UNAVAILABLE);
197
+ }
198
+ await this.sseStreamManager.exec({
199
+ onData: async (data) => {
200
+ if (outputBuilder) {
201
+ stream.onData(await outputBuilder(data));
202
+ }
203
+ else {
204
+ stream.onData(data);
205
+ }
206
+ },
207
+ reader: response.body.getReader(),
208
+ });
209
+ }
210
+ else if (isJSON) {
169
211
  payload = await response.json();
170
212
  }
171
213
  else {
@@ -196,6 +238,12 @@ let SimpleHTTPAPICaller = class SimpleHTTPAPICaller {
196
238
  }
197
239
  return payload;
198
240
  }
241
+ throwError(message, status) {
242
+ if (isClientError(status)) {
243
+ throw new IllegalArgumentError(message);
244
+ }
245
+ throw new Error(message);
246
+ }
199
247
  };
200
248
  SimpleHTTPAPICaller = __decorate([
201
249
  injectable(),
@@ -204,7 +252,10 @@ SimpleHTTPAPICaller = __decorate([
204
252
  __param(2, inject('HTTPAPICallExecutorAgentBuilder')),
205
253
  __param(3, inject(HTTPRequestBuilder)),
206
254
  __param(4, inject('Logger')),
207
- __param(5, inject('XMLManager')),
208
- __metadata("design:paramtypes", [Object, Object, Object, HTTPRequestBuilder, Object, Object])
255
+ __param(5, inject(NDJSONStreamManager)),
256
+ __param(6, inject(SSEStreamManager)),
257
+ __param(7, inject('XMLManager')),
258
+ __metadata("design:paramtypes", [Object, Object, Object, HTTPRequestBuilder, Object, NDJSONStreamManager,
259
+ SSEStreamManager, Object])
209
260
  ], SimpleHTTPAPICaller);
210
261
  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));
@@ -1,6 +1,7 @@
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';
@@ -15,6 +16,7 @@ type Props<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | u
15
16
  renderExecTouchable: RenderUCExecTouchable<I, OPI0, OPI1>;
16
17
  renderForm: RenderUCForm<I, OPI0, OPI1>;
17
18
  sleepInMs?: UIntDuration;
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;
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;
20
22
  export {};
@@ -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,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 {
@@ -1,12 +1,48 @@
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
+ onData: async (output) => {
19
+ if (!output) {
20
+ return;
21
+ }
22
+ res.write(fmtSingleDataMsg(output));
23
+ },
24
+ onDone: async () => {
25
+ res.end();
26
+ },
27
+ },
28
+ };
29
+ for (const [k, v] of SSE_HEADERS) {
30
+ res.setHeader(k, v);
31
+ }
32
+ res.flushHeaders();
33
+ res.on('close', async () => {
34
+ res.end();
35
+ await execOpts?.stream?.onClose();
36
+ });
37
+ break;
38
+ }
39
+ default:
40
+ ((_) => { })(transportType);
41
+ }
7
42
  const { body, status } = await serverRequestHandler.exec({
8
43
  appManifest,
9
44
  envelope,
45
+ execOpts,
10
46
  req: toReq(req),
11
47
  res: toRes(res),
12
48
  ucd,
@@ -16,7 +52,22 @@ export function buildHandler(appManifest, ucd, contract, serverRequestHandler, u
16
52
  res.status(status).send();
17
53
  return;
18
54
  }
19
- res.status(status).send(body);
55
+ switch (transportType) {
56
+ case 'standard':
57
+ res.status(status).send(body);
58
+ return;
59
+ case 'stream': {
60
+ if (isError(status)) {
61
+ res.write(fmtSSEError({
62
+ message: body.message,
63
+ status,
64
+ }));
65
+ }
66
+ return;
67
+ }
68
+ default:
69
+ ((_) => { })(transportType);
70
+ }
20
71
  };
21
72
  return handler;
22
73
  }
@@ -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),
@@ -13,6 +13,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
13
13
  var UCExecutor_1;
14
14
  import { inject, injectable } from 'inversify';
15
15
  import { rInput, UCBuilder, } from '../../uc/index.js';
16
+ const ERR_CLIENT_EXPECTED_UCOR = (name) => `${name} client is expected to return an ucor`;
16
17
  const ERR_CLIENT_EXPECTED_OUTPUT = (name) => `${name} client is expected to return an output but returned nothing`;
17
18
  const ERR_CLIENT_UNEXPECTED_OUTPUT = (name) => `${name} client is expected to return nothing but returned an output`;
18
19
  let UCExecutor = class UCExecutor {
@@ -70,7 +71,44 @@ let UCExecutor = class UCExecutor {
70
71
  out.io.i = input;
71
72
  out.hash = this.cryptoManager.hash(UCExecutor_1.HASH_ALG, [name, JSON.stringify(args), JSON.stringify(input)].join(UCExecutor_1.HASH_SEP), UCExecutor_1.HASH_BTT_ENCODING);
72
73
  try {
73
- const ucor = await this.ucManager.execClient(uc);
74
+ let ucor;
75
+ const transportType = ucd.ext?.http?.transportType ?? 'standard';
76
+ switch (transportType) {
77
+ case 'standard':
78
+ ucor = await this.ucManager.execClient(uc);
79
+ break;
80
+ case 'stream': {
81
+ try {
82
+ let abort;
83
+ await this.ucManager.execClient(uc, {
84
+ registerAbort: (func) => {
85
+ abort = func;
86
+ },
87
+ stream: {
88
+ onClose: async () => { },
89
+ onData: async (ucor2) => {
90
+ if (!ucor) {
91
+ ucor = ucor2;
92
+ abort();
93
+ }
94
+ },
95
+ onDone: async () => { },
96
+ },
97
+ });
98
+ }
99
+ catch (err) {
100
+ if (err.name !== 'AbortError') {
101
+ throw err;
102
+ }
103
+ }
104
+ break;
105
+ }
106
+ default:
107
+ ((_) => { })(transportType);
108
+ }
109
+ if (!ucor) {
110
+ throw new Error(ERR_CLIENT_EXPECTED_UCOR(name));
111
+ }
74
112
  const output = ucor.output();
75
113
  if (uc.hasOutputParts() && !output) {
76
114
  throw new Error(ERR_CLIENT_EXPECTED_OUTPUT(name));
@@ -1,4 +1,4 @@
1
- import type { HTTPMethod, URLPath } from '../dt/index.js';
1
+ import type { HTTPMethod, TransportType, URLPath } from '../dt/index.js';
2
2
  import type { UCOPIBase } from './opi.js';
3
3
  import type { UCOutput } from './output.js';
4
4
  import type { UCMountingPoint } from './utils/ucMountingPoint.js';
@@ -41,5 +41,11 @@ export interface UCExt<OPI0 extends UCOPIBase | undefined = undefined, OPI1 exte
41
41
  * @returns
42
42
  */
43
43
  transform?: (output: UCOutput<OPI0, OPI1>) => object;
44
+ /**
45
+ * The way the output is transported
46
+ *
47
+ * By default, it's `standard`.
48
+ */
49
+ transportType?: TransportType;
44
50
  };
45
51
  }
@@ -3,7 +3,7 @@ import type { ServerClientManager, ServerClientManagerSettings } from '../../tar
3
3
  import type { UCInput } from '../input.js';
4
4
  import type { UCOPIBase } from '../opi.js';
5
5
  import type { UCOutputOrNothing } from '../output.js';
6
- import type { UCTransporter } from '../transporter.js';
6
+ import type { UCTransporter, UCTransporterOpts } from '../transporter.js';
7
7
  import type { UC } from '../UC.js';
8
8
  type S = Pick<ServerClientManagerSettings, 'server_cookies_name_auth' | 'server_public_api_key_header_name'>;
9
9
  export declare class HTTPUCTransporter implements Configurable<S>, UCTransporter {
@@ -12,6 +12,6 @@ export declare class HTTPUCTransporter implements Configurable<S>, UCTransporter
12
12
  private settingsManager;
13
13
  constructor(httpAPICaller: HTTPAPICaller, serverClientManager: ServerClientManager, settingsManager: SettingsManager<S>);
14
14
  s(): S;
15
- send<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>): Promise<UCOutputOrNothing<OPI0, OPI1>>;
15
+ send<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>, opts: UCTransporterOpts<OPI0, OPI1>): Promise<UCOutputOrNothing<OPI0, OPI1>>;
16
16
  }
17
17
  export {};
@@ -29,7 +29,7 @@ let HTTPUCTransporter = class HTTPUCTransporter {
29
29
  server_public_api_key_header_name: this.settingsManager.get()('server_public_api_key_header_name'),
30
30
  };
31
31
  }
32
- async send(uc) {
32
+ async send(uc, opts) {
33
33
  const { auth, def: { sec }, } = uc;
34
34
  const baseURL = await this.serverClientManager.baseURL({ auth });
35
35
  const { contentType, envelope, method, path } = ucHTTPContract(uc);
@@ -96,10 +96,12 @@ let HTTPUCTransporter = class HTTPUCTransporter {
96
96
  contentType,
97
97
  errBuilder: async (error) => error.message,
98
98
  method,
99
+ registerAbort: opts.registerAbort,
99
100
  req: {
100
101
  builder: async () => rInput(uc, { ignoreUndefined: true }),
101
102
  envelope,
102
103
  },
104
+ stream: opts.stream,
103
105
  urlBuilder: async () => `${baseURL}${path}`,
104
106
  });
105
107
  // In case of 204, we get an empty object.
@@ -7,7 +7,7 @@ import { UCOutputReader } from '../helpers/UCOutputReader.js';
7
7
  import type { UCInit } from '../init.js';
8
8
  import type { UCInput } from '../input.js';
9
9
  import type { UCMain } from '../main.js';
10
- import type { UCManager, UCManagerPersistOpts } from '../manager.js';
10
+ import type { UCManager, UCManagerExecClientOpts, UCManagerExecServerOpts, UCManagerPersistOpts } from '../manager.js';
11
11
  import type { UCOPIBase } from '../opi.js';
12
12
  import type { UCOutputOrNothing } from '../output.js';
13
13
  import type { UC } from '../UC.js';
@@ -29,8 +29,8 @@ export declare class SimpleUCManager implements UCManager {
29
29
  constructor(ucClientConfirmManager: UCClientConfirmManager, clockManager: ClockManager, cryptoManager: CryptoManager, logger: Logger, ucDataStore: UCDataStore, ucExecChecker: UCExecChecker, ucInputFilesProcessor: UCInputFilesProcessor, ucInputValidator: UCInputValidator, ucInitProvider: Provider<UCInit>, ucMainProvider: Provider<UCMain>);
30
30
  commitTx(): Promise<void>;
31
31
  confirmClient<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>): Promise<boolean>;
32
- execClient<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>): Promise<UCOutputReader<I, OPI0, OPI1>>;
33
- execServer<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>): Promise<UCOutputOrNothing<OPI0, OPI1>>;
32
+ execClient<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>, opts?: UCManagerExecClientOpts<I, OPI0, OPI1>): Promise<UCOutputReader<I, OPI0, OPI1>>;
33
+ execServer<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>, opts?: UCManagerExecServerOpts<OPI0, OPI1>): Promise<UCOutputOrNothing<OPI0, OPI1>>;
34
34
  initServer<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>): Promise<void>;
35
35
  startTx(): Promise<void>;
36
36
  persist<I extends UCInput | undefined = undefined, D extends UCData | null = null, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>(uc: UC<I, OPI0, OPI1>, data?: D, opts?: UCManagerPersistOpts): Promise<UCDataStoreRecord<I, D>>;