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.
- package/AGENTS.md +44 -98
- package/README.md +121 -7
- package/agents/framework/AGENTS.md +133 -886
- package/agents/project/AGENTS.md +70 -127
- package/agents/project/client/AGENTS.md +22 -93
- package/agents/project/client/pages/AGENTS.md +24 -26
- package/agents/project/server/routes/AGENTS.md +10 -8
- package/agents/project/server/services/AGENTS.md +22 -159
- package/agents/project/tests/AGENTS.md +11 -8
- package/cli/app/config.ts +7 -20
- package/cli/bin.js +8 -0
- package/cli/commands/command.ts +243 -0
- package/cli/commands/commandLocalRunner.js +198 -0
- package/cli/commands/deploy/web.ts +1 -2
- package/cli/commands/dev.ts +96 -1
- package/cli/commands/doctor.ts +8 -74
- package/cli/commands/explain.ts +8 -186
- package/cli/commands/trace.ts +228 -0
- package/cli/compiler/artifacts/commands.ts +217 -0
- package/cli/compiler/artifacts/manifest.ts +35 -21
- package/cli/compiler/artifacts/services.ts +300 -1
- package/cli/compiler/client/index.ts +43 -8
- package/cli/compiler/common/commands.ts +175 -0
- package/cli/compiler/common/index.ts +1 -1
- package/cli/compiler/common/proteumManifest.ts +15 -114
- package/cli/compiler/index.ts +25 -2
- package/cli/compiler/server/index.ts +31 -6
- package/cli/paths.ts +16 -1
- package/cli/presentation/commands.ts +59 -5
- package/cli/presentation/devSession.ts +5 -0
- package/cli/runtime/commands.ts +60 -1
- package/cli/tsconfig.json +4 -1
- package/cli/utils/check.ts +1 -1
- package/client/app/component.tsx +13 -9
- package/client/dev/profiler/index.tsx +1511 -0
- package/client/dev/profiler/noop.tsx +5 -0
- package/client/dev/profiler/runtime.noop.ts +116 -0
- package/client/dev/profiler/runtime.ts +840 -0
- package/client/services/router/components/router.tsx +30 -2
- package/client/services/router/index.tsx +27 -3
- package/client/services/router/request/api.ts +133 -17
- package/commands/proteum/diagnostics.ts +11 -0
- package/common/dev/commands.ts +50 -0
- package/common/dev/diagnostics.ts +298 -0
- package/common/dev/profiler.ts +91 -0
- package/common/dev/proteumManifest.ts +135 -0
- package/common/dev/requestTrace.ts +109 -0
- package/common/env/proteumEnv.ts +284 -0
- package/common/router/index.ts +4 -22
- package/docs/dev-commands.md +86 -0
- package/docs/request-tracing.md +122 -0
- package/package.json +1 -2
- package/server/app/commands.ts +35 -370
- package/server/app/commandsManager.ts +393 -0
- package/server/app/container/config.ts +11 -49
- package/server/app/container/console/index.ts +2 -3
- package/server/app/container/index.ts +5 -2
- package/server/app/container/trace/index.ts +364 -0
- package/server/app/devCommands.ts +192 -0
- package/server/app/devDiagnostics.ts +53 -0
- package/server/app/index.ts +27 -4
- package/server/services/cron/CronTask.ts +73 -5
- package/server/services/cron/index.ts +34 -11
- package/server/services/fetch/index.ts +3 -10
- package/server/services/prisma/index.ts +66 -4
- package/server/services/router/http/index.ts +151 -0
- package/server/services/router/index.ts +200 -12
- package/server/services/router/request/api.ts +30 -1
- package/server/services/router/response/index.ts +83 -10
- package/server/services/router/response/page/document.tsx +16 -0
- package/server/services/router/response/page/index.tsx +27 -1
- package/skills/clean-project-code/SKILL.md +7 -2
- package/test-results/.last-run.json +4 -0
- package/types/aliases.d.ts +6 -0
- package/types/global/utils.d.ts +7 -14
- package/Rte.zip +0 -0
- package/agents/project/agents.md.zip +0 -0
- package/doc/TODO.md +0 -71
- package/doc/front/router.md +0 -27
- package/doc/workspace/workspace.png +0 -0
- package/doc/workspace/workspace2.png +0 -0
- package/doc/workspace/workspace_26.01.22.png +0 -0
- 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
|
-
|
|
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.
|
|
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
|
-
|
|
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)
|
|
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 !== '*')
|
|
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))
|
|
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)
|
|
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)
|
|
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
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
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')
|
|
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
|
|
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
|
-
...
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
package/types/aliases.d.ts
CHANGED
|
@@ -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>;
|
package/types/global/utils.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
51
|
+
trace: {
|
|
60
52
|
enable: boolean,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
53
|
+
requestsLimit: number,
|
|
54
|
+
eventsLimit: number,
|
|
55
|
+
capture: 'summary' | 'resolve' | 'deep',
|
|
56
|
+
persistOnError: boolean,
|
|
64
57
|
},
|
|
65
58
|
}*/
|
|
66
59
|
|