proteum 2.1.2 → 2.1.6
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 +22 -14
- package/README.md +112 -17
- package/agents/project/AGENTS.md +188 -25
- package/agents/project/CODING_STYLE.md +1 -0
- package/agents/project/client/AGENTS.md +13 -8
- package/agents/project/client/pages/AGENTS.md +17 -9
- package/agents/project/diagnostics.md +52 -0
- package/agents/project/optimizations.md +48 -0
- package/agents/project/server/routes/AGENTS.md +9 -6
- package/agents/project/server/services/AGENTS.md +10 -6
- package/agents/project/tests/AGENTS.md +11 -5
- package/cli/app/config.ts +13 -14
- package/cli/app/index.ts +58 -0
- package/cli/commands/command.ts +8 -0
- package/cli/commands/connect.ts +45 -0
- package/cli/commands/dev.ts +26 -11
- package/cli/commands/diagnose.ts +286 -0
- package/cli/commands/doctor.ts +18 -5
- package/cli/commands/explain.ts +25 -0
- package/cli/commands/perf.ts +243 -0
- package/cli/commands/session.ts +254 -0
- package/cli/commands/sessionLocalRunner.js +188 -0
- package/cli/commands/trace.ts +17 -1
- package/cli/commands/verify.ts +281 -0
- package/cli/compiler/artifacts/connectedProjects.ts +453 -0
- package/cli/compiler/artifacts/controllers.ts +198 -49
- package/cli/compiler/artifacts/discovery.ts +0 -34
- package/cli/compiler/artifacts/manifest.ts +90 -6
- package/cli/compiler/artifacts/routing.ts +2 -2
- package/cli/compiler/artifacts/services.ts +277 -130
- package/cli/compiler/client/index.ts +3 -0
- package/cli/compiler/common/files/style.ts +52 -0
- package/cli/compiler/common/generatedRouteModules.ts +34 -5
- package/cli/compiler/common/scripts.ts +11 -5
- package/cli/compiler/index.ts +2 -1
- package/cli/compiler/server/index.ts +3 -0
- package/cli/presentation/commands.ts +136 -7
- package/cli/presentation/devSession.ts +32 -7
- package/cli/runtime/commands.ts +193 -6
- package/cli/scaffold/index.ts +14 -25
- package/cli/scaffold/templates.ts +41 -27
- package/cli/utils/agents.ts +4 -2
- package/cli/utils/keyboard.ts +8 -0
- package/client/dev/profiler/ApexChart.tsx +66 -0
- package/client/dev/profiler/index.tsx +2798 -417
- package/client/dev/profiler/runtime.noop.ts +12 -0
- package/client/dev/profiler/runtime.ts +195 -4
- package/client/services/router/request/api.ts +6 -1
- package/common/applicationConfig.ts +173 -0
- package/common/applicationConfigLoader.ts +102 -0
- package/common/connectedProjects.ts +113 -0
- package/common/dev/connect.ts +267 -0
- package/common/dev/console.ts +31 -0
- package/common/dev/contractsDoctor.ts +128 -0
- package/common/dev/diagnostics.ts +59 -15
- package/common/dev/inspection.ts +491 -0
- package/common/dev/performance.ts +809 -0
- package/common/dev/profiler.ts +3 -0
- package/common/dev/proteumManifest.ts +31 -6
- package/common/dev/requestTrace.ts +56 -1
- package/common/dev/session.ts +24 -0
- package/common/env/proteumEnv.ts +176 -50
- package/common/router/index.ts +1 -0
- package/common/router/request/api.ts +2 -0
- package/config.ts +5 -0
- package/docs/dev-commands.md +5 -1
- package/docs/dev-sessions.md +90 -0
- package/docs/diagnostics.md +74 -11
- package/docs/request-tracing.md +50 -3
- package/package.json +1 -1
- package/server/app/container/config.ts +16 -87
- package/server/app/container/console/index.ts +42 -8
- package/server/app/container/index.ts +3 -1
- package/server/app/container/trace/index.ts +153 -0
- package/server/app/devDiagnostics.ts +138 -0
- package/server/app/index.ts +18 -8
- package/server/app/service/container.ts +0 -12
- package/server/app/service/index.ts +0 -2
- package/server/services/prisma/index.ts +121 -4
- package/server/services/router/http/index.ts +352 -0
- package/server/services/router/index.ts +50 -47
- package/server/services/router/request/api.ts +160 -19
- package/server/services/router/request/index.ts +8 -0
- package/server/services/router/response/index.ts +24 -1
- package/server/services/router/response/page/document.tsx +5 -0
- package/server/services/router/response/page/index.tsx +10 -0
- package/agents/framework/AGENTS.md +0 -177
- package/server/services/auth/router/service.json +0 -6
- package/server/services/auth/service.json +0 -6
- package/server/services/cron/service.json +0 -6
- package/server/services/disks/drivers/local/service.json +0 -6
- package/server/services/disks/drivers/s3/service.json +0 -6
- package/server/services/disks/service.json +0 -6
- package/server/services/fetch/service.json +0 -7
- package/server/services/prisma/service.json +0 -6
- package/server/services/router/service.json +0 -6
- package/server/services/schema/router/service.json +0 -6
- package/server/services/schema/service.json +0 -6
- package/server/services/security/encrypt/aes/service.json +0 -6
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
// Core
|
|
6
6
|
|
|
7
|
+
import { fromJson as errorFromJson } from '@common/errors';
|
|
8
|
+
import {
|
|
9
|
+
profilerOriginHeader,
|
|
10
|
+
profilerParentRequestIdHeader,
|
|
11
|
+
profilerSessionIdHeader,
|
|
12
|
+
} from '@common/dev/profiler';
|
|
7
13
|
import RequestService from './service';
|
|
8
14
|
|
|
9
15
|
import ApiClientService, {
|
|
@@ -21,6 +27,128 @@ import ApiClientService, {
|
|
|
21
27
|
- SERVICE
|
|
22
28
|
----------------------------------*/
|
|
23
29
|
export default class ApiClientRequest extends RequestService implements ApiClientService {
|
|
30
|
+
private isApiFetcher(fetcher: TFetcher | Promise<unknown>): fetcher is TFetcher {
|
|
31
|
+
return typeof fetcher === 'object' && fetcher !== null && 'method' in fetcher && 'path' in fetcher;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private toTraceInspectable(data: unknown) {
|
|
35
|
+
if (data === null || data === undefined) return data;
|
|
36
|
+
if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') return data;
|
|
37
|
+
if (typeof data === 'bigint' || typeof data === 'symbol' || typeof data === 'function') return data;
|
|
38
|
+
if (typeof data === 'object') return data;
|
|
39
|
+
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private getTraceCallOrigin() {
|
|
44
|
+
return this.request.path === '/api' ? 'api-batch-fetcher' as const : 'ssr-fetcher' as const;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private createTraceCall({
|
|
48
|
+
fetcherId,
|
|
49
|
+
method,
|
|
50
|
+
path,
|
|
51
|
+
data,
|
|
52
|
+
options,
|
|
53
|
+
}: {
|
|
54
|
+
fetcherId: string;
|
|
55
|
+
method: string;
|
|
56
|
+
path: string;
|
|
57
|
+
data: unknown;
|
|
58
|
+
options?: TFetcher['options'];
|
|
59
|
+
}) {
|
|
60
|
+
return this.request.router.app.container.Trace.startCall(this.request.id, {
|
|
61
|
+
origin: this.getTraceCallOrigin(),
|
|
62
|
+
label: fetcherId,
|
|
63
|
+
method,
|
|
64
|
+
path,
|
|
65
|
+
fetcherId,
|
|
66
|
+
...(options?.connected
|
|
67
|
+
? {
|
|
68
|
+
connectedControllerAccessor: options.connected.controllerAccessor,
|
|
69
|
+
connectedProjectNamespace: options.connected.namespace,
|
|
70
|
+
}
|
|
71
|
+
: {}),
|
|
72
|
+
requestDataKeys: data && typeof data === 'object' ? Object.keys(data as Record<string, unknown>) : [],
|
|
73
|
+
requestData: this.toTraceInspectable(data),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private buildConnectedRequestHeaders(fetcher: TFetcher) {
|
|
78
|
+
const headers = new Headers();
|
|
79
|
+
|
|
80
|
+
for (const [key, value] of Object.entries(this.request.headers)) {
|
|
81
|
+
if (!value) continue;
|
|
82
|
+
if (key === 'content-length' || key === 'host') continue;
|
|
83
|
+
headers.set(key, value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
headers.set('accept', 'application/json');
|
|
87
|
+
|
|
88
|
+
if (fetcher.options?.connected) {
|
|
89
|
+
headers.set(profilerOriginHeader, this.getTraceCallOrigin());
|
|
90
|
+
|
|
91
|
+
const profilerSessionId = this.request.headers[profilerSessionIdHeader];
|
|
92
|
+
if (profilerSessionId) headers.set(profilerSessionIdHeader, profilerSessionId);
|
|
93
|
+
headers.set(profilerParentRequestIdHeader, this.request.id);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return headers;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private async resolveConnectedFetcher<TData>(fetcher: TFetcher<TData>) {
|
|
100
|
+
const connected = fetcher.options?.connected;
|
|
101
|
+
if (!connected) throw new Error('Connected fetcher metadata is missing.');
|
|
102
|
+
|
|
103
|
+
const connectedProject = this.request.router.app.connectedProjects?.[connected.namespace];
|
|
104
|
+
if (!connectedProject) {
|
|
105
|
+
throw new Error(`Connected project "${connected.namespace}" is not registered on ${this.request.router.app.identity.identifier}.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const headers = this.buildConnectedRequestHeaders(fetcher);
|
|
109
|
+
const url = new URL(fetcher.path, connectedProject.urlInternal).toString();
|
|
110
|
+
const init: RequestInit = {
|
|
111
|
+
method: fetcher.method,
|
|
112
|
+
headers,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (fetcher.data) {
|
|
116
|
+
if (fetcher.method === 'GET') {
|
|
117
|
+
const params = new URLSearchParams();
|
|
118
|
+
for (const [key, value] of Object.entries(fetcher.data)) {
|
|
119
|
+
if (value === undefined || value === null) continue;
|
|
120
|
+
params.set(key, String(value));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return this.fetchConnectedResponse<TData>(`${url}?${params.toString()}`, init);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
headers.set('content-type', 'application/json');
|
|
127
|
+
init.body = JSON.stringify(fetcher.data);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return this.fetchConnectedResponse<TData>(url, init);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private async fetchConnectedResponse<TData>(url: string, init: RequestInit) {
|
|
134
|
+
const response = await fetch(url, init);
|
|
135
|
+
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
const contentType = response.headers.get('content-type') || '';
|
|
138
|
+
const errorPayload = contentType.includes('application/json') ? await response.json() : await response.text();
|
|
139
|
+
const typedError =
|
|
140
|
+
typeof errorPayload === 'object' && errorPayload && 'code' in (errorPayload as object)
|
|
141
|
+
? (errorFromJson(errorPayload as any) as Error & { http?: number })
|
|
142
|
+
: (new Error(typeof errorPayload === 'string' ? errorPayload : `Connected request failed with ${response.status}.`) as Error & {
|
|
143
|
+
http?: number;
|
|
144
|
+
});
|
|
145
|
+
typedError.http = response.status;
|
|
146
|
+
throw typedError;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return (await response.json()) as TData;
|
|
150
|
+
}
|
|
151
|
+
|
|
24
152
|
/*----------------------------------
|
|
25
153
|
- HIGH LEVEL
|
|
26
154
|
----------------------------------*/
|
|
@@ -67,39 +195,52 @@ export default class ApiClientRequest extends RequestService implements ApiClien
|
|
|
67
195
|
if (!fetcher) continue;
|
|
68
196
|
|
|
69
197
|
// Promise Fetcher (direct call from service method)
|
|
70
|
-
if (
|
|
198
|
+
if (!this.isApiFetcher(fetcher)) {
|
|
71
199
|
fetchedData[id] = await fetcher;
|
|
72
200
|
continue;
|
|
73
201
|
}
|
|
74
202
|
|
|
75
|
-
const { method, path, data } = fetcher;
|
|
203
|
+
const { method, path, data, options } = fetcher;
|
|
76
204
|
//this.router.config.debug && console.log(`[api] Resolving from internal api`, method, path, data);
|
|
77
205
|
|
|
78
206
|
// We don't fetch the already given data
|
|
79
207
|
if (id in fetchedData) continue;
|
|
80
208
|
|
|
81
|
-
|
|
82
|
-
const request = this.request.children(method, path, data);
|
|
83
|
-
const callId = this.request.router.app.container.Trace.startCall(this.request.id, {
|
|
84
|
-
origin: 'ssr-fetcher',
|
|
85
|
-
label: id,
|
|
86
|
-
method,
|
|
87
|
-
path,
|
|
88
|
-
fetcherId: id,
|
|
89
|
-
requestDataKeys: data && typeof data === 'object' ? Object.keys(data) : [],
|
|
90
|
-
requestData: data,
|
|
91
|
-
});
|
|
209
|
+
const callId = this.createTraceCall({ data, fetcherId: id, method, options, path });
|
|
92
210
|
|
|
93
211
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
212
|
+
if (options?.connected) {
|
|
213
|
+
fetchedData[id] = await this.resolveConnectedFetcher(fetcher);
|
|
214
|
+
} else {
|
|
215
|
+
const request = this.request.children(method, path, data);
|
|
216
|
+
if (callId)
|
|
217
|
+
request.traceCall = {
|
|
218
|
+
fetcherId: id,
|
|
219
|
+
id: callId,
|
|
220
|
+
label: id,
|
|
221
|
+
origin: this.getTraceCallOrigin(),
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const response = await request.router.resolve(request);
|
|
225
|
+
fetchedData[id] = response.data;
|
|
226
|
+
this.request.router.app.container.Trace.finishCall(this.request.id, callId, {
|
|
227
|
+
statusCode: response.statusCode,
|
|
228
|
+
resultKeys:
|
|
229
|
+
response.data && typeof response.data === 'object' && !Array.isArray(response.data)
|
|
230
|
+
? Object.keys(response.data as Record<string, unknown>)
|
|
231
|
+
: [],
|
|
232
|
+
result: response.data as object | string | number | boolean | bigint | symbol | null | undefined,
|
|
233
|
+
});
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
96
237
|
this.request.router.app.container.Trace.finishCall(this.request.id, callId, {
|
|
97
|
-
statusCode:
|
|
238
|
+
statusCode: 200,
|
|
98
239
|
resultKeys:
|
|
99
|
-
|
|
100
|
-
? Object.keys(
|
|
240
|
+
fetchedData[id] && typeof fetchedData[id] === 'object' && !Array.isArray(fetchedData[id])
|
|
241
|
+
? Object.keys(fetchedData[id] as Record<string, unknown>)
|
|
101
242
|
: [],
|
|
102
|
-
result:
|
|
243
|
+
result: fetchedData[id] as object | string | number | boolean | bigint | symbol | null | undefined,
|
|
103
244
|
});
|
|
104
245
|
} catch (error) {
|
|
105
246
|
const typedError = error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'Unknown error');
|
|
@@ -10,6 +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
14
|
|
|
14
15
|
// Specific
|
|
15
16
|
import type { HttpMethod, HttpHeaders } from '..';
|
|
@@ -35,6 +36,12 @@ const localeFilter = (input: any) => {
|
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
export type UploadedFile = File;
|
|
39
|
+
type TRequestTraceCallContext = {
|
|
40
|
+
fetcherId?: string;
|
|
41
|
+
id: string;
|
|
42
|
+
label: string;
|
|
43
|
+
origin: TTraceCallOrigin;
|
|
44
|
+
};
|
|
38
45
|
|
|
39
46
|
/*----------------------------------
|
|
40
47
|
- CONTEXTE
|
|
@@ -66,6 +73,7 @@ export default class ServerRequest<TRouter extends TAnyRouter = TAnyRouter> exte
|
|
|
66
73
|
|
|
67
74
|
// Services
|
|
68
75
|
public api: ApiClient;
|
|
76
|
+
public traceCall?: TRequestTraceCallContext;
|
|
69
77
|
|
|
70
78
|
/*----------------------------------
|
|
71
79
|
- INITIALISATION
|
|
@@ -141,6 +141,11 @@ export default class ServerResponse<
|
|
|
141
141
|
target: getRouteTraceTarget(route as TAnyRoute<TRouterContext<TServerRouter>>),
|
|
142
142
|
routeId: route.options.id || '',
|
|
143
143
|
filepath: route.options.filepath || '',
|
|
144
|
+
source: {
|
|
145
|
+
filepath: route.options.filepath || '',
|
|
146
|
+
line: route.options.sourceLocation?.line || 0,
|
|
147
|
+
column: route.options.sourceLocation?.column || 0,
|
|
148
|
+
},
|
|
144
149
|
accept: route.options.accept || '',
|
|
145
150
|
},
|
|
146
151
|
'summary',
|
|
@@ -220,7 +225,14 @@ export default class ServerResponse<
|
|
|
220
225
|
this.app.container.Trace.record(
|
|
221
226
|
this.request.id,
|
|
222
227
|
'setup.options',
|
|
223
|
-
{
|
|
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
|
+
},
|
|
224
236
|
'resolve',
|
|
225
237
|
);
|
|
226
238
|
|
|
@@ -260,6 +272,11 @@ export default class ServerResponse<
|
|
|
260
272
|
{
|
|
261
273
|
target: getRouteTraceTarget(route as TAnyRoute<TRouterContext<TServerRouter>>),
|
|
262
274
|
routeId: route.options.id || '',
|
|
275
|
+
source: {
|
|
276
|
+
filepath: route.options.filepath || '',
|
|
277
|
+
line: route.options.sourceLocation?.line || 0,
|
|
278
|
+
column: route.options.sourceLocation?.column || 0,
|
|
279
|
+
},
|
|
263
280
|
routerServiceKeys: Object.keys(contextServices),
|
|
264
281
|
controllerKeys: Object.keys(controllers),
|
|
265
282
|
customContextKeys: Object.keys(customSsrData as object),
|
|
@@ -318,6 +335,11 @@ export default class ServerResponse<
|
|
|
318
335
|
chunkId: page.chunkId || '',
|
|
319
336
|
dataKeys: Object.keys(page.data || {}),
|
|
320
337
|
data: page.data || {},
|
|
338
|
+
source: {
|
|
339
|
+
filepath: page.route.options.filepath || '',
|
|
340
|
+
line: page.route.options.sourceLocation?.line || 0,
|
|
341
|
+
column: page.route.options.sourceLocation?.column || 0,
|
|
342
|
+
},
|
|
321
343
|
},
|
|
322
344
|
'resolve',
|
|
323
345
|
);
|
|
@@ -338,6 +360,7 @@ export default class ServerResponse<
|
|
|
338
360
|
// NOTE: On évite le filtrage sans masque spécifié (performances + risques erreurs)
|
|
339
361
|
if (mask !== undefined) data = await jsonMask(data, mask);
|
|
340
362
|
|
|
363
|
+
this.app.container.Trace.setRequestResult(this.request.id, data);
|
|
341
364
|
this.headers['Content-Type'] = 'application/json';
|
|
342
365
|
this.data = (this.request.isVirtual ? data : JSON.stringify(data)) as TData;
|
|
343
366
|
return this.end();
|
|
@@ -195,6 +195,11 @@ export default class DocumentRenderer<TRouter extends TServerRouter> {
|
|
|
195
195
|
customContextKeys,
|
|
196
196
|
serializedBytes: Buffer.byteLength(context, 'utf8'),
|
|
197
197
|
routeCount: this.router.ssrRoutes.length,
|
|
198
|
+
source: {
|
|
199
|
+
filepath: page.route.options.filepath || '',
|
|
200
|
+
line: page.route.options.sourceLocation?.line || 0,
|
|
201
|
+
column: page.route.options.sourceLocation?.column || 0,
|
|
202
|
+
},
|
|
198
203
|
},
|
|
199
204
|
'resolve',
|
|
200
205
|
);
|
|
@@ -70,6 +70,11 @@ export default class ServerPage<TRouter extends TServerRouter = TServerRouter> e
|
|
|
70
70
|
chunkId: this.chunkId || '',
|
|
71
71
|
title: this.title || '',
|
|
72
72
|
routeId: this.route.options['id'] || '',
|
|
73
|
+
source: {
|
|
74
|
+
filepath: this.route.options.filepath || '',
|
|
75
|
+
line: this.route.options.sourceLocation?.line || 0,
|
|
76
|
+
column: this.route.options.sourceLocation?.column || 0,
|
|
77
|
+
},
|
|
73
78
|
},
|
|
74
79
|
'summary',
|
|
75
80
|
);
|
|
@@ -103,6 +108,11 @@ export default class ServerPage<TRouter extends TServerRouter = TServerRouter> e
|
|
|
103
108
|
documentLength: document.length,
|
|
104
109
|
styleCount: this.style.length,
|
|
105
110
|
scriptCount: this.scripts.length,
|
|
111
|
+
source: {
|
|
112
|
+
filepath: this.route.options.filepath || '',
|
|
113
|
+
line: this.route.options.sourceLocation?.line || 0,
|
|
114
|
+
column: this.route.options.sourceLocation?.column || 0,
|
|
115
|
+
},
|
|
106
116
|
},
|
|
107
117
|
'summary',
|
|
108
118
|
);
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
# Proteum App Contract
|
|
2
|
-
|
|
3
|
-
This is the canonical contract for Proteum-based projects. Local project `AGENTS.md` files should add deltas only, not restate these rules.
|
|
4
|
-
|
|
5
|
-
## First Pass
|
|
6
|
-
|
|
7
|
-
Inspect apps in this order:
|
|
8
|
-
|
|
9
|
-
1. Run `npx proteum explain --json` or read `./.proteum/manifest.json`.
|
|
10
|
-
2. Inspect `./server/index.ts`, `./server/config/*.ts`, and the touched files under `./commands`, `./server/controllers`, `./server/services`, `./server/routes`, and `./client/pages`.
|
|
11
|
-
3. Run `npx proteum doctor` if routing or generation looks suspicious.
|
|
12
|
-
4. For request-time issues in dev, read the default port from `PORT` or `./.proteum/manifest.json`; if a server is already running there, inspect `npx proteum trace` output before reproducing the issue or adding logs.
|
|
13
|
-
5. If existing traces are insufficient, run `npx proteum trace arm --capture deep`, reproduce once, then inspect the captured request.
|
|
14
|
-
|
|
15
|
-
## Non-Negotiable Rules
|
|
16
|
-
|
|
17
|
-
- Client pages live in `client/pages/**` and register routes with top-level `Router.page(...)` or `Router.error(...)`.
|
|
18
|
-
- Page URLs come from the explicit `Router.page('/path', ...)` call, not from the file path.
|
|
19
|
-
- Callable app APIs live only in `server/controllers/**/*.ts` files that extend `Controller`.
|
|
20
|
-
- Dev-only internal execution lives only in `commands/**/*.ts` files that extend `Commands`.
|
|
21
|
-
- Manual HTTP endpoints live only in `server/routes/**`.
|
|
22
|
-
- Controllers call `this.input(schema)` inside the method body, at most once per method.
|
|
23
|
-
- Request-scoped state lives only on `this.request` and manual-route/router context objects.
|
|
24
|
-
- SSR page data belongs in page `setup`, not in `api.fetch(...)`.
|
|
25
|
-
- Normal service methods do not read request state directly.
|
|
26
|
-
- Do not import runtime values from `@models`.
|
|
27
|
-
- Do not use `@request` runtime globals.
|
|
28
|
-
- Do not use `@app` on the client.
|
|
29
|
-
- Do not edit generated files under `.proteum` by hand.
|
|
30
|
-
- Prefer type inference rooted in the explicit application graph in `server/index.ts`.
|
|
31
|
-
|
|
32
|
-
## Source Of Truth
|
|
33
|
-
|
|
34
|
-
Proteum reads:
|
|
35
|
-
|
|
36
|
-
- `package.json`
|
|
37
|
-
- `identity.yaml`
|
|
38
|
-
- `process.env` via `PORT`, `ENV_*`, `URL`, and `TRACE_*`
|
|
39
|
-
- `server/config/*.ts`
|
|
40
|
-
- `server/index.ts`
|
|
41
|
-
- `server/services/**/service.json`
|
|
42
|
-
- `commands/**/*.ts`
|
|
43
|
-
- `server/controllers/**/*.ts`
|
|
44
|
-
- `server/routes/**/*.ts`
|
|
45
|
-
- `client/pages/**/*.ts(x)`
|
|
46
|
-
- `client/pages/**/_layout/index.tsx`
|
|
47
|
-
- `public/**`
|
|
48
|
-
|
|
49
|
-
Proteum owns:
|
|
50
|
-
|
|
51
|
-
- `.proteum/manifest.json`
|
|
52
|
-
- `.proteum/client/*`
|
|
53
|
-
- `.proteum/common/*`
|
|
54
|
-
- `.proteum/server/*`
|
|
55
|
-
|
|
56
|
-
Project code should consume:
|
|
57
|
-
|
|
58
|
-
- `@generated/client/*`
|
|
59
|
-
- `@generated/common/*`
|
|
60
|
-
- `@generated/server/*`
|
|
61
|
-
- `@/client/context` as the generated client context entrypoint
|
|
62
|
-
|
|
63
|
-
Prefer structured CLI surfaces over re-deriving framework facts from source:
|
|
64
|
-
|
|
65
|
-
- `npx proteum explain --json`
|
|
66
|
-
- `npx proteum doctor --json`
|
|
67
|
-
- `npx proteum trace ...`
|
|
68
|
-
- `npx proteum command ...`
|
|
69
|
-
- `npx proteum create ... --dry-run --json`
|
|
70
|
-
|
|
71
|
-
Prefer scaffold commands before hand-writing boilerplate:
|
|
72
|
-
|
|
73
|
-
- Use `npx proteum init <directory> --name <name>` for new apps.
|
|
74
|
-
- Use `npx proteum init ... --dry-run --json` when an agent needs a machine-readable app plan before writing files.
|
|
75
|
-
- Use `npx proteum create page|controller|command|route|service <target>` for new app artifacts before creating the files manually.
|
|
76
|
-
- Use `npx proteum create ... --dry-run --json` when an agent needs a machine-readable artifact plan before writing files.
|
|
77
|
-
|
|
78
|
-
## File Contracts
|
|
79
|
-
|
|
80
|
-
### App Bootstrap And Services
|
|
81
|
-
|
|
82
|
-
- `server/index.ts` default-exports the app `Application` subclass and is the canonical type root.
|
|
83
|
-
- Root services are public class fields instantiated with `new ServiceClass(this, config, this)`.
|
|
84
|
-
- Typed root-service config lives in `server/config/*.ts` via `Services.config(ServiceClass, { ... })`.
|
|
85
|
-
- Router plugins are instantiated explicitly inside the `Router` config `plugins` object.
|
|
86
|
-
- `server/services/**/service.json` plus `server/index.ts` drive generated service typings and manifest entries.
|
|
87
|
-
- Business logic lives in classes that extend `Service` and use `this.services`, `this.models`, and `this.app`.
|
|
88
|
-
- Keep auth, input parsing, locale, cookies, and request-derived values in controllers, then pass explicit typed arguments into services.
|
|
89
|
-
- Split growing features into explicit subservices.
|
|
90
|
-
- `proteum create service ...` scaffolds the service file, its `service.json`, a typed config export under `server/config/*.ts`, and the root registration in `server/index.ts`; review and adapt the generated names before committing.
|
|
91
|
-
|
|
92
|
-
### Controllers
|
|
93
|
-
|
|
94
|
-
- Files live under `server/controllers/**/*.ts` and default-export a class extending `Controller`.
|
|
95
|
-
- Methods with bodies become generated client-callable endpoints.
|
|
96
|
-
- Route path comes from the controller file path plus the method name.
|
|
97
|
-
- `export const controllerPath = 'Custom/path'` can override the base path.
|
|
98
|
-
- Generated client calls use `POST`.
|
|
99
|
-
- Prefer `proteum create controller ...` for new controller boilerplate, then adapt the generated method to real service calls.
|
|
100
|
-
|
|
101
|
-
### Commands
|
|
102
|
-
|
|
103
|
-
- Files live under `commands/**/*.ts` and default-export a class extending `Commands` from `@server/app/commands`.
|
|
104
|
-
- Methods with bodies become generated dev commands.
|
|
105
|
-
- Command path comes from the file path plus the method name.
|
|
106
|
-
- `export const commandPath = 'Custom/path'` can override the base path.
|
|
107
|
-
- Commands are for dev-only internal execution through `proteum command ...` or the profiler `Commands` tab.
|
|
108
|
-
- Keep command logic internal; do not turn it into a normal controller unless it is a real app API.
|
|
109
|
-
- Prefer `proteum create command ...` for new command boilerplate.
|
|
110
|
-
|
|
111
|
-
### Client Pages
|
|
112
|
-
|
|
113
|
-
- Proteum scans page files for top-level `Router.page(...)` and `Router.error(...)` calls.
|
|
114
|
-
- File path controls chunk identity and layout discovery; route path comes from the explicit `Router.page(...)` string.
|
|
115
|
-
- Supported page signatures are `Router.page(path, render)`, `Router.page(path, setup, render)`, `Router.page(path, options, render)`, and `Router.page(path, options, setup, render)`.
|
|
116
|
-
- For new work, prefer `Router.page(path, setup, render)` or `Router.page(path, options, setup, render)`.
|
|
117
|
-
- `setup` returns one flat object. Reserved keys like `_auth`, `_layout`, `_static`, and `_redirectLogged` are route options; all other keys are SSR data.
|
|
118
|
-
- Controller fetchers and promises returned from `setup` resolve before render.
|
|
119
|
-
- `render` consumes resolved setup data and uses generated controller methods from render args or `@/client/context`.
|
|
120
|
-
- Use `api.reload(...)` or `api.set(...)` only when intentionally mutating active page setup state.
|
|
121
|
-
- Error pages use `Router.error(code, options, render)` in `client/pages/_messages/**`.
|
|
122
|
-
- Prefer `proteum create page ...` for new page boilerplate, then review the explicit route path and setup payload.
|
|
123
|
-
|
|
124
|
-
### Manual Routes
|
|
125
|
-
|
|
126
|
-
- Use `server/routes/**` only for explicit HTTP behavior that should not be a generated controller action.
|
|
127
|
-
- Good fits include redirects, sitemap or RSS output, OAuth callbacks, webhooks, and public resources with custom semantics.
|
|
128
|
-
- Import server-side app services from `@app` and use route handler context for `request`, `response`, router plugins, and custom router context.
|
|
129
|
-
- If the route is a normal app API, prefer a controller.
|
|
130
|
-
- Prefer `proteum create route ...` for new manual-route boilerplate.
|
|
131
|
-
|
|
132
|
-
### Models And Aliases
|
|
133
|
-
|
|
134
|
-
- Use Prisma typings from `@models/types`.
|
|
135
|
-
- Use runtime models through `this.models` or `this.app.Models.client`.
|
|
136
|
-
- Keep Prisma runtime access inside services when possible and prefer explicit `select` or narrow `include`.
|
|
137
|
-
- Do not import runtime values from `@models` or edit generated Prisma client files.
|
|
138
|
-
- Aliases:
|
|
139
|
-
- `@/client/...`, `@/server/...`, `@/common/...`: app code
|
|
140
|
-
- `@client/...`, `@server/...`, `@common/...`: Proteum core modules
|
|
141
|
-
- `@app`: server-side application services for manual routes only
|
|
142
|
-
- `@generated/*`: generated app surfaces
|
|
143
|
-
|
|
144
|
-
## Design Rules
|
|
145
|
-
|
|
146
|
-
- Prefer explicit `server/index.ts` bootstrap over hidden registration.
|
|
147
|
-
- Prefer controller-backed app APIs over ad hoc manual `/api/...` routes.
|
|
148
|
-
- Prefer service classes over server helpers with hidden dependencies.
|
|
149
|
-
- Keep one canonical source of truth for catalogs, registries, and shared types.
|
|
150
|
-
- Reuse project-local Shadcn-based UI primitives when the app already provides them.
|
|
151
|
-
- Before inventing a helper, primitive, parser, formatter, SDK wrapper, or build-time tool, first check whether the repo already depends on a suitable package.
|
|
152
|
-
- If it does not, search npm before writing a custom implementation.
|
|
153
|
-
- Prefer widely adopted, actively maintained, flexible, well-typed packages.
|
|
154
|
-
- Only build custom infrastructure when packages would clearly hurt bundle size, SSR behavior, performance, explicit contracts, or long-term maintainability.
|
|
155
|
-
- If you choose custom over a package, state briefly why.
|
|
156
|
-
|
|
157
|
-
## Discouraged Patterns
|
|
158
|
-
|
|
159
|
-
- `api.fetch(...)` inside page files for SSR loading
|
|
160
|
-
- client-side `@app` imports
|
|
161
|
-
- runtime `@models` imports
|
|
162
|
-
- request-scoped state inside normal service methods
|
|
163
|
-
- hiding route registration behind abstractions that remove the top-level `Router.page(...)` call
|
|
164
|
-
- editing `.proteum` directly
|
|
165
|
-
|
|
166
|
-
## Verification
|
|
167
|
-
|
|
168
|
-
Verify at the correct layer:
|
|
169
|
-
|
|
170
|
-
- route additions: boot the app and hit the real URL
|
|
171
|
-
- controller changes: exercise the generated client call or generated `/api/...` endpoint
|
|
172
|
-
- SSR changes: load the real page and inspect rendered HTML plus browser console
|
|
173
|
-
- router or plugin changes: verify request context, auth, redirects, metrics, and validation on a running app
|
|
174
|
-
|
|
175
|
-
When an app may already be running, check the default port from `PORT` or `./.proteum/manifest.json` and inspect `proteum trace requests`, `proteum trace latest`, and `proteum trace show <requestId>` before reproducing the issue. If those traces are not enough, arm `npx proteum trace arm --capture deep`, reproduce once, then inspect the new request.
|
|
176
|
-
|
|
177
|
-
Useful commands: `npx proteum init <dir> --name <name>`, `npx proteum create <kind> <target>`, `proteum dev`, `npx proteum refresh`, `npx proteum typecheck`, `npx proteum lint`, `npx proteum check`, `npx proteum build prod`, `npx proteum command <path>`.
|