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.
@@ -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.',
@@ -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: ' ',
@@ -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;
@@ -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
@@ -1,13 +1,18 @@
1
1
  # Request Tracing
2
2
 
3
- Proteum ships with a dev-only in-memory request trace buffer so routing, controller execution, SSR, API, Prisma SQL, render behavior, and request-time performance can be inspected without attaching a debugger or scattering temporary logs through the runtime.
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
- - production requests are not traced by this feature
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.0",
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",