libmodulor 0.11.0 → 0.12.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 (61) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +1 -7
  3. package/dist/esm/apps/Helper/src/lib/project.js +7 -7
  4. package/dist/esm/index.d.ts +1 -0
  5. package/dist/esm/index.js +1 -0
  6. package/dist/esm/index.nextjs.d.ts +3 -0
  7. package/dist/esm/index.nextjs.js +3 -0
  8. package/dist/esm/products/Helper/index.js +1 -1
  9. package/dist/esm/std/FSManager.d.ts +1 -1
  10. package/dist/esm/std/impl/FakeFSManager.d.ts +3 -3
  11. package/dist/esm/std/impl/NodeFSManager.d.ts +1 -1
  12. package/dist/esm/std/impl/NodeFSManager.js +2 -2
  13. package/dist/esm/std/impl/WebFSManager.d.ts +1 -1
  14. package/dist/esm/target/lib/react/DIContextProvider.js +1 -1
  15. package/dist/esm/target/lib/react/UCContainer.js +1 -1
  16. package/dist/esm/target/lib/server/AuthCookieCreator.d.ts +28 -0
  17. package/dist/esm/target/lib/server/AuthCookieCreator.js +56 -0
  18. package/dist/esm/target/lib/server/AuthenticationChecker.d.ts +2 -2
  19. package/dist/esm/target/lib/server/CustomerFacingErrorBuilder.js +4 -3
  20. package/dist/esm/target/lib/server/ServerBooter.d.ts +5 -3
  21. package/dist/esm/target/lib/server/ServerBooter.js +29 -22
  22. package/dist/esm/target/lib/server/ServerRequestHandler.d.ts +71 -0
  23. package/dist/esm/target/lib/server/ServerRequestHandler.js +202 -0
  24. package/dist/esm/target/lib/server/ServerSSLCertLoader.d.ts +18 -0
  25. package/dist/esm/target/lib/server/ServerSSLCertLoader.js +45 -0
  26. package/dist/esm/target/lib/server-node/stop.d.ts +2 -0
  27. package/dist/esm/target/lib/server-node/stop.js +18 -0
  28. package/dist/esm/target/lib/server-node/types.d.ts +3 -0
  29. package/dist/esm/target/lib/server-node/types.js +1 -0
  30. package/dist/esm/target/nextjs-server/NextJSAPIRouteHandler.d.ts +23 -0
  31. package/dist/esm/target/nextjs-server/NextJSAPIRouteHandler.js +82 -0
  32. package/dist/esm/target/nextjs-server/NextJSServerManager.d.ts +13 -0
  33. package/dist/esm/target/nextjs-server/NextJSServerManager.js +34 -0
  34. package/dist/esm/target/node-express-server/NodeExpressServerManager.d.ts +10 -16
  35. package/dist/esm/target/node-express-server/NodeExpressServerManager.js +94 -81
  36. package/dist/esm/target/node-express-server/middlewares/RequestHandlerMiddlewareBuilder.js +2 -1
  37. package/dist/esm/testing/AppTester.js +2 -2
  38. package/dist/esm/testing/impl/VitestAppTestSuiteRunner.js +1 -1
  39. package/dist/esm/testing/impl/newNodeAppTester.js +1 -1
  40. package/dist/esm/uc/impl/SimpleUCManager.d.ts +2 -2
  41. package/dist/esm/uc/utils/ucHTTPContract.js +1 -1
  42. package/dist/esm/uc/workers/UCExecChecker.d.ts +2 -2
  43. package/dist/esm/uc/workers/UCInputFilesProcessor.js +9 -6
  44. package/dist/esm/utils/http/HTTPRequestBuilder.js +3 -3
  45. package/dist/esm/utils/http/form-data.d.ts +4 -0
  46. package/dist/esm/utils/http/form-data.js +20 -0
  47. package/dist/esm/utils/http/json.d.ts +3 -0
  48. package/dist/esm/utils/http/json.js +3 -0
  49. package/dist/esm/utils/http/query-params.d.ts +3 -0
  50. package/dist/esm/utils/http/query-params.js +20 -0
  51. package/dist/esm/utils/index.d.ts +3 -0
  52. package/dist/esm/utils/index.js +3 -0
  53. package/dist/esm/utils/ioc/bindNodeCLI.js +1 -1
  54. package/dist/esm/utils/ioc/bindNodeCore.js +1 -1
  55. package/dist/esm/utils/ioc/bindProduct.js +2 -2
  56. package/dist/esm/utils/ioc/bindProvider.js +1 -1
  57. package/dist/esm/utils/ioc/bindWeb.js +1 -1
  58. package/dist/esm/utils/ioc/container.d.ts +2 -2
  59. package/dist/esm/utils/ioc/container.js +2 -3
  60. package/dist/esm/utils/ioc/types.d.ts +2 -2
  61. package/package.json +14 -7
@@ -16,42 +16,29 @@ import cookieParser from 'cookie-parser';
16
16
  import express, {} from 'express';
17
17
  import fileUpload from 'express-fileupload';
18
18
  import { inject, injectable } from 'inversify';
19
+ import { stop } from '../lib/server-node/stop.js';
19
20
  import { EntrypointsBuilder } from '../lib/server/EntrypointsBuilder.js';
20
- import { AuthenticationCheckerMiddlewareBuilder } from './middlewares/AuthenticationCheckerMiddlewareBuilder.js';
21
- import { ErrorMiddlewareBuilder } from './middlewares/ErrorMiddlewareBuilder.js';
21
+ import { ServerRequestHandler, } from '../lib/server/ServerRequestHandler.js';
22
+ import { ServerSSLCertLoader } from '../lib/server/ServerSSLCertLoader.js';
22
23
  import { HelmetMiddlewareBuilder } from './middlewares/HelmetMiddlewareBuilder.js';
23
- import { PublicApiKeyCheckerMiddlewareBuilder } from './middlewares/PublicApiKeyCheckerMiddlewareBuilder.js';
24
- import { RequestCheckerMiddlewareBuilder } from './middlewares/RequestCheckerMiddlewareBuilder.js';
25
- import { RequestHandlerMiddlewareBuilder } from './middlewares/RequestHandlerMiddlewareBuilder.js';
26
- import { RequestLoggerMiddlewareBuilder } from './middlewares/RequestLoggerMiddlewareBuilder.js';
27
24
  let NodeExpressServerManager = class NodeExpressServerManager {
28
- authenticationCheckerMB;
29
25
  entrypointsBuilder;
30
26
  environmentManager;
31
- errorMB;
32
- fsManager;
33
27
  helmetMB;
34
28
  logger;
35
- publicApiKeyCheckerMB;
36
- requestCheckerMB;
37
- requestHandlerMB;
38
- requestLoggerMB;
29
+ serverRequestHandler;
30
+ serverSSLCertLoader;
39
31
  settingsManager;
40
32
  ucManager;
41
33
  runtime;
42
34
  server;
43
- constructor(authenticationCheckerMB, entrypointsBuilder, environmentManager, errorMB, fsManager, helmetMB, logger, publicApiKeyCheckerMB, requestCheckerMB, requestHandlerMB, requestLoggerMB, settingsManager, ucManager) {
44
- this.authenticationCheckerMB = authenticationCheckerMB;
35
+ constructor(entrypointsBuilder, environmentManager, helmetMB, logger, serverRequestHandler, serverSSLCertLoader, settingsManager, ucManager) {
45
36
  this.entrypointsBuilder = entrypointsBuilder;
46
37
  this.environmentManager = environmentManager;
47
- this.errorMB = errorMB;
48
- this.fsManager = fsManager;
49
38
  this.helmetMB = helmetMB;
50
39
  this.logger = logger;
51
- this.publicApiKeyCheckerMB = publicApiKeyCheckerMB;
52
- this.requestCheckerMB = requestCheckerMB;
53
- this.requestHandlerMB = requestHandlerMB;
54
- this.requestLoggerMB = requestLoggerMB;
40
+ this.serverRequestHandler = serverRequestHandler;
41
+ this.serverSSLCertLoader = serverSSLCertLoader;
55
42
  this.settingsManager = settingsManager;
56
43
  this.ucManager = ucManager;
57
44
  }
@@ -60,8 +47,6 @@ let NodeExpressServerManager = class NodeExpressServerManager {
60
47
  logger_level: this.settingsManager.get()('logger_level'),
61
48
  server_binding_host: this.settingsManager.get()('server_binding_host'),
62
49
  server_binding_port: this.settingsManager.get()('server_binding_port'),
63
- server_ssl_fullchain_path: this.settingsManager.get()('server_ssl_fullchain_path'),
64
- server_ssl_key_path: this.settingsManager.get()('server_ssl_key_path'),
65
50
  server_tmp_path: this.settingsManager.get()('server_tmp_path'),
66
51
  };
67
52
  }
@@ -88,29 +73,29 @@ let NodeExpressServerManager = class NodeExpressServerManager {
88
73
  this.runtime.use(express.json());
89
74
  this.runtime.use(express.urlencoded({ extended: true }));
90
75
  this.runtime.use(cookieParser());
91
- this.runtime.use(this.requestLoggerMB.exec({}));
92
- this.runtime.use(this.requestCheckerMB.exec({}));
93
76
  await this.createServer();
94
77
  }
95
78
  async mount(appManifest, ucd, contract) {
96
- const { sec } = ucd;
97
79
  const { envelope, method, path, pathAliases } = contract;
98
80
  const httpMethod = method.toLowerCase();
99
- const handlers = [
100
- this.publicApiKeyCheckerMB.exec({
101
- checkType: sec?.publicApiKeyCheckType,
102
- }),
103
- this.authenticationCheckerMB.exec({ appManifest, ucd }),
104
- this.requestHandlerMB.exec({
81
+ const handler = async (req, res) => {
82
+ const { body, status } = await this.serverRequestHandler.exec({
105
83
  appManifest,
106
84
  envelope,
85
+ req: this.toReq(req),
86
+ res: this.toRes(res),
107
87
  ucd,
108
88
  ucManager: this.ucManager,
109
- }),
110
- ];
111
- this.runtime[httpMethod](path, handlers);
89
+ });
90
+ if (!body) {
91
+ res.status(status).send();
92
+ return;
93
+ }
94
+ res.status(status).send(body);
95
+ };
96
+ this.runtime[httpMethod](path, handler);
112
97
  for (const pathAlias of pathAliases) {
113
- this.runtime[httpMethod](pathAlias, handlers);
98
+ this.runtime[httpMethod](pathAlias, handler);
114
99
  }
115
100
  }
116
101
  async mountStaticDir(dirPath) {
@@ -124,26 +109,10 @@ let NodeExpressServerManager = class NodeExpressServerManager {
124
109
  });
125
110
  }
126
111
  async stop() {
127
- if (!this.server?.listening) {
128
- return;
129
- }
130
- // As stated in the docs of `close`, only awaiting `.close` is not enough to make sure all the connections are closed.
131
- // Hence the wrapping in a promise, where the callback is called when the 'close' event is emitted.
132
- return new Promise((resolve, reject) => {
133
- if (!this.server) {
134
- return resolve();
135
- }
136
- this.server.close((err) => {
137
- if (err) {
138
- return reject(err);
139
- }
140
- resolve();
141
- });
142
- });
112
+ await stop(this.server);
143
113
  }
144
114
  async warmUp() {
145
- // Always at the "almost" last position to handle all the errors (from other middlewares and request handlers)
146
- this.runtime.use(this.errorMB.exec({}));
115
+ // Nothing to do
147
116
  }
148
117
  async createServer() {
149
118
  const port = this.s().server_binding_port;
@@ -153,37 +122,81 @@ let NodeExpressServerManager = class NodeExpressServerManager {
153
122
  return;
154
123
  }
155
124
  this.logger.info('Creating HTTPS server', { port });
156
- const fullchainPath = this.s().server_ssl_fullchain_path;
157
- const keyPath = this.s().server_ssl_key_path;
158
- if (!fullchainPath || !keyPath) {
159
- throw new Error('You must provide server_ssl_fullchain_path and server_ssl_key_path to start on secure port 443');
160
- }
161
- const credentials = {
162
- cert: await this.fsManager.cat(fullchainPath),
163
- key: await this.fsManager.cat(keyPath),
164
- };
125
+ const credentials = await this.serverSSLCertLoader.exec(undefined);
165
126
  this.server = https.createServer(credentials, this.runtime);
166
127
  }
128
+ toFile(f) {
129
+ return {
130
+ name: f.name,
131
+ path: f.tempFilePath,
132
+ type: f.mimetype,
133
+ };
134
+ }
135
+ toReq(req) {
136
+ return {
137
+ bodyFromFormData: async () => {
138
+ // Since express v5, if the request contains only a file, the `req.body` returns `undefined`
139
+ const input = req.body ?? {};
140
+ // files is present when using express-fileupload
141
+ if ('files' in req && req.files) {
142
+ for (const [field, value] of Object.entries(req.files)) {
143
+ input[field] = Array.isArray(value)
144
+ ? value.map(this.toFile)
145
+ : this.toFile(value);
146
+ }
147
+ }
148
+ for (const [k, v] of Object.entries(input)) {
149
+ const isMultiple = k.endsWith('[]'); // e.g. 'tags[]': 'Electronic'
150
+ const key = isMultiple ? k.replaceAll('[]', '') : k;
151
+ if (isMultiple) {
152
+ input[key] = Array.isArray(v) ? v : [v];
153
+ }
154
+ else {
155
+ input[key] = v;
156
+ }
157
+ }
158
+ return input;
159
+ },
160
+ bodyFromJSON: async () => req.body,
161
+ bodyFromQueryParams: async () => req.query,
162
+ bodyRaw: req.body,
163
+ cookie: (name) => req.cookies[name],
164
+ header: async (name) => {
165
+ const h = req.headers[name.toLowerCase()];
166
+ if (Array.isArray(h)) {
167
+ this.logger.warn(`Multiple headers found for ${name}. Returning the first one.`);
168
+ return h[0];
169
+ }
170
+ return h;
171
+ },
172
+ method: req.method,
173
+ secure: req.secure,
174
+ url: req.url,
175
+ };
176
+ }
177
+ toRes(res) {
178
+ return {
179
+ clearCookie: async (name) => {
180
+ res.clearCookie(name);
181
+ },
182
+ redirect: async (location) => res.redirect(location),
183
+ setCookie: async ({ name, opts, val }) => {
184
+ res.cookie(name, val, opts);
185
+ },
186
+ };
187
+ }
167
188
  };
168
189
  NodeExpressServerManager = __decorate([
169
190
  injectable(),
170
- __param(0, inject(AuthenticationCheckerMiddlewareBuilder)),
171
- __param(1, inject(EntrypointsBuilder)),
172
- __param(2, inject('EnvironmentManager')),
173
- __param(3, inject(ErrorMiddlewareBuilder)),
174
- __param(4, inject('FSManager')),
175
- __param(5, inject(HelmetMiddlewareBuilder)),
176
- __param(6, inject('Logger')),
177
- __param(7, inject(PublicApiKeyCheckerMiddlewareBuilder)),
178
- __param(8, inject(RequestCheckerMiddlewareBuilder)),
179
- __param(9, inject(RequestHandlerMiddlewareBuilder)),
180
- __param(10, inject(RequestLoggerMiddlewareBuilder)),
181
- __param(11, inject('SettingsManager')),
182
- __param(12, inject('UCManager')),
183
- __metadata("design:paramtypes", [AuthenticationCheckerMiddlewareBuilder,
184
- EntrypointsBuilder, Object, ErrorMiddlewareBuilder, Object, HelmetMiddlewareBuilder, Object, PublicApiKeyCheckerMiddlewareBuilder,
185
- RequestCheckerMiddlewareBuilder,
186
- RequestHandlerMiddlewareBuilder,
187
- RequestLoggerMiddlewareBuilder, Object, Object])
191
+ __param(0, inject(EntrypointsBuilder)),
192
+ __param(1, inject('EnvironmentManager')),
193
+ __param(2, inject(HelmetMiddlewareBuilder)),
194
+ __param(3, inject('Logger')),
195
+ __param(4, inject(ServerRequestHandler)),
196
+ __param(5, inject(ServerSSLCertLoader)),
197
+ __param(6, inject('SettingsManager')),
198
+ __param(7, inject('UCManager')),
199
+ __metadata("design:paramtypes", [EntrypointsBuilder, Object, HelmetMiddlewareBuilder, Object, ServerRequestHandler,
200
+ ServerSSLCertLoader, Object, Object])
188
201
  ], NodeExpressServerManager);
189
202
  export { NodeExpressServerManager };
@@ -78,7 +78,8 @@ let RequestHandlerMiddlewareBuilder = class RequestHandlerMiddlewareBuilder {
78
78
  fillUCFromReq(req, envelope, uc) {
79
79
  switch (envelope) {
80
80
  case 'form-data': {
81
- const input = req.body;
81
+ // Since express v5, if the request contains only a file, the `req.body` returns `undefined`
82
+ const input = req.body ?? {};
82
83
  // files is present when using express-fileupload
83
84
  if ('files' in req && req.files) {
84
85
  for (const [field, value] of Object.entries(req.files)) {
@@ -307,13 +307,13 @@ let AppTester = class AppTester {
307
307
  ...appI18n[l],
308
308
  };
309
309
  }
310
- container.rebind('I18n').toConstantValue(productI18n);
310
+ (await container.rebind('I18n')).toConstantValue(productI18n);
311
311
  }
312
312
  async bindServerClientSettings(serverClientSettings) {
313
313
  const { container } = this.ctx;
314
314
  const opts = optsAllSet(this.ctx.opts);
315
315
  const existingSettings = container.get('Settings');
316
- container.rebind('Settings').toConstantValue({
316
+ (await container.rebind('Settings')).toConstantValue({
317
317
  ...existingSettings,
318
318
  ...serverClientSettings,
319
319
  logger_level: opts.logger_level,
@@ -26,7 +26,7 @@ let VitestAppTestSuiteRunner = class VitestAppTestSuiteRunner {
26
26
  const args = [
27
27
  'run',
28
28
  '--dir',
29
- './examples/apps/Spotify',
29
+ appPath,
30
30
  ];
31
31
  if (!skipCoverage) {
32
32
  args.push('--coverage.enabled', '--coverage.exclude', testPath, '--coverage.include', appPath, '--coverage.reportsDirectory', this.coverageReportPath(appPath));
@@ -41,7 +41,7 @@ export async function newNodeAppTester(serverPortRangeStart, idx, args) {
41
41
  container
42
42
  .bind('UCDefASTParser')
43
43
  .to(TypeScriptLibUCDefASTParser);
44
- const tester = container.resolve(AppTester);
44
+ const tester = container.get(AppTester);
45
45
  const jwtManager = container.get('JWTManager');
46
46
  const apiKey = settings.server_private_api_key_entries[0];
47
47
  const [basicAuth] = Object.entries(settings.server_basic_auth_entries);
@@ -1,4 +1,4 @@
1
- import { type interfaces } from 'inversify';
1
+ import { type Provider } from 'inversify';
2
2
  import type { ClockManager, CryptoManager, Logger } from '../../std/index.js';
3
3
  import type { UC } from '../UC.js';
4
4
  import type { UCClientConfirmManager } from '../client.js';
@@ -26,7 +26,7 @@ export declare class SimpleUCManager implements UCManager {
26
26
  private ucInitProvider;
27
27
  private ucMainProvider;
28
28
  private tx?;
29
- constructor(ucClientConfirmManager: UCClientConfirmManager, clockManager: ClockManager, cryptoManager: CryptoManager, logger: Logger, ucDataStore: UCDataStore, ucExecChecker: UCExecChecker, ucInputFilesProcessor: UCInputFilesProcessor, ucInputValidator: UCInputValidator, ucInitProvider: interfaces.Provider<UCInit>, ucMainProvider: interfaces.Provider<UCMain>);
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
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>>;
@@ -7,7 +7,7 @@ const ACTION_HTTP_METHOD_MAPPING = {
7
7
  Update: 'PUT',
8
8
  View: 'GET',
9
9
  };
10
- const METHODS_WITH_NO_BODY = ['GET'];
10
+ const METHODS_WITH_NO_BODY = ['GET', 'HEAD'];
11
11
  export function ucHTTPContract(uc, pathPrefix = '/api/v1') {
12
12
  const { ext, metadata } = uc.def;
13
13
  const { action } = metadata;
@@ -1,4 +1,4 @@
1
- import { type interfaces } from 'inversify';
1
+ import { type Provider } from 'inversify';
2
2
  import type { ProductManifest } from '../../product/index.js';
3
3
  import type { Configurable, SettingsManager, Worker } from '../../std/index.js';
4
4
  import type { UC } from '../UC.js';
@@ -17,7 +17,7 @@ export declare class UCExecChecker implements Configurable<S>, Worker<Input, Pro
17
17
  private productManifest;
18
18
  private settingsManager;
19
19
  private ucPolicyProvider;
20
- constructor(productManifest: ProductManifest, settingsManager: SettingsManager<S>, ucPolicyProvider: interfaces.Provider<UCPolicy>);
20
+ constructor(productManifest: ProductManifest, settingsManager: SettingsManager<S>, ucPolicyProvider: Provider<UCPolicy>);
21
21
  s(): S;
22
22
  exec<I extends UCInput | undefined = undefined, OPI0 extends UCOPIBase | undefined = undefined, OPI1 extends UCOPIBase | undefined = undefined>({ lifecycle, uc }: Input<I, OPI0, OPI1>): Promise<Output>;
23
23
  }
@@ -61,12 +61,15 @@ let UCInputFilesProcessor = class UCInputFilesProcessor {
61
61
  const prefix = this.clockManager.nowToKey();
62
62
  const fileName = `${prefix}-${this.cryptoManager.randomUUID()}.${extension}`; // => 20230110143732-155eb8d3-9af5-430e-b856-248007859df1.jpg
63
63
  const fileNameRef = `${this.s().uc_file_ref_prefix}${fileName}`; // => $ref:20230110143732-155eb8d3-9af5-430e-b856-248007859df1.jpg
64
- // When calling this client side, we still have a https://developer.mozilla.org/en-US/docs/Web/API/File
65
- // Consider moving this logic to UCManager.execClient ?
66
- // TODO : Improve client/server input files management
67
- const path = file instanceof File ? file.name : file.path;
68
- await this.fsManager.cp(path, this.fsManager.path(this.s().uc_files_directory_path, fileName)); // => /path/to/files/20230110143732-155eb8d3-9af5-430e-b856-248007859df1.jpg
69
- await this.fsManager.rm(path);
64
+ const destPath = this.fsManager.path(this.s().uc_files_directory_path, fileName); // => /path/to/files/20230110143732-155eb8d3-9af5-430e-b856-248007859df1.jpg
65
+ if (file instanceof File) {
66
+ await this.fsManager.touch(destPath, await file.arrayBuffer());
67
+ }
68
+ else {
69
+ const { path } = file;
70
+ await this.fsManager.cp(path, destPath);
71
+ await this.fsManager.rm(path);
72
+ }
70
73
  return fileNameRef;
71
74
  }
72
75
  };
@@ -11,9 +11,9 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  return function (target, key) { decorator(target, key, paramIndex); }
12
12
  };
13
13
  import { inject, injectable } from 'inversify';
14
- import { toFormData } from './toFormData.js';
15
- import { toJSON } from './toJSON.js';
16
- import { toQueryParams } from './toQueryParams.js';
14
+ import { toFormData } from './form-data.js';
15
+ import { toJSON } from './json.js';
16
+ import { toQueryParams } from './query-params.js';
17
17
  let HTTPRequestBuilder = class HTTPRequestBuilder {
18
18
  formDataBuilder;
19
19
  constructor(formDataBuilder) {
@@ -0,0 +1,4 @@
1
+ import type { FormDataBuilder } from '../../std/index.js';
2
+ import type { HTTPReqData } from './types.js';
3
+ export declare function fromFormData<T extends HTTPReqData>(fd: FormData): Promise<T>;
4
+ export declare function toFormData<T extends HTTPReqData>(data: T | undefined | null, fd: FormData, formDataBuilder: FormDataBuilder): Promise<void>;
@@ -0,0 +1,20 @@
1
+ import { appendData } from './appendData.js';
2
+ export async function fromFormData(fd) {
3
+ // biome-ignore lint/suspicious/noExplicitAny: can be anything
4
+ const data = {};
5
+ for (const [k, v] of fd) {
6
+ const isMultiple = k.endsWith('[]'); // e.g. 'tags[]': 'Electronic'
7
+ const key = isMultiple ? k.replaceAll('[]', '') : k;
8
+ const value = data[key];
9
+ if (value === undefined) {
10
+ data[key] = isMultiple ? [v] : v;
11
+ }
12
+ else if (Array.isArray(value)) {
13
+ value.push(v);
14
+ }
15
+ }
16
+ return data;
17
+ }
18
+ export async function toFormData(data, fd, formDataBuilder) {
19
+ await appendData(data, (k, v) => formDataBuilder.append(fd, k, v));
20
+ }
@@ -0,0 +1,3 @@
1
+ import type { JSONString } from '../../dt/index.js';
2
+ import type { HTTPReqData } from './types.js';
3
+ export declare function toJSON<T extends HTTPReqData>(data: T | undefined | null): JSONString;
@@ -0,0 +1,3 @@
1
+ export function toJSON(data) {
2
+ return JSON.stringify(data);
3
+ }
@@ -0,0 +1,3 @@
1
+ import type { HTTPReqData } from './types.js';
2
+ export declare function fromQueryParams(url: URL): Promise<HTTPReqData>;
3
+ export declare function toQueryParams<T extends HTTPReqData>(data: T | undefined | null, url: URL): Promise<void>;
@@ -0,0 +1,20 @@
1
+ import { appendData } from './appendData.js';
2
+ export async function fromQueryParams(url) {
3
+ // biome-ignore lint/suspicious/noExplicitAny: can be anything
4
+ const data = {};
5
+ for (const [k, v] of url.searchParams) {
6
+ const isMultiple = k.endsWith('[]');
7
+ const key = isMultiple ? k.replaceAll('[]', '') : k;
8
+ const value = data[key];
9
+ if (value === undefined) {
10
+ data[key] = isMultiple ? [v] : v;
11
+ }
12
+ else if (Array.isArray(value)) {
13
+ value.push(v);
14
+ }
15
+ }
16
+ return data;
17
+ }
18
+ export async function toQueryParams(data, url) {
19
+ await appendData(data, async (k, v) => url.searchParams.append(k, v));
20
+ }
@@ -2,7 +2,10 @@ export { sleep } from './async/sleep.js';
2
2
  export type { Clearable } from './concerns/Clearable.js';
3
3
  export type { Initializable } from './concerns/Initializable.js';
4
4
  export type { SrcImporter } from './esm/srcImporter.js';
5
+ export { fromFormData, toFormData } from './http/form-data.js';
5
6
  export { HTTPRequestBuilder } from './http/HTTPRequestBuilder.js';
7
+ export { toJSON } from './http/json.js';
8
+ export { fromQueryParams, toQueryParams } from './http/query-params.js';
6
9
  export type { HTTPDataEnvelope, HTTPReqData } from './http/types.js';
7
10
  export { bindProvider } from './ioc/bindProvider.js';
8
11
  export { CONTAINER_OPTS } from './ioc/container.js';
@@ -1,5 +1,8 @@
1
1
  export { sleep } from './async/sleep.js';
2
+ export { fromFormData, toFormData } from './http/form-data.js';
2
3
  export { HTTPRequestBuilder } from './http/HTTPRequestBuilder.js';
4
+ export { toJSON } from './http/json.js';
5
+ export { fromQueryParams, toQueryParams } from './http/query-params.js';
3
6
  export { bindProvider } from './ioc/bindProvider.js';
4
7
  export { CONTAINER_OPTS } from './ioc/container.js';
5
8
  export { fmt as fmtNumber } from './numbers/fmt.js';
@@ -5,6 +5,6 @@ export function bindNodeCLI(container) {
5
5
  container.bind('PromptManager').to(NodePromptManager);
6
6
  // uc
7
7
  container
8
- .rebind('UCClientConfirmManager')
8
+ .rebindSync('UCClientConfirmManager')
9
9
  .to(PromptUCClientConfirmManager);
10
10
  }
@@ -12,6 +12,6 @@ export function bindNodeCore(container) {
12
12
  .to(NodeEnvironmentManager);
13
13
  container.bind('FSManager').to(NodeFSManager);
14
14
  container
15
- .rebind('FormDataBuilder')
15
+ .rebindSync('FormDataBuilder')
16
16
  .to(NodeFormDataBuilder);
17
17
  }
@@ -1,8 +1,8 @@
1
1
  export function bindProduct(container, productManifest, productI18n) {
2
2
  // Using rebind because these are already bound by default in {@link bindCommon}.
3
3
  // The goal is to make it easier to get started for a user.
4
- container.rebind('I18n').toConstantValue(productI18n);
4
+ container.rebindSync('I18n').toConstantValue(productI18n);
5
5
  container
6
- .rebind('ProductManifest')
6
+ .rebindSync('ProductManifest')
7
7
  .toConstantValue(productManifest);
8
8
  }
@@ -3,7 +3,7 @@ export function bindProvider(container, identifier) {
3
3
  .bind(`Provider<${identifier}>`)
4
4
  .toProvider((context) => {
5
5
  return async (clazz) => {
6
- return context.container.resolve(clazz);
6
+ return context.get(clazz);
7
7
  };
8
8
  });
9
9
  }
@@ -9,6 +9,6 @@ export function bindWeb(container) {
9
9
  container.bind('FSManager').to(WebFSManager);
10
10
  // uc
11
11
  container
12
- .rebind('UCClientConfirmManager')
12
+ .rebindSync('UCClientConfirmManager')
13
13
  .to(WebUCClientConfirmManager);
14
14
  }
@@ -1,2 +1,2 @@
1
- import type { interfaces } from 'inversify';
2
- export declare const CONTAINER_OPTS: interfaces.ContainerOptions;
1
+ import type { ContainerOptions } from 'inversify';
2
+ export declare const CONTAINER_OPTS: ContainerOptions;
@@ -2,8 +2,7 @@ export const CONTAINER_OPTS = {
2
2
  /**
3
3
  * Allows us to avoid binding concrete classes
4
4
  *
5
- * @see https://github.com/inversify/InversifyJS/issues/1302
6
- * @see https://github.com/inversify/InversifyJS/blob/master/wiki/container_api.md#autobindinjectable
5
+ * @see https://inversify.io/docs/fundamentals/binding/#autobinding
7
6
  */
8
- autoBindInjectable: true,
7
+ autobind: true,
9
8
  };
@@ -1,2 +1,2 @@
1
- import type { interfaces } from 'inversify';
2
- export type Class<T> = interfaces.Newable<T>;
1
+ import type { Newable } from 'inversify';
2
+ export type Class<T> = Newable<T>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "libmodulor",
3
3
  "description": "A TypeScript library to create business oriented applications",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "Chafik H'nini <chafik.hnini@gmail.com>",
7
7
  "homepage": "https://github.com/c100k/libmodulor#readme",
@@ -29,6 +29,9 @@
29
29
  "./locales/fr": {
30
30
  "import": "./dist/esm/i18n/locales/fr.js"
31
31
  },
32
+ "./nextjs": {
33
+ "import": "./dist/esm/index.nextjs.js"
34
+ },
32
35
  "./node-express": {
33
36
  "import": "./dist/esm/index.node-express.js"
34
37
  },
@@ -82,21 +85,22 @@
82
85
  "cookie-parser": "^1.4.7",
83
86
  "express": "^5.1.0",
84
87
  "express-fileupload": "^1.5.1",
85
- "fast-check": "^4.0.1",
88
+ "fast-check": "^4.1.0",
86
89
  "helmet": "^8.1.0",
87
90
  "hono": "^4.7.5",
88
- "inversify": "^6.2.2",
91
+ "inversify": "^7.5.0",
89
92
  "jose": "^6.0.10",
90
93
  "knex": "^3.1.0",
94
+ "next": "^15.3.0",
91
95
  "pg": "^8.14.1",
92
96
  "react": "^19.1.0",
93
97
  "react-dom": "^19.1.0",
94
- "react-native": "^0.78.1",
98
+ "react-native": "^0.78.2",
95
99
  "reflect-metadata": "^0.2.2",
96
100
  "sqlite3": "^5.1.7",
97
- "typescript": "^5.8.2",
98
- "vite": "^6.2.3",
99
- "vitest": "^3.0.9"
101
+ "typescript": "^5.8.3",
102
+ "vite": "^6.2.5",
103
+ "vitest": "^3.1.1"
100
104
  },
101
105
  "peerDependenciesMeta": {
102
106
  "@hono/node-server": {
@@ -132,6 +136,9 @@
132
136
  "knex": {
133
137
  "optional": true
134
138
  },
139
+ "next": {
140
+ "optional": true
141
+ },
135
142
  "jose": {
136
143
  "optional": true
137
144
  },