proteum 2.1.0 → 2.1.2

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 +44 -98
  2. package/README.md +143 -10
  3. package/agents/framework/AGENTS.md +146 -886
  4. package/agents/project/AGENTS.md +73 -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/create.ts +5 -0
  15. package/cli/commands/deploy/web.ts +1 -2
  16. package/cli/commands/dev.ts +98 -2
  17. package/cli/commands/doctor.ts +8 -74
  18. package/cli/commands/explain.ts +8 -186
  19. package/cli/commands/init.ts +2 -94
  20. package/cli/commands/trace.ts +228 -0
  21. package/cli/compiler/artifacts/commands.ts +217 -0
  22. package/cli/compiler/artifacts/manifest.ts +35 -21
  23. package/cli/compiler/artifacts/services.ts +300 -1
  24. package/cli/compiler/client/index.ts +43 -8
  25. package/cli/compiler/common/commands.ts +175 -0
  26. package/cli/compiler/common/index.ts +1 -1
  27. package/cli/compiler/common/proteumManifest.ts +15 -114
  28. package/cli/compiler/index.ts +25 -2
  29. package/cli/compiler/server/index.ts +31 -6
  30. package/cli/index.ts +1 -4
  31. package/cli/paths.ts +16 -1
  32. package/cli/presentation/commands.ts +104 -14
  33. package/cli/presentation/devSession.ts +22 -3
  34. package/cli/presentation/proteum_logo_400x400_square_icon.txt +400 -0
  35. package/cli/runtime/commands.ts +121 -4
  36. package/cli/scaffold/index.ts +720 -0
  37. package/cli/scaffold/templates.ts +344 -0
  38. package/cli/scaffold/types.ts +26 -0
  39. package/cli/tsconfig.json +4 -1
  40. package/cli/utils/check.ts +1 -1
  41. package/client/app/component.tsx +13 -9
  42. package/client/dev/profiler/index.tsx +2511 -0
  43. package/client/dev/profiler/noop.tsx +5 -0
  44. package/client/dev/profiler/runtime.noop.ts +116 -0
  45. package/client/dev/profiler/runtime.ts +840 -0
  46. package/client/services/router/components/router.tsx +30 -2
  47. package/client/services/router/index.tsx +27 -3
  48. package/client/services/router/request/api.ts +133 -17
  49. package/commands/proteum/diagnostics.ts +11 -0
  50. package/common/dev/commands.ts +50 -0
  51. package/common/dev/diagnostics.ts +298 -0
  52. package/common/dev/profiler.ts +92 -0
  53. package/common/dev/proteumManifest.ts +135 -0
  54. package/common/dev/requestTrace.ts +115 -0
  55. package/common/env/proteumEnv.ts +284 -0
  56. package/common/router/index.ts +4 -22
  57. package/docs/dev-commands.md +93 -0
  58. package/docs/diagnostics.md +88 -0
  59. package/docs/request-tracing.md +132 -0
  60. package/eslint.js +11 -6
  61. package/package.json +3 -3
  62. package/server/app/commands.ts +35 -370
  63. package/server/app/commandsManager.ts +393 -0
  64. package/server/app/container/config.ts +11 -49
  65. package/server/app/container/console/index.ts +2 -3
  66. package/server/app/container/index.ts +5 -2
  67. package/server/app/container/trace/index.ts +364 -0
  68. package/server/app/devCommands.ts +192 -0
  69. package/server/app/devDiagnostics.ts +53 -0
  70. package/server/app/index.ts +29 -6
  71. package/server/index.ts +0 -1
  72. package/server/services/auth/index.ts +525 -61
  73. package/server/services/auth/router/index.ts +106 -7
  74. package/server/services/cron/CronTask.ts +73 -5
  75. package/server/services/cron/index.ts +34 -11
  76. package/server/services/fetch/index.ts +3 -10
  77. package/server/services/prisma/index.ts +66 -4
  78. package/server/services/router/http/index.ts +173 -6
  79. package/server/services/router/index.ts +200 -12
  80. package/server/services/router/request/api.ts +30 -1
  81. package/server/services/router/response/index.ts +83 -10
  82. package/server/services/router/response/page/document.tsx +16 -0
  83. package/server/services/router/response/page/index.tsx +27 -1
  84. package/skills/clean-project-code/SKILL.md +7 -2
  85. package/test-results/.last-run.json +4 -0
  86. package/types/aliases.d.ts +6 -0
  87. package/types/global/utils.d.ts +7 -14
  88. package/Rte.zip +0 -0
  89. package/agents/project/agents.md.zip +0 -0
  90. package/doc/TODO.md +0 -71
  91. package/doc/front/router.md +0 -27
  92. package/doc/workspace/workspace.png +0 -0
  93. package/doc/workspace/workspace2.png +0 -0
  94. package/doc/workspace/workspace_26.01.22.png +0 -0
  95. package/server/services/router/http/session.ts.old +0 -40
@@ -28,6 +28,11 @@ export type TProps = { service?: ClientRouter; loaderComponent?: React.Component
28
28
  ----------------------------------*/
29
29
 
30
30
  const LogPrefix = `[router][component]`;
31
+ const withProfiler = <T,>(callback: (runtime: (typeof import('@client/dev/profiler/runtime'))['profilerRuntime']) => T) => {
32
+ if (!__DEV__) return undefined as T | undefined;
33
+ const profilerModule = require('@client/dev/profiler/runtime') as typeof import('@client/dev/profiler/runtime');
34
+ return callback(profilerModule.profilerRuntime);
35
+ };
31
36
 
32
37
  const PageLoading = ({
33
38
  clientRouter,
@@ -91,6 +96,12 @@ export default ({ service: clientRouter, loaderComponent }: TProps) => {
91
96
  }
92
97
 
93
98
  // Set loading state
99
+ const sessionId = withProfiler((runtime) =>
100
+ runtime.startNavigationSession({
101
+ path: request.path,
102
+ url: request.url,
103
+ }),
104
+ );
94
105
  clientRouter.runHook('page.change', request);
95
106
  window.scrollTo({ top: 0, behavior: 'smooth' });
96
107
  clientRouter.setLoading(true);
@@ -99,21 +110,25 @@ export default ({ service: clientRouter, loaderComponent }: TProps) => {
99
110
  // Unable to load (no connection, server error, ....)
100
111
  if (newpage === null) return;
101
112
 
102
- return await changePage(newpage, data, request);
113
+ return await changePage(newpage, data, request, sessionId);
103
114
  };
104
115
 
105
- async function changePage(newpage: Page, data?: {}, request?: ClientRequest) {
116
+ async function changePage(newpage: Page, data?: {}, request?: ClientRequest, sessionId?: string) {
106
117
  // Fetch API data to hydrate the page
107
118
  try {
108
119
  await newpage.preRender();
109
120
  } catch (error) {
110
121
  console.error(LogPrefix, 'Unable to fetch data:', error);
122
+ withProfiler((runtime) =>
123
+ runtime.failNavigation(error instanceof Error ? error.message : String(error), sessionId),
124
+ );
111
125
  clientRouter?.setLoading(false);
112
126
  return;
113
127
  }
114
128
 
115
129
  // Add additional data
116
130
  if (data) newpage.data = { ...newpage.data, ...data };
131
+ withProfiler((runtime) => runtime.startRenderStep(sessionId));
117
132
 
118
133
  // Add page container
119
134
  setCurrentPage((page) => {
@@ -172,6 +187,19 @@ export default ({ service: clientRouter, loaderComponent }: TProps) => {
172
187
  currentPage?.updateClient();
173
188
  // Scroll to the selected content via url hash
174
189
  restoreScroll(currentPage);
190
+ const routeLabel =
191
+ currentPage && 'path' in currentPage.route && currentPage.route.path
192
+ ? currentPage.route.path
193
+ : currentPage && 'code' in currentPage.route
194
+ ? String(currentPage.route.code)
195
+ : undefined;
196
+ withProfiler((runtime) =>
197
+ runtime.finishNavigation({
198
+ chunkId: currentPage?.chunkId,
199
+ routeLabel,
200
+ title: currentPage?.title,
201
+ }),
202
+ );
175
203
 
176
204
  // Hooks
177
205
  clientRouter.runHook('page.changed', (currentPage?.context.request || context.request) as ClientRequest);
@@ -21,7 +21,6 @@ import BaseRouter, {
21
21
  TErrorRoute,
22
22
  TRouteOptions,
23
23
  TRouteModule,
24
- TDomainsList,
25
24
  matchRoute,
26
25
  buildUrl,
27
26
  } from '@common/router';
@@ -53,6 +52,11 @@ import appRoutes from '@generated/client/routes';
53
52
  const debug = false;
54
53
  const LogPrefix = '[router]';
55
54
  const browserWindow = window as Window & { routes?: TSsrUnresolvedRoute[]; ssr?: TBasicSSrData };
55
+ const withProfiler = <T,>(callback: (runtime: (typeof import('@client/dev/profiler/runtime'))['profilerRuntime']) => T) => {
56
+ if (!__DEV__) return undefined as T | undefined;
57
+ const profilerModule = require('@client/dev/profiler/runtime') as typeof import('@client/dev/profiler/runtime');
58
+ return callback(profilerModule.profilerRuntime);
59
+ };
56
60
 
57
61
  /*----------------------------------
58
62
  - TYPES
@@ -136,7 +140,7 @@ export default class ClientRouter<
136
140
  // Context data
137
141
  public ssrRoutes = browserWindow.routes || [];
138
142
  public ssrContext = browserWindow.ssr;
139
- public domains: TDomainsList = browserWindow.ssr?.domains || ({ current: window.location.origin } as TDomainsList);
143
+ public currentDomain = browserWindow.ssr?.currentDomain || window.location.origin;
140
144
  public context!: TRouterContext<this, this['app']>;
141
145
 
142
146
  public setLoading!: React.Dispatch<React.SetStateAction<boolean>>;
@@ -153,7 +157,7 @@ export default class ClientRouter<
153
157
  }
154
158
 
155
159
  public url = (path: string, params: {} = {}, absolute: boolean = true) =>
156
- buildUrl(path, params, this.domains, absolute);
160
+ buildUrl(path, params, this.currentDomain, absolute);
157
161
 
158
162
  public go(url: string | number, data: {} = {}, opt: { newTab?: boolean } = {}) {
159
163
  // Error code
@@ -321,12 +325,19 @@ export default class ClientRouter<
321
325
 
322
326
  // Create response
323
327
  debug && console.log(LogPrefix, 'Resolved request', request.path, '| Route:', route);
328
+ withProfiler((runtime) =>
329
+ runtime.completeResolveStep({
330
+ chunkId: 'chunk' in route ? route.chunk : route.options.id,
331
+ routeLabel: request.path,
332
+ }),
333
+ );
324
334
  const page = await this.createResponse(route, request);
325
335
 
326
336
  return page;
327
337
  }
328
338
 
329
339
  const notFoundRoute = this.errors[404];
340
+ withProfiler((runtime) => runtime.completeResolveStep({ routeLabel: '404' }));
330
341
  return await this.createResponse(notFoundRoute, request, { error: new Error('Page not found') });
331
342
  }
332
343
 
@@ -338,16 +349,21 @@ export default class ClientRouter<
338
349
  //throw new Error(`Failed to load route: ${route.chunk}`);
339
350
 
340
351
  debug && console.log(`Fetching route ${route.chunk} ...`, route);
352
+ const stepId = withProfiler((runtime) => runtime.startChunkStep(route.chunk));
341
353
  try {
342
354
  const loaded = await route.load();
343
355
  const fetched = loaded.__register(this.app);
344
356
 
345
357
  debug && console.log(`Route fetched: ${route.chunk}`, fetched);
358
+ withProfiler((runtime) => runtime.finishStep(stepId));
346
359
 
347
360
  if ('code' in route) return fetched as TClientPageErrorRoute<this>;
348
361
 
349
362
  return { ...(fetched as TClientPageRoute<this>), regex: route.regex, keys: route.keys };
350
363
  } catch (e) {
364
+ withProfiler((runtime) =>
365
+ runtime.finishStep(stepId, 'error', e instanceof Error ? e.message : String(e)),
366
+ );
351
367
  console.error(`Failed to fetch the route ${route.chunk}`, e);
352
368
  try {
353
369
  this.app.handleUpdate();
@@ -368,6 +384,13 @@ export default class ClientRouter<
368
384
  if (!route) throw new Error(`Unable to resolve route.`);
369
385
 
370
386
  const request = new ClientRequest(location, this);
387
+ withProfiler((runtime) =>
388
+ runtime.ensureInitialSession({
389
+ path: request.path,
390
+ requestId: this.ssrContext?.request.id,
391
+ url: request.url,
392
+ }),
393
+ );
371
394
 
372
395
  // Restituate SSR response
373
396
  let apiData: {} = {};
@@ -385,6 +408,7 @@ export default class ClientRouter<
385
408
 
386
409
  ReactDOM.hydrate(<App context={response.context as AppPropsContext} />, document.body, () => {
387
410
  console.log(`Render complete`);
411
+ withProfiler((runtime) => runtime.markInitialHydrated({ chunkId: response.chunkId, title: response.title }));
388
412
 
389
413
  this.runHook('page.rendered', request);
390
414
  });
@@ -24,6 +24,16 @@ import { toMultipart } from './multipart';
24
24
  ----------------------------------*/
25
25
 
26
26
  const debug = false;
27
+ const getProfilerModule = () => {
28
+ if (!__DEV__) return undefined;
29
+ return require('@client/dev/profiler/runtime') as typeof import('@client/dev/profiler/runtime');
30
+ };
31
+ const withProfiler = <T>(callback: (runtime: (typeof import('@client/dev/profiler/runtime'))['profilerRuntime']) => T) => {
32
+ const profilerModule = getProfilerModule();
33
+ return profilerModule ? callback(profilerModule.profilerRuntime) : undefined;
34
+ };
35
+
36
+ type TExecuteResult<TData> = { data: TData; durationMs: number; response: Response };
27
37
 
28
38
  export type Config = {};
29
39
 
@@ -126,8 +136,54 @@ export default class ApiClient implements ApiClientService {
126
136
  ): Promise<TData> {
127
137
  /*if (options?.captcha !== undefined)
128
138
  await this.gui.captcha.check(options?.captcha);*/
139
+ const pendingTrace = withProfiler((runtime) =>
140
+ runtime.startTrace('async', {
141
+ label: `${method} ${path}`,
142
+ method,
143
+ path,
144
+ }),
145
+ );
146
+
147
+ try {
148
+ const result = await this.executeDetailed<TData>('client-async', method, path, data, options);
149
+ const profilerModule = getProfilerModule();
150
+ const traceRequestId = profilerModule?.readProfilerTraceRequestId(result.response);
151
+
152
+ if (pendingTrace && traceRequestId) {
153
+ await profilerModule?.profilerRuntime.attachTraceByRequestId(
154
+ pendingTrace.sessionId,
155
+ pendingTrace.traceId,
156
+ traceRequestId,
157
+ );
158
+ } else if (pendingTrace) {
159
+ withProfiler((runtime) =>
160
+ runtime.completeTrace(pendingTrace.traceId, {
161
+ durationMs: result.durationMs,
162
+ status: 'completed',
163
+ }),
164
+ );
165
+ }
129
166
 
130
- return await this.execute<TData>(method, path, data, options);
167
+ return result.data;
168
+ } catch (error) {
169
+ const profilerModule = getProfilerModule();
170
+ const errorResponse = (error as Error & { response?: Response }).response;
171
+ const traceRequestId = errorResponse ? profilerModule?.readProfilerTraceRequestId(errorResponse) : undefined;
172
+ if (pendingTrace && traceRequestId) {
173
+ await profilerModule?.profilerRuntime.attachTraceByRequestId(
174
+ pendingTrace.sessionId,
175
+ pendingTrace.traceId,
176
+ traceRequestId,
177
+ );
178
+ }
179
+ withProfiler((runtime) =>
180
+ runtime.completeTrace(pendingTrace?.traceId, {
181
+ errorMessage: error instanceof Error ? error.message : String(error),
182
+ status: 'error',
183
+ }),
184
+ );
185
+ throw error;
186
+ }
131
187
  }
132
188
 
133
189
  public async fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees> {
@@ -145,23 +201,68 @@ export default class ApiClient implements ApiClientService {
145
201
  const fetchedData =
146
202
  fetchersCount === 0
147
203
  ? {}
148
- : await this.execute<TObjetDonnees>(
149
- 'POST',
150
- '/api',
151
- ({ fetchers: fetchersToRun } as unknown) as TPostData,
152
- )
153
- .then((res: TObjetDonnees) => {
154
- const data: TObjetDonnees = {};
155
- for (const id in res) data[id] = res[id];
156
-
157
- return data;
158
- })
159
- .catch((e: Error) => {
204
+ : await (async () => {
205
+ const pendingTrace = withProfiler((runtime) =>
206
+ runtime.startTrace('navigation-data', {
207
+ fetcherIds: Object.keys(fetchersToRun),
208
+ label: 'Navigation data',
209
+ method: 'POST',
210
+ path: '/api',
211
+ }),
212
+ );
213
+
214
+ try {
215
+ const result = await this.executeDetailed<TObjetDonnees>(
216
+ 'client-navigation',
217
+ 'POST',
218
+ '/api',
219
+ ({ fetchers: fetchersToRun } as unknown) as TPostData,
220
+ );
221
+ const profilerModule = getProfilerModule();
222
+ const traceRequestId = profilerModule?.readProfilerTraceRequestId(result.response);
223
+
224
+ if (pendingTrace && traceRequestId) {
225
+ await profilerModule?.profilerRuntime.attachTraceByRequestId(
226
+ pendingTrace.sessionId,
227
+ pendingTrace.traceId,
228
+ traceRequestId,
229
+ );
230
+ } else if (pendingTrace) {
231
+ withProfiler((runtime) =>
232
+ runtime.completeTrace(pendingTrace.traceId, {
233
+ durationMs: result.durationMs,
234
+ status: 'completed',
235
+ }),
236
+ );
237
+ }
238
+
239
+ const responseData: TObjetDonnees = {};
240
+ for (const id in result.data) responseData[id] = result.data[id];
241
+ return responseData;
242
+ } catch (e) {
243
+ const profilerModule = getProfilerModule();
244
+ const errorResponse = (e as Error & { response?: Response }).response;
245
+ const traceRequestId = errorResponse ? profilerModule?.readProfilerTraceRequestId(errorResponse) : undefined;
246
+ if (pendingTrace && traceRequestId) {
247
+ await profilerModule?.profilerRuntime.attachTraceByRequestId(
248
+ pendingTrace.sessionId,
249
+ pendingTrace.traceId,
250
+ traceRequestId,
251
+ );
252
+ }
253
+ withProfiler((runtime) =>
254
+ runtime.completeTrace(pendingTrace?.traceId, {
255
+ errorMessage: e instanceof Error ? e.message : String(e),
256
+ status: 'error',
257
+ }),
258
+ );
259
+
160
260
  // API Error hook
161
- this.app.handleError(e);
261
+ this.app.handleError(e as Error);
162
262
 
163
263
  throw e;
164
- });
264
+ }
265
+ })();
165
266
 
166
267
  // Errors will be catched in the caller
167
268
 
@@ -214,21 +315,36 @@ export default class ApiClient implements ApiClientService {
214
315
  };
215
316
 
216
317
  public execute<TData = unknown>(...args: TFetcherArgs): Promise<TData> {
318
+ return this.executeDetailed<TData>('client-async', ...args).then((result) => result.data);
319
+ }
320
+
321
+ private async executeDetailed<TData = unknown>(
322
+ profilerOrigin: string,
323
+ ...args: TFetcherArgs
324
+ ): Promise<TExecuteResult<TData>> {
217
325
  const { url, config } = this.configure(...args);
326
+ const startedAt = Date.now();
327
+ const headers = config.headers instanceof Headers ? config.headers : new Headers(config.headers as HeadersInit);
328
+ const profilerHeaders = withProfiler((runtime) => runtime.getRequestHeaders(profilerOrigin)) || {};
329
+ for (const [key, value] of Object.entries(profilerHeaders)) headers.set(key, value);
330
+ config.headers = headers;
218
331
 
219
332
  console.log(`[api] Fetching`, url, config);
220
333
 
221
334
  return fetch(url, config)
222
335
  .then(async (response) => {
336
+ const requestDurationMs = Math.max(0, Date.now() - startedAt);
223
337
  if (!response.ok) {
224
338
  const errorData = await response.json();
225
339
  console.warn(`[api] Failure:`, response.status, errorData);
226
- const error = errorFromJson(errorData);
340
+ const error = errorFromJson(errorData) as Error & { durationMs?: number; response?: Response };
341
+ error.durationMs = requestDurationMs;
342
+ error.response = response;
227
343
  throw error;
228
344
  }
229
345
  const json = (await response.json()) as TData;
230
346
  debug && console.log(`[api] Success:`, json);
231
- return json;
347
+ return { data: json, durationMs: requestDurationMs, response };
232
348
  })
233
349
  .catch((error) => {
234
350
  if (error instanceof TypeError) {
@@ -0,0 +1,11 @@
1
+ import { Commands } from '@server/app/commands';
2
+
3
+ export default class ProteumDiagnosticsCommands extends Commands {
4
+ public async ping() {
5
+ return {
6
+ app: this.app.identity.identifier,
7
+ envProfile: this.app.env.profile,
8
+ services: Object.keys(this.app.getRootServices()),
9
+ };
10
+ }
11
+ }
@@ -0,0 +1,50 @@
1
+ import type { TTraceSummaryValue } from './requestTrace';
2
+
3
+ export type TDevCommandScope = 'app' | 'framework';
4
+ export type TDevCommandSourceLocation = { line: number; column: number };
5
+ export type TDevCommandExecutionStatus = 'completed' | 'error';
6
+
7
+ export type TDevCommandDefinition = {
8
+ path: string;
9
+ className: string;
10
+ methodName: string;
11
+ importPath: string;
12
+ filepath: string;
13
+ sourceLocation: TDevCommandSourceLocation;
14
+ scope: TDevCommandScope;
15
+ };
16
+
17
+ export type TDevCommandSerializedResult = {
18
+ json?: unknown;
19
+ summary: TTraceSummaryValue;
20
+ };
21
+
22
+ export type TDevCommandExecution = {
23
+ command: TDevCommandDefinition;
24
+ startedAt: string;
25
+ finishedAt: string;
26
+ durationMs: number;
27
+ status: TDevCommandExecutionStatus;
28
+ result?: TDevCommandSerializedResult;
29
+ errorMessage?: string;
30
+ };
31
+
32
+ export type TDevCommandListResponse = {
33
+ commands: TDevCommandDefinition[];
34
+ };
35
+
36
+ export type TDevCommandRunResponse = {
37
+ execution: TDevCommandExecution;
38
+ };
39
+
40
+ export type TDevCommandErrorResponse = {
41
+ error: string;
42
+ execution?: TDevCommandExecution;
43
+ };
44
+
45
+ export const normalizeDevCommandPath = (value: string) =>
46
+ value
47
+ .trim()
48
+ .replace(/^\/+/, '')
49
+ .replace(/\/+$/, '')
50
+ .replace(/\/{2,}/g, '/');