proteum 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/AGENTS.md +44 -98
  2. package/README.md +121 -7
  3. package/agents/framework/AGENTS.md +133 -886
  4. package/agents/project/AGENTS.md +70 -127
  5. package/agents/project/client/AGENTS.md +22 -93
  6. package/agents/project/client/pages/AGENTS.md +24 -26
  7. package/agents/project/server/routes/AGENTS.md +10 -8
  8. package/agents/project/server/services/AGENTS.md +22 -159
  9. package/agents/project/tests/AGENTS.md +11 -8
  10. package/cli/app/config.ts +7 -20
  11. package/cli/bin.js +8 -0
  12. package/cli/commands/command.ts +243 -0
  13. package/cli/commands/commandLocalRunner.js +198 -0
  14. package/cli/commands/deploy/web.ts +1 -2
  15. package/cli/commands/dev.ts +96 -1
  16. package/cli/commands/doctor.ts +8 -74
  17. package/cli/commands/explain.ts +8 -186
  18. package/cli/commands/trace.ts +228 -0
  19. package/cli/compiler/artifacts/commands.ts +217 -0
  20. package/cli/compiler/artifacts/manifest.ts +35 -21
  21. package/cli/compiler/artifacts/services.ts +300 -1
  22. package/cli/compiler/client/index.ts +43 -8
  23. package/cli/compiler/common/commands.ts +175 -0
  24. package/cli/compiler/common/index.ts +1 -1
  25. package/cli/compiler/common/proteumManifest.ts +15 -114
  26. package/cli/compiler/index.ts +25 -2
  27. package/cli/compiler/server/index.ts +31 -6
  28. package/cli/paths.ts +16 -1
  29. package/cli/presentation/commands.ts +59 -5
  30. package/cli/presentation/devSession.ts +5 -0
  31. package/cli/runtime/commands.ts +60 -1
  32. package/cli/tsconfig.json +4 -1
  33. package/cli/utils/check.ts +1 -1
  34. package/client/app/component.tsx +13 -9
  35. package/client/dev/profiler/index.tsx +1511 -0
  36. package/client/dev/profiler/noop.tsx +5 -0
  37. package/client/dev/profiler/runtime.noop.ts +116 -0
  38. package/client/dev/profiler/runtime.ts +840 -0
  39. package/client/services/router/components/router.tsx +30 -2
  40. package/client/services/router/index.tsx +27 -3
  41. package/client/services/router/request/api.ts +133 -17
  42. package/commands/proteum/diagnostics.ts +11 -0
  43. package/common/dev/commands.ts +50 -0
  44. package/common/dev/diagnostics.ts +298 -0
  45. package/common/dev/profiler.ts +91 -0
  46. package/common/dev/proteumManifest.ts +135 -0
  47. package/common/dev/requestTrace.ts +109 -0
  48. package/common/env/proteumEnv.ts +284 -0
  49. package/common/router/index.ts +4 -22
  50. package/docs/dev-commands.md +86 -0
  51. package/docs/request-tracing.md +122 -0
  52. package/package.json +1 -2
  53. package/server/app/commands.ts +35 -370
  54. package/server/app/commandsManager.ts +393 -0
  55. package/server/app/container/config.ts +11 -49
  56. package/server/app/container/console/index.ts +2 -3
  57. package/server/app/container/index.ts +5 -2
  58. package/server/app/container/trace/index.ts +364 -0
  59. package/server/app/devCommands.ts +192 -0
  60. package/server/app/devDiagnostics.ts +53 -0
  61. package/server/app/index.ts +27 -4
  62. package/server/services/cron/CronTask.ts +73 -5
  63. package/server/services/cron/index.ts +34 -11
  64. package/server/services/fetch/index.ts +3 -10
  65. package/server/services/prisma/index.ts +66 -4
  66. package/server/services/router/http/index.ts +151 -0
  67. package/server/services/router/index.ts +200 -12
  68. package/server/services/router/request/api.ts +30 -1
  69. package/server/services/router/response/index.ts +83 -10
  70. package/server/services/router/response/page/document.tsx +16 -0
  71. package/server/services/router/response/page/index.tsx +27 -1
  72. package/skills/clean-project-code/SKILL.md +7 -2
  73. package/test-results/.last-run.json +4 -0
  74. package/types/aliases.d.ts +6 -0
  75. package/types/global/utils.d.ts +7 -14
  76. package/Rte.zip +0 -0
  77. package/agents/project/agents.md.zip +0 -0
  78. package/doc/TODO.md +0 -71
  79. package/doc/front/router.md +0 -27
  80. package/doc/workspace/workspace.png +0 -0
  81. package/doc/workspace/workspace2.png +0 -0
  82. package/doc/workspace/workspace_26.01.22.png +0 -0
  83. package/server/services/router/http/session.ts.old +0 -40
@@ -32,11 +32,16 @@ import BaseRouter, {
32
32
  defaultOptions,
33
33
  matchRoute,
34
34
  buildUrl,
35
- TDomainsList,
36
35
  } from '@common/router';
37
36
  import type { TSsrUnresolvedRoute, TRegisterPageArgs } from '@common/router/contracts';
38
37
  import { buildRegex, getRegisterPageArgs } from '@common/router/register';
39
38
  import { layoutsList, getLayout } from '@common/router/layouts';
39
+ import {
40
+ profilerOriginHeader,
41
+ profilerParentRequestIdHeader,
42
+ profilerSessionIdHeader,
43
+ profilerTraceRequestIdHeader,
44
+ } from '@common/dev/profiler';
40
45
  import { TFetcherList } from '@common/router/request/api';
41
46
  import type { TFrontRenderer } from '@common/router/response/page';
42
47
 
@@ -108,7 +113,7 @@ export type Config<
108
113
 
109
114
  disk?: string; // Disk driver ID
110
115
 
111
- domains: TDomainsList;
116
+ currentDomain: string;
112
117
 
113
118
  http: HttpServiceConfig;
114
119
 
@@ -356,7 +361,7 @@ export default class ServerRouter<
356
361
  }
357
362
 
358
363
  public url = (path: string, params: {} = {}, absolute: boolean = true) =>
359
- buildUrl(path, params, this.config.domains, absolute);
364
+ buildUrl(path, params, this.config.currentDomain, absolute);
360
365
 
361
366
  /*----------------------------------
362
367
  - REGISTER
@@ -581,14 +586,33 @@ export default class ServerRouter<
581
586
  this,
582
587
  );
583
588
 
589
+ this.app.container.Trace.startRequest({
590
+ id: request.id,
591
+ method: request.method,
592
+ path: request.path,
593
+ url: request.url,
594
+ headers: request.headers,
595
+ data: request.data,
596
+ profilerSessionId: request.headers[profilerSessionIdHeader] || undefined,
597
+ profilerOrigin: request.headers[profilerOriginHeader] || undefined,
598
+ profilerParentRequestId: request.headers[profilerParentRequestIdHeader] || undefined,
599
+ });
600
+ if (this.app.container.Trace.isEnabled()) res.setHeader(profilerTraceRequestIdHeader, request.id);
601
+
584
602
  let response: ServerResponse<this>;
585
603
  try {
586
604
  // Hook
587
605
  await this.runHook('request', request);
606
+ this.app.container.Trace.setRequestUser(request.id, request.user?.email);
588
607
 
589
608
  // Bulk API Requests
590
609
  if (request.path === '/api' && typeof request.data.fetchers === 'object') {
591
- return await this.resolveApiBatch(request.data.fetchers, request);
610
+ await this.resolveApiBatch(request.data.fetchers, request);
611
+ this.app.container.Trace.finishRequest(request.id, {
612
+ statusCode: request.res.statusCode || 200,
613
+ user: request.user?.email,
614
+ });
615
+ return;
592
616
  } else {
593
617
  response = await this.resolve(
594
618
  request,
@@ -604,7 +628,17 @@ export default class ServerRouter<
604
628
  // Static pages
605
629
  if (cachedPage) {
606
630
  console.log('[router] Get static page from cache', req.path);
631
+ this.app.container.Trace.record(
632
+ request.id,
633
+ 'response.send',
634
+ { cached: true, statusCode: response.statusCode, contentType: 'text/html' },
635
+ 'summary',
636
+ );
607
637
  res.send(cachedPage.rendered);
638
+ this.app.container.Trace.finishRequest(request.id, {
639
+ statusCode: response.statusCode,
640
+ user: request.user?.email,
641
+ });
608
642
  return;
609
643
  }
610
644
 
@@ -613,9 +647,34 @@ export default class ServerRouter<
613
647
  // Headers
614
648
  res.header(response.headers);
615
649
  // Data
650
+ this.app.container.Trace.record(
651
+ request.id,
652
+ 'response.send',
653
+ {
654
+ cached: false,
655
+ statusCode: response.statusCode,
656
+ contentType: response.headers['Content-Type'] || '',
657
+ headerKeys: Object.keys(response.headers),
658
+ },
659
+ 'summary',
660
+ );
616
661
  res.send(response.data);
662
+ this.app.container.Trace.finishRequest(request.id, {
663
+ statusCode: response.statusCode,
664
+ user: request.user?.email,
665
+ });
617
666
  } else if (response.data !== 'true') {
667
+ this.app.container.Trace.finishRequest(request.id, {
668
+ statusCode: res.statusCode || response.statusCode,
669
+ user: request.user?.email,
670
+ errorMessage: "Can't return data from the controller since response has already been sent via express.",
671
+ });
618
672
  throw new Error("Can't return data from the controller since response has already been sent via express.");
673
+ } else {
674
+ this.app.container.Trace.finishRequest(request.id, {
675
+ statusCode: res.statusCode || response.statusCode,
676
+ user: request.user?.email,
677
+ });
619
678
  }
620
679
  }
621
680
 
@@ -655,6 +714,16 @@ export default class ServerRouter<
655
714
  },
656
715
  async () => {
657
716
  const timeStart = Date.now();
717
+ const routeStats = {
718
+ total: this.routes.length,
719
+ staticSkipped: 0,
720
+ methodMismatch: 0,
721
+ acceptMismatch: 0,
722
+ pathMismatch: 0,
723
+ matched: 0,
724
+ };
725
+
726
+ this.app.container.Trace.record(request.id, 'resolve.start', { isStatic: Boolean(isStatic) }, 'summary');
658
727
 
659
728
  if (this.status === 'starting') {
660
729
  console.log(LogPrefix, `Waiting for servert to be resdy before resolving request`);
@@ -669,6 +738,16 @@ export default class ServerRouter<
669
738
  // Controller route
670
739
  const controllerRoute = this.controllers[request.path];
671
740
  if (controllerRoute !== undefined) {
741
+ this.app.container.Trace.record(
742
+ request.id,
743
+ 'resolve.controller-route',
744
+ {
745
+ path: request.path,
746
+ accept: controllerRoute.options.accept || '',
747
+ filepath: controllerRoute.options.filepath || '',
748
+ },
749
+ 'summary',
750
+ );
672
751
  // Create response
673
752
  await response.runController(controllerRoute);
674
753
  if (response.wasProvided) return resolve(response);
@@ -679,21 +758,81 @@ export default class ServerRouter<
679
758
 
680
759
  // Classic routes
681
760
  for (const route of this.routes) {
682
- if (isStatic && !route.options.whenStatic) continue;
761
+ if (isStatic && !route.options.whenStatic) {
762
+ routeStats.staticSkipped++;
763
+ continue;
764
+ }
683
765
 
684
766
  // Match Method
685
- if (request.method !== route.method && route.method !== '*') continue;
767
+ if (request.method !== route.method && route.method !== '*') {
768
+ routeStats.methodMismatch++;
769
+ if (this.app.container.Trace.shouldCapture(request.id, 'deep')) {
770
+ this.app.container.Trace.record(
771
+ request.id,
772
+ 'resolve.route-skip',
773
+ {
774
+ reason: 'method',
775
+ routeMethod: route.method,
776
+ requestMethod: request.method,
777
+ routePath: route.path || '',
778
+ routeId: route.options.id || '',
779
+ filepath: route.options.filepath || '',
780
+ },
781
+ 'deep',
782
+ );
783
+ }
784
+ continue;
785
+ }
686
786
 
687
787
  // Match Response format
688
- if (!request.accepts(route.options.accept)) continue;
788
+ if (!request.accepts(route.options.accept)) {
789
+ routeStats.acceptMismatch++;
790
+ if (this.app.container.Trace.shouldCapture(request.id, 'deep')) {
791
+ this.app.container.Trace.record(
792
+ request.id,
793
+ 'resolve.route-skip',
794
+ {
795
+ reason: 'accept',
796
+ routeAccept: route.options.accept || '',
797
+ routePath: route.path || '',
798
+ routeId: route.options.id || '',
799
+ filepath: route.options.filepath || '',
800
+ },
801
+ 'deep',
802
+ );
803
+ }
804
+ continue;
805
+ }
689
806
 
690
807
  const isMatching = matchRoute(route, request);
691
- if (!isMatching) continue;
808
+ if (!isMatching) {
809
+ routeStats.pathMismatch++;
810
+ if (this.app.container.Trace.shouldCapture(request.id, 'deep')) {
811
+ this.app.container.Trace.record(
812
+ request.id,
813
+ 'resolve.route-skip',
814
+ {
815
+ reason: 'path',
816
+ routePath: route.path || '',
817
+ routeId: route.options.id || '',
818
+ filepath: route.options.filepath || '',
819
+ },
820
+ 'deep',
821
+ );
822
+ }
823
+ continue;
824
+ }
692
825
 
826
+ routeStats.matched++;
693
827
  await this.resolvedRoute(route, response, timeStart);
694
- if (response.wasProvided) return resolve(response);
828
+ if (response.wasProvided) {
829
+ this.app.container.Trace.record(request.id, 'resolve.routes-evaluated', routeStats, 'resolve');
830
+ return resolve(response);
831
+ }
695
832
  }
696
833
 
834
+ this.app.container.Trace.record(request.id, 'resolve.routes-evaluated', routeStats, 'resolve');
835
+ this.app.container.Trace.record(request.id, 'resolve.not-found', { path: request.path }, 'summary');
697
836
  reject(new NotFound());
698
837
  } catch (error) {
699
838
  const typedError =
@@ -721,6 +860,19 @@ export default class ServerRouter<
721
860
  private async resolvedRoute(route: TMatchedRoute, response: ServerResponse<this>, timeStart: number) {
722
861
  route = await response.resolveRouteOptions(route);
723
862
 
863
+ this.app.container.Trace.record(
864
+ response.request.id,
865
+ 'resolve.route-match',
866
+ {
867
+ routePath: route.path || '',
868
+ routeId: route.options.id || '',
869
+ filepath: route.options.filepath || '',
870
+ accept: route.options.accept || '',
871
+ method: route.method,
872
+ },
873
+ 'summary',
874
+ );
875
+
724
876
  // Run on resolution hooks. Ex: authentication check
725
877
  await this.runHook('resolved', route, response.request, response);
726
878
 
@@ -757,10 +909,36 @@ export default class ServerRouter<
757
909
  if (!fetcher || !('method' in fetcher)) continue;
758
910
 
759
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
+ });
760
921
 
761
- const response = await this.resolve(request.children(method, path, data));
762
-
763
- responseData[id] = response.data;
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
+ }
764
942
 
765
943
  // TODO: merge response.headers ?
766
944
  }
@@ -782,6 +960,16 @@ export default class ServerRouter<
782
960
 
783
961
  const response = new ServerResponse(request).status(code);
784
962
 
963
+ this.app.container.Trace.record(
964
+ request.id,
965
+ 'error',
966
+ {
967
+ code,
968
+ error,
969
+ },
970
+ 'summary',
971
+ );
972
+
785
973
  // Rapport / debug
786
974
  if (code === 500) {
787
975
  // Print the error here so the stacktrace appears in the bug report logs
@@ -80,7 +80,36 @@ export default class ApiClientRequest extends RequestService implements ApiClien
80
80
 
81
81
  // Create a children request to resolve the api data
82
82
  const request = this.request.children(method, path, data);
83
- fetchedData[id] = await request.router.resolve(request).then((res) => res.data);
83
+ const callId = this.request.router.app.container.Trace.startCall(this.request.id, {
84
+ origin: 'ssr-fetcher',
85
+ label: id,
86
+ method,
87
+ path,
88
+ fetcherId: id,
89
+ requestDataKeys: data && typeof data === 'object' ? Object.keys(data) : [],
90
+ requestData: data,
91
+ });
92
+
93
+ try {
94
+ const response = await request.router.resolve(request);
95
+ fetchedData[id] = response.data;
96
+ this.request.router.app.container.Trace.finishCall(this.request.id, callId, {
97
+ statusCode: response.statusCode,
98
+ resultKeys:
99
+ response.data && typeof response.data === 'object' && !Array.isArray(response.data)
100
+ ? Object.keys(response.data as Record<string, unknown>)
101
+ : [],
102
+ result: response.data,
103
+ });
104
+ } catch (error) {
105
+ const typedError = error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'Unknown error');
106
+ const statusCode = 'http' in typedError ? Number((typedError as Error & { http?: number }).http) : undefined;
107
+ this.request.router.app.container.Trace.finishCall(this.request.id, callId, {
108
+ statusCode: Number.isFinite(statusCode) ? statusCode : undefined,
109
+ errorMessage: typedError.message,
110
+ });
111
+ throw error;
112
+ }
84
113
  }
85
114
 
86
115
  return fetchedData;
@@ -14,7 +14,7 @@ import express from 'express';
14
14
  import context from '@server/context';
15
15
  import type { AnyRouterService, default as ServerRouter, TServerRouter, TAnyRouter } from '@server/services/router';
16
16
  import ServerRequest from '@server/services/router/request';
17
- import { TMatchedRoute, TRoute, TAnyRoute, TDomainsList } from '@common/router';
17
+ import { TMatchedRoute, TRoute, TAnyRoute } from '@common/router';
18
18
  import { NotFound, Forbidden, Anomaly } from '@common/errors';
19
19
  import BaseResponse, { TResponseData } from '@common/router/response';
20
20
  import { splitRouteSetupResult } from '@common/router/pageSetup';
@@ -38,7 +38,7 @@ export type TBasicSSrData = {
38
38
  request: { data: TObjetDonnees; id: string };
39
39
  page: { chunkId: string; data?: TObjetDonnees };
40
40
  user: TBasicUser | null;
41
- domains: TDomainsList;
41
+ currentDomain: string;
42
42
  };
43
43
 
44
44
  type TServerRouterApplication<TRouter extends TServerRouter> =
@@ -88,6 +88,11 @@ export type TRouterContextServices<
88
88
 
89
89
  export type TRouterRequestContext<TRouter extends TServerRouter> = TServerRouterCustomContext<TRouter>;
90
90
 
91
+ const getRouteTraceTarget = (route: TAnyRoute<TRouterContext<TServerRouter>>) => {
92
+ if ('code' in route) return String(route.code);
93
+ return route.path || '';
94
+ };
95
+
91
96
  /*----------------------------------
92
97
  - CLASSE
93
98
  ----------------------------------*/
@@ -129,6 +134,18 @@ export default class ServerResponse<
129
134
  // Update canonical url
130
135
  this.updateCanonicalUrl(route);
131
136
 
137
+ this.app.container.Trace.record(
138
+ this.request.id,
139
+ 'controller.start',
140
+ {
141
+ target: getRouteTraceTarget(route as TAnyRoute<TRouterContext<TServerRouter>>),
142
+ routeId: route.options.id || '',
143
+ filepath: route.options.filepath || '',
144
+ accept: route.options.accept || '',
145
+ },
146
+ 'summary',
147
+ );
148
+
132
149
  // Create response context for controllers
133
150
  const requestContext = await this.createContext(route);
134
151
  const contextStore = context.getStore() as
@@ -141,16 +158,41 @@ export default class ServerResponse<
141
158
 
142
159
  // Run controller
143
160
  const content = await this.route.controller(requestContext);
144
- if (content === undefined) return;
161
+ if (content === undefined) {
162
+ this.app.container.Trace.record(this.request.id, 'controller.result', { kind: 'undefined' }, 'summary');
163
+ return;
164
+ }
145
165
 
146
166
  // No need to process the content
147
- if (content instanceof ServerResponse) return;
167
+ if (content instanceof ServerResponse) {
168
+ this.app.container.Trace.record(this.request.id, 'controller.result', { kind: 'response' }, 'summary');
169
+ return;
170
+ }
148
171
  // Render react page to html
149
- else if (content instanceof Page) await this.render(content, requestContext, additionnalData);
172
+ else if (content instanceof Page) {
173
+ this.app.container.Trace.record(
174
+ this.request.id,
175
+ 'controller.result',
176
+ { kind: 'page', chunkId: content.chunkId || '' },
177
+ 'summary',
178
+ );
179
+ await this.render(content, requestContext, additionnalData);
180
+ }
150
181
  // Return HTML
151
- else if (typeof content === 'string' && this.route.options.accept === 'html') await this.html(content);
182
+ else if (typeof content === 'string' && this.route.options.accept === 'html') {
183
+ this.app.container.Trace.record(
184
+ this.request.id,
185
+ 'controller.result',
186
+ { kind: 'html', length: content.length },
187
+ 'summary',
188
+ );
189
+ await this.html(content);
190
+ }
152
191
  // Return JSON
153
- else await this.json(content);
192
+ else {
193
+ this.app.container.Trace.record(this.request.id, 'controller.result', { kind: 'json', data: content }, 'resolve');
194
+ await this.json(content);
195
+ }
154
196
  }
155
197
 
156
198
  private updateCanonicalUrl(route: TAnyRoute<TRouterContext<TRouter>>) {
@@ -175,13 +217,20 @@ export default class ServerResponse<
175
217
  const requestContext = await this.createContext(route);
176
218
  const { options } = splitRouteSetupResult(((setup as any)({ ...requestContext, data: this.request.data }) as {}) || {});
177
219
 
220
+ this.app.container.Trace.record(
221
+ this.request.id,
222
+ 'setup.options',
223
+ { optionKeys: Object.keys(options) },
224
+ 'resolve',
225
+ );
226
+
178
227
  return { ...route, options: { ...route.options, ...options } };
179
228
  }
180
229
 
181
230
  // Start controller services
182
231
  private async createContext(route: TAnyRoute<TRouterContext<TRouter>>): Promise<TRequestContext> {
183
232
  const contextServices = this.router.createContextServices(this.request);
184
-
233
+ const controllers = createControllers(this.request.api);
185
234
  const customSsrData = this.router.config.context(this.request, this.app) as TRouterRequestContext<TRouter>;
186
235
 
187
236
  // TODO: transmiss safe data (especially for Router), as Router info could be printed on client side
@@ -196,7 +245,7 @@ export default class ServerResponse<
196
245
 
197
246
  Router: this.router,
198
247
  ...(this.app as {}),
199
- ...createControllers(this.request.api),
248
+ ...controllers,
200
249
 
201
250
  // Router services
202
251
  ...(contextServices as TRouterContextServices<TRouter>),
@@ -205,6 +254,19 @@ export default class ServerResponse<
205
254
 
206
255
  requestContext.context = requestContext;
207
256
 
257
+ this.app.container.Trace.record(
258
+ this.request.id,
259
+ 'context.create',
260
+ {
261
+ target: getRouteTraceTarget(route as TAnyRoute<TRouterContext<TServerRouter>>),
262
+ routeId: route.options.id || '',
263
+ routerServiceKeys: Object.keys(contextServices),
264
+ controllerKeys: Object.keys(controllers),
265
+ customContextKeys: Object.keys(customSsrData as object),
266
+ },
267
+ 'resolve',
268
+ );
269
+
208
270
  return requestContext;
209
271
  }
210
272
 
@@ -215,7 +277,7 @@ export default class ServerResponse<
215
277
  request: { id: this.request.id, data: this.request.data },
216
278
  page: { chunkId: page.chunkId || '', data: page.data },
217
279
  user: this.request.user,
218
- domains: this.router.config.domains,
280
+ currentDomain: this.router.config.currentDomain,
219
281
  ...customSsrData,
220
282
  };
221
283
  }
@@ -249,6 +311,17 @@ export default class ServerResponse<
249
311
  // Example: error message for error pages
250
312
  page.data = { ...page.data, ...additionnalData };
251
313
 
314
+ this.app.container.Trace.record(
315
+ this.request.id,
316
+ 'page.data',
317
+ {
318
+ chunkId: page.chunkId || '',
319
+ dataKeys: Object.keys(page.data || {}),
320
+ data: page.data || {},
321
+ },
322
+ 'resolve',
323
+ );
324
+
252
325
  // Render page
253
326
  await this.router.runHook('render', page);
254
327
  const document = await page.render();
@@ -182,6 +182,22 @@ export default class DocumentRenderer<TRouter extends TServerRouter> {
182
182
  const ssrData = response.forSsr(page);
183
183
  const context = safeStringify(ssrData);
184
184
  const routesForClient = JSON.stringify(this.router.ssrRoutes);
185
+ const customContextKeys = Object.keys(ssrData).filter((key) => !['request', 'page', 'user', 'domains'].includes(key));
186
+
187
+ this.app.container.Trace.record(
188
+ response.request.id,
189
+ 'ssr.payload',
190
+ {
191
+ chunkId: page.chunkId || '',
192
+ topLevelKeys: Object.keys(ssrData),
193
+ requestDataKeys: Object.keys(ssrData.request.data || {}),
194
+ pageDataKeys: Object.keys(ssrData.page.data || {}),
195
+ customContextKeys,
196
+ serializedBytes: Buffer.byteLength(context, 'utf8'),
197
+ routeCount: this.router.ssrRoutes.length,
198
+ },
199
+ 'resolve',
200
+ );
185
201
 
186
202
  return (
187
203
  <>
@@ -62,6 +62,17 @@ export default class ServerPage<TRouter extends TServerRouter = TServerRouter> e
62
62
  // Ex: runtime added scripts, title, metas, ....
63
63
 
64
64
  const context = this.context as TPageRenderContext & TRouterContext<TRouter>;
65
+ const requestId = context.response.request.id;
66
+ this.app.container.Trace.record(
67
+ requestId,
68
+ 'render.start',
69
+ {
70
+ chunkId: this.chunkId || '',
71
+ title: this.title || '',
72
+ routeId: this.route.options['id'] || '',
73
+ },
74
+ 'summary',
75
+ );
65
76
  const html = renderToString(
66
77
  <App context={context as Parameters<typeof App>[0]['context'] & TRouterContext<TRouter>} />,
67
78
  );
@@ -82,7 +93,22 @@ export default class ServerPage<TRouter extends TServerRouter = TServerRouter> e
82
93
  if (page.theme)
83
94
  attrsBody.className += ' ' + page.theme;*/
84
95
 
85
- return this.router.render.page(html, this, context.response);
96
+ return this.router.render.page(html, this, context.response).then((document) => {
97
+ this.app.container.Trace.record(
98
+ requestId,
99
+ 'render.end',
100
+ {
101
+ chunkId: this.chunkId || '',
102
+ htmlLength: html.length,
103
+ documentLength: document.length,
104
+ styleCount: this.style.length,
105
+ scriptCount: this.scripts.length,
106
+ },
107
+ 'summary',
108
+ );
109
+
110
+ return document;
111
+ });
86
112
  }
87
113
 
88
114
  /*----------------------------------
@@ -32,10 +32,13 @@ Use fast search tools such as `rg` to verify references. Check exports, dynamic
32
32
  5. Keep uncertain cases.
33
33
  If code looks unused but safety is not clear, keep it and report it separately. Favor false negatives over unsafe deletions.
34
34
 
35
- 6. Apply style only on touched files.
35
+ 6. Audit redundancy candidates across the project.
36
+ List catalogs, constants, and functions that look redundant and could be centralized, unified, or merged. Treat this as a reporting task by default unless the user explicitly asked for structural refactoring. Give each candidate an impact score from 1 to 5, where 5 is the highest expected payoff for maintainability, consistency, or simplification.
37
+
38
+ 7. Apply style only on touched files.
36
39
  Follow the repository coding style for files you change. Run Prettier with the repo config, preferably on touched files only unless the user explicitly asks for repo-wide formatting.
37
40
 
38
- 7. Verify.
41
+ 8. Verify.
39
42
  Run the smallest relevant verification available after edits. Prefer project-native checks. If no suitable automated verification exists, say so explicitly.
40
43
 
41
44
  ## Deletion Rules
@@ -50,6 +53,7 @@ Run the smallest relevant verification available after edits. Prefer project-nat
50
53
  Always report:
51
54
  - what was removed
52
55
  - what was intentionally left unchanged because it was uncertain
56
+ - which catalogs, constants, and functions appear redundant across the project, with a proposed centralization or merge direction and an impact score from 1 to 5
53
57
  - what verification was run
54
58
  - what could not be verified automatically
55
59
 
@@ -59,5 +63,6 @@ When the user provides a cleanup brief, turn it into an execution checklist befo
59
63
  - instructions read
60
64
  - safe deletion targets identified
61
65
  - risky candidates deferred
66
+ - redundancy candidates and impact scoring captured
62
67
  - formatting scope confirmed
63
68
  - verification plan chosen
@@ -0,0 +1,4 @@
1
+ {
2
+ "status": "failed",
3
+ "failedTests": []
4
+ }
@@ -18,6 +18,12 @@ declare module '@/server' {
18
18
  export = InstanceType<ServerApplicationClass>;
19
19
  }
20
20
 
21
+ declare module '@/server/index' {
22
+ import ServerApplicationBase from '../server/app';
23
+
24
+ export default class ServerApplication extends ServerApplicationBase {}
25
+ }
26
+
21
27
  declare module '@/client' {
22
28
  const ClientApplicationClass: import('../client/app').default;
23
29
  export = InstanceType<ClientApplicationClass>;
@@ -43,24 +43,17 @@ declare type PrimitiveValue = string | number | boolean;
43
43
  /*type TEnvConfig = {
44
44
  name: 'local' | 'server',
45
45
  profile: 'dev' | 'testing' | 'prod',
46
-
46
+
47
47
  router: {
48
48
  port: number,
49
- domains: TDomainsList
50
- },
51
- database: {
52
- name: string,
53
- databases: string[],
54
- host: string,
55
- port: number,
56
- login: string,
57
- password: string,
49
+ currentDomain: string
58
50
  },
59
- console: {
51
+ trace: {
60
52
  enable: boolean,
61
- debug: boolean,
62
- bufferLimit: number,
63
- level: TLogProfile,
53
+ requestsLimit: number,
54
+ eventsLimit: number,
55
+ capture: 'summary' | 'resolve' | 'deep',
56
+ persistOnError: boolean,
64
57
  },
65
58
  }*/
66
59