proteum 2.1.9 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/.codex/environments/environment.toml +11 -0
  2. package/AGENTS.md +25 -11
  3. package/README.md +19 -9
  4. package/agents/project/AGENTS.md +165 -120
  5. package/agents/project/CODING_STYLE.md +1 -1
  6. package/agents/project/app-root/AGENTS.md +16 -0
  7. package/agents/project/client/AGENTS.md +5 -5
  8. package/agents/project/client/pages/AGENTS.md +13 -13
  9. package/agents/project/diagnostics.md +19 -10
  10. package/agents/project/optimizations.md +5 -6
  11. package/agents/project/root/AGENTS.md +295 -0
  12. package/agents/project/server/routes/AGENTS.md +2 -2
  13. package/agents/project/server/services/AGENTS.md +4 -2
  14. package/agents/project/tests/AGENTS.md +2 -2
  15. package/cli/app/index.ts +31 -7
  16. package/cli/commands/configure.ts +226 -0
  17. package/cli/commands/dev.ts +0 -2
  18. package/cli/commands/diagnose.ts +33 -1
  19. package/cli/commands/explain.ts +1 -1
  20. package/cli/commands/migrate.ts +51 -0
  21. package/cli/commands/orient.ts +169 -0
  22. package/cli/commands/perf.ts +8 -1
  23. package/cli/commands/verify.ts +1003 -49
  24. package/cli/compiler/artifacts/manifest.ts +4 -4
  25. package/cli/compiler/artifacts/routing.ts +2 -2
  26. package/cli/compiler/artifacts/services.ts +12 -3
  27. package/cli/compiler/client/index.ts +65 -19
  28. package/cli/compiler/common/files/style.ts +47 -2
  29. package/cli/compiler/common/generatedRouteModules.ts +31 -38
  30. package/cli/compiler/common/index.ts +10 -0
  31. package/cli/compiler/common/proteumManifest.ts +1 -0
  32. package/cli/compiler/server/index.ts +34 -9
  33. package/cli/context.ts +6 -1
  34. package/cli/index.ts +7 -8
  35. package/cli/migrate/pageContract.ts +516 -0
  36. package/cli/paths.ts +47 -6
  37. package/cli/presentation/commands.ts +100 -10
  38. package/cli/presentation/devSession.ts +4 -6
  39. package/cli/presentation/help.ts +2 -2
  40. package/cli/presentation/ink.ts +10 -5
  41. package/cli/presentation/welcome.ts +2 -4
  42. package/cli/runtime/commands.ts +94 -1
  43. package/cli/scaffold/index.ts +2 -2
  44. package/cli/scaffold/templates.ts +4 -2
  45. package/cli/utils/agents.ts +273 -58
  46. package/client/dev/profiler/index.tsx +3 -2
  47. package/client/router.ts +10 -2
  48. package/client/services/router/index.tsx +6 -22
  49. package/common/dev/connect.ts +20 -4
  50. package/common/dev/console.ts +7 -0
  51. package/common/dev/contractsDoctor.ts +354 -0
  52. package/common/dev/diagnostics.ts +10 -7
  53. package/common/dev/inspection.ts +830 -38
  54. package/common/dev/performance.ts +19 -5
  55. package/common/dev/profiler.ts +1 -0
  56. package/common/dev/proteumManifest.ts +5 -4
  57. package/common/dev/requestTrace.ts +12 -1
  58. package/common/router/contracts.ts +8 -11
  59. package/common/router/index.ts +2 -2
  60. package/common/router/pageData.ts +72 -0
  61. package/common/router/register.ts +10 -46
  62. package/common/router/response/page.ts +28 -16
  63. package/docs/dev-sessions.md +8 -4
  64. package/docs/diagnostics.md +77 -11
  65. package/docs/migrate-from-2.1.3.md +388 -0
  66. package/docs/request-tracing.md +25 -6
  67. package/package.json +6 -1
  68. package/scripts/update-codex-agents.ts +2 -2
  69. package/server/app/container/console/index.ts +11 -1
  70. package/server/app/container/trace/index.ts +117 -0
  71. package/server/app/devDiagnostics.ts +1 -1
  72. package/server/app/index.ts +5 -1
  73. package/server/services/auth/index.ts +9 -0
  74. package/server/services/router/index.ts +64 -14
  75. package/server/services/router/request/api.ts +7 -1
  76. package/server/services/router/response/index.ts +8 -28
  77. package/types/global/vendors.d.ts +12 -0
  78. package/types/vendors.d.ts +12 -0
  79. package/common/router/pageSetup.ts +0 -51
@@ -1,7 +1,10 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
+ import { createHash } from 'crypto';
3
4
 
4
5
  import type ApplicationContainer from '..';
6
+ import context from '@server/context';
7
+ import type { ChannelInfos } from '../console';
5
8
  import {
6
9
  traceCaptureModes,
7
10
  type TTraceCaptureMode,
@@ -17,6 +20,7 @@ import {
17
20
  type TTraceMemorySnapshot,
18
21
  type TRequestTraceListItem,
19
22
  } from '@common/dev/requestTrace';
23
+ import type { TProteumManifest } from '@common/dev/proteumManifest';
20
24
 
21
25
  export type Config = {
22
26
  enable: boolean;
@@ -33,6 +37,10 @@ const capturePriority: Record<TTraceCaptureMode, number> = { summary: 0, resolve
33
37
  const sensitiveKeyPattern =
34
38
  /(^|\.)(authorization|cookie|set-cookie|password|pass|pwd|secret|token|refreshToken|accessToken|apiKey|apiSecret|secretAccessKey|accessKeyId|privateKey|session|jwt|rawBody)$/i;
35
39
  const maxStringLength = 240;
40
+ const normalizeFilepath = (value: string) => value.replace(/\\/g, '/');
41
+ const sqlCommentPattern = /\/\*[\s\S]*?\*\//g;
42
+ const sqlLineCommentPattern = /--.*$/gm;
43
+ const stackFilepathPatterns = [/\((\/.+?):\d+:\d+\)$/, /at (\/.+?):\d+:\d+$/];
36
44
 
37
45
  const isTraceCaptureMode = (value: string): value is TTraceCaptureMode =>
38
46
  traceCaptureModes.includes(value as TTraceCaptureMode);
@@ -176,6 +184,11 @@ export default class Trace {
176
184
  private requests = new Map<string, TRequestTrace>();
177
185
  private order: string[] = [];
178
186
  private armedCapture?: TTraceCaptureMode;
187
+ private manifestCache?: {
188
+ manifest: TProteumManifest;
189
+ mtimeMs: number;
190
+ serviceByFilepath: Map<string, string>;
191
+ };
179
192
  private activeMeasurements = new Map<
180
193
  string,
181
194
  {
@@ -193,6 +206,86 @@ export default class Trace {
193
206
  return __DEV__ && this.config.enable && this.container.Environment.profile === 'dev';
194
207
  }
195
208
 
209
+ private getContextChannel() {
210
+ return context.getStore() as ChannelInfos | undefined;
211
+ }
212
+
213
+ private readManifestCache() {
214
+ const manifestFilepath = path.join(this.container.path.root, '.proteum', 'manifest.json');
215
+ if (!fs.existsSync(manifestFilepath)) return undefined;
216
+
217
+ const stats = fs.statSync(manifestFilepath);
218
+ if (this.manifestCache && this.manifestCache.mtimeMs === stats.mtimeMs) return this.manifestCache;
219
+
220
+ const manifest = fs.readJSONSync(manifestFilepath) as TProteumManifest;
221
+ const serviceByFilepath = new Map<string, string>();
222
+ for (const service of [...manifest.services.app, ...manifest.services.routerPlugins]) {
223
+ if (!service.sourceFilepath) continue;
224
+ serviceByFilepath.set(normalizeFilepath(path.resolve(service.sourceFilepath)), service.registeredName);
225
+ }
226
+
227
+ this.manifestCache = {
228
+ manifest,
229
+ mtimeMs: stats.mtimeMs,
230
+ serviceByFilepath,
231
+ };
232
+
233
+ return this.manifestCache;
234
+ }
235
+
236
+ private getStackFilepaths(stack?: string) {
237
+ if (!stack) return [];
238
+
239
+ const filepaths: string[] = [];
240
+ for (const line of stack.split('\n')) {
241
+ const trimmedLine = line.trim();
242
+ let matchedFilepath: string | undefined;
243
+
244
+ for (const pattern of stackFilepathPatterns) {
245
+ const match = trimmedLine.match(pattern);
246
+ if (match?.[1]) {
247
+ matchedFilepath = match[1];
248
+ break;
249
+ }
250
+ }
251
+
252
+ if (!matchedFilepath) continue;
253
+ if (matchedFilepath.includes('/node_modules/')) continue;
254
+
255
+ filepaths.push(normalizeFilepath(path.resolve(matchedFilepath)));
256
+ }
257
+
258
+ return filepaths;
259
+ }
260
+
261
+ private inferServiceLabelFromStack(stack?: string) {
262
+ const manifestCache = this.readManifestCache();
263
+ if (!manifestCache) return undefined;
264
+
265
+ for (const filepath of this.getStackFilepaths(stack)) {
266
+ const serviceLabel = manifestCache.serviceByFilepath.get(filepath);
267
+ if (serviceLabel) return serviceLabel;
268
+ }
269
+
270
+ return undefined;
271
+ }
272
+
273
+ private createSqlFingerprint(query: string) {
274
+ const normalized = query
275
+ .replace(sqlCommentPattern, ' ')
276
+ .replace(sqlLineCommentPattern, ' ')
277
+ .replace(/'([^']|'')*'/g, '?')
278
+ .replace(/"([^"]|"")*"/g, '?')
279
+ .replace(/\b\d+(?:\.\d+)?\b/g, '?')
280
+ .replace(/\s+/g, ' ')
281
+ .trim()
282
+ .toUpperCase();
283
+
284
+ if (!normalized) return undefined;
285
+
286
+ return createHash('sha1').update(normalized).digest('hex').slice(0, 12);
287
+ }
288
+
196
289
  public armNextRequest(capture: string) {
197
290
  if (!isTraceCaptureMode(capture)) {
198
291
  throw new Error(`Unsupported trace capture mode "${capture}". Expected one of: ${traceCaptureModes.join(', ')}.`);
@@ -330,6 +423,11 @@ export default class Trace {
330
423
  fetcherId?: string;
331
424
  connectedProjectNamespace?: string;
332
425
  connectedControllerAccessor?: string;
426
+ ownerLabel?: string;
427
+ ownerFilepath?: string;
428
+ serviceLabel?: string;
429
+ cacheKey?: string;
430
+ cachePhase?: string;
333
431
  parentId?: string;
334
432
  requestDataKeys?: string[];
335
433
  requestData?: TTraceInspectable;
@@ -337,6 +435,8 @@ export default class Trace {
337
435
  ) {
338
436
  const trace = this.requests.get(requestId);
339
437
  if (!trace) return undefined;
438
+ const channel = this.getContextChannel();
439
+ const inferredServiceLabel = input.serviceLabel || channel?.serviceLabel || this.inferServiceLabelFromStack(new Error().stack);
340
440
 
341
441
  const call: TTraceCall = {
342
442
  id: `${requestId}:call:${trace.calls.length}`,
@@ -348,6 +448,11 @@ export default class Trace {
348
448
  fetcherId: input.fetcherId,
349
449
  connectedProjectNamespace: input.connectedProjectNamespace,
350
450
  connectedControllerAccessor: input.connectedControllerAccessor,
451
+ ownerLabel: input.ownerLabel || channel?.ownerLabel,
452
+ ownerFilepath: input.ownerFilepath || channel?.ownerFilepath,
453
+ serviceLabel: inferredServiceLabel,
454
+ cacheKey: input.cacheKey || channel?.cacheKey,
455
+ cachePhase: input.cachePhase || channel?.cachePhase,
351
456
  startedAt: nowIso(),
352
457
  requestDataKeys: input.requestDataKeys || [],
353
458
  requestData: input.requestData !== undefined ? summarizeCaptureValue(input.requestData, trace.capture, 'requestData') : undefined,
@@ -405,6 +510,10 @@ export default class Trace {
405
510
  kind: TTraceSqlQueryKind;
406
511
  model?: string;
407
512
  operation: string;
513
+ ownerLabel?: string;
514
+ ownerFilepath?: string;
515
+ serviceLabel?: string;
516
+ connectedNamespace?: string;
408
517
  paramsJson?: unknown;
409
518
  paramsText?: string;
410
519
  query: string;
@@ -413,12 +522,15 @@ export default class Trace {
413
522
  ) {
414
523
  const trace = this.requests.get(requestId);
415
524
  if (!trace) return;
525
+ const channel = this.getContextChannel();
416
526
 
417
527
  const durationMs = Math.max(0, input.durationMs || 0);
418
528
  const finishedAt = input.finishedAt || nowIso();
419
529
  const finishedAtMs = Date.parse(finishedAt);
420
530
  const startedAt =
421
531
  Number.isFinite(finishedAtMs) && durationMs > 0 ? new Date(finishedAtMs - durationMs).toISOString() : finishedAt;
532
+ const fingerprint = this.createSqlFingerprint(input.query);
533
+ const inferredServiceLabel = input.serviceLabel || channel?.serviceLabel || this.inferServiceLabelFromStack(new Error().stack);
422
534
 
423
535
  const sqlQuery: TTraceSqlQuery = {
424
536
  id: `${requestId}:sql:${trace.sqlQueries.length}`,
@@ -433,6 +545,11 @@ export default class Trace {
433
545
  kind: input.kind,
434
546
  model: input.model,
435
547
  operation: input.operation,
548
+ fingerprint,
549
+ ownerLabel: input.ownerLabel || channel?.ownerLabel,
550
+ ownerFilepath: input.ownerFilepath || channel?.ownerFilepath,
551
+ serviceLabel: inferredServiceLabel,
552
+ connectedNamespace: input.connectedNamespace || channel?.connectedNamespace,
436
553
  paramsJson: input.paramsJson,
437
554
  paramsText: input.paramsText,
438
555
  query: input.query.trim(),
@@ -186,6 +186,6 @@ export default class DevDiagnosticsRegistry<TApplication extends Application = A
186
186
  }
187
187
 
188
188
  public perfRequest(requestIdOrPath: string): TPerfRequestResponse {
189
- return { request: resolvePerfRequest(this.readPerfRequests(), requestIdOrPath) };
189
+ return { request: resolvePerfRequest(this.readPerfRequests(), requestIdOrPath, this.readManifest()) };
190
190
  }
191
191
  }
@@ -202,7 +202,11 @@ export abstract class Application<
202
202
  const connectedProject = this.getConnectedProject(namespace);
203
203
  if (connectedProject) return connectedProject;
204
204
 
205
- throw new Error(`Connected project "${namespace}" is not configured on ${this.identity.identifier}.`);
205
+ throw new Error(
206
+ `Proteum connected boundary mismatch: "${namespace}" is not configured on ${this.identity.identifier}. ` +
207
+ `Likely fix: add connect.${namespace} in proteum.config.ts for this app or stop calling that connected namespace from this side. ` +
208
+ `Re-check both SSR and client navigation if the namespace is used from page render or setup code.`,
209
+ );
206
210
  }
207
211
 
208
212
  public register(service: AnyService) {
@@ -853,6 +853,9 @@ export default abstract class AuthService<
853
853
  return user as TUser;
854
854
  }
855
855
 
856
+ /**
857
+ * @deprecated Use `check(request, null, tracking)` to make the authenticated-user requirement explicit.
858
+ */
856
859
  public check(request: TRequest): TUser;
857
860
 
858
861
  public check(request: TRequest, conditions: null, tracking?: TAuthTrackingContext): TUser;
@@ -861,8 +864,14 @@ export default abstract class AuthService<
861
864
 
862
865
  public check(request: TRequest, conditions: false, tracking?: TAuthTrackingContext): null;
863
866
 
867
+ /**
868
+ * @deprecated Use `check(request, { role }, tracking)` or another explicit conditions object instead.
869
+ */
864
870
  public check(request: TRequest, role?: TUserRole | boolean): TUser | null;
865
871
 
872
+ /**
873
+ * @deprecated Use `check(request, { role, ...rules }, tracking)` with app-defined auth rules instead of legacy feature/action arguments.
874
+ */
866
875
  public check(request: TRequest, role: TUserRole | boolean, feature: FeatureKeys, action?: string): TUser | null;
867
876
 
868
877
  public check(
@@ -37,6 +37,7 @@ import type { TSsrUnresolvedRoute, TRegisterPageArgs } from '@common/router/cont
37
37
  import { buildRegex, getRegisterPageArgs } from '@common/router/register';
38
38
  import { layoutsList, getLayout } from '@common/router/layouts';
39
39
  import {
40
+ profilerConnectedNamespaceHeader,
40
41
  profilerOriginHeader,
41
42
  profilerParentRequestIdHeader,
42
43
  profilerSessionIdHeader,
@@ -221,6 +222,7 @@ export default class ServerRouter<
221
222
  const methodName = match ? match[2] : '<anonymous>';*/
222
223
 
223
224
  const contextData = context.getStore() || { channelType: 'master' };
225
+ if (contextData.silentLogs) return;
224
226
 
225
227
  const requestPrefix =
226
228
  contextData.channelType === 'request'
@@ -272,21 +274,18 @@ export default class ServerRouter<
272
274
 
273
275
  public async renderStatic(url: string, options: TRouteOptions['static'], rendered?: any) {
274
276
  // Wildcard: tell that the newly rendered pages should be cached
275
- if (url === '*' || !url) return;
276
-
277
- if (!rendered) {
278
- console.log('[router] renderStatic: url', url);
277
+ if (url === '*' || !url) throw new Error(`Unable to cache a dynamic or empty URL.`);
279
278
 
279
+ if (rendered === undefined) {
280
280
  const fullUrl = this.url(url, {}, true);
281
281
  const response = await got(fullUrl, {
282
282
  method: 'GET',
283
- headers: { Accept: 'text/html', bypasscache: '1' },
283
+ headers: { Accept: 'text/html', bypasscache: '1', 'x-proteum-static-warmup': '1' },
284
284
  throwHttpErrors: false,
285
285
  });
286
286
 
287
287
  if (response.statusCode !== 200) {
288
- console.error('[router] renderStatic: page returned code', response.statusCode, fullUrl);
289
- return;
288
+ throw new Error(`Static render returned ${response.statusCode} for ${fullUrl}`);
290
289
  }
291
290
 
292
291
  rendered = response.body;
@@ -301,17 +300,50 @@ export default class ServerRouter<
301
300
 
302
301
  private initStaticRoutes() {
303
302
  this.clearStaticRoutesRefreshInterval();
303
+ const staticEntries: Array<{ routePath: string; url: string; options: TRouteOptions['static'] }> = [];
304
+ const seenStaticUrls = new Set<string>();
304
305
 
305
306
  for (const route of this.routes) {
307
+ if (route.method !== 'GET' || route.options.accept !== 'html') continue;
308
+
306
309
  if (!route.options.static) continue;
307
310
 
308
311
  // Add to static pages
309
312
  // Should be a GET oage that don't take any parameter
310
313
  for (const url of route.options.static.urls) {
311
- this.renderStatic(url, route.options.static);
314
+ if (!url || url === '*' || seenStaticUrls.has(url)) continue;
315
+
316
+ staticEntries.push({
317
+ routePath: route.path || '(unknown route)',
318
+ url,
319
+ options: route.options.static,
320
+ });
321
+ seenStaticUrls.add(url);
312
322
  }
313
323
  }
314
324
 
325
+ void (async () => {
326
+ const warmedUrls: string[] = [];
327
+ let failedCount = 0;
328
+
329
+ for (const entry of staticEntries) {
330
+ try {
331
+ await this.renderStatic(entry.url, entry.options);
332
+ warmedUrls.push(entry.url);
333
+ } catch (error) {
334
+ failedCount += 1;
335
+ console.error('[router] Static warmup failed', entry.url, `route=${entry.routePath}`, error);
336
+ }
337
+ }
338
+
339
+ console.log(
340
+ '[router] Static warmup finished',
341
+ `warmed=${warmedUrls.length}`,
342
+ `failed=${failedCount}`,
343
+ `urls=${warmedUrls.length > 0 ? warmedUrls.join(', ') : 'none'}`,
344
+ );
345
+ })();
346
+
315
347
  // Every hours, refresh static pages
316
348
  this.staticRoutesRefreshInterval = setInterval(
317
349
  () => {
@@ -327,7 +359,9 @@ export default class ServerRouter<
327
359
  for (const pageUrl in this.cache) {
328
360
  const page = this.cache[pageUrl];
329
361
  if (page.expire && page.expire < Date.now()) {
330
- this.renderStatic(pageUrl, page.options);
362
+ void this.renderStatic(pageUrl, page.options).catch((error) => {
363
+ console.error('[router] Static refresh failed', pageUrl, error);
364
+ });
331
365
  }
332
366
  }
333
367
  }
@@ -382,7 +416,7 @@ export default class ServerRouter<
382
416
  ----------------------------------*/
383
417
 
384
418
  public page(...args: TRegisterPageArgs<any, TRouteOptions>) {
385
- const { path, options, setup, renderer, layout } = getRegisterPageArgs(...args);
419
+ const { path, options, data, renderer, layout } = getRegisterPageArgs(...args);
386
420
 
387
421
  const { regex, keys } = buildRegex(path);
388
422
 
@@ -391,10 +425,10 @@ export default class ServerRouter<
391
425
  path,
392
426
  regex,
393
427
  keys,
428
+ data,
394
429
  controller: (context: TRouterContext<this>) => new Page(route, renderer, context, layout),
395
430
  options: this.buildRouteOptions({
396
431
  accept: 'html', // Les pages retournent forcémment du html
397
- setup,
398
432
  ...options,
399
433
  }),
400
434
  };
@@ -607,6 +641,14 @@ export default class ServerRouter<
607
641
  profilerParentRequestId: request.headers[profilerParentRequestIdHeader] || undefined,
608
642
  });
609
643
  if (this.app.container.Trace.isEnabled()) res.setHeader(profilerTraceRequestIdHeader, request.id);
644
+ if (cachedPage) {
645
+ this.app.container.Trace.record(
646
+ request.id,
647
+ 'cache.hit',
648
+ { cacheKey: req.path, cachePhase: 'hit' },
649
+ 'summary',
650
+ );
651
+ }
610
652
 
611
653
  let response: ServerResponse<this>;
612
654
  try {
@@ -747,8 +789,10 @@ export default class ServerRouter<
747
789
  // This is for debugging
748
790
  channelType: 'request',
749
791
  channelId: request.id,
792
+ silentLogs: request.headers['x-proteum-static-warmup'] === '1',
750
793
  method: request.method,
751
794
  path: request.path,
795
+ connectedNamespace: request.headers[profilerConnectedNamespaceHeader] || undefined,
752
796
  ...(request.traceCall
753
797
  ? {
754
798
  traceCallFetcherId: request.traceCall.fetcherId,
@@ -930,8 +974,6 @@ export default class ServerRouter<
930
974
  });
931
975
 
932
976
  private async resolvedRoute(route: TMatchedRoute, response: ServerResponse<this>, timeStart: number) {
933
- route = await response.resolveRouteOptions(route);
934
-
935
977
  this.app.container.Trace.record(
936
978
  response.request.id,
937
979
  'resolve.route-match',
@@ -964,11 +1006,19 @@ export default class ServerRouter<
964
1006
  if (!staticUrl) continue;
965
1007
 
966
1008
  console.log('[router] Set in cache', staticUrl);
1009
+ this.app.container.Trace.record(
1010
+ response.request.id,
1011
+ 'cache.write',
1012
+ { cacheKey: staticUrl, cachePhase: 'write' },
1013
+ 'summary',
1014
+ );
967
1015
  void this.renderStatic(
968
1016
  staticUrl,
969
1017
  route.options.static,
970
1018
  staticUrl === response.request.path ? response.data : undefined,
971
- );
1019
+ ).catch((error) => {
1020
+ console.error('[router] Static cache write failed', staticUrl, error);
1021
+ });
972
1022
  }
973
1023
  }
974
1024
 
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { fromJson as errorFromJson } from '@common/errors';
8
8
  import {
9
+ profilerConnectedNamespaceHeader,
9
10
  profilerOriginHeader,
10
11
  profilerParentRequestIdHeader,
11
12
  profilerSessionIdHeader,
@@ -87,6 +88,7 @@ export default class ApiClientRequest extends RequestService implements ApiClien
87
88
 
88
89
  if (fetcher.options?.connected) {
89
90
  headers.set(profilerOriginHeader, this.getTraceCallOrigin());
91
+ headers.set(profilerConnectedNamespaceHeader, fetcher.options.connected.namespace);
90
92
 
91
93
  const profilerSessionId = this.request.headers[profilerSessionIdHeader];
92
94
  if (profilerSessionId) headers.set(profilerSessionIdHeader, profilerSessionId);
@@ -102,7 +104,11 @@ export default class ApiClientRequest extends RequestService implements ApiClien
102
104
 
103
105
  const connectedProject = this.request.router.app.connectedProjects?.[connected.namespace];
104
106
  if (!connectedProject) {
105
- throw new Error(`Connected project "${connected.namespace}" is not registered on ${this.request.router.app.identity.identifier}.`);
107
+ throw new Error(
108
+ `Proteum connected boundary mismatch: "${connected.namespace}" is not registered on ${this.request.router.app.identity.identifier}. ` +
109
+ `Likely fix: declare connect.${connected.namespace} in proteum.config.ts for the consumer app or stop using that connected controller accessor here. ` +
110
+ `Re-check both SSR and client navigation if this fetcher is used from a page data or render path.`,
111
+ );
106
112
  }
107
113
 
108
114
  const headers = this.buildConnectedRequestHeaders(fetcher);
@@ -17,7 +17,6 @@ import ServerRequest from '@server/services/router/request';
17
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
- import { splitRouteSetupResult } from '@common/router/pageSetup';
21
20
  import Page from './page';
22
21
  import createControllers from '@generated/common/controllers';
23
22
  import type { TControllers } from '@generated/common/controllers';
@@ -154,11 +153,18 @@ export default class ServerResponse<
154
153
  // Create response context for controllers
155
154
  const requestContext = await this.createContext(route);
156
155
  const contextStore = context.getStore() as
157
- | { requestContext?: TRouterContext<TAnyRouter>; inputSchemaUsed?: boolean }
156
+ | {
157
+ requestContext?: TRouterContext<TAnyRouter>;
158
+ inputSchemaUsed?: boolean;
159
+ ownerLabel?: string;
160
+ ownerFilepath?: string;
161
+ }
158
162
  | undefined;
159
163
  if (contextStore) {
160
164
  contextStore.requestContext = requestContext;
161
165
  contextStore.inputSchemaUsed = false;
166
+ contextStore.ownerLabel = getRouteTraceTarget(route as TAnyRoute<TRouterContext<TServerRouter>>);
167
+ contextStore.ownerFilepath = route.options.filepath || undefined;
162
168
  }
163
169
 
164
170
  // Run controller
@@ -213,32 +219,6 @@ export default class ServerResponse<
213
219
  - INTERNAL
214
220
  ----------------------------------*/
215
221
 
216
- public async resolveRouteOptions(
217
- route: TMatchedRoute<TRouterContext<TRouter>>,
218
- ): Promise<TMatchedRoute<TRouterContext<TRouter>>> {
219
- const setup = route.options.setup;
220
- if (!setup) return route;
221
-
222
- const requestContext = await this.createContext(route);
223
- const { options } = splitRouteSetupResult(((setup as any)({ ...requestContext, data: this.request.data }) as {}) || {});
224
-
225
- this.app.container.Trace.record(
226
- this.request.id,
227
- 'setup.options',
228
- {
229
- optionKeys: Object.keys(options),
230
- source: {
231
- filepath: route.options.filepath || '',
232
- line: route.options.sourceLocation?.line || 0,
233
- column: route.options.sourceLocation?.column || 0,
234
- },
235
- },
236
- 'resolve',
237
- );
238
-
239
- return { ...route, options: { ...route.options, ...options } };
240
- }
241
-
242
222
  // Start controller services
243
223
  private async createContext(route: TAnyRoute<TRouterContext<TRouter>>): Promise<TRequestContext> {
244
224
  const contextServices = this.router.createContextServices(this.request);
@@ -3,6 +3,18 @@ declare module 'accepts' {
3
3
  export default accepts;
4
4
  }
5
5
 
6
+ declare module '@babel/generator' {
7
+ const generate: any;
8
+ export default generate;
9
+ }
10
+
11
+ declare module '@babel/traverse' {
12
+ export type Binding = any;
13
+ export type NodePath<T = any> = any;
14
+ const traverse: any;
15
+ export default traverse;
16
+ }
17
+
6
18
  declare module 'bytes' {
7
19
  const bytes: (value: string | number) => number;
8
20
  export default bytes;
@@ -3,6 +3,18 @@ declare module 'accepts' {
3
3
  export default accepts;
4
4
  }
5
5
 
6
+ declare module '@babel/generator' {
7
+ const generate: any;
8
+ export default generate;
9
+ }
10
+
11
+ declare module '@babel/traverse' {
12
+ export type Binding = any;
13
+ export type NodePath<T = any> = any;
14
+ const traverse: any;
15
+ export default traverse;
16
+ }
17
+
6
18
  declare module 'bytes' {
7
19
  const bytes: (value: string | number) => number;
8
20
  export default bytes;
@@ -1,51 +0,0 @@
1
- /*----------------------------------
2
- - TYPES
3
- ----------------------------------*/
4
-
5
- import type { TRouteOptions } from '.';
6
-
7
- export const routeSetupOptionKeys = [
8
- 'priority',
9
- 'preload',
10
- 'domain',
11
- 'accept',
12
- 'raw',
13
- 'auth',
14
- 'authTracking',
15
- 'redirectLogged',
16
- 'static',
17
- 'whenStatic',
18
- 'canonicalParams',
19
- 'layout',
20
- 'TESTING',
21
- 'logging',
22
- ] as const satisfies (keyof TRouteOptions)[];
23
-
24
- export const reservedRouteSetupKeys = ['id', 'filepath', 'bodyId', 'data', 'setup'] as const;
25
-
26
- const routeSetupOptionKeysSet = new Set<string>(routeSetupOptionKeys);
27
- const reservedRouteSetupKeysSet = new Set<string>(reservedRouteSetupKeys);
28
-
29
- export const getRouteSetupOptionKey = (key: string) => {
30
- const normalizedKey = key.startsWith('_') ? key.substring(1) : key;
31
-
32
- if (reservedRouteSetupKeysSet.has(normalizedKey)) throw new Error(`"${key}" is a reserved Router.page setup key.`);
33
-
34
- return routeSetupOptionKeysSet.has(normalizedKey) ? (normalizedKey as keyof TRouteOptions) : null;
35
- };
36
-
37
- export const splitRouteSetupResult = (result: TObjetDonnees | undefined) => {
38
- const options: Partial<TRouteOptions> = {};
39
- const data: TObjetDonnees = {};
40
-
41
- if (!result) return { options, data };
42
-
43
- for (const key in result) {
44
- const optionKey = getRouteSetupOptionKey(key);
45
-
46
- if (optionKey) options[optionKey] = result[key];
47
- else data[key] = result[key];
48
- }
49
-
50
- return { options, data };
51
- };