proteum 2.1.3-1 → 2.1.6

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 (95) hide show
  1. package/AGENTS.md +22 -14
  2. package/README.md +109 -17
  3. package/agents/project/AGENTS.md +188 -25
  4. package/agents/project/CODING_STYLE.md +1 -0
  5. package/agents/project/client/AGENTS.md +13 -8
  6. package/agents/project/client/pages/AGENTS.md +17 -9
  7. package/agents/project/diagnostics.md +52 -0
  8. package/agents/project/optimizations.md +48 -0
  9. package/agents/project/server/routes/AGENTS.md +9 -6
  10. package/agents/project/server/services/AGENTS.md +10 -6
  11. package/agents/project/tests/AGENTS.md +11 -5
  12. package/cli/app/config.ts +13 -14
  13. package/cli/app/index.ts +58 -0
  14. package/cli/commands/connect.ts +45 -0
  15. package/cli/commands/dev.ts +26 -11
  16. package/cli/commands/diagnose.ts +286 -0
  17. package/cli/commands/doctor.ts +18 -5
  18. package/cli/commands/explain.ts +25 -0
  19. package/cli/commands/perf.ts +243 -0
  20. package/cli/commands/trace.ts +9 -1
  21. package/cli/commands/verify.ts +281 -0
  22. package/cli/compiler/artifacts/connectedProjects.ts +453 -0
  23. package/cli/compiler/artifacts/controllers.ts +198 -49
  24. package/cli/compiler/artifacts/discovery.ts +0 -34
  25. package/cli/compiler/artifacts/manifest.ts +90 -6
  26. package/cli/compiler/artifacts/routing.ts +2 -2
  27. package/cli/compiler/artifacts/services.ts +277 -130
  28. package/cli/compiler/client/index.ts +3 -0
  29. package/cli/compiler/common/files/style.ts +52 -0
  30. package/cli/compiler/common/generatedRouteModules.ts +34 -5
  31. package/cli/compiler/common/scripts.ts +11 -5
  32. package/cli/compiler/index.ts +2 -1
  33. package/cli/compiler/server/index.ts +3 -0
  34. package/cli/presentation/commands.ts +110 -7
  35. package/cli/presentation/devSession.ts +32 -7
  36. package/cli/runtime/commands.ts +165 -6
  37. package/cli/scaffold/index.ts +14 -25
  38. package/cli/scaffold/templates.ts +41 -27
  39. package/cli/utils/agents.ts +4 -2
  40. package/cli/utils/keyboard.ts +8 -0
  41. package/client/dev/profiler/ApexChart.tsx +66 -0
  42. package/client/dev/profiler/index.tsx +2508 -302
  43. package/client/dev/profiler/runtime.noop.ts +12 -0
  44. package/client/dev/profiler/runtime.ts +195 -4
  45. package/client/services/router/request/api.ts +6 -1
  46. package/common/applicationConfig.ts +173 -0
  47. package/common/applicationConfigLoader.ts +102 -0
  48. package/common/connectedProjects.ts +113 -0
  49. package/common/dev/connect.ts +267 -0
  50. package/common/dev/console.ts +31 -0
  51. package/common/dev/contractsDoctor.ts +128 -0
  52. package/common/dev/diagnostics.ts +59 -15
  53. package/common/dev/inspection.ts +491 -0
  54. package/common/dev/performance.ts +809 -0
  55. package/common/dev/profiler.ts +3 -0
  56. package/common/dev/proteumManifest.ts +31 -6
  57. package/common/dev/requestTrace.ts +52 -1
  58. package/common/env/proteumEnv.ts +176 -50
  59. package/common/router/index.ts +1 -0
  60. package/common/router/request/api.ts +2 -0
  61. package/config.ts +5 -0
  62. package/docs/dev-commands.md +5 -1
  63. package/docs/dev-sessions.md +90 -0
  64. package/docs/diagnostics.md +74 -11
  65. package/docs/request-tracing.md +50 -3
  66. package/package.json +1 -1
  67. package/server/app/container/config.ts +16 -87
  68. package/server/app/container/console/index.ts +42 -8
  69. package/server/app/container/index.ts +3 -1
  70. package/server/app/container/trace/index.ts +105 -0
  71. package/server/app/devDiagnostics.ts +138 -0
  72. package/server/app/index.ts +18 -8
  73. package/server/app/service/container.ts +0 -12
  74. package/server/app/service/index.ts +0 -2
  75. package/server/services/prisma/index.ts +121 -4
  76. package/server/services/router/http/index.ts +266 -0
  77. package/server/services/router/index.ts +50 -47
  78. package/server/services/router/request/api.ts +160 -19
  79. package/server/services/router/request/index.ts +8 -0
  80. package/server/services/router/response/index.ts +23 -1
  81. package/server/services/router/response/page/document.tsx +5 -0
  82. package/server/services/router/response/page/index.tsx +10 -0
  83. package/agents/framework/AGENTS.md +0 -177
  84. package/server/services/auth/router/service.json +0 -6
  85. package/server/services/auth/service.json +0 -6
  86. package/server/services/cron/service.json +0 -6
  87. package/server/services/disks/drivers/local/service.json +0 -6
  88. package/server/services/disks/drivers/s3/service.json +0 -6
  89. package/server/services/disks/service.json +0 -6
  90. package/server/services/fetch/service.json +0 -7
  91. package/server/services/prisma/service.json +0 -6
  92. package/server/services/router/service.json +0 -6
  93. package/server/services/schema/router/service.json +0 -6
  94. package/server/services/schema/service.json +0 -6
  95. package/server/services/security/encrypt/aes/service.json +0 -6
@@ -24,9 +24,17 @@ import type CronManager from '@server/services/cron';
24
24
  import type CronTask from '@server/services/cron/CronTask';
25
25
  import type { TBasicUser } from '@server/services/auth';
26
26
  import type { TServerRouter } from '..';
27
+ import type { TDevConsoleLogLevel } from '@common/dev/console';
28
+ import type { TPerfGroupBy } from '@common/dev/performance';
27
29
  import type { TDevSessionStartResponse, TDevSessionUserSummary } from '@common/dev/session';
28
30
  import { serverHotReloadMessageType } from '@common/dev/serverHotReload';
29
31
  import { explainSectionNames } from '@common/dev/diagnostics';
32
+ import {
33
+ connectedProjectHealthPath,
34
+ connectedProjectProxyPathPrefix,
35
+ parseConnectedProjectProxyPath,
36
+ } from '@common/connectedProjects';
37
+ import { profilerTraceRequestIdHeader } from '@common/dev/profiler';
30
38
 
31
39
  // Middlewaees (core)
32
40
  import { isMutipart, MiddlewareFormData } from './multipart';
@@ -138,6 +146,127 @@ export default class HttpServer<TRouter extends TServerRouter = TServerRouter> {
138
146
  };
139
147
  }
140
148
 
149
+ private async verifyConnectedProjectsBeforeStart() {
150
+ for (const connectedProject of Object.values(this.app.connectedProjects || {})) {
151
+ const healthUrl = new URL(connectedProjectHealthPath, connectedProject.urlInternal).toString();
152
+
153
+ let response: Response;
154
+ try {
155
+ response = await fetch(healthUrl, {
156
+ headers: { Accept: 'application/json' },
157
+ });
158
+ } catch (error) {
159
+ throw new Error(
160
+ `Connected project "${connectedProject.namespace}" is unreachable at ${connectedProject.urlInternal}. ${error instanceof Error ? error.message : String(error)}`,
161
+ );
162
+ }
163
+
164
+ if (!response.ok) {
165
+ throw new Error(
166
+ `Connected project "${connectedProject.namespace}" health check failed at ${healthUrl} with status ${response.status}.`,
167
+ );
168
+ }
169
+ }
170
+ }
171
+
172
+ private registerConnectedProjectRoutes(routes: express.Express) {
173
+ routes.get(connectedProjectHealthPath, (_req, res) => {
174
+ res.json({
175
+ connectedProjects: Object.keys(this.app.connectedProjects || {}),
176
+ identifier: this.app.identity.identifier,
177
+ ok: true,
178
+ });
179
+ });
180
+
181
+ routes.all(`${connectedProjectHealthPath}/*`, (_req, res) => {
182
+ res.status(404).json({ error: 'Unknown Proteum connected-project route.' });
183
+ });
184
+
185
+ routes.all(`${connectedProjectProxyPathPrefix}/:namespace/*`, async (req, res, next) => {
186
+ const parsed = parseConnectedProjectProxyPath(req.path);
187
+ if (!parsed) {
188
+ next();
189
+ return;
190
+ }
191
+
192
+ const connectedProject = this.app.connectedProjects?.[parsed.namespace];
193
+ if (!connectedProject) {
194
+ res.status(404).json({ error: `Unknown connected project "${parsed.namespace}".` });
195
+ return;
196
+ }
197
+
198
+ const search = new URL(req.originalUrl, 'http://proteum.local').search;
199
+ const targetUrl = new URL(`${parsed.httpPath}${search}`, connectedProject.urlInternal).toString();
200
+ const headers = new Headers();
201
+
202
+ for (const [key, value] of Object.entries(req.headers)) {
203
+ if (!value) continue;
204
+ if (key === 'content-length' || key === 'host') continue;
205
+ headers.set(key, Array.isArray(value) ? value.join(', ') : String(value));
206
+ }
207
+
208
+ if (!headers.has('accept')) headers.set('accept', 'application/json');
209
+
210
+ const init: RequestInit = {
211
+ method: req.method,
212
+ headers,
213
+ };
214
+
215
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
216
+ if (req.files && Object.keys(req.files).length > 0) {
217
+ res.status(501).json({
218
+ error: `Connected project proxy does not support multipart payloads for ${parsed.namespace} yet.`,
219
+ });
220
+ return;
221
+ }
222
+
223
+ const contentType = String(req.headers['content-type'] || '').toLowerCase();
224
+
225
+ if (contentType.includes('application/json')) {
226
+ headers.set('content-type', 'application/json');
227
+ init.body = JSON.stringify(req.body || {});
228
+ } else if (contentType.includes('application/x-www-form-urlencoded')) {
229
+ headers.set('content-type', 'application/x-www-form-urlencoded');
230
+ init.body = new URLSearchParams(req.body as Record<string, string>).toString();
231
+ } else {
232
+ headers.delete('content-type');
233
+ init.body = undefined;
234
+ }
235
+ }
236
+
237
+ let response: Response;
238
+ try {
239
+ response = await fetch(targetUrl, init);
240
+ } catch (error) {
241
+ res.status(502).json({
242
+ error: `Failed to proxy connected request to ${connectedProject.namespace}. ${error instanceof Error ? error.message : String(error)}`,
243
+ });
244
+ return;
245
+ }
246
+
247
+ const traceRequestId = response.headers.get(profilerTraceRequestIdHeader);
248
+ if (traceRequestId) res.setHeader(profilerTraceRequestIdHeader, traceRequestId);
249
+
250
+ const setCookie = typeof (response.headers as Headers & { getSetCookie?: () => string[] }).getSetCookie === 'function'
251
+ ? (response.headers as Headers & { getSetCookie: () => string[] }).getSetCookie()
252
+ : [];
253
+ if (setCookie.length > 0) {
254
+ res.setHeader('set-cookie', setCookie);
255
+ }
256
+
257
+ const contentType = response.headers.get('content-type') || '';
258
+ res.status(response.status);
259
+ if (contentType) res.setHeader('content-type', contentType);
260
+
261
+ if (contentType.includes('application/json')) {
262
+ res.json(await response.json());
263
+ return;
264
+ }
265
+
266
+ res.send(await response.text());
267
+ });
268
+ }
269
+
141
270
  /*----------------------------------
142
271
  - HOOKS
143
272
  ----------------------------------*/
@@ -180,6 +309,7 @@ export default class HttpServer<TRouter extends TServerRouter = TServerRouter> {
180
309
  );
181
310
  routes.use(apiMultipartOnly(MiddlewareFormData));
182
311
  if (this.config.cors !== undefined) routes.use(apiOnly(cors(this.config.cors)));
312
+ this.registerConnectedProjectRoutes(routes);
183
313
  routes.use(apiOnly(routeRequest));
184
314
 
185
315
  // Diverses protections (dont le disable x-powered-by)
@@ -268,6 +398,8 @@ export default class HttpServer<TRouter extends TServerRouter = TServerRouter> {
268
398
  /*----------------------------------
269
399
  - BOOT SERVICES
270
400
  ----------------------------------*/
401
+ await this.verifyConnectedProjectsBeforeStart();
402
+
271
403
  this.http.listen(this.config.port, () => {
272
404
  if (__DEV__ && typeof process.send === 'function') {
273
405
  process.send({
@@ -348,6 +480,17 @@ export default class HttpServer<TRouter extends TServerRouter = TServerRouter> {
348
480
  }
349
481
  });
350
482
 
483
+ routes.get('/__proteum/explain/owner', (req, res) => {
484
+ const query = Array.isArray(req.query.query) ? req.query.query[0] : req.query.query;
485
+
486
+ try {
487
+ res.json(this.app.getDevDiagnostics().explainOwner(typeof query === 'string' ? query : ''));
488
+ } catch (error) {
489
+ const message = error instanceof Error ? error.message : String(error);
490
+ res.status(message.includes('required') ? 400 : 500).json({ error: message });
491
+ }
492
+ });
493
+
351
494
  routes.get('/__proteum/doctor', (req, res) => {
352
495
  const rawStrict = Array.isArray(req.query.strict) ? req.query.strict[0] : req.query.strict;
353
496
  const strict = rawStrict === '1' || rawStrict === 'true';
@@ -359,6 +502,129 @@ export default class HttpServer<TRouter extends TServerRouter = TServerRouter> {
359
502
  }
360
503
  });
361
504
 
505
+ routes.get('/__proteum/doctor/contracts', (req, res) => {
506
+ const rawStrict = Array.isArray(req.query.strict) ? req.query.strict[0] : req.query.strict;
507
+ const strict = rawStrict === '1' || rawStrict === 'true';
508
+
509
+ try {
510
+ res.json(this.app.getDevDiagnostics().doctorContracts(strict));
511
+ } catch (error) {
512
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
513
+ }
514
+ });
515
+
516
+ routes.get('/__proteum/logs', (req, res) => {
517
+ const rawLimit = Array.isArray(req.query.limit) ? req.query.limit[0] : req.query.limit;
518
+ const rawLevel = Array.isArray(req.query.level) ? req.query.level[0] : req.query.level;
519
+ const limit = Math.max(0, Math.min(500, Number(rawLimit) || 100));
520
+ const level = typeof rawLevel === 'string' ? (rawLevel as TDevConsoleLogLevel) : 'log';
521
+
522
+ try {
523
+ res.json(this.app.getDevDiagnostics().readLogs(limit, level));
524
+ } catch (error) {
525
+ res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
526
+ }
527
+ });
528
+
529
+ routes.get('/__proteum/diagnose', (req, res) => {
530
+ const readString = (value: unknown) => (Array.isArray(value) ? value[0] : value);
531
+ const readNumber = (value: unknown, fallback: number) => {
532
+ const parsed = Number(readString(value));
533
+ return Number.isFinite(parsed) ? parsed : fallback;
534
+ };
535
+
536
+ try {
537
+ res.json(
538
+ this.app.getDevDiagnostics().diagnose({
539
+ logsLevel:
540
+ typeof readString(req.query.logsLevel) === 'string'
541
+ ? (readString(req.query.logsLevel) as TDevConsoleLogLevel)
542
+ : 'warn',
543
+ logsLimit: readNumber(req.query.logsLimit, 40),
544
+ path: typeof readString(req.query.path) === 'string' ? readString(req.query.path) : undefined,
545
+ query: typeof readString(req.query.query) === 'string' ? readString(req.query.query) : undefined,
546
+ requestId: typeof readString(req.query.requestId) === 'string' ? readString(req.query.requestId) : undefined,
547
+ strict: readString(req.query.strict) === '1' || readString(req.query.strict) === 'true',
548
+ }),
549
+ );
550
+ } catch (error) {
551
+ const message = error instanceof Error ? error.message : String(error);
552
+ res.status(message.includes('required') || message.includes('Diagnose requires') ? 400 : 500).json({ error: message });
553
+ }
554
+ });
555
+
556
+ routes.get('/__proteum/perf/top', (req, res) => {
557
+ const readString = (value: unknown) => (Array.isArray(value) ? value[0] : value);
558
+ const readNumber = (value: unknown, fallback: number) => {
559
+ const parsed = Number(readString(value));
560
+ return Number.isFinite(parsed) ? parsed : fallback;
561
+ };
562
+
563
+ try {
564
+ res.json(
565
+ this.app.getDevDiagnostics().perfTop({
566
+ groupBy: typeof readString(req.query.groupBy) === 'string' ? (readString(req.query.groupBy) as TPerfGroupBy) : undefined,
567
+ limit: readNumber(req.query.limit, 12),
568
+ since: typeof readString(req.query.since) === 'string' ? readString(req.query.since) : undefined,
569
+ }),
570
+ );
571
+ } catch (error) {
572
+ res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
573
+ }
574
+ });
575
+
576
+ routes.get('/__proteum/perf/compare', (req, res) => {
577
+ const readString = (value: unknown) => (Array.isArray(value) ? value[0] : value);
578
+ const readNumber = (value: unknown, fallback: number) => {
579
+ const parsed = Number(readString(value));
580
+ return Number.isFinite(parsed) ? parsed : fallback;
581
+ };
582
+
583
+ try {
584
+ res.json(
585
+ this.app.getDevDiagnostics().perfCompare({
586
+ baseline: typeof readString(req.query.baseline) === 'string' ? readString(req.query.baseline) : undefined,
587
+ groupBy: typeof readString(req.query.groupBy) === 'string' ? (readString(req.query.groupBy) as TPerfGroupBy) : undefined,
588
+ limit: readNumber(req.query.limit, 12),
589
+ target: typeof readString(req.query.target) === 'string' ? readString(req.query.target) : undefined,
590
+ }),
591
+ );
592
+ } catch (error) {
593
+ res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
594
+ }
595
+ });
596
+
597
+ routes.get('/__proteum/perf/memory', (req, res) => {
598
+ const readString = (value: unknown) => (Array.isArray(value) ? value[0] : value);
599
+ const readNumber = (value: unknown, fallback: number) => {
600
+ const parsed = Number(readString(value));
601
+ return Number.isFinite(parsed) ? parsed : fallback;
602
+ };
603
+
604
+ try {
605
+ res.json(
606
+ this.app.getDevDiagnostics().perfMemory({
607
+ groupBy: typeof readString(req.query.groupBy) === 'string' ? (readString(req.query.groupBy) as TPerfGroupBy) : undefined,
608
+ limit: readNumber(req.query.limit, 12),
609
+ since: typeof readString(req.query.since) === 'string' ? readString(req.query.since) : undefined,
610
+ }),
611
+ );
612
+ } catch (error) {
613
+ res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
614
+ }
615
+ });
616
+
617
+ routes.get('/__proteum/perf/request', (req, res) => {
618
+ const query = Array.isArray(req.query.query) ? req.query.query[0] : req.query.query;
619
+
620
+ try {
621
+ res.json(this.app.getDevDiagnostics().perfRequest(typeof query === 'string' ? query : ''));
622
+ } catch (error) {
623
+ const message = error instanceof Error ? error.message : String(error);
624
+ res.status(message.includes('Could not find') || message.includes('required') ? 404 : 400).json({ error: message });
625
+ }
626
+ });
627
+
362
628
  routes.get('/__proteum/cron/tasks', (_req, res) => {
363
629
  const cron = this.getCronManager();
364
630
  res.json({
@@ -72,6 +72,8 @@ type TGeneratedRouteModule = { filepath: string; register?: TRouteModule['__regi
72
72
 
73
73
  type TGeneratedControllerDefinition = {
74
74
  path: string;
75
+ filepath: string;
76
+ sourceLocation: { line: number; column: number };
75
77
  Controller: new (request: TRouterContext<TServerRouter>) => { [method: string]: () => any };
76
78
  method: string;
77
79
  };
@@ -114,6 +116,7 @@ export type Config<
114
116
  disk?: string; // Disk driver ID
115
117
 
116
118
  currentDomain: string;
119
+ defaultRouteOptions?: Partial<TRouteOptions>;
117
120
 
118
121
  http: HttpServiceConfig;
119
122
 
@@ -353,7 +356,7 @@ export default class ServerRouter<
353
356
  const controller = new definition.Controller(requestContext);
354
357
  return controller[definition.method]();
355
358
  },
356
- options: { ...defaultOptions },
359
+ options: { ...defaultOptions, filepath: definition.filepath, sourceLocation: definition.sourceLocation },
357
360
  };
358
361
 
359
362
  this.controllers[route.path] = route;
@@ -363,6 +366,14 @@ export default class ServerRouter<
363
366
  public url = (path: string, params: {} = {}, absolute: boolean = true) =>
364
367
  buildUrl(path, params, this.config.currentDomain, absolute);
365
368
 
369
+ private buildRouteOptions(options: Partial<TRouteOptions> = {}): TRouteOptions {
370
+ return {
371
+ ...defaultOptions,
372
+ ...(this.config.defaultRouteOptions || {}),
373
+ ...options,
374
+ };
375
+ }
376
+
366
377
  /*----------------------------------
367
378
  - REGISTER
368
379
  ----------------------------------*/
@@ -378,12 +389,11 @@ export default class ServerRouter<
378
389
  regex,
379
390
  keys,
380
391
  controller: (context: TRouterContext<this>) => new Page(route, renderer, context, layout),
381
- options: {
382
- ...defaultOptions,
392
+ options: this.buildRouteOptions({
383
393
  accept: 'html', // Les pages retournent forcémment du html
384
394
  setup,
385
395
  ...options,
386
- },
396
+ }),
387
397
  };
388
398
 
389
399
  this.routes.push(route);
@@ -396,7 +406,7 @@ export default class ServerRouter<
396
406
  options: Partial<TRouteOptions>,
397
407
  renderer: TFrontRenderer<{}, { message: string }>,
398
408
  ) {
399
- const finalOptions = { ...defaultOptions, ...options };
409
+ const finalOptions = this.buildRouteOptions(options);
400
410
 
401
411
  // Automatic layout form the nearest _layout folder
402
412
  const layout = getLayout('Error ' + code, finalOptions);
@@ -454,7 +464,7 @@ export default class ServerRouter<
454
464
  path: path,
455
465
  regex,
456
466
  keys: keys,
457
- options: { ...defaultOptions, ...options },
467
+ options: this.buildRouteOptions(options),
458
468
  controller,
459
469
  };
460
470
 
@@ -711,6 +721,14 @@ export default class ServerRouter<
711
721
  channelId: request.id,
712
722
  method: request.method,
713
723
  path: request.path,
724
+ ...(request.traceCall
725
+ ? {
726
+ traceCallFetcherId: request.traceCall.fetcherId,
727
+ traceCallId: request.traceCall.id,
728
+ traceCallLabel: request.traceCall.label,
729
+ traceCallOrigin: request.traceCall.origin,
730
+ }
731
+ : {}),
714
732
  },
715
733
  async () => {
716
734
  const timeStart = Date.now();
@@ -745,6 +763,11 @@ export default class ServerRouter<
745
763
  path: request.path,
746
764
  accept: controllerRoute.options.accept || '',
747
765
  filepath: controllerRoute.options.filepath || '',
766
+ source: {
767
+ filepath: controllerRoute.options.filepath || '',
768
+ line: controllerRoute.options.sourceLocation?.line || 0,
769
+ column: controllerRoute.options.sourceLocation?.column || 0,
770
+ },
748
771
  },
749
772
  'summary',
750
773
  );
@@ -777,6 +800,11 @@ export default class ServerRouter<
777
800
  routePath: route.path || '',
778
801
  routeId: route.options.id || '',
779
802
  filepath: route.options.filepath || '',
803
+ source: {
804
+ filepath: route.options.filepath || '',
805
+ line: route.options.sourceLocation?.line || 0,
806
+ column: route.options.sourceLocation?.column || 0,
807
+ },
780
808
  },
781
809
  'deep',
782
810
  );
@@ -797,6 +825,11 @@ export default class ServerRouter<
797
825
  routePath: route.path || '',
798
826
  routeId: route.options.id || '',
799
827
  filepath: route.options.filepath || '',
828
+ source: {
829
+ filepath: route.options.filepath || '',
830
+ line: route.options.sourceLocation?.line || 0,
831
+ column: route.options.sourceLocation?.column || 0,
832
+ },
800
833
  },
801
834
  'deep',
802
835
  );
@@ -816,6 +849,11 @@ export default class ServerRouter<
816
849
  routePath: route.path || '',
817
850
  routeId: route.options.id || '',
818
851
  filepath: route.options.filepath || '',
852
+ source: {
853
+ filepath: route.options.filepath || '',
854
+ line: route.options.sourceLocation?.line || 0,
855
+ column: route.options.sourceLocation?.column || 0,
856
+ },
819
857
  },
820
858
  'deep',
821
859
  );
@@ -867,6 +905,11 @@ export default class ServerRouter<
867
905
  routePath: route.path || '',
868
906
  routeId: route.options.id || '',
869
907
  filepath: route.options.filepath || '',
908
+ source: {
909
+ filepath: route.options.filepath || '',
910
+ line: route.options.sourceLocation?.line || 0,
911
+ column: route.options.sourceLocation?.column || 0,
912
+ },
870
913
  accept: route.options.accept || '',
871
914
  method: route.method,
872
915
  },
@@ -901,47 +944,7 @@ export default class ServerRouter<
901
944
  };
902
945
 
903
946
  private async resolveApiBatch(fetchers: TFetcherList, request: ServerRequest<this>) {
904
- // TODO: use api.fetchSync instead
905
-
906
- const responseData: TObjetDonnees = {};
907
- for (const id in fetchers) {
908
- const fetcher = fetchers[id];
909
- if (!fetcher || !('method' in fetcher)) continue;
910
-
911
- const { method, path, data } = fetcher;
912
- const callId = this.app.container.Trace.startCall(request.id, {
913
- origin: 'api-batch-fetcher',
914
- label: id,
915
- method,
916
- path,
917
- fetcherId: id,
918
- requestDataKeys: data && typeof data === 'object' ? Object.keys(data) : [],
919
- requestData: data,
920
- });
921
-
922
- try {
923
- const response = await this.resolve(request.children(method, path, data));
924
- responseData[id] = response.data;
925
- this.app.container.Trace.finishCall(request.id, callId, {
926
- statusCode: response.statusCode,
927
- resultKeys:
928
- response.data && typeof response.data === 'object' && !Array.isArray(response.data)
929
- ? Object.keys(response.data as Record<string, unknown>)
930
- : [],
931
- result: response.data,
932
- });
933
- } catch (error) {
934
- const typedError = error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'Unknown error');
935
- const statusCode = 'http' in typedError ? Number((typedError as Error & { http?: number }).http) : undefined;
936
- this.app.container.Trace.finishCall(request.id, callId, {
937
- statusCode: Number.isFinite(statusCode) ? statusCode : undefined,
938
- errorMessage: typedError.message,
939
- });
940
- throw error;
941
- }
942
-
943
- // TODO: merge response.headers ?
944
- }
947
+ const responseData = await request.api.fetchSync(fetchers, {});
945
948
 
946
949
  // Status
947
950
  request.res.status(200);