proteum 2.0.0 → 2.1.0
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 +13 -1
- package/README.md +375 -0
- package/agents/framework/AGENTS.md +917 -0
- package/agents/project/AGENTS.md +138 -0
- package/agents/{codex → project}/CODING_STYLE.md +3 -2
- package/agents/project/client/AGENTS.md +108 -0
- package/agents/{codex → project}/client/pages/AGENTS.md +8 -8
- package/agents/{codex → project}/server/routes/AGENTS.md +2 -1
- package/agents/project/server/services/AGENTS.md +170 -0
- package/agents/{codex → project}/tests/AGENTS.md +1 -0
- package/cli/app/config.ts +3 -2
- package/cli/app/index.ts +6 -66
- package/cli/bin.js +7 -2
- package/cli/commands/build.ts +94 -27
- package/cli/commands/check.ts +15 -1
- package/cli/commands/dev.ts +288 -132
- package/cli/commands/doctor.ts +108 -0
- package/cli/commands/explain.ts +226 -0
- package/cli/commands/init.ts +76 -70
- package/cli/commands/lint.ts +18 -1
- package/cli/commands/refresh.ts +16 -6
- package/cli/commands/typecheck.ts +14 -1
- package/cli/compiler/artifacts/controllers.ts +150 -0
- package/cli/compiler/artifacts/discovery.ts +132 -0
- package/cli/compiler/artifacts/manifest.ts +267 -0
- package/cli/compiler/artifacts/routing.ts +315 -0
- package/cli/compiler/artifacts/services.ts +480 -0
- package/cli/compiler/artifacts/shared.ts +12 -0
- package/cli/compiler/client/identite.ts +2 -1
- package/cli/compiler/client/index.ts +13 -3
- package/cli/compiler/common/controllers.ts +23 -28
- package/cli/compiler/common/files/style.ts +3 -4
- package/cli/compiler/common/generatedRouteModules.ts +333 -19
- package/cli/compiler/common/proteumManifest.ts +133 -0
- package/cli/compiler/index.ts +33 -896
- package/cli/compiler/server/index.ts +21 -4
- package/cli/context.ts +71 -0
- package/cli/index.ts +39 -181
- package/cli/presentation/commands.ts +208 -0
- package/cli/presentation/compileReporter.ts +65 -0
- package/cli/presentation/devSession.ts +70 -0
- package/cli/presentation/help.ts +193 -0
- package/cli/presentation/ink.ts +69 -0
- package/cli/presentation/layout.ts +83 -0
- package/cli/runtime/argv.ts +49 -0
- package/cli/runtime/command.ts +25 -0
- package/cli/runtime/commands.ts +221 -0
- package/cli/runtime/importEsm.ts +7 -0
- package/cli/runtime/verbose.ts +15 -0
- package/cli/utils/agents.ts +5 -4
- package/cli/utils/keyboard.ts +12 -6
- package/client/app/index.ts +0 -6
- package/client/services/router/index.tsx +1 -1
- package/client/services/router/response/index.tsx +2 -2
- package/common/dev/serverHotReload.ts +12 -0
- package/common/router/index.ts +3 -2
- package/common/router/layouts.ts +1 -1
- package/common/router/pageSetup.ts +1 -0
- package/package.json +10 -8
- package/prettier/router-registration-plugin.cjs +52 -0
- package/prettier.config.cjs +1 -0
- package/scripts/cleanup-generated-controllers.ts +2 -2
- package/scripts/fix-reference-app-typing.ts +2 -2
- package/scripts/format-router-registrations.ts +119 -0
- package/scripts/migrate-explicit-controllers-and-request.ts +423 -0
- package/scripts/refactor-server-controllers.ts +19 -18
- package/scripts/refactor-server-runtime-aliases.ts +1 -1
- package/server/app/commands.ts +309 -25
- package/server/app/container/config.ts +1 -1
- package/server/app/container/index.ts +2 -2
- package/server/app/controller/index.ts +13 -4
- package/server/app/index.ts +53 -37
- package/server/app/service/container.ts +26 -28
- package/server/app/service/index.ts +10 -20
- package/server/app.tsconfig.json +9 -2
- package/server/index.ts +32 -1
- package/server/services/auth/index.ts +234 -15
- package/server/services/auth/router/index.ts +39 -7
- package/server/services/auth/router/request.ts +40 -8
- package/server/services/disks/index.ts +1 -1
- package/server/services/prisma/Facet.ts +2 -2
- package/server/services/prisma/index.ts +22 -5
- package/server/services/prisma/mariadb.ts +47 -0
- package/server/services/router/http/index.ts +9 -1
- package/server/services/router/index.ts +10 -4
- package/server/services/router/response/index.ts +26 -6
- package/types/auth-check-rules.test.ts +51 -0
- package/types/controller-request-context.test.ts +55 -0
- package/types/service-config.test.ts +39 -0
- package/agents/codex/AGENTS.md +0 -95
- package/agents/codex/client/AGENTS.md +0 -102
- package/agents/codex/server/services/AGENTS.md +0 -137
- package/server/services/models.7z +0 -0
- /package/agents/{codex → project}/agents.md.zip +0 -0
package/server/app/commands.ts
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
5
|
// Npm
|
|
6
|
-
import
|
|
6
|
+
import { Cli, Command as ClipanionCommand, Option, UsageError } from 'clipanion';
|
|
7
7
|
|
|
8
8
|
// Core
|
|
9
9
|
import type { Application } from './index';
|
|
10
10
|
import Service from '@server/app/service';
|
|
11
|
-
import { NotFound } from '@common/errors';
|
|
11
|
+
import { InputError, NotFound } from '@common/errors';
|
|
12
12
|
|
|
13
13
|
/*----------------------------------
|
|
14
14
|
- TYPES
|
|
@@ -16,9 +16,9 @@ import { NotFound } from '@common/errors';
|
|
|
16
16
|
|
|
17
17
|
type CommandCallback<TArgs extends any[]> = (...args: TArgs) => Promise<any>;
|
|
18
18
|
|
|
19
|
-
export type CommandsList = { [commandName: string]:
|
|
19
|
+
export type CommandsList = { [commandName: string]: RuntimeCommand };
|
|
20
20
|
|
|
21
|
-
export type
|
|
21
|
+
export type RuntimeCommand<TArgs extends any[] = any[]> = {
|
|
22
22
|
name: string;
|
|
23
23
|
description: string;
|
|
24
24
|
run?: CommandCallback<TArgs>;
|
|
@@ -37,6 +37,166 @@ export type Hooks = {};
|
|
|
37
37
|
|
|
38
38
|
export type Services = {};
|
|
39
39
|
|
|
40
|
+
type TCommandArgumentValue = string | number | boolean;
|
|
41
|
+
type TParsedCommandArgs = { [key: string]: TCommandArgumentValue | TCommandArgumentValue[] };
|
|
42
|
+
|
|
43
|
+
const commandValuePattern = /^-?(?:\d+|\d*\.\d+)$/;
|
|
44
|
+
|
|
45
|
+
const tokenizeCommandString = (commandString: string) => {
|
|
46
|
+
const tokens: string[] = [];
|
|
47
|
+
let currentToken = '';
|
|
48
|
+
let quote: '"' | "'" | undefined;
|
|
49
|
+
let escaping = false;
|
|
50
|
+
|
|
51
|
+
for (const character of commandString) {
|
|
52
|
+
if (escaping) {
|
|
53
|
+
currentToken += character;
|
|
54
|
+
escaping = false;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (character === '\\') {
|
|
59
|
+
escaping = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (quote) {
|
|
64
|
+
if (character === quote) {
|
|
65
|
+
quote = undefined;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
currentToken += character;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (character === '"' || character === "'") {
|
|
74
|
+
quote = character;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (/\s/.test(character)) {
|
|
79
|
+
if (!currentToken) continue;
|
|
80
|
+
|
|
81
|
+
tokens.push(currentToken);
|
|
82
|
+
currentToken = '';
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
currentToken += character;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (escaping) currentToken += '\\';
|
|
90
|
+
if (currentToken) tokens.push(currentToken);
|
|
91
|
+
|
|
92
|
+
return tokens;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const isOptionToken = (token: string) => /^-(?!\d+(\.\d+)?$).+/.test(token);
|
|
96
|
+
|
|
97
|
+
const normalizeCommandValue = (value: string): TCommandArgumentValue => {
|
|
98
|
+
if (value === 'true') return true;
|
|
99
|
+
if (value === 'false') return false;
|
|
100
|
+
if (commandValuePattern.test(value)) return Number(value);
|
|
101
|
+
|
|
102
|
+
return value;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const addParsedArgValue = (args: TParsedCommandArgs, key: string, value: TCommandArgumentValue) => {
|
|
106
|
+
const existingValue = args[key];
|
|
107
|
+
|
|
108
|
+
if (existingValue === undefined) {
|
|
109
|
+
args[key] = value;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
args[key] = Array.isArray(existingValue) ? [...existingValue, value] : [existingValue, value];
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const parseCommandOptionTokens = (tokens: string[]) => {
|
|
117
|
+
const namedArguments: TParsedCommandArgs = {};
|
|
118
|
+
const positionalArguments: string[] = [];
|
|
119
|
+
|
|
120
|
+
let usePositionalOnly = false;
|
|
121
|
+
|
|
122
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
123
|
+
const token = tokens[index];
|
|
124
|
+
|
|
125
|
+
if (usePositionalOnly) {
|
|
126
|
+
positionalArguments.push(token);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (token === '--') {
|
|
131
|
+
usePositionalOnly = true;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (token === '--help' || token === '-h') return { help: true as const, args: namedArguments, positionalArguments };
|
|
136
|
+
|
|
137
|
+
if (token.startsWith('--') && token.length > 2) {
|
|
138
|
+
const body = token.slice(2);
|
|
139
|
+
|
|
140
|
+
if (body.startsWith('no-') && body.length > 3) {
|
|
141
|
+
addParsedArgValue(namedArguments, body.slice(3), false);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const equalsIndex = body.indexOf('=');
|
|
146
|
+
|
|
147
|
+
if (equalsIndex >= 0) {
|
|
148
|
+
addParsedArgValue(
|
|
149
|
+
namedArguments,
|
|
150
|
+
body.slice(0, equalsIndex),
|
|
151
|
+
normalizeCommandValue(body.slice(equalsIndex + 1)),
|
|
152
|
+
);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const nextToken = tokens[index + 1];
|
|
157
|
+
|
|
158
|
+
if (nextToken !== undefined && !isOptionToken(nextToken)) {
|
|
159
|
+
addParsedArgValue(namedArguments, body, normalizeCommandValue(nextToken));
|
|
160
|
+
index++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
addParsedArgValue(namedArguments, body, true);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (token.startsWith('-') && token.length > 1 && !commandValuePattern.test(token)) {
|
|
169
|
+
const body = token.slice(1);
|
|
170
|
+
const equalsIndex = body.indexOf('=');
|
|
171
|
+
|
|
172
|
+
if (equalsIndex >= 0) {
|
|
173
|
+
addParsedArgValue(
|
|
174
|
+
namedArguments,
|
|
175
|
+
body.slice(0, equalsIndex),
|
|
176
|
+
normalizeCommandValue(body.slice(equalsIndex + 1)),
|
|
177
|
+
);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const nextToken = tokens[index + 1];
|
|
182
|
+
|
|
183
|
+
if (body.length === 1 && nextToken !== undefined && !isOptionToken(nextToken)) {
|
|
184
|
+
addParsedArgValue(namedArguments, body, normalizeCommandValue(nextToken));
|
|
185
|
+
index++;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const shortFlag of body) addParsedArgValue(namedArguments, shortFlag, true);
|
|
190
|
+
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
positionalArguments.push(token);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { help: false as const, args: namedArguments, positionalArguments };
|
|
198
|
+
};
|
|
199
|
+
|
|
40
200
|
/*----------------------------------
|
|
41
201
|
- SERVICE
|
|
42
202
|
----------------------------------*/
|
|
@@ -45,68 +205,192 @@ export default class CommandsManager extends Service<Config, Hooks, Application>
|
|
|
45
205
|
|
|
46
206
|
public commandsIndex: CommandsList = {};
|
|
47
207
|
|
|
208
|
+
private runtimeCli?: Cli;
|
|
209
|
+
|
|
48
210
|
public command<TArgs extends any[]>(
|
|
49
211
|
...args:
|
|
50
|
-
| [name: string, description: string, childrens:
|
|
51
|
-
| [name: string, description: string, run: CommandCallback<TArgs>, childrens?:
|
|
52
|
-
):
|
|
212
|
+
| [name: string, description: string, childrens: RuntimeCommand[]]
|
|
213
|
+
| [name: string, description: string, run: CommandCallback<TArgs>, childrens?: RuntimeCommand[]]
|
|
214
|
+
): RuntimeCommand {
|
|
53
215
|
let name: string, description: string;
|
|
54
|
-
let childrens:
|
|
216
|
+
let childrens: RuntimeCommand[] | undefined;
|
|
55
217
|
let run: CommandCallback<TArgs> | undefined;
|
|
56
218
|
|
|
57
219
|
if (typeof args[2] === 'object') [name, description, childrens] = args;
|
|
58
220
|
else [name, description, run, childrens] = args;
|
|
59
221
|
|
|
60
|
-
const command:
|
|
222
|
+
const command: RuntimeCommand = { name, description, run, childrens: childrens ? this.indexFromList(childrens) : {} };
|
|
61
223
|
|
|
62
224
|
return command;
|
|
63
225
|
}
|
|
64
226
|
|
|
65
|
-
private indexFromList(list:
|
|
227
|
+
private indexFromList(list: RuntimeCommand[]): CommandsList {
|
|
66
228
|
const index: CommandsList = {};
|
|
67
229
|
for (const command of list) index[command.name] = command;
|
|
68
230
|
|
|
69
231
|
return index;
|
|
70
232
|
}
|
|
71
233
|
|
|
234
|
+
private invalidateRuntimeCli() {
|
|
235
|
+
this.runtimeCli = undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private createRuntimeCommandClass(command: RuntimeCommand, path: string[]) {
|
|
239
|
+
const manager = this;
|
|
240
|
+
const usage = ClipanionCommand.Usage({ description: command.description });
|
|
241
|
+
|
|
242
|
+
if (command.run === undefined) {
|
|
243
|
+
class RuntimeNamespaceCommand extends ClipanionCommand {
|
|
244
|
+
public static override paths = [path];
|
|
245
|
+
public static override usage = usage;
|
|
246
|
+
|
|
247
|
+
public async execute() {
|
|
248
|
+
throw new NotFound(`This command isn't runnable.`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Object.defineProperty(RuntimeNamespaceCommand, 'name', {
|
|
253
|
+
value: `${path.map((segment) => segment.replace(/[^A-Za-z0-9]/g, '_')).join('_') || 'Root'}NamespaceCommand`,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return RuntimeNamespaceCommand;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
class RuntimeRunnableCommand extends ClipanionCommand {
|
|
260
|
+
public static override paths = [path];
|
|
261
|
+
public static override usage = usage;
|
|
262
|
+
|
|
263
|
+
public proxy = Option.Proxy({ name: 'args' });
|
|
264
|
+
|
|
265
|
+
public async execute() {
|
|
266
|
+
return manager.executeRegisteredCommand(command, path, this.proxy);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
Object.defineProperty(RuntimeRunnableCommand, 'name', {
|
|
271
|
+
value: `${path.map((segment) => segment.replace(/[^A-Za-z0-9]/g, '_')).join('_') || 'Root'}RunnableCommand`,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
return RuntimeRunnableCommand;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private createRuntimeCli() {
|
|
278
|
+
const cli = new Cli({
|
|
279
|
+
binaryName: this.app.identity.identifier || 'app',
|
|
280
|
+
enableCapture: false,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const registerCommands = (commands: CommandsList, parentPath: string[] = []) => {
|
|
284
|
+
for (const command of Object.values(commands)) {
|
|
285
|
+
const path = [...parentPath, command.name];
|
|
286
|
+
|
|
287
|
+
cli.register(this.createRuntimeCommandClass(command, path));
|
|
288
|
+
|
|
289
|
+
if (Object.keys(command.childrens).length > 0) registerCommands(command.childrens, path);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
registerCommands(this.commandsIndex);
|
|
294
|
+
|
|
295
|
+
return cli;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private getRuntimeCli() {
|
|
299
|
+
this.runtimeCli ??= this.createRuntimeCli();
|
|
300
|
+
|
|
301
|
+
return this.runtimeCli;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private async executeRegisteredCommand(command: RuntimeCommand, path: string[], proxyTokens: string[]) {
|
|
305
|
+
if (command.run === undefined) throw new NotFound(`This command isn't runnable.`);
|
|
306
|
+
|
|
307
|
+
const { help, args, positionalArguments } = parseCommandOptionTokens(proxyTokens);
|
|
308
|
+
|
|
309
|
+
this.config.debug &&
|
|
310
|
+
console.log(LogPrefix, `Run command path: ${path.join(' ')} | Parsed proxy tokens:`, {
|
|
311
|
+
proxyTokens,
|
|
312
|
+
args,
|
|
313
|
+
positionalArguments,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (help) {
|
|
317
|
+
const cli = this.getRuntimeCli();
|
|
318
|
+
const runtimeCommand = cli.process(path, Cli.defaultContext);
|
|
319
|
+
|
|
320
|
+
return cli.usage(runtimeCommand, { detailed: true });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (positionalArguments.length > 0) {
|
|
324
|
+
throw new UsageError(
|
|
325
|
+
`Unexpected positional arguments for "${path.join(' ')}": ${positionalArguments.join(', ')}.`,
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const argsList = Object.values(args);
|
|
330
|
+
|
|
331
|
+
return command.run(...(argsList as Parameters<NonNullable<typeof command.run>>));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private createRuntimeCliApi(cli: Cli) {
|
|
335
|
+
return {
|
|
336
|
+
binaryLabel: cli.binaryLabel,
|
|
337
|
+
binaryName: cli.binaryName,
|
|
338
|
+
binaryVersion: cli.binaryVersion,
|
|
339
|
+
enableCapture: cli.enableCapture,
|
|
340
|
+
enableColors: cli.enableColors,
|
|
341
|
+
definitions: () => cli.definitions(),
|
|
342
|
+
definition: (commandClass: any) => cli.definition(commandClass),
|
|
343
|
+
error: (error: Error, opts?: any) => cli.error(error, opts),
|
|
344
|
+
format: (colored?: boolean) => cli.format(colored),
|
|
345
|
+
process: (input: string[]) => cli.process(input, Cli.defaultContext),
|
|
346
|
+
run: (input: string[]) => cli.run(input, Cli.defaultContext),
|
|
347
|
+
usage: (command?: any, opts?: any) => cli.usage(command, opts),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
72
351
|
/*----------------------------------
|
|
73
352
|
- REGISTER
|
|
74
353
|
----------------------------------*/
|
|
75
|
-
public fromList(list:
|
|
354
|
+
public fromList(list: RuntimeCommand[]) {
|
|
76
355
|
for (const command of list) {
|
|
77
356
|
if (this.commandsIndex[command.name] !== undefined)
|
|
78
357
|
throw new Error(`Tried to register command "${command.name}", but it already has been defined.`);
|
|
79
358
|
|
|
80
359
|
this.commandsIndex[command.name] = command;
|
|
81
360
|
}
|
|
361
|
+
|
|
362
|
+
this.invalidateRuntimeCli();
|
|
82
363
|
}
|
|
83
364
|
|
|
84
365
|
/*----------------------------------
|
|
85
366
|
- RUN
|
|
86
367
|
----------------------------------*/
|
|
87
368
|
public async run(commandString: string) {
|
|
88
|
-
const
|
|
369
|
+
const tokens = tokenizeCommandString(commandString);
|
|
89
370
|
|
|
90
|
-
this.config.debug && console.log(LogPrefix, `Run command: ${commandString} |
|
|
371
|
+
this.config.debug && console.log(LogPrefix, `Run command: ${commandString} | Tokens:`, tokens);
|
|
91
372
|
|
|
92
|
-
|
|
93
|
-
for (const commandName of _) {
|
|
94
|
-
const commandsList: CommandsList = command === undefined ? this.commandsIndex : command.childrens;
|
|
373
|
+
if (tokens.length === 0) throw new NotFound(`Command not found.`);
|
|
95
374
|
|
|
96
|
-
|
|
375
|
+
const cli = this.getRuntimeCli();
|
|
97
376
|
|
|
98
|
-
|
|
99
|
-
|
|
377
|
+
try {
|
|
378
|
+
const command = cli.process(tokens, Cli.defaultContext);
|
|
100
379
|
|
|
101
|
-
|
|
380
|
+
if (command.help) return cli.usage(command, { detailed: true });
|
|
102
381
|
|
|
103
|
-
|
|
382
|
+
command.context = Cli.defaultContext;
|
|
383
|
+
command.cli = this.createRuntimeCliApi(cli);
|
|
104
384
|
|
|
105
|
-
|
|
106
|
-
|
|
385
|
+
return await command.validateAndExecute();
|
|
386
|
+
} catch (error) {
|
|
387
|
+
if (error instanceof UsageError) throw new InputError(error.message);
|
|
107
388
|
|
|
108
|
-
|
|
389
|
+
if (error instanceof Error && ['UnknownSyntaxError', 'AmbiguousSyntaxError'].includes(error.name)) {
|
|
390
|
+
throw new NotFound(error.message);
|
|
391
|
+
}
|
|
109
392
|
|
|
110
|
-
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
111
395
|
}
|
|
112
396
|
}
|
|
@@ -122,7 +122,7 @@ export default class ConfigParser {
|
|
|
122
122
|
public env(): TEnvConfig {
|
|
123
123
|
// We assume that when we run 5htp dev, we're in local
|
|
124
124
|
// Otherwise, we're in production environment (docker)
|
|
125
|
-
console.
|
|
125
|
+
debug && console.info('[app] Using environment:', process.env.NODE_ENV);
|
|
126
126
|
const envFileName = this.appDir + '/env.yaml';
|
|
127
127
|
const envFile = this.loadYaml(envFileName);
|
|
128
128
|
const routerPortOverride = getRouterPortOverride();
|
|
@@ -38,8 +38,8 @@ export class ApplicationContainer<TServicesIndex extends StartedServicesIndex =
|
|
|
38
38
|
public: path.join(process.cwd(), '/public'),
|
|
39
39
|
var: path.join(process.cwd(), '/var'),
|
|
40
40
|
|
|
41
|
-
client: { generated: path.join(process.cwd(), '
|
|
42
|
-
server: { generated: path.join(process.cwd(), '
|
|
41
|
+
client: { generated: path.join(process.cwd(), '.proteum', 'client') },
|
|
42
|
+
server: { generated: path.join(process.cwd(), '.proteum', 'server') },
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
/*----------------------------------
|
|
@@ -8,6 +8,7 @@ import zod from 'zod';
|
|
|
8
8
|
// Core
|
|
9
9
|
import context from '@server/context';
|
|
10
10
|
import type { Application } from '../index';
|
|
11
|
+
import type { TServiceModelsClient } from '../service';
|
|
11
12
|
import type { TRouterContext, TAnyRouter } from '@server/services/router';
|
|
12
13
|
import {
|
|
13
14
|
toValidationSchema,
|
|
@@ -22,7 +23,15 @@ export type { z } from '@server/services/router/request/validation/zod';
|
|
|
22
23
|
- TYPES
|
|
23
24
|
----------------------------------*/
|
|
24
25
|
|
|
25
|
-
type
|
|
26
|
+
type TControllerRouter<TApplication extends Application = Application> = TApplication extends { Router: infer TRouter }
|
|
27
|
+
? TRouter extends TAnyRouter
|
|
28
|
+
? TRouter
|
|
29
|
+
: TAnyRouter
|
|
30
|
+
: TAnyRouter;
|
|
31
|
+
|
|
32
|
+
export type TControllerRequestContext<TApplication extends Application = Application> = TRouterContext<
|
|
33
|
+
TControllerRouter<TApplication>
|
|
34
|
+
>;
|
|
26
35
|
|
|
27
36
|
/*----------------------------------
|
|
28
37
|
- CLASS
|
|
@@ -30,7 +39,7 @@ type TControllerContext = TRouterContext<TAnyRouter>;
|
|
|
30
39
|
|
|
31
40
|
export default abstract class Controller<
|
|
32
41
|
TApplication extends Application = Application,
|
|
33
|
-
TContext extends
|
|
42
|
+
TContext extends TControllerRequestContext<TApplication> = TControllerRequestContext<TApplication>,
|
|
34
43
|
> {
|
|
35
44
|
public constructor(public request: TContext) {}
|
|
36
45
|
|
|
@@ -42,9 +51,9 @@ export default abstract class Controller<
|
|
|
42
51
|
return this.app;
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
public get models() {
|
|
54
|
+
public get models(): TServiceModelsClient<TApplication> {
|
|
46
55
|
const app = this.app as { models?: { client?: unknown }; Models?: { client?: unknown } };
|
|
47
|
-
return app.models?.client ?? app.Models?.client
|
|
56
|
+
return (app.models?.client ?? app.Models?.client) as TServiceModelsClient<TApplication>;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
public input<TSchema extends TValidationSchema>(schema: TSchema): zod.output<TSchema>;
|
package/server/app/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { Anomaly } from '@common/errors';
|
|
|
14
14
|
import { TBasicUser } from '@server/services/auth';
|
|
15
15
|
|
|
16
16
|
export { default as Services } from './service/container';
|
|
17
|
+
export type { ServiceConfig } from './service/container';
|
|
17
18
|
export type { TEnvConfig as Environment } from './container/config';
|
|
18
19
|
|
|
19
20
|
/*----------------------------------
|
|
@@ -34,6 +35,17 @@ export const Service = ServicesContainer;
|
|
|
34
35
|
type Prettify<T> = { [K in keyof T]: T[K] } & {};
|
|
35
36
|
|
|
36
37
|
export type ApplicationProperties = Prettify<keyof Application>;
|
|
38
|
+
export type RootServicesOf<TApplication extends Application = Application> = Prettify<{
|
|
39
|
+
[TKey in Exclude<keyof TApplication, ApplicationProperties> as TApplication[TKey] extends AnyService ? TKey : never]: TApplication[TKey];
|
|
40
|
+
}>;
|
|
41
|
+
|
|
42
|
+
const isServiceInstance = (value: unknown): value is AnyService => {
|
|
43
|
+
if (!value || typeof value !== 'object') return false;
|
|
44
|
+
|
|
45
|
+
const service = value as Partial<AnyService>;
|
|
46
|
+
|
|
47
|
+
return typeof service.runHook === 'function' && typeof service.getServiceInstance === 'function' && service.status !== undefined;
|
|
48
|
+
};
|
|
37
49
|
|
|
38
50
|
/*----------------------------------
|
|
39
51
|
- FUNCTIONS
|
|
@@ -68,8 +80,6 @@ export abstract class Application<
|
|
|
68
80
|
public debug: boolean = false;
|
|
69
81
|
public launched: boolean = false;
|
|
70
82
|
|
|
71
|
-
protected abstract registered: { [serviceId: string]: { name: string; start: () => AnyService } };
|
|
72
|
-
|
|
73
83
|
/*----------------------------------
|
|
74
84
|
- INIT
|
|
75
85
|
----------------------------------*/
|
|
@@ -120,14 +130,8 @@ export abstract class Application<
|
|
|
120
130
|
console.log('Core version', CORE_VERSION);
|
|
121
131
|
const startTime = Date.now();
|
|
122
132
|
|
|
123
|
-
this.startServices();
|
|
124
|
-
|
|
125
|
-
console.log('----------------------------------');
|
|
126
|
-
console.log('- SERVICES');
|
|
127
|
-
console.log('----------------------------------');
|
|
128
133
|
const startingServices = await this.ready();
|
|
129
134
|
await Promise.all(startingServices);
|
|
130
|
-
console.log('All services are ready');
|
|
131
135
|
await this.runHook('ready');
|
|
132
136
|
|
|
133
137
|
const startedTime = (Date.now() - startTime) / 1000;
|
|
@@ -139,65 +143,77 @@ export abstract class Application<
|
|
|
139
143
|
- ERROR HANDLING
|
|
140
144
|
----------------------------------*/
|
|
141
145
|
|
|
142
|
-
private
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
private listRootServices(): Array<[string, AnyService]> {
|
|
147
|
+
return Object.keys(this)
|
|
148
|
+
.map((serviceName) => [serviceName, (this as Record<string, unknown>)[serviceName]] as const)
|
|
149
|
+
.filter(
|
|
150
|
+
([, service]) =>
|
|
151
|
+
isServiceInstance(service) &&
|
|
152
|
+
service !== this &&
|
|
153
|
+
service.parent === this &&
|
|
154
|
+
service.app === this,
|
|
155
|
+
)
|
|
156
|
+
.map(([serviceName, service]) => [serviceName, service as AnyService]);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public getRootServices(): RootServicesOf<this> {
|
|
160
|
+
const services: Record<string, AnyService> = {};
|
|
161
|
+
|
|
162
|
+
for (const [serviceName, service] of this.listRootServices()) {
|
|
163
|
+
services[serviceName] = service;
|
|
153
164
|
}
|
|
165
|
+
|
|
166
|
+
return services as RootServicesOf<this>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public findService(serviceId: string): AnyService | undefined {
|
|
170
|
+
const rootServices = this.getRootServices() as Record<string, AnyService>;
|
|
171
|
+
const directService = rootServices[serviceId];
|
|
172
|
+
if (directService) return directService;
|
|
173
|
+
|
|
174
|
+
const serviceName = serviceId.split('/').pop();
|
|
175
|
+
if (!serviceName) return undefined;
|
|
176
|
+
|
|
177
|
+
return rootServices[serviceName];
|
|
154
178
|
}
|
|
155
179
|
|
|
156
180
|
public register(service: AnyService) {
|
|
157
|
-
return service.ready();
|
|
181
|
+
return (service as AnyService & { ready: () => Promise<any> }).ready();
|
|
158
182
|
}
|
|
159
183
|
|
|
160
184
|
public async ready() {
|
|
161
185
|
const startingServices: Promise<any>[] = [];
|
|
162
186
|
|
|
163
|
-
|
|
164
|
-
const processService = async (propKey: string, service: AnyService, level: number = 0) => {
|
|
187
|
+
const processService = async (_propKey: string, service: AnyService) => {
|
|
165
188
|
if (service.status !== 'starting') return;
|
|
166
189
|
|
|
167
190
|
// Services start shouldn't block app boot
|
|
168
191
|
// use await ServiceName.started to make services depends on each other
|
|
169
|
-
service.starting = service.ready();
|
|
192
|
+
service.starting = (service as AnyService & { ready: () => Promise<any> }).ready();
|
|
170
193
|
startingServices.push(service.starting);
|
|
171
194
|
service.status = 'running';
|
|
172
|
-
console.log('-' + '-'.repeat(level * 1), propKey + ': ' + service.constructor.name);
|
|
173
195
|
|
|
174
196
|
// Subservices
|
|
175
197
|
for (const propKey in service) {
|
|
176
|
-
if (propKey === 'app') continue;
|
|
177
|
-
|
|
198
|
+
if (propKey === 'app' || propKey === 'parent') continue;
|
|
199
|
+
const propValue = (service as Record<string, any>)[propKey];
|
|
178
200
|
|
|
179
201
|
// Check if service
|
|
180
|
-
|
|
181
|
-
typeof propValue === 'object' &&
|
|
182
|
-
!(propValue instanceof Application) &&
|
|
183
|
-
propValue !== null &&
|
|
184
|
-
propValue.status !== undefined;
|
|
185
|
-
if (!isService) continue;
|
|
202
|
+
if (!isServiceInstance(propValue) || propValue instanceof Application) continue;
|
|
186
203
|
|
|
187
204
|
// Services start shouldn't block app boot
|
|
188
|
-
processService(propKey, propValue
|
|
205
|
+
processService(propKey, propValue);
|
|
189
206
|
}
|
|
190
207
|
};
|
|
191
208
|
|
|
192
|
-
for (const
|
|
193
|
-
const
|
|
194
|
-
const service = (this as Record<string, any>)[registeredService.name];
|
|
209
|
+
for (const [serviceName, service] of this.listRootServices()) {
|
|
210
|
+
const rootService = service as AnyService;
|
|
195
211
|
|
|
196
212
|
// TODO: move to router
|
|
197
213
|
// Application.on('service.ready')
|
|
198
214
|
|
|
199
215
|
// Services start shouldn't block app boot
|
|
200
|
-
processService(
|
|
216
|
+
processService(serviceName, rootService);
|
|
201
217
|
}
|
|
202
218
|
|
|
203
219
|
return startingServices;
|