proteum 2.1.3-1 → 2.1.7

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 +22 -14
  2. package/README.md +109 -17
  3. package/agents/project/AGENTS.md +188 -25
  4. package/agents/project/CODING_STYLE.md +1 -0
  5. package/agents/project/client/AGENTS.md +13 -8
  6. package/agents/project/client/pages/AGENTS.md +17 -9
  7. package/agents/project/diagnostics.md +52 -0
  8. package/agents/project/optimizations.md +48 -0
  9. package/agents/project/server/routes/AGENTS.md +9 -6
  10. package/agents/project/server/services/AGENTS.md +10 -6
  11. package/agents/project/tests/AGENTS.md +11 -5
  12. package/cli/app/config.ts +13 -14
  13. package/cli/app/index.ts +58 -0
  14. package/cli/commands/connect.ts +45 -0
  15. package/cli/commands/dev.ts +37 -13
  16. package/cli/commands/diagnose.ts +286 -0
  17. package/cli/commands/doctor.ts +18 -5
  18. package/cli/commands/explain.ts +25 -0
  19. package/cli/commands/perf.ts +243 -0
  20. package/cli/commands/trace.ts +9 -1
  21. package/cli/commands/verify.ts +281 -0
  22. package/cli/compiler/artifacts/connectedProjects.ts +453 -0
  23. package/cli/compiler/artifacts/controllers.ts +198 -49
  24. package/cli/compiler/artifacts/discovery.ts +0 -34
  25. package/cli/compiler/artifacts/manifest.ts +95 -6
  26. package/cli/compiler/artifacts/routing.ts +2 -2
  27. package/cli/compiler/artifacts/services.ts +277 -130
  28. package/cli/compiler/client/index.ts +3 -0
  29. package/cli/compiler/common/files/style.ts +52 -0
  30. package/cli/compiler/common/generatedRouteModules.ts +34 -5
  31. package/cli/compiler/common/scripts.ts +11 -5
  32. package/cli/compiler/index.ts +2 -1
  33. package/cli/compiler/server/index.ts +3 -0
  34. package/cli/presentation/commands.ts +110 -7
  35. package/cli/presentation/devSession.ts +32 -7
  36. package/cli/runtime/commands.ts +165 -6
  37. package/cli/scaffold/index.ts +18 -27
  38. package/cli/scaffold/templates.ts +48 -28
  39. package/cli/utils/agents.ts +106 -13
  40. package/cli/utils/keyboard.ts +8 -0
  41. package/client/dev/profiler/ApexChart.tsx +66 -0
  42. package/client/dev/profiler/index.tsx +2508 -302
  43. package/client/dev/profiler/runtime.noop.ts +12 -0
  44. package/client/dev/profiler/runtime.ts +195 -4
  45. package/client/services/router/request/api.ts +6 -1
  46. package/common/applicationConfig.ts +173 -0
  47. package/common/applicationConfigLoader.ts +102 -0
  48. package/common/connectedProjects.ts +113 -0
  49. package/common/dev/connect.ts +267 -0
  50. package/common/dev/console.ts +31 -0
  51. package/common/dev/contractsDoctor.ts +128 -0
  52. package/common/dev/diagnostics.ts +59 -15
  53. package/common/dev/inspection.ts +491 -0
  54. package/common/dev/performance.ts +809 -0
  55. package/common/dev/profiler.ts +3 -0
  56. package/common/dev/proteumManifest.ts +31 -6
  57. package/common/dev/requestTrace.ts +52 -1
  58. package/common/env/proteumEnv.ts +176 -50
  59. package/common/router/index.ts +1 -0
  60. package/common/router/request/api.ts +2 -0
  61. package/config.ts +5 -0
  62. package/docs/dev-commands.md +5 -1
  63. package/docs/dev-sessions.md +90 -0
  64. package/docs/diagnostics.md +74 -11
  65. package/docs/request-tracing.md +50 -3
  66. package/package.json +1 -1
  67. package/server/app/container/config.ts +16 -87
  68. package/server/app/container/console/index.ts +42 -8
  69. package/server/app/container/index.ts +10 -2
  70. package/server/app/container/trace/index.ts +105 -0
  71. package/server/app/devDiagnostics.ts +138 -0
  72. package/server/app/index.ts +18 -8
  73. package/server/app/service/container.ts +0 -12
  74. package/server/app/service/index.ts +0 -2
  75. package/server/services/prisma/index.ts +121 -4
  76. package/server/services/router/http/index.ts +305 -11
  77. package/server/services/router/index.ts +116 -57
  78. package/server/services/router/request/api.ts +160 -19
  79. package/server/services/router/request/index.ts +8 -0
  80. package/server/services/router/response/index.ts +23 -1
  81. package/server/services/router/response/page/document.tsx +31 -14
  82. package/server/services/router/response/page/index.tsx +10 -0
  83. package/agents/framework/AGENTS.md +0 -177
  84. package/server/services/auth/router/service.json +0 -6
  85. package/server/services/auth/service.json +0 -6
  86. package/server/services/cron/service.json +0 -6
  87. package/server/services/disks/drivers/local/service.json +0 -6
  88. package/server/services/disks/drivers/s3/service.json +0 -6
  89. package/server/services/disks/service.json +0 -6
  90. package/server/services/fetch/service.json +0 -7
  91. package/server/services/prisma/service.json +0 -6
  92. package/server/services/router/service.json +0 -6
  93. package/server/services/schema/router/service.json +0 -6
  94. package/server/services/schema/service.json +0 -6
  95. package/server/services/security/encrypt/aes/service.json +0 -6
@@ -9,11 +9,14 @@ export type TProfilerUiState = 'expanded' | 'minimized' | 'pinned-handle';
9
9
  export type TProfilerPanel =
10
10
  | 'summary'
11
11
  | 'timeline'
12
+ | 'perf'
12
13
  | 'auth'
13
14
  | 'routing'
14
15
  | 'controller'
15
16
  | 'ssr'
16
17
  | 'api'
18
+ | 'sql'
19
+ | 'diagnose'
17
20
  | 'explain'
18
21
  | 'doctor'
19
22
  | 'commands'
@@ -1,4 +1,9 @@
1
- export type TProteumManifestScope = 'app' | 'framework';
1
+ import type {
2
+ TConnectedProjectSourceKind,
3
+ TConnectedProjectTypingMode,
4
+ } from '../connectedProjects';
5
+
6
+ export type TProteumManifestScope = 'app' | 'framework' | 'connected';
2
7
  export type TProteumManifestSourceLocation = { line: number; column: number };
3
8
  export type TProteumManifestRouteTargetResolution = 'literal' | 'static-expression' | 'dynamic-expression';
4
9
  export type TProteumManifestDiagnosticLevel = 'warning' | 'error';
@@ -14,14 +19,12 @@ export type TProteumManifestDiagnostic = {
14
19
 
15
20
  export type TProteumManifestService = {
16
21
  kind: 'service' | 'ref';
17
- id?: string;
18
22
  registeredName: string;
19
- metaName?: string;
23
+ className?: string;
20
24
  parent: string;
21
25
  priority: number;
22
26
  importPath?: string;
23
- sourceDir?: string;
24
- metasFilepath?: string;
27
+ sourceFilepath?: string;
25
28
  refTo?: string;
26
29
  scope: TProteumManifestScope;
27
30
  };
@@ -39,6 +42,21 @@ export type TProteumManifestController = {
39
42
  httpPath: string;
40
43
  clientAccessor: string;
41
44
  scope: TProteumManifestScope;
45
+ connectedProjectNamespace?: string;
46
+ connectedProjectIdentifier?: string;
47
+ };
48
+
49
+ export type TProteumManifestConnectedProject = {
50
+ namespace: string;
51
+ packageName?: string;
52
+ identityIdentifier?: string;
53
+ identityName?: string;
54
+ sourceKind?: TConnectedProjectSourceKind;
55
+ sourceValue?: string;
56
+ cachedContractFilepath?: string;
57
+ typingMode?: TConnectedProjectTypingMode;
58
+ urlInternal?: string;
59
+ controllerCount: number;
42
60
  };
43
61
 
44
62
  export type TProteumManifestCommand = {
@@ -83,11 +101,12 @@ export type TProteumManifestLayout = {
83
101
  };
84
102
 
85
103
  export type TProteumManifest = {
86
- version: 2;
104
+ version: 9;
87
105
  app: {
88
106
  root: string;
89
107
  coreRoot: string;
90
108
  identityFilepath: string;
109
+ setupFilepath: string;
91
110
  identity: {
92
111
  name: string;
93
112
  identifier: string;
@@ -100,6 +119,10 @@ export type TProteumManifest = {
100
119
  webDescription?: string;
101
120
  version?: string;
102
121
  };
122
+ setup: {
123
+ transpile?: string[];
124
+ connect?: Record<string, { source?: string; urlInternal?: string }>;
125
+ };
103
126
  };
104
127
  conventions: {
105
128
  routeSetupOptionKeys: string[];
@@ -118,8 +141,10 @@ export type TProteumManifest = {
118
141
  profile: string;
119
142
  routerPort: number;
120
143
  routerCurrentDomain: string;
144
+ routerInternalUrl: string;
121
145
  };
122
146
  };
147
+ connectedProjects: TProteumManifestConnectedProject[];
123
148
  services: {
124
149
  app: TProteumManifestService[];
125
150
  routerPlugins: TProteumManifestService[];
@@ -6,6 +6,10 @@ type TTracePrimitive = string | number | boolean;
6
6
  export const traceCallOrigins = ['ssr-fetcher', 'api-batch-fetcher', 'client-async'] as const;
7
7
 
8
8
  export type TTraceCallOrigin = (typeof traceCallOrigins)[number];
9
+ export const traceSqlQueryKinds = ['orm', 'raw'] as const;
10
+
11
+ export type TTraceSqlQueryKind = (typeof traceSqlQueryKinds)[number];
12
+ export type TTraceSqlQueryCallerOrigin = TTraceCallOrigin | 'request';
9
13
 
10
14
  export const traceEventTypes = [
11
15
  'request.start',
@@ -75,6 +79,8 @@ export type TTraceCall = {
75
79
  method: string;
76
80
  path: string;
77
81
  fetcherId?: string;
82
+ connectedProjectNamespace?: string;
83
+ connectedControllerAccessor?: string;
78
84
  startedAt: string;
79
85
  finishedAt?: string;
80
86
  durationMs?: number;
@@ -88,6 +94,45 @@ export type TTraceCall = {
88
94
  resultJson?: unknown;
89
95
  };
90
96
 
97
+ export type TTraceSqlQuery = {
98
+ id: string;
99
+ callerCallId?: string;
100
+ callerFetcherId?: string;
101
+ callerLabel?: string;
102
+ callerMethod: string;
103
+ callerOrigin: TTraceSqlQueryCallerOrigin;
104
+ callerPath: string;
105
+ durationMs: number;
106
+ finishedAt: string;
107
+ kind: TTraceSqlQueryKind;
108
+ model?: string;
109
+ operation: string;
110
+ paramsJson?: unknown;
111
+ paramsText?: string;
112
+ query: string;
113
+ startedAt: string;
114
+ target?: string;
115
+ };
116
+
117
+ export type TTraceMemorySnapshot = {
118
+ arrayBuffers: number;
119
+ external: number;
120
+ heapTotal: number;
121
+ heapUsed: number;
122
+ rss: number;
123
+ };
124
+
125
+ export type TRequestTracePerformance = {
126
+ cpu: {
127
+ systemMicros: number;
128
+ userMicros: number;
129
+ };
130
+ memory: {
131
+ after: TTraceMemorySnapshot;
132
+ before: TTraceMemorySnapshot;
133
+ };
134
+ };
135
+
91
136
  export type TRequestTrace = {
92
137
  id: string;
93
138
  method: string;
@@ -107,11 +152,17 @@ export type TRequestTrace = {
107
152
  errorMessage?: string;
108
153
  requestDataJson?: unknown;
109
154
  resultJson?: unknown;
155
+ performance?: TRequestTracePerformance;
110
156
  calls: TTraceCall[];
157
+ sqlQueries: TTraceSqlQuery[];
111
158
  events: TTraceEvent[];
112
159
  };
113
160
 
114
- export type TRequestTraceListItem = Omit<TRequestTrace, 'events' | 'calls'> & { eventCount: number; callCount: number };
161
+ export type TRequestTraceListItem = Omit<TRequestTrace, 'events' | 'calls' | 'sqlQueries'> & {
162
+ eventCount: number;
163
+ callCount: number;
164
+ sqlQueryCount: number;
165
+ };
115
166
 
116
167
  export type TRequestTraceListResponse = { requests: TRequestTraceListItem[] };
117
168
  export type TRequestTraceResponse = { request: TRequestTrace };
@@ -2,11 +2,16 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import dotenv from 'dotenv';
4
4
 
5
+ import {
6
+ normalizeConnectedProjectsConfig,
7
+ type TConnectedProjectsConfig,
8
+ } from '../connectedProjects';
9
+
5
10
  export type TProteumEnvName = 'local' | 'server';
6
11
  export type TProteumEnvProfile = 'dev' | 'testing' | 'prod';
7
12
  export type TProteumTraceCapture = 'summary' | 'resolve' | 'deep';
8
13
  export type TProteumRequiredEnvVariable = {
9
- key: TProteumRequiredEnvVariableKey;
14
+ key: string;
10
15
  possibleValues: string[];
11
16
  provided: boolean;
12
17
  };
@@ -15,13 +20,20 @@ export type TProteumEnvInspection = {
15
20
  requiredVariables: TProteumRequiredEnvVariable[];
16
21
  };
17
22
 
23
+ export type TProteumConnectedProjectEnvConfig = {
24
+ namespace: string;
25
+ urlInternal: string;
26
+ };
27
+
18
28
  export type TProteumEnvConfig = {
19
29
  name: TProteumEnvName;
20
30
  profile: TProteumEnvProfile;
21
31
  router: {
22
32
  port: number;
23
33
  currentDomain: string;
34
+ internalUrl: string;
24
35
  };
36
+ connectedProjects: Record<string, TProteumConnectedProjectEnvConfig>;
25
37
  trace: {
26
38
  enable: boolean;
27
39
  requestsLimit: number;
@@ -34,32 +46,40 @@ export type TProteumEnvConfig = {
34
46
  export type TProteumLoadedEnvConfig = TProteumEnvConfig & { version: string };
35
47
 
36
48
  const dotenvFileNames = ['.env'];
37
- const requiredProteumEnvVariableKeys = ['ENV_NAME', 'ENV_PROFILE', 'PORT', 'URL'] as const;
49
+ const baseRequiredProteumEnvVariableDefinitions = [
50
+ { key: 'ENV_NAME', possibleValues: ['local', 'server'] },
51
+ { key: 'ENV_PROFILE', possibleValues: ['dev', 'testing', 'prod'] },
52
+ { key: 'PORT', possibleValues: ['integer between 1 and 65535'] },
53
+ { key: 'URL', possibleValues: ['absolute URL'] },
54
+ { key: 'URL_INTERNAL', possibleValues: ['absolute URL'] },
55
+ ] as const;
38
56
  const optionalProteumEnvVariablePrefixes = ['TRACE_'] as const;
39
57
 
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'],
58
+ type TEnvContext = {
59
+ appDir: string;
60
+ connectedProjects: TConnectedProjectsConfig;
47
61
  };
48
62
 
49
63
  const envDefinitionHint = (appDir: string) => `Define it in process.env or ${appDir}/.env.`;
50
64
  const isProvidedEnvValue = (value: string | undefined) => typeof value === 'string' && value.trim() !== '';
51
65
 
66
+ const buildRequiredEnvVariableDefinitions = (_connectedProjects: TConnectedProjectsConfig) => [...baseRequiredProteumEnvVariableDefinitions];
67
+
68
+ const buildOptionalEnvKeys = (_connectedProjects: TConnectedProjectsConfig) => [] as string[];
69
+
52
70
  const formatRequiredEnvVariableStatus = (variable: TProteumRequiredEnvVariable) =>
53
71
  `- ${variable.key} possibleValues=${variable.possibleValues.join(' | ')} provided=${variable.provided ? 'yes' : 'no'}`;
54
72
 
55
73
  const createProteumEnvError = ({
56
74
  appDir,
75
+ connectedProjects,
57
76
  message,
58
77
  }: {
59
78
  appDir: string;
79
+ connectedProjects: TConnectedProjectsConfig;
60
80
  message: string;
61
81
  }) => {
62
- const inspection = inspectProteumEnv(appDir);
82
+ const inspection = inspectProteumEnv(appDir, connectedProjects);
63
83
 
64
84
  return new Error(
65
85
  [message, envDefinitionHint(appDir), '', 'Required env variables:', ...inspection.requiredVariables.map(formatRequiredEnvVariableStatus)].join(
@@ -71,11 +91,11 @@ const createProteumEnvError = ({
71
91
  const parseBooleanEnvValue = ({
72
92
  key,
73
93
  value,
74
- appDir,
94
+ context,
75
95
  }: {
76
96
  key: string;
77
97
  value: string | undefined;
78
- appDir: string;
98
+ context: TEnvContext;
79
99
  }) => {
80
100
  if (value === undefined || value === '') return undefined;
81
101
 
@@ -84,7 +104,7 @@ const parseBooleanEnvValue = ({
84
104
  if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
85
105
 
86
106
  throw createProteumEnvError({
87
- appDir,
107
+ ...context,
88
108
  message: `Invalid boolean value for ${key}: "${value}". Expected one of: 1, 0, true, false, yes, no, on, off.`,
89
109
  });
90
110
  };
@@ -92,18 +112,18 @@ const parseBooleanEnvValue = ({
92
112
  const parseIntegerEnvValue = ({
93
113
  key,
94
114
  value,
95
- appDir,
115
+ context,
96
116
  min = 1,
97
117
  }: {
98
118
  key: string;
99
119
  value: string;
100
- appDir: string;
120
+ context: TEnvContext;
101
121
  min?: number;
102
122
  }) => {
103
123
  const parsed = Number.parseInt(value, 10);
104
124
  if (Number.isNaN(parsed) || parsed < min) {
105
125
  throw createProteumEnvError({
106
- appDir,
126
+ ...context,
107
127
  message: `Invalid integer value for ${key}: "${value}". Expected an integer greater than or equal to ${min}.`,
108
128
  });
109
129
  }
@@ -111,44 +131,78 @@ const parseIntegerEnvValue = ({
111
131
  return parsed;
112
132
  };
113
133
 
114
- const getRequiredEnvValue = ({ key, appDir }: { key: TProteumRequiredEnvVariableKey; appDir: string }) => {
134
+ const getRequiredEnvValue = ({
135
+ key,
136
+ context,
137
+ }: {
138
+ key: string;
139
+ context: TEnvContext;
140
+ }) => {
115
141
  const value = process.env[key]?.trim();
116
142
  if (value) return value;
117
143
 
118
144
  throw createProteumEnvError({
119
- appDir,
145
+ ...context,
120
146
  message: `Missing required Proteum env variable "${key}".`,
121
147
  });
122
148
  };
123
149
 
124
- const parseEnvName = (value: string, appDir: string): TProteumEnvName => {
150
+ const createConnectedProjectConfigError = ({
151
+ appDir,
152
+ message,
153
+ }: {
154
+ appDir: string;
155
+ message: string;
156
+ }) => new Error(`${message} Define it explicitly in ${path.join(appDir, 'proteum.config.ts')}.`);
157
+
158
+ const getRequiredConnectedConfigValue = ({
159
+ appDir,
160
+ namespace,
161
+ field,
162
+ value,
163
+ }: {
164
+ appDir: string;
165
+ namespace: string;
166
+ field: 'source' | 'urlInternal';
167
+ value: string | undefined;
168
+ }) => {
169
+ const normalized = value?.trim();
170
+ if (normalized) return normalized;
171
+
172
+ throw createConnectedProjectConfigError({
173
+ appDir,
174
+ message: `Connected project "${namespace}" requires connect.${namespace}.${field}.`,
175
+ });
176
+ };
177
+
178
+ const parseEnvName = (value: string, context: TEnvContext): TProteumEnvName => {
125
179
  if (value === 'local' || value === 'server') return value;
126
180
  throw createProteumEnvError({
127
- appDir,
181
+ ...context,
128
182
  message: `Invalid ENV_NAME "${value}". Expected "local" or "server".`,
129
183
  });
130
184
  };
131
185
 
132
- const parseEnvProfile = (value: string, appDir: string): TProteumEnvProfile => {
186
+ const parseEnvProfile = (value: string, context: TEnvContext): TProteumEnvProfile => {
133
187
  if (value === 'dev' || value === 'testing' || value === 'prod') return value;
134
188
  throw createProteumEnvError({
135
- appDir,
189
+ ...context,
136
190
  message: `Invalid ENV_PROFILE "${value}". Expected "dev", "testing", or "prod".`,
137
191
  });
138
192
  };
139
193
 
140
194
  const parseTraceCapture = ({
141
195
  value,
142
- appDir,
196
+ context,
143
197
  }: {
144
198
  value: string | undefined;
145
- appDir: string;
199
+ context: TEnvContext;
146
200
  }): TProteumTraceCapture | undefined => {
147
201
  if (value === undefined || value === '') return undefined;
148
202
  if (value === 'summary' || value === 'resolve' || value === 'deep') return value;
149
203
 
150
204
  throw createProteumEnvError({
151
- appDir,
205
+ ...context,
152
206
  message: `Invalid TRACE_CAPTURE "${value}". Expected "summary", "resolve", or "deep".`,
153
207
  });
154
208
  };
@@ -156,11 +210,11 @@ const parseTraceCapture = ({
156
210
  const parseAbsoluteUrl = ({
157
211
  key,
158
212
  value,
159
- appDir,
213
+ context,
160
214
  }: {
161
215
  key: string;
162
216
  value: string;
163
- appDir: string;
217
+ context: TEnvContext;
164
218
  }) => {
165
219
  let url: URL;
166
220
 
@@ -168,14 +222,14 @@ const parseAbsoluteUrl = ({
168
222
  url = new URL(value);
169
223
  } catch {
170
224
  throw createProteumEnvError({
171
- appDir,
225
+ ...context,
172
226
  message: `Invalid absolute URL for ${key}: "${value}". Expected an absolute http:// or https:// URL.`,
173
227
  });
174
228
  }
175
229
 
176
230
  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
177
231
  throw createProteumEnvError({
178
- appDir,
232
+ ...context,
179
233
  message: `Invalid absolute URL for ${key}: "${value}". Expected an absolute http:// or https:// URL.`,
180
234
  });
181
235
  }
@@ -183,6 +237,29 @@ const parseAbsoluteUrl = ({
183
237
  return value;
184
238
  };
185
239
 
240
+ const parseConnectedProjectAbsoluteUrl = ({
241
+ appDir,
242
+ namespace,
243
+ field,
244
+ value,
245
+ }: {
246
+ appDir: string;
247
+ namespace: string;
248
+ field: 'urlInternal';
249
+ value: string;
250
+ }) => {
251
+ try {
252
+ const url = new URL(value);
253
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') throw new Error();
254
+ return value;
255
+ } catch {
256
+ throw createConnectedProjectConfigError({
257
+ appDir,
258
+ message: `Invalid connect.${namespace}.${field} "${value}". Expected an absolute http:// or https:// URL.`,
259
+ });
260
+ }
261
+ };
262
+
186
263
  export const loadOptionalProteumDotenv = (appDir: string) => {
187
264
  for (const filename of dotenvFileNames) {
188
265
  const filepath = path.join(appDir, filename);
@@ -191,74 +268,123 @@ export const loadOptionalProteumDotenv = (appDir: string) => {
191
268
  }
192
269
  };
193
270
 
194
- export const getLoadedProteumEnvVariableKeys = () =>
195
- Object.keys(process.env)
271
+ export const getLoadedProteumEnvVariableKeys = (connectedProjects: TConnectedProjectsConfig = {}) => {
272
+ const requiredKeys = new Set(buildRequiredEnvVariableDefinitions(connectedProjects).map((definition) => definition.key));
273
+ const optionalKeys = new Set(buildOptionalEnvKeys(connectedProjects));
274
+
275
+ return Object.keys(process.env)
196
276
  .filter(
197
277
  (key) =>
198
- requiredProteumEnvVariableKeys.includes(key as TProteumRequiredEnvVariableKey) ||
278
+ requiredKeys.has(key) ||
279
+ optionalKeys.has(key) ||
199
280
  optionalProteumEnvVariablePrefixes.some((prefix) => key.startsWith(prefix)),
200
281
  )
201
- .sort((a, b) => a.localeCompare(b));
282
+ .sort((left, right) => left.localeCompare(right));
283
+ };
202
284
 
203
- export const inspectProteumEnv = (appDir: string): TProteumEnvInspection => {
285
+ export const inspectProteumEnv = (
286
+ appDir: string,
287
+ rawConnectedProjects: TConnectedProjectsConfig = {},
288
+ ): TProteumEnvInspection => {
204
289
  loadOptionalProteumDotenv(appDir);
205
290
 
291
+ const connectedProjects = normalizeConnectedProjectsConfig(rawConnectedProjects);
292
+ const requiredVariables = buildRequiredEnvVariableDefinitions(connectedProjects);
293
+
206
294
  return {
207
- loadedVariableKeys: getLoadedProteumEnvVariableKeys(),
208
- requiredVariables: requiredProteumEnvVariableKeys.map((key) => ({
209
- key,
210
- possibleValues: [...requiredProteumEnvVariablePossibleValues[key]],
211
- provided: isProvidedEnvValue(process.env[key]),
295
+ loadedVariableKeys: getLoadedProteumEnvVariableKeys(connectedProjects),
296
+ requiredVariables: requiredVariables.map((definition) => ({
297
+ key: definition.key,
298
+ possibleValues: [...definition.possibleValues],
299
+ provided: isProvidedEnvValue(process.env[definition.key]),
212
300
  })),
213
301
  };
214
302
  };
215
303
 
216
304
  export const parseProteumEnvConfig = ({
217
305
  appDir,
306
+ connectedProjects: rawConnectedProjects = {},
218
307
  routerPortOverride,
219
308
  }: {
220
309
  appDir: string;
310
+ connectedProjects?: TConnectedProjectsConfig;
221
311
  routerPortOverride?: number;
222
312
  }): TProteumEnvConfig => {
223
313
  loadOptionalProteumDotenv(appDir);
224
314
 
225
- const name = parseEnvName(getRequiredEnvValue({ key: 'ENV_NAME', appDir }), appDir);
226
- const profile = parseEnvProfile(getRequiredEnvValue({ key: 'ENV_PROFILE', appDir }), appDir);
315
+ const connectedProjects = normalizeConnectedProjectsConfig(rawConnectedProjects);
316
+ const context = { appDir, connectedProjects } satisfies TEnvContext;
317
+
318
+ const name = parseEnvName(getRequiredEnvValue({ key: 'ENV_NAME', context }), context);
319
+ const profile = parseEnvProfile(getRequiredEnvValue({ key: 'ENV_PROFILE', context }), context);
227
320
  const configuredRouterPort = parseIntegerEnvValue({
228
321
  key: 'PORT',
229
- value: getRequiredEnvValue({ key: 'PORT', appDir }),
230
- appDir,
322
+ value: getRequiredEnvValue({ key: 'PORT', context }),
323
+ context,
231
324
  });
232
325
  const currentDomain = parseAbsoluteUrl({
233
326
  key: 'URL',
234
- value: getRequiredEnvValue({ key: 'URL', appDir }),
235
- appDir,
327
+ value: getRequiredEnvValue({ key: 'URL', context }),
328
+ context,
329
+ });
330
+ const internalUrl = parseAbsoluteUrl({
331
+ key: 'URL_INTERNAL',
332
+ value: getRequiredEnvValue({ key: 'URL_INTERNAL', context }),
333
+ context,
236
334
  });
237
335
 
238
336
  const traceEnable = parseBooleanEnvValue({
239
337
  key: 'TRACE_ENABLE',
240
338
  value: process.env.TRACE_ENABLE,
241
- appDir,
339
+ context,
242
340
  });
243
341
  const tracePersistOnError = parseBooleanEnvValue({
244
342
  key: 'TRACE_PERSIST_ON_ERROR',
245
343
  value: process.env.TRACE_PERSIST_ON_ERROR,
246
- appDir,
344
+ context,
247
345
  });
248
346
  const traceRequestsLimit = process.env.TRACE_REQUESTS_LIMIT?.trim();
249
347
  const traceEventsLimit = process.env.TRACE_EVENTS_LIMIT?.trim();
250
348
  const traceCapture = parseTraceCapture({
251
349
  value: process.env.TRACE_CAPTURE?.trim(),
252
- appDir,
350
+ context,
253
351
  });
254
352
 
353
+ const resolvedConnectedProjects = Object.fromEntries(
354
+ Object.entries(connectedProjects)
355
+ .sort(([left], [right]) => left.localeCompare(right))
356
+ .map(([namespace, config]) => {
357
+ const urlInternal = parseConnectedProjectAbsoluteUrl({
358
+ appDir,
359
+ namespace,
360
+ field: 'urlInternal',
361
+ value: getRequiredConnectedConfigValue({
362
+ appDir,
363
+ namespace,
364
+ field: 'urlInternal',
365
+ value: config.urlInternal,
366
+ }),
367
+ });
368
+
369
+ return [
370
+ namespace,
371
+ {
372
+ namespace,
373
+ urlInternal,
374
+ } satisfies TProteumConnectedProjectEnvConfig,
375
+ ];
376
+ }),
377
+ );
378
+
255
379
  return {
256
380
  name,
257
381
  profile,
258
382
  router: {
259
383
  port: routerPortOverride === undefined ? configuredRouterPort : routerPortOverride,
260
384
  currentDomain,
385
+ internalUrl,
261
386
  },
387
+ connectedProjects: resolvedConnectedProjects,
262
388
  trace: {
263
389
  enable: traceEnable ?? profile === 'dev',
264
390
  requestsLimit:
@@ -267,7 +393,7 @@ export const parseProteumEnvConfig = ({
267
393
  : parseIntegerEnvValue({
268
394
  key: 'TRACE_REQUESTS_LIMIT',
269
395
  value: traceRequestsLimit,
270
- appDir,
396
+ context,
271
397
  }),
272
398
  eventsLimit:
273
399
  traceEventsLimit === undefined || traceEventsLimit === ''
@@ -275,7 +401,7 @@ export const parseProteumEnvConfig = ({
275
401
  : parseIntegerEnvValue({
276
402
  key: 'TRACE_EVENTS_LIMIT',
277
403
  value: traceEventsLimit,
278
- appDir,
404
+ context,
279
405
  }),
280
406
  capture: traceCapture ?? 'resolve',
281
407
  persistOnError: tracePersistOnError ?? profile === 'dev',
@@ -75,6 +75,7 @@ export type TRouteOptions = {
75
75
  // Injected by the page plugin
76
76
  id?: string;
77
77
  filepath?: string;
78
+ sourceLocation?: { line: number; column: number };
78
79
  setup?: TPageSetup;
79
80
 
80
81
  // Indexing
@@ -2,6 +2,7 @@
2
2
  - DEPENDANCES
3
3
  ----------------------------------*/
4
4
 
5
+ import type { TConnectedFetcherTarget } from '@common/connectedProjects';
5
6
  import type { HttpMethod } from '@server/services/router';
6
7
 
7
8
  /*----------------------------------
@@ -37,6 +38,7 @@ export type TApiFetchOptions = {
37
38
  onProgress?: (percent: number) => void;
38
39
  // Default: json
39
40
  encoding?: 'json' | 'multipart';
41
+ connected?: TConnectedFetcherTarget;
40
42
  };
41
43
 
42
44
  export type TPostData = TPostDataWithFile;
package/config.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { Application } from './common/applicationConfig';
2
+ export type {
3
+ TApplicationIdentityConfig as ApplicationIdentityConfig,
4
+ TApplicationSetupConfig as ApplicationSetupConfig,
5
+ } from './common/applicationConfig';
@@ -67,13 +67,17 @@ In `proteum dev`, the bottom profiler exposes a `Commands` tab.
67
67
  - it shows the backing class, method, scope, and source location
68
68
  - clicking `Run now` executes the command through the running dev server
69
69
  - the last result or error stays attached to that command row in the panel
70
+ - it now adds scope, execution-duration, and status charts over the same command definitions and latest execution snapshots
70
71
 
71
72
  The profiler also exposes the shared diagnostics surfaces for humans:
72
73
 
73
74
  - `Explain` renders the same manifest-backed data as `proteum explain`
74
75
  - `Doctor` renders the same manifest diagnostics as `proteum doctor`
76
+ - `Diagnose` renders the same owner, suspect, contract, trace-summary, and buffered-log view as `proteum diagnose`
77
+ - `Perf` renders the same hot-path, request-waterfall, compare, and memory views as `proteum perf`
78
+ - the other profiler tabs now add focused visual charts over the same trace, manifest, and dev-runtime contracts instead of only row lists
75
79
 
76
- For the shared diagnostics contract and the corresponding dev HTTP endpoints, see [diagnostics.md](diagnostics.md).
80
+ For the shared diagnostics contract, trace-derived perf contract, and the corresponding dev HTTP endpoints, see [diagnostics.md](diagnostics.md) and [request-tracing.md](request-tracing.md).
77
81
 
78
82
  ### HTTP Endpoints
79
83