proteum 2.2.0 → 2.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/AGENTS.md +5 -3
- package/README.md +14 -3
- package/agents/project/AGENTS.md +14 -10
- package/agents/project/app-root/AGENTS.md +2 -2
- package/agents/project/diagnostics.md +2 -2
- package/agents/project/root/AGENTS.md +9 -7
- package/agents/project/tests/AGENTS.md +8 -1
- package/cli/commands/configure.ts +12 -3
- package/cli/commands/dev.ts +162 -12
- package/cli/compiler/common/index.ts +16 -0
- package/cli/presentation/commands.ts +1 -0
- package/cli/presentation/help.ts +4 -0
- package/cli/utils/agents.ts +36 -0
- package/common/dev/requestTrace.ts +66 -0
- package/common/env/proteumEnv.ts +10 -3
- package/docs/assets/unique-domains-chip.png +0 -0
- package/docs/request-tracing.md +17 -3
- package/package.json +1 -1
- package/server/app/container/trace/index.ts +255 -74
- package/server/services/prisma/index.ts +15 -12
- package/server/services/router/http/index.ts +1 -1
- package/server/services/router/index.ts +41 -9
- package/server/services/router/request/index.ts +21 -2
|
@@ -45,6 +45,15 @@ export default function createCommonConfig(
|
|
|
45
45
|
): Configuration {
|
|
46
46
|
const dev = mode === 'dev';
|
|
47
47
|
const enableFilesystemCache = dev ? cli.args.cache !== false : cli.args.cache === true;
|
|
48
|
+
const transpileModuleDirectories = app.transpileModuleDirectories;
|
|
49
|
+
const transpileModuleSnapshot =
|
|
50
|
+
dev && transpileModuleDirectories.length > 0
|
|
51
|
+
? {
|
|
52
|
+
// Transpiled local packages can resolve through node_modules symlinks,
|
|
53
|
+
// but they still need live invalidation like mutable app sources in dev.
|
|
54
|
+
unmanagedPaths: transpileModuleDirectories,
|
|
55
|
+
}
|
|
56
|
+
: undefined;
|
|
48
57
|
const frameworkPackageRoots = [cli.paths.framework.installedRoot, cli.paths.framework.activeRoot].filter(
|
|
49
58
|
(rootPath, index, list): rootPath is string => typeof rootPath === 'string' && list.indexOf(rootPath) === index,
|
|
50
59
|
);
|
|
@@ -109,6 +118,8 @@ export default function createCommonConfig(
|
|
|
109
118
|
]*/
|
|
110
119
|
},
|
|
111
120
|
|
|
121
|
+
...(transpileModuleSnapshot ? { snapshot: transpileModuleSnapshot } : {}),
|
|
122
|
+
|
|
112
123
|
// Turn off performance processing because we utilize
|
|
113
124
|
// our own hints via the FileSizeReporter
|
|
114
125
|
performance: false,
|
|
@@ -123,6 +134,11 @@ export default function createCommonConfig(
|
|
|
123
134
|
cacheDirectory: path.join(app.paths.cache, 'rspack', side, mode),
|
|
124
135
|
compression: false,
|
|
125
136
|
buildDependencies: { config: [__filename] },
|
|
137
|
+
...(transpileModuleSnapshot
|
|
138
|
+
? {
|
|
139
|
+
snapshot: transpileModuleSnapshot,
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
126
142
|
}
|
|
127
143
|
: false,
|
|
128
144
|
|
|
@@ -190,6 +190,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
190
190
|
notes: [
|
|
191
191
|
'Use `--cwd` when the target Proteum app lives in another worktree or checkout and you do not want to `cd` first.',
|
|
192
192
|
'Proteum writes a machine-readable dev session file under `var/run/proteum/dev/<port>.json` by default; override it with `--session-file` when an agent needs a stable path.',
|
|
193
|
+
'Before the interactive dev loop starts, Proteum offers to launch `proteum configure agents` when the app root is missing `AGENTS.md`.',
|
|
193
194
|
'Use `--replace-existing` when retries should stop the previously tracked matching session before starting a new one.',
|
|
194
195
|
'`proteum dev list` inspects tracked sessions for the current app root. Add `--stale` to show only orphaned or dead sessions.',
|
|
195
196
|
'`proteum dev stop` targets the current session file by default. Add `--all` to stop every tracked session for the current app root.',
|
package/cli/presentation/help.ts
CHANGED
|
@@ -139,6 +139,10 @@ export const renderCliOverview = async ({
|
|
|
139
139
|
indent: ' ',
|
|
140
140
|
nextIndent: ' ',
|
|
141
141
|
}),
|
|
142
|
+
wrapText('When the app root is missing `AGENTS.md`, the interactive `proteum dev` start offers to launch `proteum configure agents` before the dev loop begins.', {
|
|
143
|
+
indent: ' ',
|
|
144
|
+
nextIndent: ' ',
|
|
145
|
+
}),
|
|
142
146
|
wrapText('Legacy single-dash flags and positional booleans remain accepted for older app scripts, but new docs should prefer modern long flags.', {
|
|
143
147
|
indent: ' ',
|
|
144
148
|
nextIndent: ' ',
|
package/cli/utils/agents.ts
CHANGED
|
@@ -42,6 +42,11 @@ export type TConfigureProjectAgentSymlinksResult = {
|
|
|
42
42
|
updatedGitignores: string[];
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
export type TProjectAgentFileInspection = {
|
|
46
|
+
existing: string[];
|
|
47
|
+
missing: string[];
|
|
48
|
+
};
|
|
49
|
+
|
|
45
50
|
/*----------------------------------
|
|
46
51
|
- CONSTANTS
|
|
47
52
|
----------------------------------*/
|
|
@@ -147,6 +152,37 @@ export function renderProjectInstructionGitignoreBlock({ coreRoot }: TProjectIns
|
|
|
147
152
|
return renderInstructionGitignoreBlock({ linkDefinitions: getAppAgentLinkDefinitions({ coreRoot, mode: 'standalone' }) });
|
|
148
153
|
}
|
|
149
154
|
|
|
155
|
+
export function inspectProjectAgentFiles({ appRoot }: { appRoot: string }): TProjectAgentFileInspection {
|
|
156
|
+
const normalizedAppRoot = path.resolve(appRoot);
|
|
157
|
+
const expectedAgentPaths = Array.from(
|
|
158
|
+
new Set(
|
|
159
|
+
standaloneAppAgentLinkDefinitions
|
|
160
|
+
.map((linkDefinition) => linkDefinition.projectPath)
|
|
161
|
+
.filter((projectPath) => projectPath.endsWith('AGENTS.md')),
|
|
162
|
+
),
|
|
163
|
+
);
|
|
164
|
+
const result: TProjectAgentFileInspection = {
|
|
165
|
+
existing: [],
|
|
166
|
+
missing: [],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
for (const projectPath of expectedAgentPaths) {
|
|
170
|
+
const absolutePath = path.join(normalizedAppRoot, projectPath);
|
|
171
|
+
const parentPath = path.dirname(absolutePath);
|
|
172
|
+
|
|
173
|
+
if (projectPath !== 'AGENTS.md' && !fs.existsSync(parentPath)) continue;
|
|
174
|
+
|
|
175
|
+
if (fs.existsSync(absolutePath)) {
|
|
176
|
+
result.existing.push(projectPath);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
result.missing.push(projectPath);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
150
186
|
/*----------------------------------
|
|
151
187
|
- HELPERS
|
|
152
188
|
----------------------------------*/
|
|
@@ -114,6 +114,7 @@ export type TTraceSqlQuery = {
|
|
|
114
114
|
model?: string;
|
|
115
115
|
operation: string;
|
|
116
116
|
fingerprint?: string;
|
|
117
|
+
normalizedQuery?: string;
|
|
117
118
|
ownerLabel?: string;
|
|
118
119
|
ownerFilepath?: string;
|
|
119
120
|
serviceLabel?: string;
|
|
@@ -169,6 +170,71 @@ export type TRequestTrace = {
|
|
|
169
170
|
events: TTraceEvent[];
|
|
170
171
|
};
|
|
171
172
|
|
|
173
|
+
export type TRequestProfilingApiCall = {
|
|
174
|
+
id: string;
|
|
175
|
+
origin: TTraceCallOrigin;
|
|
176
|
+
label: string;
|
|
177
|
+
method: string;
|
|
178
|
+
path: string;
|
|
179
|
+
fetcherId?: string;
|
|
180
|
+
connectedProjectNamespace?: string;
|
|
181
|
+
connectedControllerAccessor?: string;
|
|
182
|
+
ownerLabel?: string;
|
|
183
|
+
ownerFilepath?: string;
|
|
184
|
+
serviceLabel?: string;
|
|
185
|
+
startedAt: string;
|
|
186
|
+
finishedAt?: string;
|
|
187
|
+
durationMs?: number;
|
|
188
|
+
statusCode?: number;
|
|
189
|
+
errorMessage?: string;
|
|
190
|
+
requestBodyJson?: unknown;
|
|
191
|
+
responseBodyJson?: unknown;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export type TRequestProfilingSqlQuery = {
|
|
195
|
+
id: string;
|
|
196
|
+
callerCallId?: string;
|
|
197
|
+
callerFetcherId?: string;
|
|
198
|
+
callerLabel?: string;
|
|
199
|
+
callerMethod: string;
|
|
200
|
+
callerOrigin: TTraceSqlQueryCallerOrigin;
|
|
201
|
+
callerPath: string;
|
|
202
|
+
durationMs: number;
|
|
203
|
+
finishedAt: string;
|
|
204
|
+
kind: TTraceSqlQueryKind;
|
|
205
|
+
model?: string;
|
|
206
|
+
operation: string;
|
|
207
|
+
fingerprint?: string;
|
|
208
|
+
normalizedQuery?: string;
|
|
209
|
+
ownerLabel?: string;
|
|
210
|
+
ownerFilepath?: string;
|
|
211
|
+
serviceLabel?: string;
|
|
212
|
+
connectedNamespace?: string;
|
|
213
|
+
paramsJson?: unknown;
|
|
214
|
+
paramsText?: string;
|
|
215
|
+
query: string;
|
|
216
|
+
startedAt: string;
|
|
217
|
+
target?: string;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export type TRequestProfiling = {
|
|
221
|
+
enabled: boolean;
|
|
222
|
+
requestId: string;
|
|
223
|
+
method: string;
|
|
224
|
+
path: string;
|
|
225
|
+
url: string;
|
|
226
|
+
startedAt: string;
|
|
227
|
+
finishedAt?: string;
|
|
228
|
+
durationMs?: number;
|
|
229
|
+
statusCode?: number;
|
|
230
|
+
user?: string;
|
|
231
|
+
errorMessage?: string;
|
|
232
|
+
profilerOrigin?: string;
|
|
233
|
+
profilerParentRequestId?: string;
|
|
234
|
+
apiCalls: TRequestProfilingApiCall[];
|
|
235
|
+
sqlQueries: TRequestProfilingSqlQuery[];
|
|
236
|
+
};
|
|
237
|
+
|
|
172
238
|
export type TRequestTraceListItem = Omit<TRequestTrace, 'events' | 'calls' | 'sqlQueries'> & {
|
|
173
239
|
eventCount: number;
|
|
174
240
|
callCount: number;
|
package/common/env/proteumEnv.ts
CHANGED
|
@@ -36,6 +36,7 @@ export type TProteumEnvConfig = {
|
|
|
36
36
|
connectedProjects: Record<string, TProteumConnectedProjectEnvConfig>;
|
|
37
37
|
trace: {
|
|
38
38
|
enable: boolean;
|
|
39
|
+
profilerEnable: boolean;
|
|
39
40
|
requestsLimit: number;
|
|
40
41
|
eventsLimit: number;
|
|
41
42
|
capture: TProteumTraceCapture;
|
|
@@ -65,7 +66,7 @@ const isProvidedEnvValue = (value: string | undefined) => typeof value === 'stri
|
|
|
65
66
|
|
|
66
67
|
const buildRequiredEnvVariableDefinitions = (_connectedProjects: TConnectedProjectsConfig) => [...baseRequiredProteumEnvVariableDefinitions];
|
|
67
68
|
|
|
68
|
-
const buildOptionalEnvKeys = (_connectedProjects: TConnectedProjectsConfig) => [] as string[];
|
|
69
|
+
const buildOptionalEnvKeys = (_connectedProjects: TConnectedProjectsConfig) => ['ENABLE_PROFILER'] as string[];
|
|
69
70
|
|
|
70
71
|
const formatRequiredEnvVariableStatus = (variable: TProteumRequiredEnvVariable) =>
|
|
71
72
|
`- ${variable.key} possibleValues=${variable.possibleValues.join(' | ')} provided=${variable.provided ? 'yes' : 'no'}`;
|
|
@@ -269,8 +270,8 @@ export const loadOptionalProteumDotenv = (appDir: string) => {
|
|
|
269
270
|
};
|
|
270
271
|
|
|
271
272
|
export const getLoadedProteumEnvVariableKeys = (connectedProjects: TConnectedProjectsConfig = {}) => {
|
|
272
|
-
const requiredKeys = new Set(buildRequiredEnvVariableDefinitions(connectedProjects).map((definition) => definition.key));
|
|
273
|
-
const optionalKeys = new Set(buildOptionalEnvKeys(connectedProjects));
|
|
273
|
+
const requiredKeys = new Set<string>(buildRequiredEnvVariableDefinitions(connectedProjects).map((definition) => definition.key));
|
|
274
|
+
const optionalKeys = new Set<string>(buildOptionalEnvKeys(connectedProjects));
|
|
274
275
|
|
|
275
276
|
return Object.keys(process.env)
|
|
276
277
|
.filter(
|
|
@@ -338,6 +339,11 @@ export const parseProteumEnvConfig = ({
|
|
|
338
339
|
value: process.env.TRACE_ENABLE,
|
|
339
340
|
context,
|
|
340
341
|
});
|
|
342
|
+
const profilerEnable = parseBooleanEnvValue({
|
|
343
|
+
key: 'ENABLE_PROFILER',
|
|
344
|
+
value: process.env.ENABLE_PROFILER,
|
|
345
|
+
context,
|
|
346
|
+
});
|
|
341
347
|
const tracePersistOnError = parseBooleanEnvValue({
|
|
342
348
|
key: 'TRACE_PERSIST_ON_ERROR',
|
|
343
349
|
value: process.env.TRACE_PERSIST_ON_ERROR,
|
|
@@ -387,6 +393,7 @@ export const parseProteumEnvConfig = ({
|
|
|
387
393
|
connectedProjects: resolvedConnectedProjects,
|
|
388
394
|
trace: {
|
|
389
395
|
enable: traceEnable ?? profile === 'dev',
|
|
396
|
+
profilerEnable: profilerEnable ?? false,
|
|
390
397
|
requestsLimit:
|
|
391
398
|
traceRequestsLimit === undefined || traceRequestsLimit === ''
|
|
392
399
|
? 200
|
|
Binary file
|
package/docs/request-tracing.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# Request Tracing
|
|
2
2
|
|
|
3
|
-
Proteum ships with
|
|
3
|
+
Proteum ships with one request-instrumentation system with two runtime shapes:
|
|
4
|
+
|
|
5
|
+
- retained dev traces for `proteum trace`, `proteum perf`, the dev-only HTTP endpoints, and the bottom profiler
|
|
6
|
+
- reduced request-local profiling for `request.profiling` and the router `request.finished` hook
|
|
7
|
+
|
|
8
|
+
The same API and SQL instrumentation feeds both shapes. Dev trace keeps the in-memory buffer and event timeline. Reduced profiling keeps only the finalized request/API/SQL snapshot and releases it after the `request.finished` hook runs.
|
|
4
9
|
|
|
5
10
|
## Scope
|
|
6
11
|
|
|
7
|
-
- tracing is available only when the app runs with `profile: dev`
|
|
12
|
+
- retained dev tracing is available only when the app runs with `profile: dev`
|
|
8
13
|
- traces are exposed through `proteum trace`, `proteum perf`, and the dev-only `__proteum/trace` and `__proteum/perf` HTTP endpoints
|
|
9
14
|
- `proteum diagnose` is a separate composite surface that reads the same framework diagnostics plus one matching request trace and buffered server logs; see [diagnostics.md](diagnostics.md)
|
|
10
|
-
-
|
|
15
|
+
- `ENABLE_PROFILER=true` enables reduced request-local profiling in any environment, including production
|
|
11
16
|
|
|
12
17
|
## Main Commands
|
|
13
18
|
|
|
@@ -77,6 +82,13 @@ Depending on capture mode, traces can include:
|
|
|
77
82
|
- normalized request errors
|
|
78
83
|
- additive owner, service, cache, and connected-boundary metadata propagated from route/controller resolution into downstream calls and SQL
|
|
79
84
|
|
|
85
|
+
Reduced request-local profiling keeps the finalized request summary plus API and SQL rows only:
|
|
86
|
+
|
|
87
|
+
- `request.profiling` exists before the router `request` hook runs
|
|
88
|
+
- `request.profiling.apiCalls` and `request.profiling.sqlQueries` start empty and are populated during request handling
|
|
89
|
+
- the router `request.finished` hook receives that same object after status, duration, API calls, and SQL queries are finalized
|
|
90
|
+
- when only reduced profiling is enabled, finished requests are released immediately after `request.finished` instead of being retained in the global trace buffer
|
|
91
|
+
|
|
80
92
|
## SQL Tracing
|
|
81
93
|
|
|
82
94
|
Prisma query tracing covers both ORM operations and raw queries.
|
|
@@ -140,6 +152,7 @@ export TRACE_REQUESTS_LIMIT=200
|
|
|
140
152
|
export TRACE_EVENTS_LIMIT=800
|
|
141
153
|
export TRACE_CAPTURE=resolve
|
|
142
154
|
export TRACE_PERSIST_ON_ERROR=true
|
|
155
|
+
export ENABLE_PROFILER=true
|
|
143
156
|
```
|
|
144
157
|
|
|
145
158
|
Notes:
|
|
@@ -150,6 +163,7 @@ Notes:
|
|
|
150
163
|
- `eventsLimit` defaults to `800`
|
|
151
164
|
- `proteum dev` removes auto-persisted crash traces from `var/traces/` when the dev session stops
|
|
152
165
|
- explicit `proteum trace export` files under `var/traces/exports/` are left in place
|
|
166
|
+
- `ENABLE_PROFILER` reuses the same request instrumentation path but skips the retained global buffer and event timeline when dev trace is otherwise off
|
|
153
167
|
|
|
154
168
|
## Memory Model
|
|
155
169
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proteum",
|
|
3
3
|
"description": "LLM-first Opinionated Typescript Framework for web applications.",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.2-1",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/proteum.git",
|
|
7
7
|
"license": "MIT",
|