proteum 2.1.0 → 2.1.2

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 (95) hide show
  1. package/AGENTS.md +44 -98
  2. package/README.md +143 -10
  3. package/agents/framework/AGENTS.md +146 -886
  4. package/agents/project/AGENTS.md +73 -127
  5. package/agents/project/client/AGENTS.md +22 -93
  6. package/agents/project/client/pages/AGENTS.md +24 -26
  7. package/agents/project/server/routes/AGENTS.md +10 -8
  8. package/agents/project/server/services/AGENTS.md +22 -159
  9. package/agents/project/tests/AGENTS.md +11 -8
  10. package/cli/app/config.ts +7 -20
  11. package/cli/bin.js +8 -0
  12. package/cli/commands/command.ts +243 -0
  13. package/cli/commands/commandLocalRunner.js +198 -0
  14. package/cli/commands/create.ts +5 -0
  15. package/cli/commands/deploy/web.ts +1 -2
  16. package/cli/commands/dev.ts +98 -2
  17. package/cli/commands/doctor.ts +8 -74
  18. package/cli/commands/explain.ts +8 -186
  19. package/cli/commands/init.ts +2 -94
  20. package/cli/commands/trace.ts +228 -0
  21. package/cli/compiler/artifacts/commands.ts +217 -0
  22. package/cli/compiler/artifacts/manifest.ts +35 -21
  23. package/cli/compiler/artifacts/services.ts +300 -1
  24. package/cli/compiler/client/index.ts +43 -8
  25. package/cli/compiler/common/commands.ts +175 -0
  26. package/cli/compiler/common/index.ts +1 -1
  27. package/cli/compiler/common/proteumManifest.ts +15 -114
  28. package/cli/compiler/index.ts +25 -2
  29. package/cli/compiler/server/index.ts +31 -6
  30. package/cli/index.ts +1 -4
  31. package/cli/paths.ts +16 -1
  32. package/cli/presentation/commands.ts +104 -14
  33. package/cli/presentation/devSession.ts +22 -3
  34. package/cli/presentation/proteum_logo_400x400_square_icon.txt +400 -0
  35. package/cli/runtime/commands.ts +121 -4
  36. package/cli/scaffold/index.ts +720 -0
  37. package/cli/scaffold/templates.ts +344 -0
  38. package/cli/scaffold/types.ts +26 -0
  39. package/cli/tsconfig.json +4 -1
  40. package/cli/utils/check.ts +1 -1
  41. package/client/app/component.tsx +13 -9
  42. package/client/dev/profiler/index.tsx +2511 -0
  43. package/client/dev/profiler/noop.tsx +5 -0
  44. package/client/dev/profiler/runtime.noop.ts +116 -0
  45. package/client/dev/profiler/runtime.ts +840 -0
  46. package/client/services/router/components/router.tsx +30 -2
  47. package/client/services/router/index.tsx +27 -3
  48. package/client/services/router/request/api.ts +133 -17
  49. package/commands/proteum/diagnostics.ts +11 -0
  50. package/common/dev/commands.ts +50 -0
  51. package/common/dev/diagnostics.ts +298 -0
  52. package/common/dev/profiler.ts +92 -0
  53. package/common/dev/proteumManifest.ts +135 -0
  54. package/common/dev/requestTrace.ts +115 -0
  55. package/common/env/proteumEnv.ts +284 -0
  56. package/common/router/index.ts +4 -22
  57. package/docs/dev-commands.md +93 -0
  58. package/docs/diagnostics.md +88 -0
  59. package/docs/request-tracing.md +132 -0
  60. package/eslint.js +11 -6
  61. package/package.json +3 -3
  62. package/server/app/commands.ts +35 -370
  63. package/server/app/commandsManager.ts +393 -0
  64. package/server/app/container/config.ts +11 -49
  65. package/server/app/container/console/index.ts +2 -3
  66. package/server/app/container/index.ts +5 -2
  67. package/server/app/container/trace/index.ts +364 -0
  68. package/server/app/devCommands.ts +192 -0
  69. package/server/app/devDiagnostics.ts +53 -0
  70. package/server/app/index.ts +29 -6
  71. package/server/index.ts +0 -1
  72. package/server/services/auth/index.ts +525 -61
  73. package/server/services/auth/router/index.ts +106 -7
  74. package/server/services/cron/CronTask.ts +73 -5
  75. package/server/services/cron/index.ts +34 -11
  76. package/server/services/fetch/index.ts +3 -10
  77. package/server/services/prisma/index.ts +66 -4
  78. package/server/services/router/http/index.ts +173 -6
  79. package/server/services/router/index.ts +200 -12
  80. package/server/services/router/request/api.ts +30 -1
  81. package/server/services/router/response/index.ts +83 -10
  82. package/server/services/router/response/page/document.tsx +16 -0
  83. package/server/services/router/response/page/index.tsx +27 -1
  84. package/skills/clean-project-code/SKILL.md +7 -2
  85. package/test-results/.last-run.json +4 -0
  86. package/types/aliases.d.ts +6 -0
  87. package/types/global/utils.d.ts +7 -14
  88. package/Rte.zip +0 -0
  89. package/agents/project/agents.md.zip +0 -0
  90. package/doc/TODO.md +0 -71
  91. package/doc/front/router.md +0 -27
  92. package/doc/workspace/workspace.png +0 -0
  93. package/doc/workspace/workspace2.png +0 -0
  94. package/doc/workspace/workspace_26.01.22.png +0 -0
  95. package/server/services/router/http/session.ts.old +0 -40
@@ -0,0 +1,284 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import dotenv from 'dotenv';
4
+
5
+ export type TProteumEnvName = 'local' | 'server';
6
+ export type TProteumEnvProfile = 'dev' | 'testing' | 'prod';
7
+ export type TProteumTraceCapture = 'summary' | 'resolve' | 'deep';
8
+ export type TProteumRequiredEnvVariable = {
9
+ key: TProteumRequiredEnvVariableKey;
10
+ possibleValues: string[];
11
+ provided: boolean;
12
+ };
13
+ export type TProteumEnvInspection = {
14
+ loadedVariableKeys: string[];
15
+ requiredVariables: TProteumRequiredEnvVariable[];
16
+ };
17
+
18
+ export type TProteumEnvConfig = {
19
+ name: TProteumEnvName;
20
+ profile: TProteumEnvProfile;
21
+ router: {
22
+ port: number;
23
+ currentDomain: string;
24
+ };
25
+ trace: {
26
+ enable: boolean;
27
+ requestsLimit: number;
28
+ eventsLimit: number;
29
+ capture: TProteumTraceCapture;
30
+ persistOnError: boolean;
31
+ };
32
+ };
33
+
34
+ export type TProteumLoadedEnvConfig = TProteumEnvConfig & { version: string };
35
+
36
+ const dotenvFileNames = ['.env'];
37
+ const requiredProteumEnvVariableKeys = ['ENV_NAME', 'ENV_PROFILE', 'PORT', 'URL'] as const;
38
+ const optionalProteumEnvVariablePrefixes = ['TRACE_'] as const;
39
+
40
+ export type TProteumRequiredEnvVariableKey = (typeof requiredProteumEnvVariableKeys)[number];
41
+
42
+ const requiredProteumEnvVariablePossibleValues: Record<TProteumRequiredEnvVariableKey, string[]> = {
43
+ ENV_NAME: ['local', 'server'],
44
+ ENV_PROFILE: ['dev', 'testing', 'prod'],
45
+ PORT: ['integer between 1 and 65535'],
46
+ URL: ['absolute URL'],
47
+ };
48
+
49
+ const envDefinitionHint = (appDir: string) => `Define it in process.env or ${appDir}/.env.`;
50
+ const isProvidedEnvValue = (value: string | undefined) => typeof value === 'string' && value.trim() !== '';
51
+
52
+ const formatRequiredEnvVariableStatus = (variable: TProteumRequiredEnvVariable) =>
53
+ `- ${variable.key} possibleValues=${variable.possibleValues.join(' | ')} provided=${variable.provided ? 'yes' : 'no'}`;
54
+
55
+ const createProteumEnvError = ({
56
+ appDir,
57
+ message,
58
+ }: {
59
+ appDir: string;
60
+ message: string;
61
+ }) => {
62
+ const inspection = inspectProteumEnv(appDir);
63
+
64
+ return new Error(
65
+ [message, envDefinitionHint(appDir), '', 'Required env variables:', ...inspection.requiredVariables.map(formatRequiredEnvVariableStatus)].join(
66
+ '\n',
67
+ ),
68
+ );
69
+ };
70
+
71
+ const parseBooleanEnvValue = ({
72
+ key,
73
+ value,
74
+ appDir,
75
+ }: {
76
+ key: string;
77
+ value: string | undefined;
78
+ appDir: string;
79
+ }) => {
80
+ if (value === undefined || value === '') return undefined;
81
+
82
+ const normalized = value.trim().toLowerCase();
83
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
84
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
85
+
86
+ throw createProteumEnvError({
87
+ appDir,
88
+ message: `Invalid boolean value for ${key}: "${value}". Expected one of: 1, 0, true, false, yes, no, on, off.`,
89
+ });
90
+ };
91
+
92
+ const parseIntegerEnvValue = ({
93
+ key,
94
+ value,
95
+ appDir,
96
+ min = 1,
97
+ }: {
98
+ key: string;
99
+ value: string;
100
+ appDir: string;
101
+ min?: number;
102
+ }) => {
103
+ const parsed = Number.parseInt(value, 10);
104
+ if (Number.isNaN(parsed) || parsed < min) {
105
+ throw createProteumEnvError({
106
+ appDir,
107
+ message: `Invalid integer value for ${key}: "${value}". Expected an integer greater than or equal to ${min}.`,
108
+ });
109
+ }
110
+
111
+ return parsed;
112
+ };
113
+
114
+ const getRequiredEnvValue = ({ key, appDir }: { key: TProteumRequiredEnvVariableKey; appDir: string }) => {
115
+ const value = process.env[key]?.trim();
116
+ if (value) return value;
117
+
118
+ throw createProteumEnvError({
119
+ appDir,
120
+ message: `Missing required Proteum env variable "${key}".`,
121
+ });
122
+ };
123
+
124
+ const parseEnvName = (value: string, appDir: string): TProteumEnvName => {
125
+ if (value === 'local' || value === 'server') return value;
126
+ throw createProteumEnvError({
127
+ appDir,
128
+ message: `Invalid ENV_NAME "${value}". Expected "local" or "server".`,
129
+ });
130
+ };
131
+
132
+ const parseEnvProfile = (value: string, appDir: string): TProteumEnvProfile => {
133
+ if (value === 'dev' || value === 'testing' || value === 'prod') return value;
134
+ throw createProteumEnvError({
135
+ appDir,
136
+ message: `Invalid ENV_PROFILE "${value}". Expected "dev", "testing", or "prod".`,
137
+ });
138
+ };
139
+
140
+ const parseTraceCapture = ({
141
+ value,
142
+ appDir,
143
+ }: {
144
+ value: string | undefined;
145
+ appDir: string;
146
+ }): TProteumTraceCapture | undefined => {
147
+ if (value === undefined || value === '') return undefined;
148
+ if (value === 'summary' || value === 'resolve' || value === 'deep') return value;
149
+
150
+ throw createProteumEnvError({
151
+ appDir,
152
+ message: `Invalid TRACE_CAPTURE "${value}". Expected "summary", "resolve", or "deep".`,
153
+ });
154
+ };
155
+
156
+ const parseAbsoluteUrl = ({
157
+ key,
158
+ value,
159
+ appDir,
160
+ }: {
161
+ key: string;
162
+ value: string;
163
+ appDir: string;
164
+ }) => {
165
+ let url: URL;
166
+
167
+ try {
168
+ url = new URL(value);
169
+ } catch {
170
+ throw createProteumEnvError({
171
+ appDir,
172
+ message: `Invalid absolute URL for ${key}: "${value}". Expected an absolute http:// or https:// URL.`,
173
+ });
174
+ }
175
+
176
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
177
+ throw createProteumEnvError({
178
+ appDir,
179
+ message: `Invalid absolute URL for ${key}: "${value}". Expected an absolute http:// or https:// URL.`,
180
+ });
181
+ }
182
+
183
+ return value;
184
+ };
185
+
186
+ export const loadOptionalProteumDotenv = (appDir: string) => {
187
+ for (const filename of dotenvFileNames) {
188
+ const filepath = path.join(appDir, filename);
189
+ if (!fs.existsSync(filepath)) continue;
190
+ dotenv.config({ path: filepath, quiet: true });
191
+ }
192
+ };
193
+
194
+ export const getLoadedProteumEnvVariableKeys = () =>
195
+ Object.keys(process.env)
196
+ .filter(
197
+ (key) =>
198
+ requiredProteumEnvVariableKeys.includes(key as TProteumRequiredEnvVariableKey) ||
199
+ optionalProteumEnvVariablePrefixes.some((prefix) => key.startsWith(prefix)),
200
+ )
201
+ .sort((a, b) => a.localeCompare(b));
202
+
203
+ export const inspectProteumEnv = (appDir: string): TProteumEnvInspection => {
204
+ loadOptionalProteumDotenv(appDir);
205
+
206
+ return {
207
+ loadedVariableKeys: getLoadedProteumEnvVariableKeys(),
208
+ requiredVariables: requiredProteumEnvVariableKeys.map((key) => ({
209
+ key,
210
+ possibleValues: [...requiredProteumEnvVariablePossibleValues[key]],
211
+ provided: isProvidedEnvValue(process.env[key]),
212
+ })),
213
+ };
214
+ };
215
+
216
+ export const parseProteumEnvConfig = ({
217
+ appDir,
218
+ routerPortOverride,
219
+ }: {
220
+ appDir: string;
221
+ routerPortOverride?: number;
222
+ }): TProteumEnvConfig => {
223
+ loadOptionalProteumDotenv(appDir);
224
+
225
+ const name = parseEnvName(getRequiredEnvValue({ key: 'ENV_NAME', appDir }), appDir);
226
+ const profile = parseEnvProfile(getRequiredEnvValue({ key: 'ENV_PROFILE', appDir }), appDir);
227
+ const configuredRouterPort = parseIntegerEnvValue({
228
+ key: 'PORT',
229
+ value: getRequiredEnvValue({ key: 'PORT', appDir }),
230
+ appDir,
231
+ });
232
+ const currentDomain = parseAbsoluteUrl({
233
+ key: 'URL',
234
+ value: getRequiredEnvValue({ key: 'URL', appDir }),
235
+ appDir,
236
+ });
237
+
238
+ const traceEnable = parseBooleanEnvValue({
239
+ key: 'TRACE_ENABLE',
240
+ value: process.env.TRACE_ENABLE,
241
+ appDir,
242
+ });
243
+ const tracePersistOnError = parseBooleanEnvValue({
244
+ key: 'TRACE_PERSIST_ON_ERROR',
245
+ value: process.env.TRACE_PERSIST_ON_ERROR,
246
+ appDir,
247
+ });
248
+ const traceRequestsLimit = process.env.TRACE_REQUESTS_LIMIT?.trim();
249
+ const traceEventsLimit = process.env.TRACE_EVENTS_LIMIT?.trim();
250
+ const traceCapture = parseTraceCapture({
251
+ value: process.env.TRACE_CAPTURE?.trim(),
252
+ appDir,
253
+ });
254
+
255
+ return {
256
+ name,
257
+ profile,
258
+ router: {
259
+ port: routerPortOverride === undefined ? configuredRouterPort : routerPortOverride,
260
+ currentDomain,
261
+ },
262
+ trace: {
263
+ enable: traceEnable ?? profile === 'dev',
264
+ requestsLimit:
265
+ traceRequestsLimit === undefined || traceRequestsLimit === ''
266
+ ? 200
267
+ : parseIntegerEnvValue({
268
+ key: 'TRACE_REQUESTS_LIMIT',
269
+ value: traceRequestsLimit,
270
+ appDir,
271
+ }),
272
+ eventsLimit:
273
+ traceEventsLimit === undefined || traceEventsLimit === ''
274
+ ? 800
275
+ : parseIntegerEnvValue({
276
+ key: 'TRACE_EVENTS_LIMIT',
277
+ value: traceEventsLimit,
278
+ appDir,
279
+ }),
280
+ capture: traceCapture ?? 'resolve',
281
+ persistOnError: tracePersistOnError ?? profile === 'dev',
282
+ },
283
+ };
284
+ };
@@ -106,8 +106,6 @@ export type TRouteModule<TRegisteredRoute = any> = {
106
106
  __register: TAppArrowFunction<TRegisteredRoute>;
107
107
  };
108
108
 
109
- export type TDomainsList = { [endpointId: string]: string } & { current: string };
110
-
111
109
  export const defaultOptions: Pick<TRouteOptions, 'priority'> = { priority: 0 };
112
110
 
113
111
  /*----------------------------------
@@ -116,31 +114,15 @@ export const defaultOptions: Pick<TRouteOptions, 'priority'> = { priority: 0 };
116
114
  export const buildUrl = (
117
115
  path: string,
118
116
  params: { [key: string]: any },
119
- domains: { [alias: string]: string },
117
+ currentDomain: string,
120
118
  absolute: boolean,
121
119
  ) => {
122
120
  let prefix: string = '';
123
121
 
124
122
  // Relative to domain
125
- if (path[0] === '/' && absolute) prefix = domains.current;
126
- // Other domains of the project
127
- else if (path[0] === '@') {
128
- // Extract domain ID from path
129
- let domainId: string;
130
- let slackPos = path.indexOf('/');
131
- if (slackPos === -1) slackPos = path.length;
132
- domainId = path.substring(1, slackPos);
133
- path = path.substring(slackPos);
134
-
135
- // Get domain
136
- const domain = domains[domainId];
137
- if (domain === undefined) throw new Error('Unknown API endpoint ID: ' + domainId);
138
-
139
- // Return full url
140
- prefix = domain;
141
-
142
- // Absolute URL
143
- }
123
+ if (path[0] === '/' && absolute) prefix = currentDomain;
124
+ else if (path[0] === '@')
125
+ throw new Error(`Proteum no longer supports Router.url() domain aliases. Use a root-relative path or absolute URL instead: "${path}".`);
144
126
 
145
127
  // Path parapeters
146
128
  const searchParams = new URLSearchParams();
@@ -0,0 +1,93 @@
1
+ ## Dev Commands
2
+
3
+ Proteum supports a dev-only internal command surface for testing, debugging, and one-off server-side execution that should not be exposed as a normal controller or route.
4
+
5
+ ### Source Contract
6
+
7
+ - command files live under `./commands/**/*.ts`
8
+ - each file default-exports a class extending `Commands` from `@server/app/commands`
9
+ - every method with a body becomes a runnable command
10
+ - the command path is `file/path/methodName`
11
+ - `export const commandPath = 'Custom/path'` can override the file-derived base path
12
+
13
+ Example:
14
+
15
+ ```ts
16
+ import { Commands } from '@server/app/commands';
17
+
18
+ export default class DiagnosticsCommands extends Commands {
19
+ public async ping() {
20
+ return {
21
+ app: this.app.identity.identifier,
22
+ env: this.app.env.profile,
23
+ };
24
+ }
25
+ }
26
+ ```
27
+
28
+ The example above is available as `diagnostics/ping`.
29
+
30
+ When `./commands` exists, Proteum also creates `./commands/tsconfig.json` plus a generated command typing surface under `.proteum/server/commands.d.ts`.
31
+
32
+ - command files inherit the server alias project
33
+ - `extends Commands` works without importing your app class
34
+ - `@/server/index` remains available inside `/commands` as a generated command-only app type alias
35
+
36
+ ### CLI
37
+
38
+ Run a command locally:
39
+
40
+ ```bash
41
+ proteum command proteum/diagnostics/ping
42
+ ```
43
+
44
+ Local mode does this:
45
+
46
+ 1. refreshes `.proteum` artifacts
47
+ 2. picks a temporary local port
48
+ 3. builds the dev server output
49
+ 4. starts a temporary local dev server
50
+ 5. runs the command through the dev-only command endpoint
51
+ 6. prints the result and exits
52
+
53
+ Run a command against an existing dev instance:
54
+
55
+ ```bash
56
+ proteum command proteum/diagnostics/ping --port 3101
57
+ proteum command proteum/diagnostics/ping --url http://127.0.0.1:3101
58
+ ```
59
+
60
+ Use `--port` or `--url` when you want to reuse an existing `proteum dev` instance instead of building and starting a temporary local one.
61
+
62
+ ### Profiler
63
+
64
+ In `proteum dev`, the bottom profiler exposes a `Commands` tab.
65
+
66
+ - it lists every generated command path
67
+ - it shows the backing class, method, scope, and source location
68
+ - clicking `Run now` executes the command through the running dev server
69
+ - the last result or error stays attached to that command row in the panel
70
+
71
+ The profiler also exposes the shared diagnostics surfaces for humans:
72
+
73
+ - `Explain` renders the same manifest-backed data as `proteum explain`
74
+ - `Doctor` renders the same manifest diagnostics as `proteum doctor`
75
+
76
+ For the shared diagnostics contract and the corresponding dev HTTP endpoints, see [diagnostics.md](diagnostics.md).
77
+
78
+ ### HTTP Endpoints
79
+
80
+ The CLI remote mode and the profiler use the same dev-only endpoints:
81
+
82
+ - `GET /__proteum/commands`
83
+ - `POST /__proteum/commands/run`
84
+
85
+ These endpoints exist only in dev mode and are not available in production.
86
+
87
+ ### Built-In Command
88
+
89
+ Proteum ships one framework command by default:
90
+
91
+ - `proteum/diagnostics/ping`
92
+
93
+ It returns the current app identifier, active env profile, and discovered root services so every dev app has a real command surface available immediately.
@@ -0,0 +1,88 @@
1
+ # Diagnostics and Explainability
2
+
3
+ Proteum exposes two manifest-backed diagnostics surfaces:
4
+
5
+ - `proteum explain`: inspect the generated app structure
6
+ - `proteum doctor`: inspect manifest diagnostics
7
+
8
+ These are not separate models for different tools. They share the same generated snapshot and the same diagnostics contract.
9
+
10
+ ## Shared Contract
11
+
12
+ The canonical snapshot lives in `./.proteum/manifest.json`.
13
+
14
+ Proteum uses that same manifest in four places:
15
+
16
+ - `proteum explain` for human-readable and `--json` output
17
+ - `proteum doctor` for human-readable and `--json` output
18
+ - the dev-only `__proteum/explain` and `__proteum/doctor` HTTP endpoints
19
+ - the `Explain` and `Doctor` tabs in the bottom profiler during `proteum dev`
20
+
21
+ This means the CLI, the dev HTTP endpoints, and the profiler all describe the same manifest-backed snapshot.
22
+
23
+ If a command such as `proteum explain`, `proteum doctor`, or `proteum refresh` regenerates `.proteum/manifest.json`, the next CLI call, HTTP call, or profiler refresh will reflect that same updated snapshot.
24
+
25
+ ## CLI
26
+
27
+ Common usage:
28
+
29
+ ```bash
30
+ proteum explain
31
+ proteum explain --routes --controllers --commands
32
+ proteum explain --all --json
33
+
34
+ proteum doctor
35
+ proteum doctor --json
36
+ proteum doctor --strict
37
+ ```
38
+
39
+ `proteum explain --json` emits the selected manifest sections as machine-readable JSON.
40
+
41
+ `proteum doctor --json` emits:
42
+
43
+ - `summary.errors`
44
+ - `summary.warnings`
45
+ - `summary.strictFailed`
46
+ - `diagnostics`
47
+
48
+ ## Dev HTTP Endpoints
49
+
50
+ In `profile: dev`, the running app exposes:
51
+
52
+ - `GET /__proteum/explain`
53
+ - `GET /__proteum/doctor`
54
+
55
+ `/__proteum/explain` supports optional section selection:
56
+
57
+ ```text
58
+ GET /__proteum/explain?sections=routes,controllers,commands
59
+ GET /__proteum/explain?section=env&section=diagnostics
60
+ ```
61
+
62
+ `/__proteum/doctor` supports optional strict mode:
63
+
64
+ ```text
65
+ GET /__proteum/doctor?strict=true
66
+ ```
67
+
68
+ These endpoints are intended for local tooling and are not available in production.
69
+
70
+ ## Profiler
71
+
72
+ During `proteum dev`, the bottom profiler is the human-facing UI over the same dev diagnostics surfaces.
73
+
74
+ - `Explain` calls `/__proteum/explain`
75
+ - `Doctor` calls `/__proteum/doctor`
76
+ - `Commands` uses the dev command endpoints
77
+ - `Auth`, `Timeline`, `Routing`, `Controller`, `SSR`, `API`, and related panels remain request-trace views
78
+
79
+ Use the profiler when a human needs to browse the same data that an agent or CLI command can already inspect directly.
80
+
81
+ ## Agent Workflow
82
+
83
+ For AI coding agents or automation:
84
+
85
+ 1. Read `./.proteum/manifest.json` or run `proteum explain --json`.
86
+ 2. Run `proteum doctor --json` to inspect framework diagnostics.
87
+ 3. For request-time behavior, use `proteum trace ...` because traces are live runtime data, not manifest snapshots.
88
+ 4. Open the profiler only when a human-readable view helps; it should agree with the CLI after refresh.
@@ -0,0 +1,132 @@
1
+ # Request Tracing
2
+
3
+ Proteum ships with a dev-only in-memory request trace buffer so routing, controller execution, SSR, and render behavior can be inspected without attaching a debugger or scattering temporary logs through the runtime.
4
+
5
+ ## Scope
6
+
7
+ - tracing is available only when the app runs with `profile: dev`
8
+ - traces are exposed through `proteum trace` and the dev-only `__proteum/trace` HTTP endpoints
9
+ - explain/doctor are separate manifest-backed diagnostics surfaces; see [diagnostics.md](diagnostics.md)
10
+ - production requests are not traced by this feature
11
+
12
+ ## Main Commands
13
+
14
+ ```bash
15
+ proteum trace requests
16
+ proteum trace latest
17
+ proteum trace show <requestId>
18
+ proteum trace arm --capture deep
19
+ proteum trace export <requestId>
20
+ proteum trace latest --url http://127.0.0.1:3010
21
+ ```
22
+
23
+ Before reproducing a bug or starting a new test pass:
24
+
25
+ - read the default port from `PORT` or `./.proteum/manifest.json`
26
+ - check whether a dev server is already running on that port
27
+ - if it is, inspect `proteum trace requests`, `proteum trace latest`, and `proteum trace show <requestId>` first so you can capture past errors and their context
28
+
29
+ Typical debugging flow:
30
+
31
+ ```bash
32
+ proteum trace arm --capture deep --port 3103
33
+ # reproduce the failing request once
34
+ proteum trace requests --port 3103
35
+ proteum trace show <requestId> --port 3103
36
+ ```
37
+
38
+ Use `--url http://host:port` when the dev server is reachable on a non-standard host and `--port` is not enough.
39
+
40
+ ## What Gets Recorded
41
+
42
+ Depending on capture mode, traces can include:
43
+
44
+ - request start, finish, user identity, status code, and duration
45
+ - auth decode input and outcome, route auth decisions, matched auth rules, rule inputs/results, and session create or clear events
46
+ - direct controller route matches
47
+ - route resolution start, match, and deep-mode skip reasons
48
+ - controller start and result shape
49
+ - created router/context keys
50
+ - setup output keys and page data summaries
51
+ - SSR payload shape and serialized byte size
52
+ - render start/end timings and document output sizes
53
+ - normalized request errors
54
+
55
+ ## Capture Modes
56
+
57
+ - `summary`: smallest capture, focused on request lifecycle and high-signal events
58
+ - `resolve`: adds route matching, controller, setup, and context milestones
59
+ - `deep`: adds route skip reasons and deeper summarized payload inspection for one request
60
+
61
+ Use `deep` selectively. It is for one-off investigation, not continuous capture.
62
+
63
+ ## Profiler
64
+
65
+ During `proteum dev`, the bottom profiler renders the same live request traces.
66
+
67
+ - `Timeline` shows the full request event stream
68
+ - `Auth` filters the selected session down to auth-specific events so matched rules, tracking, and allow/deny outcomes can be inspected without scanning unrelated events
69
+ - expanding an auth event shows the summarized detail payload exactly as stored in the trace
70
+
71
+ ## Configuration
72
+
73
+ Set trace behavior with env vars:
74
+
75
+ ```bash
76
+ export TRACE_ENABLE=true
77
+ export TRACE_REQUESTS_LIMIT=200
78
+ export TRACE_EVENTS_LIMIT=800
79
+ export TRACE_CAPTURE=resolve
80
+ export TRACE_PERSIST_ON_ERROR=true
81
+ ```
82
+
83
+ Notes:
84
+
85
+ - `enable` and `persistOnError` still remain dev-only in the current runtime
86
+ - `capture` defaults to `resolve`
87
+ - `requestsLimit` defaults to `200`
88
+ - `eventsLimit` defaults to `800`
89
+ - `proteum dev` removes auto-persisted crash traces from `var/traces/` when the dev session stops
90
+ - explicit `proteum trace export` files under `var/traces/exports/` are left in place
91
+
92
+ ## Memory Model
93
+
94
+ Traces are kept in memory per Node process.
95
+
96
+ - requests are stored in a ring buffer capped by `requestsLimit`
97
+ - the oldest request traces are evicted first
98
+ - each request is capped by `eventsLimit`
99
+ - once the event cap is reached, extra events are dropped and counted in `droppedEvents`
100
+ - payloads are summarized rather than stored as raw objects
101
+
102
+ Current summarization rules:
103
+
104
+ - arrays keep at most 10 sampled items
105
+ - objects keep at most 20 keys per level
106
+ - deep capture stops at depth 3
107
+ - long strings are truncated
108
+
109
+ ## Redaction
110
+
111
+ Sensitive values are redacted before they enter the trace store.
112
+
113
+ This includes keys such as:
114
+
115
+ - `cookie`
116
+ - `authorization`
117
+ - `password`
118
+ - token-like fields such as `accessToken`, `refreshToken`, `apiKey`, `jwt`, and similar names
119
+ - `rawBody`
120
+
121
+ The goal is to make traces useful for debugging without turning the dev server into a secret dump.
122
+
123
+ ## Dev HTTP Endpoints
124
+
125
+ These endpoints back the CLI:
126
+
127
+ - `GET /__proteum/trace/requests`
128
+ - `GET /__proteum/trace/latest`
129
+ - `GET /__proteum/trace/requests/:id`
130
+ - `POST /__proteum/trace/arm`
131
+
132
+ The CLI should be the primary interface. Use the HTTP endpoints when you need direct machine access from another local dev tool.
package/eslint.js CHANGED
@@ -11,8 +11,8 @@ const defaultIgnores = [
11
11
  '**/var/**',
12
12
  ];
13
13
 
14
- const createDoubleAssertionSelector = (typeKeyword) =>
15
- `TSAsExpression[expression.type='TSAsExpression'][expression.typeAnnotation.type='${typeKeyword}']`;
14
+ const createZodTypeFactorySelector = (factoryName) =>
15
+ `CallExpression[callee.type='MemberExpression'][callee.computed=false][callee.object.type='Identifier'][callee.object.name=/^(schema|z|zod)$/][callee.property.name='${factoryName}']`;
16
16
 
17
17
  const createProteumEslintConfig = ({ ignores = [] } = {}) => [
18
18
  {
@@ -42,15 +42,20 @@ const createProteumEslintConfig = ({ ignores = [] } = {}) => [
42
42
  'jsx-a11y': jsxA11yPlugin,
43
43
  },
44
44
  rules: {
45
+ '@typescript-eslint/no-explicit-any': 'error',
45
46
  'no-restricted-syntax': [
46
47
  'error',
47
48
  {
48
- selector: createDoubleAssertionSelector('TSUnknownKeyword'),
49
- message: 'Do not use double assertions through `unknown`.',
49
+ selector: 'TSUnknownKeyword',
50
+ message: 'Do not use `unknown`; define an explicit type instead.',
50
51
  },
51
52
  {
52
- selector: createDoubleAssertionSelector('TSAnyKeyword'),
53
- message: 'Do not use double assertions through `any`.',
53
+ selector: createZodTypeFactorySelector('any'),
54
+ message: 'Do not use Zod `any()` schemas; define an explicit schema instead.',
55
+ },
56
+ {
57
+ selector: createZodTypeFactorySelector('unknown'),
58
+ message: 'Do not use Zod `unknown()` schemas; define an explicit schema instead.',
54
59
  },
55
60
  ],
56
61
  },