proteum 2.1.9 → 2.2.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/.codex/environments/environment.toml +11 -0
- package/AGENTS.md +27 -11
- package/README.md +30 -11
- package/agents/project/AGENTS.md +172 -123
- package/agents/project/CODING_STYLE.md +1 -1
- package/agents/project/app-root/AGENTS.md +16 -0
- package/agents/project/client/AGENTS.md +5 -5
- package/agents/project/client/pages/AGENTS.md +13 -13
- package/agents/project/diagnostics.md +19 -10
- package/agents/project/optimizations.md +5 -6
- package/agents/project/root/AGENTS.md +297 -0
- package/agents/project/server/routes/AGENTS.md +2 -2
- package/agents/project/server/services/AGENTS.md +4 -2
- package/agents/project/tests/AGENTS.md +9 -2
- package/cli/app/index.ts +31 -7
- package/cli/commands/configure.ts +226 -0
- package/cli/commands/dev.ts +0 -2
- package/cli/commands/diagnose.ts +33 -1
- package/cli/commands/explain.ts +1 -1
- package/cli/commands/migrate.ts +51 -0
- package/cli/commands/orient.ts +169 -0
- package/cli/commands/perf.ts +8 -1
- package/cli/commands/verify.ts +1003 -49
- package/cli/compiler/artifacts/manifest.ts +4 -4
- package/cli/compiler/artifacts/routing.ts +2 -2
- package/cli/compiler/artifacts/services.ts +12 -3
- package/cli/compiler/client/index.ts +65 -19
- package/cli/compiler/common/files/style.ts +47 -2
- package/cli/compiler/common/generatedRouteModules.ts +31 -38
- package/cli/compiler/common/index.ts +10 -0
- package/cli/compiler/common/proteumManifest.ts +1 -0
- package/cli/compiler/server/index.ts +34 -9
- package/cli/context.ts +6 -1
- package/cli/index.ts +7 -8
- package/cli/migrate/pageContract.ts +516 -0
- package/cli/paths.ts +47 -6
- package/cli/presentation/commands.ts +100 -10
- package/cli/presentation/devSession.ts +4 -6
- package/cli/presentation/help.ts +2 -2
- package/cli/presentation/ink.ts +10 -5
- package/cli/presentation/welcome.ts +2 -4
- package/cli/runtime/commands.ts +94 -1
- package/cli/scaffold/index.ts +2 -2
- package/cli/scaffold/templates.ts +4 -2
- package/cli/utils/agents.ts +273 -58
- package/client/dev/profiler/index.tsx +3 -2
- package/client/router.ts +10 -2
- package/client/services/router/index.tsx +6 -22
- package/common/dev/connect.ts +20 -4
- package/common/dev/console.ts +7 -0
- package/common/dev/contractsDoctor.ts +354 -0
- package/common/dev/diagnostics.ts +10 -7
- package/common/dev/inspection.ts +830 -38
- package/common/dev/performance.ts +19 -5
- package/common/dev/profiler.ts +1 -0
- package/common/dev/proteumManifest.ts +5 -4
- package/common/dev/requestTrace.ts +78 -1
- package/common/env/proteumEnv.ts +10 -3
- package/common/router/contracts.ts +8 -11
- package/common/router/index.ts +2 -2
- package/common/router/pageData.ts +72 -0
- package/common/router/register.ts +10 -46
- package/common/router/response/page.ts +28 -16
- package/docs/assets/unique-domains-chip.png +0 -0
- package/docs/dev-sessions.md +8 -4
- package/docs/diagnostics.md +77 -11
- package/docs/migrate-from-2.1.3.md +388 -0
- package/docs/request-tracing.md +42 -9
- package/package.json +6 -1
- package/scripts/update-codex-agents.ts +2 -2
- package/server/app/container/console/index.ts +11 -1
- package/server/app/container/trace/index.ts +370 -72
- package/server/app/devDiagnostics.ts +1 -1
- package/server/app/index.ts +5 -1
- package/server/services/auth/index.ts +9 -0
- package/server/services/prisma/index.ts +15 -12
- package/server/services/router/http/index.ts +1 -1
- package/server/services/router/index.ts +105 -23
- package/server/services/router/request/api.ts +7 -1
- package/server/services/router/request/index.ts +2 -1
- package/server/services/router/response/index.ts +8 -28
- package/types/global/vendors.d.ts +12 -0
- package/types/vendors.d.ts +12 -0
- package/common/router/pageSetup.ts +0 -51
package/server/app/index.ts
CHANGED
|
@@ -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(
|
|
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(
|
|
@@ -46,6 +46,12 @@ type TPrismaExtensionOperation = {
|
|
|
46
46
|
query: (args: unknown) => Promise<unknown>;
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
+
declare global {
|
|
50
|
+
interface BigInt {
|
|
51
|
+
toJSON: () => number | string;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
49
55
|
/*----------------------------------
|
|
50
56
|
- HELPERS
|
|
51
57
|
----------------------------------*/
|
|
@@ -173,21 +179,18 @@ export default class ModelsManager extends Service<Config, Hooks, Application, A
|
|
|
173
179
|
'DATABASE_URL is required before starting the Models service. Prisma 7 no longer auto-loads runtime env files.',
|
|
174
180
|
);
|
|
175
181
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
log: [{ emit: 'event', level: 'query' }],
|
|
181
|
-
})
|
|
182
|
-
: new PrismaClient({
|
|
183
|
-
adapter: createMariaDbAdapter(databaseUrl),
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
if (!shouldTraceQueries) {
|
|
187
|
-
this.client = prismaClient;
|
|
182
|
+
if (!this.app.container.Trace.shouldInstrumentRequests()) {
|
|
183
|
+
this.client = new PrismaClient({
|
|
184
|
+
adapter: createMariaDbAdapter(databaseUrl),
|
|
185
|
+
});
|
|
188
186
|
return;
|
|
189
187
|
}
|
|
190
188
|
|
|
189
|
+
const prismaClient = new PrismaClient({
|
|
190
|
+
adapter: createMariaDbAdapter(databaseUrl),
|
|
191
|
+
log: [{ emit: 'event', level: 'query' }],
|
|
192
|
+
});
|
|
193
|
+
|
|
191
194
|
prismaClient.$on('query', (event: TPrismaQueryEvent) => this.traceQuery(event));
|
|
192
195
|
|
|
193
196
|
this.client = prismaClient.$extends(
|
|
@@ -510,7 +510,7 @@ export default class HttpServer<TRouter extends TServerRouter = TServerRouter> {
|
|
|
510
510
|
private registerDevTraceRoutes(routes: express.Express) {
|
|
511
511
|
if (!__DEV__ || this.app.env.profile !== 'dev') return;
|
|
512
512
|
|
|
513
|
-
if (this.app.container.Trace.
|
|
513
|
+
if (this.app.container.Trace.isDevTraceEnabled()) {
|
|
514
514
|
routes.get('/__proteum/trace/requests', (req, res) => {
|
|
515
515
|
const rawLimit = Array.isArray(req.query.limit) ? req.query.limit[0] : req.query.limit;
|
|
516
516
|
const parsedLimit = typeof rawLimit === 'string' ? Number.parseInt(rawLimit, 10) : NaN;
|
|
@@ -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,
|
|
@@ -131,7 +132,13 @@ export type Config<
|
|
|
131
132
|
// Set it as a function, so when we instanciate the services, we can callthis.router to pass the router instance in roiuter services
|
|
132
133
|
type TRouterServicesList = { [serviceName: string]: AnyRouterService };
|
|
133
134
|
|
|
134
|
-
export type Hooks = {
|
|
135
|
+
export type Hooks = {
|
|
136
|
+
request: { args: [request: ServerRequest<TServerRouter>] };
|
|
137
|
+
'request.finished': { args: [request: ServerRequest<TServerRouter>] };
|
|
138
|
+
resolve: { args: [request: ServerRequest<TServerRouter>] };
|
|
139
|
+
resolved: { args: [route: TMatchedRoute, request: ServerRequest<TServerRouter>, response: ServerResponse<TServerRouter>] };
|
|
140
|
+
render: { args: [page: Page<TServerRouter>] };
|
|
141
|
+
};
|
|
135
142
|
|
|
136
143
|
export type TControllerDefinition = {
|
|
137
144
|
path?: string;
|
|
@@ -221,6 +228,7 @@ export default class ServerRouter<
|
|
|
221
228
|
const methodName = match ? match[2] : '<anonymous>';*/
|
|
222
229
|
|
|
223
230
|
const contextData = context.getStore() || { channelType: 'master' };
|
|
231
|
+
if (contextData.silentLogs) return;
|
|
224
232
|
|
|
225
233
|
const requestPrefix =
|
|
226
234
|
contextData.channelType === 'request'
|
|
@@ -272,21 +280,18 @@ export default class ServerRouter<
|
|
|
272
280
|
|
|
273
281
|
public async renderStatic(url: string, options: TRouteOptions['static'], rendered?: any) {
|
|
274
282
|
// Wildcard: tell that the newly rendered pages should be cached
|
|
275
|
-
if (url === '*' || !url)
|
|
276
|
-
|
|
277
|
-
if (!rendered) {
|
|
278
|
-
console.log('[router] renderStatic: url', url);
|
|
283
|
+
if (url === '*' || !url) throw new Error(`Unable to cache a dynamic or empty URL.`);
|
|
279
284
|
|
|
285
|
+
if (rendered === undefined) {
|
|
280
286
|
const fullUrl = this.url(url, {}, true);
|
|
281
287
|
const response = await got(fullUrl, {
|
|
282
288
|
method: 'GET',
|
|
283
|
-
headers: { Accept: 'text/html', bypasscache: '1' },
|
|
289
|
+
headers: { Accept: 'text/html', bypasscache: '1', 'x-proteum-static-warmup': '1' },
|
|
284
290
|
throwHttpErrors: false,
|
|
285
291
|
});
|
|
286
292
|
|
|
287
293
|
if (response.statusCode !== 200) {
|
|
288
|
-
|
|
289
|
-
return;
|
|
294
|
+
throw new Error(`Static render returned ${response.statusCode} for ${fullUrl}`);
|
|
290
295
|
}
|
|
291
296
|
|
|
292
297
|
rendered = response.body;
|
|
@@ -301,17 +306,50 @@ export default class ServerRouter<
|
|
|
301
306
|
|
|
302
307
|
private initStaticRoutes() {
|
|
303
308
|
this.clearStaticRoutesRefreshInterval();
|
|
309
|
+
const staticEntries: Array<{ routePath: string; url: string; options: TRouteOptions['static'] }> = [];
|
|
310
|
+
const seenStaticUrls = new Set<string>();
|
|
304
311
|
|
|
305
312
|
for (const route of this.routes) {
|
|
313
|
+
if (route.method !== 'GET' || route.options.accept !== 'html') continue;
|
|
314
|
+
|
|
306
315
|
if (!route.options.static) continue;
|
|
307
316
|
|
|
308
317
|
// Add to static pages
|
|
309
318
|
// Should be a GET oage that don't take any parameter
|
|
310
319
|
for (const url of route.options.static.urls) {
|
|
311
|
-
|
|
320
|
+
if (!url || url === '*' || seenStaticUrls.has(url)) continue;
|
|
321
|
+
|
|
322
|
+
staticEntries.push({
|
|
323
|
+
routePath: route.path || '(unknown route)',
|
|
324
|
+
url,
|
|
325
|
+
options: route.options.static,
|
|
326
|
+
});
|
|
327
|
+
seenStaticUrls.add(url);
|
|
312
328
|
}
|
|
313
329
|
}
|
|
314
330
|
|
|
331
|
+
void (async () => {
|
|
332
|
+
const warmedUrls: string[] = [];
|
|
333
|
+
let failedCount = 0;
|
|
334
|
+
|
|
335
|
+
for (const entry of staticEntries) {
|
|
336
|
+
try {
|
|
337
|
+
await this.renderStatic(entry.url, entry.options);
|
|
338
|
+
warmedUrls.push(entry.url);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
failedCount += 1;
|
|
341
|
+
console.error('[router] Static warmup failed', entry.url, `route=${entry.routePath}`, error);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log(
|
|
346
|
+
'[router] Static warmup finished',
|
|
347
|
+
`warmed=${warmedUrls.length}`,
|
|
348
|
+
`failed=${failedCount}`,
|
|
349
|
+
`urls=${warmedUrls.length > 0 ? warmedUrls.join(', ') : 'none'}`,
|
|
350
|
+
);
|
|
351
|
+
})();
|
|
352
|
+
|
|
315
353
|
// Every hours, refresh static pages
|
|
316
354
|
this.staticRoutesRefreshInterval = setInterval(
|
|
317
355
|
() => {
|
|
@@ -327,7 +365,9 @@ export default class ServerRouter<
|
|
|
327
365
|
for (const pageUrl in this.cache) {
|
|
328
366
|
const page = this.cache[pageUrl];
|
|
329
367
|
if (page.expire && page.expire < Date.now()) {
|
|
330
|
-
this.renderStatic(pageUrl, page.options)
|
|
368
|
+
void this.renderStatic(pageUrl, page.options).catch((error) => {
|
|
369
|
+
console.error('[router] Static refresh failed', pageUrl, error);
|
|
370
|
+
});
|
|
331
371
|
}
|
|
332
372
|
}
|
|
333
373
|
}
|
|
@@ -382,7 +422,7 @@ export default class ServerRouter<
|
|
|
382
422
|
----------------------------------*/
|
|
383
423
|
|
|
384
424
|
public page(...args: TRegisterPageArgs<any, TRouteOptions>) {
|
|
385
|
-
const { path, options,
|
|
425
|
+
const { path, options, data, renderer, layout } = getRegisterPageArgs(...args);
|
|
386
426
|
|
|
387
427
|
const { regex, keys } = buildRegex(path);
|
|
388
428
|
|
|
@@ -391,10 +431,10 @@ export default class ServerRouter<
|
|
|
391
431
|
path,
|
|
392
432
|
regex,
|
|
393
433
|
keys,
|
|
434
|
+
data,
|
|
394
435
|
controller: (context: TRouterContext<this>) => new Page(route, renderer, context, layout),
|
|
395
436
|
options: this.buildRouteOptions({
|
|
396
437
|
accept: 'html', // Les pages retournent forcémment du html
|
|
397
|
-
setup,
|
|
398
438
|
...options,
|
|
399
439
|
}),
|
|
400
440
|
};
|
|
@@ -573,6 +613,32 @@ export default class ServerRouter<
|
|
|
573
613
|
/*----------------------------------
|
|
574
614
|
- RESOLUTION
|
|
575
615
|
----------------------------------*/
|
|
616
|
+
private async finalizeRequest(
|
|
617
|
+
request: ServerRequest<this>,
|
|
618
|
+
output: {
|
|
619
|
+
statusCode: number;
|
|
620
|
+
user?: string;
|
|
621
|
+
errorMessage?: string;
|
|
622
|
+
},
|
|
623
|
+
) {
|
|
624
|
+
this.app.container.Trace.finishRequest(request.id, output);
|
|
625
|
+
|
|
626
|
+
try {
|
|
627
|
+
await this.runHook('request.finished', request);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
const typedError =
|
|
630
|
+
error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'Unknown request.finished hook error');
|
|
631
|
+
|
|
632
|
+
try {
|
|
633
|
+
await this.app.runHook('error', typedError, request);
|
|
634
|
+
} catch (hookError) {
|
|
635
|
+
console.error('request.finished hook error', typedError, 'Error hook failure', hookError);
|
|
636
|
+
}
|
|
637
|
+
} finally {
|
|
638
|
+
this.app.container.Trace.releaseRequest(request.id);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
576
642
|
public async middleware(req: express.Request, res: express.Response) {
|
|
577
643
|
// Create request
|
|
578
644
|
let requestId = uuid();
|
|
@@ -595,7 +661,7 @@ export default class ServerRouter<
|
|
|
595
661
|
this,
|
|
596
662
|
);
|
|
597
663
|
|
|
598
|
-
this.app.container.Trace.startRequest({
|
|
664
|
+
request.profiling = this.app.container.Trace.startRequest({
|
|
599
665
|
id: request.id,
|
|
600
666
|
method: request.method,
|
|
601
667
|
path: request.path,
|
|
@@ -606,7 +672,15 @@ export default class ServerRouter<
|
|
|
606
672
|
profilerOrigin: request.headers[profilerOriginHeader] || undefined,
|
|
607
673
|
profilerParentRequestId: request.headers[profilerParentRequestIdHeader] || undefined,
|
|
608
674
|
});
|
|
609
|
-
if (this.app.container.Trace.
|
|
675
|
+
if (this.app.container.Trace.isDevTraceEnabled()) res.setHeader(profilerTraceRequestIdHeader, request.id);
|
|
676
|
+
if (cachedPage) {
|
|
677
|
+
this.app.container.Trace.record(
|
|
678
|
+
request.id,
|
|
679
|
+
'cache.hit',
|
|
680
|
+
{ cacheKey: req.path, cachePhase: 'hit' },
|
|
681
|
+
'summary',
|
|
682
|
+
);
|
|
683
|
+
}
|
|
610
684
|
|
|
611
685
|
let response: ServerResponse<this>;
|
|
612
686
|
try {
|
|
@@ -617,7 +691,7 @@ export default class ServerRouter<
|
|
|
617
691
|
// Bulk API Requests
|
|
618
692
|
if (request.path === '/api' && typeof request.data.fetchers === 'object') {
|
|
619
693
|
await this.resolveApiBatch(request.data.fetchers, request);
|
|
620
|
-
this.
|
|
694
|
+
await this.finalizeRequest(request, {
|
|
621
695
|
statusCode: request.res.statusCode || 200,
|
|
622
696
|
user: request.user?.email,
|
|
623
697
|
});
|
|
@@ -654,7 +728,7 @@ export default class ServerRouter<
|
|
|
654
728
|
},
|
|
655
729
|
'summary',
|
|
656
730
|
);
|
|
657
|
-
this.
|
|
731
|
+
await this.finalizeRequest(request, {
|
|
658
732
|
statusCode: response.statusCode,
|
|
659
733
|
user: request.user?.email,
|
|
660
734
|
});
|
|
@@ -673,7 +747,7 @@ export default class ServerRouter<
|
|
|
673
747
|
'summary',
|
|
674
748
|
);
|
|
675
749
|
res.send(cachedPage.rendered);
|
|
676
|
-
this.
|
|
750
|
+
await this.finalizeRequest(request, {
|
|
677
751
|
statusCode: response.statusCode,
|
|
678
752
|
user: request.user?.email,
|
|
679
753
|
});
|
|
@@ -697,19 +771,19 @@ export default class ServerRouter<
|
|
|
697
771
|
'summary',
|
|
698
772
|
);
|
|
699
773
|
res.send(response.data);
|
|
700
|
-
this.
|
|
774
|
+
await this.finalizeRequest(request, {
|
|
701
775
|
statusCode: response.statusCode,
|
|
702
776
|
user: request.user?.email,
|
|
703
777
|
});
|
|
704
778
|
} else if (response.data !== 'true') {
|
|
705
|
-
this.
|
|
779
|
+
await this.finalizeRequest(request, {
|
|
706
780
|
statusCode: res.statusCode || response.statusCode,
|
|
707
781
|
user: request.user?.email,
|
|
708
782
|
errorMessage: "Can't return data from the controller since response has already been sent via express.",
|
|
709
783
|
});
|
|
710
784
|
throw new Error("Can't return data from the controller since response has already been sent via express.");
|
|
711
785
|
} else {
|
|
712
|
-
this.
|
|
786
|
+
await this.finalizeRequest(request, {
|
|
713
787
|
statusCode: res.statusCode || response.statusCode,
|
|
714
788
|
user: request.user?.email,
|
|
715
789
|
});
|
|
@@ -747,8 +821,10 @@ export default class ServerRouter<
|
|
|
747
821
|
// This is for debugging
|
|
748
822
|
channelType: 'request',
|
|
749
823
|
channelId: request.id,
|
|
824
|
+
silentLogs: request.headers['x-proteum-static-warmup'] === '1',
|
|
750
825
|
method: request.method,
|
|
751
826
|
path: request.path,
|
|
827
|
+
connectedNamespace: request.headers[profilerConnectedNamespaceHeader] || undefined,
|
|
752
828
|
...(request.traceCall
|
|
753
829
|
? {
|
|
754
830
|
traceCallFetcherId: request.traceCall.fetcherId,
|
|
@@ -930,8 +1006,6 @@ export default class ServerRouter<
|
|
|
930
1006
|
});
|
|
931
1007
|
|
|
932
1008
|
private async resolvedRoute(route: TMatchedRoute, response: ServerResponse<this>, timeStart: number) {
|
|
933
|
-
route = await response.resolveRouteOptions(route);
|
|
934
|
-
|
|
935
1009
|
this.app.container.Trace.record(
|
|
936
1010
|
response.request.id,
|
|
937
1011
|
'resolve.route-match',
|
|
@@ -964,11 +1038,19 @@ export default class ServerRouter<
|
|
|
964
1038
|
if (!staticUrl) continue;
|
|
965
1039
|
|
|
966
1040
|
console.log('[router] Set in cache', staticUrl);
|
|
1041
|
+
this.app.container.Trace.record(
|
|
1042
|
+
response.request.id,
|
|
1043
|
+
'cache.write',
|
|
1044
|
+
{ cacheKey: staticUrl, cachePhase: 'write' },
|
|
1045
|
+
'summary',
|
|
1046
|
+
);
|
|
967
1047
|
void this.renderStatic(
|
|
968
1048
|
staticUrl,
|
|
969
1049
|
route.options.static,
|
|
970
1050
|
staticUrl === response.request.path ? response.data : undefined,
|
|
971
|
-
)
|
|
1051
|
+
).catch((error) => {
|
|
1052
|
+
console.error('[router] Static cache write failed', staticUrl, error);
|
|
1053
|
+
});
|
|
972
1054
|
}
|
|
973
1055
|
}
|
|
974
1056
|
|
|
@@ -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(
|
|
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);
|
|
@@ -10,7 +10,7 @@ import Bowser from 'bowser';
|
|
|
10
10
|
|
|
11
11
|
// Core
|
|
12
12
|
import BaseRequest from '@common/router/request';
|
|
13
|
-
import type { TTraceCallOrigin } from '@common/dev/requestTrace';
|
|
13
|
+
import type { TRequestProfiling, TTraceCallOrigin } from '@common/dev/requestTrace';
|
|
14
14
|
|
|
15
15
|
// Specific
|
|
16
16
|
import type { HttpMethod, HttpHeaders } from '..';
|
|
@@ -74,6 +74,7 @@ export default class ServerRequest<TRouter extends TAnyRouter = TAnyRouter> exte
|
|
|
74
74
|
// Services
|
|
75
75
|
public api: ApiClient;
|
|
76
76
|
public traceCall?: TRequestTraceCallContext;
|
|
77
|
+
public profiling!: TRequestProfiling;
|
|
77
78
|
|
|
78
79
|
/*----------------------------------
|
|
79
80
|
- INITIALISATION
|
|
@@ -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
|
-
| {
|
|
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;
|
package/types/vendors.d.ts
CHANGED
|
@@ -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
|
-
};
|