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
package/common/dev/profiler.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
23
|
+
className?: string;
|
|
20
24
|
parent: string;
|
|
21
25
|
priority: number;
|
|
22
26
|
importPath?: string;
|
|
23
|
-
|
|
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:
|
|
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;
|
|
@@ -82,8 +88,49 @@ export type TTraceCall = {
|
|
|
82
88
|
errorMessage?: string;
|
|
83
89
|
requestDataKeys: string[];
|
|
84
90
|
requestData?: TTraceSummaryValue;
|
|
91
|
+
requestDataJson?: unknown;
|
|
85
92
|
resultKeys: string[];
|
|
86
93
|
result?: TTraceSummaryValue;
|
|
94
|
+
resultJson?: unknown;
|
|
95
|
+
};
|
|
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
|
+
};
|
|
87
134
|
};
|
|
88
135
|
|
|
89
136
|
export type TRequestTrace = {
|
|
@@ -103,11 +150,19 @@ export type TRequestTrace = {
|
|
|
103
150
|
droppedEvents: number;
|
|
104
151
|
persistedFilepath?: string;
|
|
105
152
|
errorMessage?: string;
|
|
153
|
+
requestDataJson?: unknown;
|
|
154
|
+
resultJson?: unknown;
|
|
155
|
+
performance?: TRequestTracePerformance;
|
|
106
156
|
calls: TTraceCall[];
|
|
157
|
+
sqlQueries: TTraceSqlQuery[];
|
|
107
158
|
events: TTraceEvent[];
|
|
108
159
|
};
|
|
109
160
|
|
|
110
|
-
export type TRequestTraceListItem = Omit<TRequestTrace, 'events' | 'calls'> & {
|
|
161
|
+
export type TRequestTraceListItem = Omit<TRequestTrace, 'events' | 'calls' | 'sqlQueries'> & {
|
|
162
|
+
eventCount: number;
|
|
163
|
+
callCount: number;
|
|
164
|
+
sqlQueryCount: number;
|
|
165
|
+
};
|
|
111
166
|
|
|
112
167
|
export type TRequestTraceListResponse = { requests: TRequestTraceListItem[] };
|
|
113
168
|
export type TRequestTraceResponse = { request: TRequestTrace };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type TDevSessionUserSummary = {
|
|
2
|
+
email: string;
|
|
3
|
+
name: string | null;
|
|
4
|
+
type: string;
|
|
5
|
+
roles: string[];
|
|
6
|
+
locale?: string | null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type TDevSessionPayload = {
|
|
10
|
+
token: string;
|
|
11
|
+
cookieName: 'authorization';
|
|
12
|
+
expiresInMs: number;
|
|
13
|
+
issuedAt: string;
|
|
14
|
+
expiresAt: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type TDevSessionStartResponse = {
|
|
18
|
+
user: TDevSessionUserSummary;
|
|
19
|
+
session: TDevSessionPayload;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type TDevSessionErrorResponse = {
|
|
23
|
+
error: string;
|
|
24
|
+
};
|
package/common/env/proteumEnv.ts
CHANGED
|
@@ -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:
|
|
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
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
94
|
+
context,
|
|
75
95
|
}: {
|
|
76
96
|
key: string;
|
|
77
97
|
value: string | undefined;
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
115
|
+
context,
|
|
96
116
|
min = 1,
|
|
97
117
|
}: {
|
|
98
118
|
key: string;
|
|
99
119
|
value: string;
|
|
100
|
-
|
|
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
|
-
|
|
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 = ({
|
|
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
|
-
|
|
145
|
+
...context,
|
|
120
146
|
message: `Missing required Proteum env variable "${key}".`,
|
|
121
147
|
});
|
|
122
148
|
};
|
|
123
149
|
|
|
124
|
-
const
|
|
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
|
-
|
|
181
|
+
...context,
|
|
128
182
|
message: `Invalid ENV_NAME "${value}". Expected "local" or "server".`,
|
|
129
183
|
});
|
|
130
184
|
};
|
|
131
185
|
|
|
132
|
-
const parseEnvProfile = (value: string,
|
|
186
|
+
const parseEnvProfile = (value: string, context: TEnvContext): TProteumEnvProfile => {
|
|
133
187
|
if (value === 'dev' || value === 'testing' || value === 'prod') return value;
|
|
134
188
|
throw createProteumEnvError({
|
|
135
|
-
|
|
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
|
-
|
|
196
|
+
context,
|
|
143
197
|
}: {
|
|
144
198
|
value: string | undefined;
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
+
context,
|
|
160
214
|
}: {
|
|
161
215
|
key: string;
|
|
162
216
|
value: string;
|
|
163
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
278
|
+
requiredKeys.has(key) ||
|
|
279
|
+
optionalKeys.has(key) ||
|
|
199
280
|
optionalProteumEnvVariablePrefixes.some((prefix) => key.startsWith(prefix)),
|
|
200
281
|
)
|
|
201
|
-
.sort((
|
|
282
|
+
.sort((left, right) => left.localeCompare(right));
|
|
283
|
+
};
|
|
202
284
|
|
|
203
|
-
export const inspectProteumEnv = (
|
|
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:
|
|
209
|
-
key,
|
|
210
|
-
possibleValues: [...
|
|
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
|
|
226
|
-
const
|
|
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',
|
|
230
|
-
|
|
322
|
+
value: getRequiredEnvValue({ key: 'PORT', context }),
|
|
323
|
+
context,
|
|
231
324
|
});
|
|
232
325
|
const currentDomain = parseAbsoluteUrl({
|
|
233
326
|
key: 'URL',
|
|
234
|
-
value: getRequiredEnvValue({ key: 'URL',
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
404
|
+
context,
|
|
279
405
|
}),
|
|
280
406
|
capture: traceCapture ?? 'resolve',
|
|
281
407
|
persistOnError: tracePersistOnError ?? profile === 'dev',
|
package/common/router/index.ts
CHANGED
|
@@ -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;
|