bazis 0.136.0 → 0.138.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/README.md +7 -4
- package/cli/generateProject.ts +11 -18
- package/cli/main.ts +9 -1
- package/cli/parseCli.ts +9 -0
- package/cli/templates/module.ts +16 -19
- package/cli/templates/passport.ts +2 -2
- package/cli/upgrade/markColumns.ts +151 -0
- package/cli/upgrade/runUpgrade.ts +57 -0
- package/core/access/Access.module.ts +1 -1
- package/core/access/catalog/moduleFields.ts +41 -4
- package/core/access/catalog/permissions.ts +10 -0
- package/core/access/check/Access.ts +5 -5
- package/core/access/check/can.ts +2 -1
- package/core/access/index.ts +11 -1
- package/core/access/roles/roles.ts +4 -2
- package/core/access/testing/index.ts +26 -4
- package/core/agent/ToolRunner.ts +7 -1
- package/core/agent/defineTool.ts +109 -0
- package/core/agent/index.ts +8 -3
- package/core/agent/moduleContributions-v1.ts +3 -10
- package/core/agent/session/entities.ts +45 -45
- package/core/app/index.ts +1 -1
- package/core/app/messages.ts +3 -3
- package/core/app/runApp.ts +90 -26
- package/core/cache/ICache.ts +1 -1
- package/core/cache/README.md +4 -2
- package/core/cache/SPEC.md +16 -9
- package/core/cache/http/missingCacheModuleCheck.ts +5 -4
- package/core/cache/internal/buildCacheProviders.ts +4 -5
- package/core/cache/providers/cachedProviders.ts +1 -1
- package/core/di/index.ts +0 -4
- package/core/di/lowLevel.ts +12 -0
- package/core/di/module/index.ts +0 -3
- package/core/di/module/shortcuts.ts +31 -86
- package/core/generatedRuntime.ts +11 -0
- package/core/grpc/Grpc.module.ts +2 -1
- package/core/grpc/grpcClients.ts +2 -1
- package/core/http/Authorization/metadata.ts +1 -1
- package/core/http/Decorators/metadata.ts +3 -0
- package/core/http/Decorators/routes.ts +12 -0
- package/core/http/Errors/HttpError.ts +16 -2
- package/core/http/Http.config.ts +22 -0
- package/core/http/HttpContext/HttpContext.ts +1 -1
- package/core/http/HttpServer.ts +29 -9
- package/core/http/Middleware/errorHandler.ts +16 -1
- package/core/http/OpenApi/openApiDocument.ts +44 -5
- package/core/http/Results/normalize.ts +38 -1
- package/core/http/Results/results.ts +5 -2
- package/core/http/Routing/RouterBuilder.ts +27 -2
- package/core/http/httpModule.ts +10 -8
- package/core/http/index.ts +4 -0
- package/core/http/middlewareTokens.ts +12 -0
- package/core/http/options.ts +5 -1
- package/core/http-client/httpClients.ts +2 -1
- package/core/infra/Infra.ts +3 -3
- package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
- package/core/infra/connectors/codex/contracts.ts +3 -3
- package/core/infra/connectors/llm.ts +2 -1
- package/core/infra/connectors/postgres.ts +4 -2
- package/core/infra/connectors/redis.ts +2 -1
- package/core/infra/index.ts +1 -1
- package/core/kernel/KernelBuilder.ts +2 -1
- package/core/kernel/config/ConfigRegistry.ts +2 -1
- package/core/kernel/config/addConfigOptions.ts +2 -1
- package/core/kernel/config/defineConfig.ts +56 -20
- package/core/kernel/events/EventBus.ts +1 -0
- package/core/kernel/events/eventSubscribers.ts +1 -1
- package/core/kernel/events/onEvent.ts +2 -1
- package/core/kernel/index.ts +3 -0
- package/core/orm/buildOrmProviders.ts +4 -2
- package/core/orm/contextEntities.ts +36 -0
- package/core/orm/databaseConnector.ts +9 -3
- package/core/orm/databaseHttpError.ts +21 -0
- package/core/orm/encryptedWith.ts +1 -1
- package/core/orm/index.ts +12 -1
- package/core/orm/ormModule.ts +19 -8
- package/core/orm/ownedStoreContributions.ts +2 -1
- package/core/orm/registerRepositories.ts +2 -1
- package/core/scripts/di-generate.ts +113 -12
- package/core/testing/index.ts +51 -11
- package/core/tools/StandardTools.module.ts +1 -1
- package/core/tools/contracts/Tools.config.ts +13 -7
- package/core/tools/generated/deps.ts +3 -3
- package/core/tools/http/HttpRequest.tool.ts +1 -2
- package/core/tools/http/publicAddress.ts +2 -2
- package/core/tools/index.ts +1 -1
- package/core/tools/orm/OrmEntity.references.ts +7 -8
- package/core/tools/orm/ormEntityTools.ts +54 -28
- package/core/websocket/DESIGN.md +5 -3
- package/core/websocket/middleware.ts +1 -1
- package/core/websocket/websocketModule.ts +3 -3
- package/core/workflow/contracts/Workflow.config.ts +5 -5
- package/core/workflow/definitions_module/WorkflowDefinitions.module.ts +3 -4
- package/core/workflow/definitions_module/code/defineProcess.ts +213 -11
- package/core/workflow/definitions_module/code/inspectProcesses.ts +12 -2
- package/core/workflow/definitions_module/http/Processes.controller.ts +2 -2
- package/core/workflow/definitions_module/internal/PublishedVersions.ts +39 -0
- package/core/workflow/definitions_module/model/WfProcess.model.ts +24 -11
- package/core/workflow/definitions_module/model/WfVersion.model.ts +7 -1
- package/core/workflow/definitions_module/services/IWorkflowDefinitions.service.ts +4 -1
- package/core/workflow/definitions_module/services/WorkflowDefinitions.service.ts +17 -7
- package/core/workflow/generated/deps.ts +2 -1
- package/core/workflow/index.ts +23 -1
- package/core/workflow/internal/WorkflowLanguages.ts +1 -1
- package/core/workflow/internal/http.ts +2 -2
- package/core/workflow/runtime_module/background/WorkflowWorker.ts +6 -2
- package/core/workflow/runtime_module/http/Instances.controller.ts +1 -1
- package/core/workflow/runtime_module/internal/WorkflowJobs.ts +43 -1
- package/core/workflow/runtime_module/model/WfHistory.model.ts +13 -6
- package/core/workflow/runtime_module/model/WfInstance.model.ts +30 -10
- package/core/workflow/runtime_module/model/WfJob.model.ts +22 -10
- package/core/workflow/runtime_module/model/WfSubscription.model.ts +8 -3
- package/core/workflow/runtime_module/model/WfToken.model.ts +16 -7
- package/core/workflow/runtime_module/services/IWorkflowRuntime.service.ts +10 -6
- package/core/workflow/runtime_module/services/WorkflowRuntime.service.ts +5 -2
- package/core/workflow/tasks_module/model/WfTask.model.ts +25 -9
- package/core/workflow/testing/index.ts +46 -4
- package/index.ts +4 -3
- package/library/openapi/codegen.ts +2 -2
- package/library/orm/DbContext.ts +37 -1
- package/library/orm/Metadata/ModelBuilder.ts +23 -7
- package/library/orm/Metadata/decorators.ts +23 -10
- package/library/orm/Metadata/types.ts +2 -0
- package/library/orm/Transactions/TransactionScopeCoordinator.ts +3 -3
- package/library/orm/errors.ts +39 -1
- package/library/orm/index.ts +1 -0
- package/library/terminal/messages.ts +6 -6
- package/library/validation/README.md +1 -1
- package/library/validation/SPEC.md +1 -1
- package/library/workflow/index.ts +1 -0
- package/library/workflow/nodeConfigs.ts +130 -0
- package/package.json +2 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { HttpMiddleware } from "../Middleware/types";
|
|
2
2
|
import { HttpSetupError } from "../Errors/HttpError";
|
|
3
|
+
import type { HttpResult } from "../Results/HttpResult";
|
|
3
4
|
import { ownActionMeta, ownRoutes } from "./metadata";
|
|
4
5
|
|
|
5
6
|
type MethodDecorator = (value: (...args: never[]) => unknown, context: ClassMethodDecoratorContext) => void;
|
|
@@ -27,6 +28,11 @@ export interface RouteOptions {
|
|
|
27
28
|
readonly version?: string;
|
|
28
29
|
/** Route middleware (= `@Middleware` on the method). */
|
|
29
30
|
readonly middleware?: readonly HttpMiddleware[];
|
|
31
|
+
/**
|
|
32
|
+
* What `null` or `undefined` from the action means, as a result helper:
|
|
33
|
+
* `{ nullAs: NotFound }` answers 404 with the standard body instead of 204.
|
|
34
|
+
*/
|
|
35
|
+
readonly nullAs?: () => HttpResult;
|
|
30
36
|
/** Maximum body size: an integer and a binary unit together, for example `4mb`. */
|
|
31
37
|
readonly maxBodySize?: `${bigint}${"b" | "kb" | "mb" | "gb"}`;
|
|
32
38
|
}
|
|
@@ -59,6 +65,12 @@ function route(httpMethod: string, templateOrOptions?: string | RouteOptions, ma
|
|
|
59
65
|
if (options.middleware !== undefined) {
|
|
60
66
|
action.middleware.push(...options.middleware);
|
|
61
67
|
}
|
|
68
|
+
if (options.nullAs !== undefined) {
|
|
69
|
+
if (typeof options.nullAs !== "function") {
|
|
70
|
+
throw new HttpSetupError("nullAs must be a result helper, for example NotFound.");
|
|
71
|
+
}
|
|
72
|
+
action.nullAs = options.nullAs;
|
|
73
|
+
}
|
|
62
74
|
if (options.maxBodySize !== undefined) {
|
|
63
75
|
const maxBodyBytes = parseRouteMaxBodySize(options.maxBodySize);
|
|
64
76
|
if (action.maxBodyBytes !== undefined && action.maxBodyBytes !== maxBodyBytes) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { KernelError } from "../../kernel";
|
|
2
2
|
import type { ModelValidationIssue } from "../Binding/modelValidator";
|
|
3
|
-
import { englishHttpMessage, type HttpMessageKey, type LocalizedMessage, type MessageParams } from "./messages";
|
|
3
|
+
import { englishHttpMessage, STATUS_MESSAGE_KEYS, type HttpMessageKey, type LocalizedMessage, type MessageParams } from "./messages";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Base class for errors that map directly to an HTTP response.
|
|
@@ -14,7 +14,7 @@ export class HttpError extends Error {
|
|
|
14
14
|
readonly details?: unknown,
|
|
15
15
|
/**
|
|
16
16
|
* Key and parameters of the message: the error handler renders it in the
|
|
17
|
-
* application language (
|
|
17
|
+
* application language (`@App({ language, messages })`); `message` is the
|
|
18
18
|
* English text. Errors created with their own text have no key.
|
|
19
19
|
*/
|
|
20
20
|
readonly localized?: LocalizedMessage,
|
|
@@ -59,6 +59,14 @@ export class NotFoundError extends HttpError {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** The request conflicts with what is stored: the name is taken, the record changed meanwhile. */
|
|
63
|
+
export class ConflictError extends HttpError {
|
|
64
|
+
constructor(message?: string, details?: unknown) {
|
|
65
|
+
const [text, localized] = textOf(message, "conflict");
|
|
66
|
+
super(409, text, details, localized);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
62
70
|
export class MethodNotAllowedError extends HttpError {
|
|
63
71
|
constructor(readonly allow: readonly string[]) {
|
|
64
72
|
super(405, englishHttpMessage("methodNotAllowed"), undefined, { key: "methodNotAllowed" });
|
|
@@ -116,3 +124,9 @@ export function frameworkBadRequest(key: HttpMessageKey, params?: MessageParams,
|
|
|
116
124
|
* KernelError: the start reports it as a configuration error of the code.
|
|
117
125
|
*/
|
|
118
126
|
export class HttpSetupError extends KernelError {}
|
|
127
|
+
|
|
128
|
+
/** An error of a status with the framework text for it, translated at the response: what an empty result helper stands for. */
|
|
129
|
+
export function httpErrorOfStatus(status: number): HttpError {
|
|
130
|
+
const key = STATUS_MESSAGE_KEYS[status] ?? (status >= 500 ? "serverError" : "clientError");
|
|
131
|
+
return new HttpError(status, englishHttpMessage(key), undefined, { key });
|
|
132
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from "../kernel";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where the HTTP server listens. `BAZIS_HTTP__HOST` and `BAZIS_HTTP__PORT` set it
|
|
5
|
+
* from the environment. Outside `production` the server is reachable from this
|
|
6
|
+
* machine only; in `production` it listens on every interface.
|
|
7
|
+
*
|
|
8
|
+
* A `port` or `hostname` written in `@App({ http })` is fixed in code and wins.
|
|
9
|
+
*/
|
|
10
|
+
export const httpConfig = defineConfig("http", {
|
|
11
|
+
default: {
|
|
12
|
+
host: "127.0.0.1",
|
|
13
|
+
port: 3000,
|
|
14
|
+
},
|
|
15
|
+
production: {
|
|
16
|
+
host: "0.0.0.0",
|
|
17
|
+
},
|
|
18
|
+
validate: {
|
|
19
|
+
port: (port) => (Number.isInteger(port) && port >= 0 && port <= 65535 ? undefined : "must be a TCP port (0-65535)"),
|
|
20
|
+
host: (host) => (host.trim() === "" ? "must not be empty" : undefined),
|
|
21
|
+
},
|
|
22
|
+
});
|
|
@@ -50,7 +50,7 @@ export class HttpContext {
|
|
|
50
50
|
/** Validator captured by the owning HTTP server. */
|
|
51
51
|
readonly modelValidator?: ModelValidator,
|
|
52
52
|
/**
|
|
53
|
-
* Response language of this request:
|
|
53
|
+
* Response language of this request: `@App({ language })`, or the best
|
|
54
54
|
* match of `Accept-Language` when several languages are supported.
|
|
55
55
|
* `undefined` without `language`.
|
|
56
56
|
*/
|
package/core/http/HttpServer.ts
CHANGED
|
@@ -19,10 +19,11 @@ import { defaultAuthorizeChecks } from "./Authorization/metadata";
|
|
|
19
19
|
import type { Router } from "./Routing/Router";
|
|
20
20
|
import { parseRequestPath } from "./Routing/template";
|
|
21
21
|
import { assertVersioningOptions, createVersionReader, type VersionReader } from "./Versioning/VersionReader";
|
|
22
|
-
import { HTTP_ERROR_HOOK, ROUTE_MIDDLEWARE_COMPOSER, SERVER_MIDDLEWARE, sortByOrder } from "./middlewareTokens";
|
|
22
|
+
import { HTTP_ERROR_HOOK, HTTP_ERROR_MAPPER, ROUTE_MIDDLEWARE_COMPOSER, SERVER_MIDDLEWARE, sortByOrder } from "./middlewareTokens";
|
|
23
23
|
import type { HealthEndpointOptions, RouteMiddlewareComposer, HttpModuleOptions, HttpServerOptions } from "./options";
|
|
24
24
|
import { WEBSOCKET_UPGRADE, type WebSocketUpgrade } from "./WebSocketUpgrade";
|
|
25
|
-
import { HttpSetupError } from "./Errors/HttpError";
|
|
25
|
+
import { HttpSetupError, type HttpError } from "./Errors/HttpError";
|
|
26
|
+
import { httpConfig } from "./Http.config";
|
|
26
27
|
import { clientAddress, trustedProxies, type TrustedProxies } from "./HttpContext/clientAddress";
|
|
27
28
|
import { EN_HTTP_MESSAGES, formatMessage, languageHeaders, type HttpMessageKey, type MessageParams, type SelectedLanguage } from "./Errors/messages";
|
|
28
29
|
import { getModelValidator, type ModelValidator } from "./Binding/modelValidator";
|
|
@@ -180,6 +181,7 @@ export class HttpServer implements HostedService {
|
|
|
180
181
|
...(this.options.languages ? { languages: this.options.languages } : {}),
|
|
181
182
|
...this.options.errorHandler,
|
|
182
183
|
onUnexpectedError: this.collectErrorHook(),
|
|
184
|
+
map: this.collectErrorMapper(),
|
|
183
185
|
}),
|
|
184
186
|
);
|
|
185
187
|
this.shortCircuitChain = [...serverChain];
|
|
@@ -281,14 +283,14 @@ export class HttpServer implements HostedService {
|
|
|
281
283
|
}
|
|
282
284
|
|
|
283
285
|
private async startBunServer(create: (listen: ListenOptions) => ReturnType<typeof Bun.serve>): Promise<ReturnType<typeof Bun.serve>> {
|
|
284
|
-
const
|
|
286
|
+
const listen = this.listenOptions();
|
|
287
|
+
const attempts = listen.port === 0 ? 10 : 1;
|
|
285
288
|
let lastError: unknown;
|
|
286
289
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
287
|
-
const listen = await this.resolveListenOptions();
|
|
288
290
|
try {
|
|
289
291
|
return create(listen);
|
|
290
292
|
} catch (error) {
|
|
291
|
-
if (
|
|
293
|
+
if (listen.port !== 0 || !isAddressInUse(error)) {
|
|
292
294
|
throw error;
|
|
293
295
|
}
|
|
294
296
|
lastError = error;
|
|
@@ -297,12 +299,14 @@ export class HttpServer implements HostedService {
|
|
|
297
299
|
throw lastError;
|
|
298
300
|
}
|
|
299
301
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
/** The address: what the options fix in code, otherwise the `http` settings. */
|
|
303
|
+
private listenOptions(): ListenOptions {
|
|
304
|
+
const settings = this.resolver.tryResolve(httpConfig.token);
|
|
302
305
|
// Let the operating system reserve an ephemeral port atomically. Picking a
|
|
303
306
|
// random port in user space creates a check/use race between concurrent
|
|
304
307
|
// application or test processes and can still exhaust every retry.
|
|
305
|
-
const port = this.options.port ?? 3000;
|
|
308
|
+
const port = this.options.port ?? settings?.get("port") ?? 3000;
|
|
309
|
+
const hostname = this.options.hostname ?? (this.options.port === 0 ? "127.0.0.1" : settings?.get("host"));
|
|
306
310
|
return {
|
|
307
311
|
port,
|
|
308
312
|
...(hostname !== undefined ? { hostname } : {}),
|
|
@@ -332,6 +336,22 @@ export class HttpServer implements HostedService {
|
|
|
332
336
|
};
|
|
333
337
|
}
|
|
334
338
|
|
|
339
|
+
/** Explicit `options.errorHandler.map` first, then DI {@link HTTP_ERROR_MAPPER} mappers; the first answer wins. */
|
|
340
|
+
private collectErrorMapper(): ((error: unknown) => HttpError | undefined) | undefined {
|
|
341
|
+
const explicit = this.options.errorHandler?.map;
|
|
342
|
+
const mappers = [...(explicit === undefined ? [] : [explicit]), ...this.resolver.resolveAll(HTTP_ERROR_MAPPER)];
|
|
343
|
+
if (mappers.length === 0) {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
return (error) => {
|
|
347
|
+
for (const mapper of mappers) {
|
|
348
|
+
const mapped = mapper(error);
|
|
349
|
+
if (mapped !== undefined) return mapped;
|
|
350
|
+
}
|
|
351
|
+
return undefined;
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
335
355
|
/** Explicit `options.routeMiddlewareComposer` (order 0) merged with DI {@link ROUTE_MIDDLEWARE_COMPOSER}. */
|
|
336
356
|
private collectRouteComposer(): RouteMiddlewareComposer | undefined {
|
|
337
357
|
const explicit = this.options.routeMiddlewareComposer
|
|
@@ -400,7 +420,7 @@ export class HttpServer implements HostedService {
|
|
|
400
420
|
return request.method === "HEAD" ? stripBody(response) : response;
|
|
401
421
|
}
|
|
402
422
|
|
|
403
|
-
/** The response language of a request; `undefined` without
|
|
423
|
+
/** The response language of a request; `undefined` without `@App({ language })`. */
|
|
404
424
|
private selected(request: Request | undefined): SelectedLanguage | undefined {
|
|
405
425
|
try {
|
|
406
426
|
return this.options.languages?.select(request?.headers.get("accept-language"));
|
|
@@ -28,6 +28,12 @@ export interface ErrorHandlerOptions {
|
|
|
28
28
|
* mistake and is not logged or sent to `onUnexpectedError`.
|
|
29
29
|
*/
|
|
30
30
|
status?: (error: unknown) => number | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Turns a non-{@link HttpError} failure into an HttpError, with its own text
|
|
33
|
+
* and details; `HTTP_ERROR_MAPPER` registrations are merged in. Asked before
|
|
34
|
+
* {@link status}. A mapped 5xx is still logged as unexpected.
|
|
35
|
+
*/
|
|
36
|
+
map?: (error: unknown) => HttpError | undefined;
|
|
31
37
|
/**
|
|
32
38
|
* Logger for unexpected errors when {@link logError} is absent: one `error`
|
|
33
39
|
* line with the method, path, request id and the redacted error. The server
|
|
@@ -114,7 +120,16 @@ export function errorHandler(options: ErrorHandlerOptions = {}): HttpMiddleware
|
|
|
114
120
|
return async (ctx, next) => {
|
|
115
121
|
try {
|
|
116
122
|
await next();
|
|
117
|
-
} catch (
|
|
123
|
+
} catch (thrown) {
|
|
124
|
+
let error = thrown;
|
|
125
|
+
if (!(thrown instanceof HttpError) && options.map !== undefined) {
|
|
126
|
+
let mapped: HttpError | undefined;
|
|
127
|
+
try { mapped = options.map(thrown); } catch { mapped = undefined; }
|
|
128
|
+
if (mapped instanceof HttpError) {
|
|
129
|
+
if (mapped.status >= 500) safelyReportUnexpected(options, log, ctx, thrown);
|
|
130
|
+
error = mapped;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
118
133
|
const selected = options.languages?.select(ctx.header("accept-language"));
|
|
119
134
|
const text = textRenderer(selected?.localize);
|
|
120
135
|
const language = languageHeaders(options.languages, selected);
|
|
@@ -23,7 +23,7 @@ export interface HttpOpenApiDocumentBuildInput {
|
|
|
23
23
|
readonly versioning?: ApiVersioningOptions;
|
|
24
24
|
readonly title: string;
|
|
25
25
|
readonly version: string;
|
|
26
|
-
/** Default checks of
|
|
26
|
+
/** Default checks of `@App({ http: { authorize } })`: such routes are secured too. */
|
|
27
27
|
readonly authorize?: readonly AuthorizeCheck[];
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -87,7 +87,42 @@ function collectOpenApiOperations(
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
return operations;
|
|
90
|
+
return withCreatedLocation(operations);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Documents the automatic `Location` of a 201: the server sets it when the
|
|
95
|
+
* body has `id` and a GET route answers `<path>/<id>`.
|
|
96
|
+
*/
|
|
97
|
+
function withCreatedLocation(operations: OpenApiCatalogOperation[]): OpenApiCatalogOperation[] {
|
|
98
|
+
const itemPaths = new Set<string>();
|
|
99
|
+
for (const operation of operations) {
|
|
100
|
+
const parent = /^(.*)\/\{[^{}/]+\}$/u.exec(operation.path)?.[1];
|
|
101
|
+
if (operation.httpMethod === "GET" && parent !== undefined) {
|
|
102
|
+
itemPaths.add(parent);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return operations.map((operation) => {
|
|
106
|
+
const created = operation.responses["201"];
|
|
107
|
+
if (!created || !itemPaths.has(operation.path.replace(/\/+$/u, ""))) {
|
|
108
|
+
return operation;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
...operation,
|
|
112
|
+
responses: {
|
|
113
|
+
...operation.responses,
|
|
114
|
+
"201": {
|
|
115
|
+
...created,
|
|
116
|
+
headers: {
|
|
117
|
+
Location: {
|
|
118
|
+
description: "Address of the created item, when the response has `id`.",
|
|
119
|
+
schema: { type: "string" },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
});
|
|
91
126
|
}
|
|
92
127
|
|
|
93
128
|
interface CreateOperationInput {
|
|
@@ -140,7 +175,7 @@ function createOperation(input: CreateOperationInput): OpenApiCatalogOperation {
|
|
|
140
175
|
},
|
|
141
176
|
}
|
|
142
177
|
: { description: "Success" },
|
|
143
|
-
...errorResponses(errorStatuses(input.bindings, authorized, generatedOperation?.errors)),
|
|
178
|
+
...errorResponses(errorStatuses(input.bindings, authorized, generatedOperation?.errors, input.action)),
|
|
144
179
|
},
|
|
145
180
|
authorized,
|
|
146
181
|
...(version !== undefined ? { versions: [version] } : {}),
|
|
@@ -380,15 +415,19 @@ function successStatus(method: string, action: ActionMeta, generated: number | u
|
|
|
380
415
|
/**
|
|
381
416
|
* Errors the operation can answer with: 400 when the request carries values
|
|
382
417
|
* that are converted or validated (body, list query, query and header values
|
|
383
|
-
* and models, typed route parameters), 401/403 under @Authorize,
|
|
384
|
-
* errors the method body returns or throws itself.
|
|
418
|
+
* and models, typed route parameters), 401/403 under @Authorize, the status of
|
|
419
|
+
* `nullAs`, and the errors the method body returns or throws itself.
|
|
385
420
|
*/
|
|
386
421
|
function errorStatuses(
|
|
387
422
|
bindings: readonly ParameterBinding[],
|
|
388
423
|
authorized: boolean,
|
|
389
424
|
generated: readonly number[] | undefined,
|
|
425
|
+
action: ActionMeta,
|
|
390
426
|
): number[] {
|
|
391
427
|
const statuses = new Set(generated ?? []);
|
|
428
|
+
// `nullAs: NotFound` is an answer of the route the method body does not show.
|
|
429
|
+
const instead = action.nullAs?.().status;
|
|
430
|
+
if (instead !== undefined && instead >= 400) statuses.add(instead);
|
|
392
431
|
const validated = bindings.some((binding) =>
|
|
393
432
|
binding.source === "body" || binding.source === "list" || binding.source === "query" || binding.source === "header"
|
|
394
433
|
|| binding.source === "queryModel" || binding.source === "headerModel"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HttpContext } from "../HttpContext/HttpContext";
|
|
2
|
+
import { httpErrorOfStatus } from "../Errors/HttpError";
|
|
2
3
|
import { HttpResult } from "./HttpResult";
|
|
3
4
|
|
|
4
5
|
/** Per-action response defaults coming from decorators. */
|
|
@@ -7,6 +8,26 @@ export interface ResponseDefaults {
|
|
|
7
8
|
httpCode?: number;
|
|
8
9
|
/** `@Produces(...)` value. */
|
|
9
10
|
produces?: string;
|
|
11
|
+
/** `nullAs` of the route: the result that stands for `null` and `undefined`. */
|
|
12
|
+
nullAs?: () => HttpResult;
|
|
13
|
+
/**
|
|
14
|
+
* Address of a created item for an automatic `Location` header: given the
|
|
15
|
+
* `id` of a 201 body, returns the address or `undefined` when the
|
|
16
|
+
* application has no GET route for it.
|
|
17
|
+
*/
|
|
18
|
+
locate?: (ctx: HttpContext, id: string | number) => string | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** `id` of a 201 body that can name the created item. */
|
|
22
|
+
function createdItemId(body: unknown): string | number | undefined {
|
|
23
|
+
if (body === null || typeof body !== "object" || Array.isArray(body) || isBinaryBody(body)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const id = (body as { id?: unknown }).id;
|
|
27
|
+
if (typeof id === "number" && Number.isFinite(id)) {
|
|
28
|
+
return id;
|
|
29
|
+
}
|
|
30
|
+
return typeof id === "string" && id !== "" ? id : undefined;
|
|
10
31
|
}
|
|
11
32
|
|
|
12
33
|
type ResponseBody = ConstructorParameters<typeof Response>[0];
|
|
@@ -26,15 +47,24 @@ function isBinaryBody(body: unknown): body is Blob | Uint8Array | ArrayBuffer |
|
|
|
26
47
|
* - `string` — text/plain (unless `@Produces` says otherwise);
|
|
27
48
|
* - `Blob`/bytes/stream — binary passthrough;
|
|
28
49
|
* - any other object/array/number/boolean — JSON;
|
|
29
|
-
* - `undefined`/`null` — empty body, 204 (or `@HttpCode`)
|
|
50
|
+
* - `undefined`/`null` — empty body, 204 (or `@HttpCode`), unless the route says `nullAs`.
|
|
30
51
|
*
|
|
31
52
|
* Status priority: result helper > `@HttpCode` > `ctx.res.status(...)` > default.
|
|
32
53
|
* Headers from `ctx.res.header(...)` are merged into every non-raw response.
|
|
54
|
+
*
|
|
55
|
+
* A 201 without `Location` whose body is an object with `id` gets
|
|
56
|
+
* `Location: <request path>/<id>` when a GET route answers that address.
|
|
33
57
|
*/
|
|
34
58
|
export function normalizeResult(result: unknown, ctx: HttpContext, defaults: ResponseDefaults): Response {
|
|
35
59
|
if (result instanceof Response) {
|
|
36
60
|
return result;
|
|
37
61
|
}
|
|
62
|
+
if ((result === null || result === undefined) && defaults.nullAs !== undefined) {
|
|
63
|
+
const instead = defaults.nullAs();
|
|
64
|
+
// An error goes through the error handler: the standard body, in the language of the request.
|
|
65
|
+
if (instead.status >= 400) throw httpErrorOfStatus(instead.status);
|
|
66
|
+
result = instead;
|
|
67
|
+
}
|
|
38
68
|
|
|
39
69
|
const builderStatus = ctx.res.statusOverride;
|
|
40
70
|
const builderHeaders = ctx.res.headers;
|
|
@@ -63,6 +93,13 @@ export function normalizeResult(result: unknown, ctx: HttpContext, defaults: Res
|
|
|
63
93
|
for (const [name, value] of builderHeaders) {
|
|
64
94
|
headers.append(name, value);
|
|
65
95
|
}
|
|
96
|
+
if (status === 201 && defaults.locate && !headers.has("location")) {
|
|
97
|
+
const id = createdItemId(body);
|
|
98
|
+
const location = id === undefined ? undefined : defaults.locate(ctx, id);
|
|
99
|
+
if (location !== undefined) {
|
|
100
|
+
headers.set("location", location);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
66
103
|
|
|
67
104
|
let payload: ResponseBody = null;
|
|
68
105
|
if (body === undefined || body === null) {
|
|
@@ -5,8 +5,11 @@ export function Ok(body?: unknown): HttpResult {
|
|
|
5
5
|
return new HttpResult(200, body);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* 201 Created. Without `location` the server derives it: a body with `id`
|
|
10
|
+
* gets `Location: <request path>/<id>` when a GET route answers that address.
|
|
11
|
+
*/
|
|
12
|
+
export function Created(body?: unknown, location?: string): HttpResult {
|
|
10
13
|
return new HttpResult(201, body, location ? [["location", location]] : undefined);
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -23,7 +23,7 @@ export interface RegisteredRoute {
|
|
|
23
23
|
readonly anonymous: boolean;
|
|
24
24
|
}
|
|
25
25
|
import { Router, type RouteAction } from "./Router";
|
|
26
|
-
import { joinPaths, parseTemplate } from "./template";
|
|
26
|
+
import { joinPaths, parseRequestPath, parseTemplate } from "./template";
|
|
27
27
|
|
|
28
28
|
type ControllerInstance = Record<string | symbol, (...args: unknown[]) => unknown>;
|
|
29
29
|
|
|
@@ -43,6 +43,8 @@ export class RouterBuilder {
|
|
|
43
43
|
private readonly authorizeComposer: RouteMiddlewareComposer;
|
|
44
44
|
/** Every route of the last {@link build}, in registration order. */
|
|
45
45
|
readonly routes: RegisteredRoute[] = [];
|
|
46
|
+
/** Router of the last {@link build}: automatic `Location` looks up GET routes in it. */
|
|
47
|
+
private router?: Router;
|
|
46
48
|
|
|
47
49
|
constructor(
|
|
48
50
|
/** Middleware prepended to every route: [logger?, cors?, errorHandler, ...global]. */
|
|
@@ -58,6 +60,7 @@ export class RouterBuilder {
|
|
|
58
60
|
|
|
59
61
|
build(controllers: readonly Class<object>[]): Router {
|
|
60
62
|
const router = new Router();
|
|
63
|
+
this.router = router;
|
|
61
64
|
for (const controllerClass of controllers) {
|
|
62
65
|
const meta = controllerMetaOf(controllerClass);
|
|
63
66
|
if (!meta?.isController) {
|
|
@@ -122,6 +125,23 @@ export class RouterBuilder {
|
|
|
122
125
|
}
|
|
123
126
|
}
|
|
124
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Address of an item created by a request: the request path plus `/<id>`,
|
|
130
|
+
* but only when a GET route of this application answers it.
|
|
131
|
+
*/
|
|
132
|
+
private locateCreated(ctx: HttpContext, id: string | number): string | undefined {
|
|
133
|
+
const segments = parseRequestPath(ctx.url.pathname);
|
|
134
|
+
if (!segments || !this.router) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
const value = String(id);
|
|
138
|
+
const match = this.router.match("GET", [...segments, value], ctx.apiVersion);
|
|
139
|
+
if (match.kind !== "matched") {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return `${ctx.url.pathname.replace(/\/+$/u, "")}/${encodeURIComponent(value)}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
125
145
|
/**
|
|
126
146
|
* Precompiles binding + ActionFilter.before as a boundary middleware and
|
|
127
147
|
* controller execution + ActionFilter.after as the terminal. This lets
|
|
@@ -138,7 +158,12 @@ export class RouterBuilder {
|
|
|
138
158
|
const catches = meta.catches;
|
|
139
159
|
const bindings = resolveGeneratedBindings(controllerClass, methodName);
|
|
140
160
|
assertBindingsFresh(controllerClass, methodName, bindings);
|
|
141
|
-
const responseDefaults = {
|
|
161
|
+
const responseDefaults = {
|
|
162
|
+
httpCode: action.httpCode,
|
|
163
|
+
nullAs: action.nullAs,
|
|
164
|
+
produces: action.produces,
|
|
165
|
+
locate: (ctx: HttpContext, id: string | number) => this.locateCreated(ctx, id),
|
|
166
|
+
};
|
|
142
167
|
const bindingDefaults = { consumes: action.consumes };
|
|
143
168
|
const token = controllerClass as unknown as Token<object>;
|
|
144
169
|
const boundArguments = new WeakMap<HttpContext, readonly unknown[]>();
|
package/core/http/httpModule.ts
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HOSTED_SERVICE, Module, SERVICE_PROVIDER, collectModuleControllers, type BazisModule, type ProviderDefinition } from "../di";
|
|
2
|
+
import { DI } from "../di/lowLevel";
|
|
2
3
|
import { HttpServer } from "./HttpServer";
|
|
4
|
+
import { httpConfig } from "./Http.config";
|
|
3
5
|
import type { HttpModuleOptions, HttpServerOptions } from "./options";
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Builds the HTTP module: discovers the `controllers` of the modules in
|
|
7
|
-
* `imports` and registers the HttpServer hosted service.
|
|
9
|
+
* `imports` and registers the HttpServer hosted service. The address comes from
|
|
10
|
+
* {@link httpConfig} unless the options fix it.
|
|
8
11
|
*
|
|
9
12
|
* ```ts
|
|
10
13
|
* @Module({
|
|
11
14
|
* controllers: [UsersController],
|
|
12
|
-
* providers: [scoped(IUserStore, UserService
|
|
15
|
+
* providers: [scoped(IUserStore, UserService)],
|
|
13
16
|
* })
|
|
14
17
|
* export class UsersModule {}
|
|
15
18
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* http: { port: 3000, prefix: "api" },
|
|
19
|
-
* }).module;
|
|
19
|
+
* @App({ modules: [UsersModule], http: { prefix: "api" } })
|
|
20
|
+
* export class Application {}
|
|
20
21
|
*
|
|
21
|
-
* await
|
|
22
|
+
* await runApp(Application);
|
|
22
23
|
* ```
|
|
23
24
|
*/
|
|
24
25
|
export function httpModule(options: HttpModuleOptions): BazisModule {
|
|
@@ -37,6 +38,7 @@ export function httpModule(options: HttpModuleOptions): BazisModule {
|
|
|
37
38
|
|
|
38
39
|
@Module({
|
|
39
40
|
imports: options.imports,
|
|
41
|
+
config: httpConfig,
|
|
40
42
|
providers,
|
|
41
43
|
})
|
|
42
44
|
class HttpModule {}
|
package/core/http/index.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* dependencies, compatible with `bun build --compile`.
|
|
7
7
|
*/
|
|
8
8
|
export { httpModule } from "./httpModule";
|
|
9
|
+
export { httpConfig } from "./Http.config";
|
|
9
10
|
export { HttpServer } from "./HttpServer";
|
|
10
11
|
export { WEBSOCKET_UPGRADE, type WebSocketUpgrade } from "./WebSocketUpgrade";
|
|
11
12
|
export type {
|
|
@@ -18,10 +19,12 @@ export type { OpenApiDocsOptions } from "../../library/openapi";
|
|
|
18
19
|
export { composeRouteMiddlewareComposers } from "./composeRouteMiddlewareComposers";
|
|
19
20
|
export {
|
|
20
21
|
HTTP_ERROR_HOOK,
|
|
22
|
+
HTTP_ERROR_MAPPER,
|
|
21
23
|
ROUTE_MIDDLEWARE_COMPOSER,
|
|
22
24
|
SERVER_MIDDLEWARE,
|
|
23
25
|
sortByOrder,
|
|
24
26
|
type HttpErrorHook,
|
|
27
|
+
type HttpErrorMapper,
|
|
25
28
|
type RouteMiddlewareComposerRegistration,
|
|
26
29
|
type ServerMiddlewareRegistration,
|
|
27
30
|
} from "./middlewareTokens";
|
|
@@ -131,6 +134,7 @@ export type { ActionFilterHooks, HttpMiddleware } from "./Middleware/types";
|
|
|
131
134
|
// Errors
|
|
132
135
|
export {
|
|
133
136
|
BadRequestError,
|
|
137
|
+
ConflictError,
|
|
134
138
|
ForbiddenError,
|
|
135
139
|
HttpError,
|
|
136
140
|
HttpSetupError,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createToken } from "../di";
|
|
2
|
+
import type { HttpError } from "./Errors/HttpError";
|
|
2
3
|
import type { HttpContext } from "./HttpContext/HttpContext";
|
|
3
4
|
import type { HttpMiddleware } from "./Middleware/types";
|
|
4
5
|
import type { RouteMiddlewareComposer } from "./options";
|
|
@@ -39,6 +40,17 @@ export type HttpErrorHook = (ctx: HttpContext, error: unknown) => void;
|
|
|
39
40
|
/** Enumerable token: register any number of unexpected-error hooks. */
|
|
40
41
|
export const HTTP_ERROR_HOOK = createToken<HttpErrorHook>("BazisHttpErrorHook");
|
|
41
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Turns a failure that is not an {@link HttpError} into one: a module that owns
|
|
45
|
+
* a kind of errors says how they answer. `undefined` leaves the error to the
|
|
46
|
+
* next mapper and then to 500. The ORM registers one for database errors.
|
|
47
|
+
* Collected via `resolveAll`.
|
|
48
|
+
*/
|
|
49
|
+
export type HttpErrorMapper = (error: unknown) => HttpError | undefined;
|
|
50
|
+
|
|
51
|
+
/** Enumerable token: register any number of error mappers. */
|
|
52
|
+
export const HTTP_ERROR_MAPPER = createToken<HttpErrorMapper>("BazisHttpErrorMapper");
|
|
53
|
+
|
|
42
54
|
/** Stable order by `order ?? 0` (registration order preserved within a tier). */
|
|
43
55
|
export function sortByOrder<T extends { readonly order?: number }>(items: readonly T[]): T[] {
|
|
44
56
|
return [...items]
|
package/core/http/options.ts
CHANGED
|
@@ -72,8 +72,12 @@ export interface HttpModuleOptions {
|
|
|
72
72
|
* either listed here or exported by global modules.
|
|
73
73
|
*/
|
|
74
74
|
readonly imports?: readonly import("../di").BazisModuleRef[];
|
|
75
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Fixes the listen port in code (0 — a random free port). Leave it out: the
|
|
77
|
+
* port then comes from the `http.port` setting, 3000 unless `BAZIS_HTTP__PORT` says otherwise.
|
|
78
|
+
*/
|
|
76
79
|
readonly port?: number;
|
|
80
|
+
/** Fixes the listen address in code. Leave it out: it comes from the `http.host` setting (`BAZIS_HTTP__HOST`). */
|
|
77
81
|
readonly hostname?: string;
|
|
78
82
|
/** Global route prefix, e.g. "api". */
|
|
79
83
|
readonly prefix?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { registerModuleMetadataExpander, type BazisModuleMetadata, type BazisModuleRef, type ServiceResolver } from "../di";
|
|
2
|
+
import { DI } from "../di/lowLevel";
|
|
2
3
|
import { getOutboundCorrelationHeaders, type ConfigDefinition } from "../kernel";
|
|
3
4
|
import { HttpClientBase, HttpClientError, type RequestConfig } from "../../library/http-client";
|
|
4
5
|
|
package/core/infra/Infra.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DI,
|
|
3
2
|
HOSTED_SERVICE,
|
|
4
3
|
Module,
|
|
5
4
|
createToken,
|
|
@@ -12,6 +11,7 @@ import {
|
|
|
12
11
|
type BazisModuleRef,
|
|
13
12
|
type ProviderDefinition,
|
|
14
13
|
} from "../di";
|
|
14
|
+
import { DI } from "../di/lowLevel";
|
|
15
15
|
import { ConfigRegistry, HEALTH_CHECK, StartupReport } from "../kernel";
|
|
16
16
|
import { InfraError, type InfraConnector } from "./InfraConnector";
|
|
17
17
|
import { InfraLifecycle } from "./InfraLifecycle";
|
|
@@ -136,7 +136,7 @@ function buildInfraDefinitions(manifest: InfraManifest): {
|
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
138
|
* Dynamic infrastructure module (a plain metadata object): the same as
|
|
139
|
-
* `@Infra`, but without a class. Handy for `imports` or
|
|
139
|
+
* `@Infra`, but without a class. Handy for `imports` or `@App({ infra })`.
|
|
140
140
|
*/
|
|
141
141
|
export function infraModule(manifest: InfraManifest): BazisModuleRef {
|
|
142
142
|
try {
|
|
@@ -153,7 +153,7 @@ export function infraModule(manifest: InfraManifest): BazisModuleRef {
|
|
|
153
153
|
/**
|
|
154
154
|
* Infrastructure decorator. Puts the metadata of a regular **global module**
|
|
155
155
|
* on the class, so the class becomes a valid `BazisModuleRef`: it can be passed
|
|
156
|
-
* to
|
|
156
|
+
* to `@App({ infra: AppInfra })` or to `imports`.
|
|
157
157
|
*
|
|
158
158
|
* The class stays pure metadata (like `@Module`) and is **never instantiated**:
|
|
159
159
|
* only the clients it lists live and hold state.
|
|
@@ -232,7 +232,7 @@ export class CodexAppServerClient implements CodexClient {
|
|
|
232
232
|
if (method === "account/updated") this.modelCache = undefined;
|
|
233
233
|
}, () => { this.clearLogin(); this.modelCache = undefined; });
|
|
234
234
|
const initialized = await client.request("initialize", {
|
|
235
|
-
clientInfo: { name: "bazis", title: "Bazis", version: "0.
|
|
235
|
+
clientInfo: { name: "bazis", title: "Bazis", version: "0.138.0" }, capabilities: { experimentalApi: true },
|
|
236
236
|
}, 15_000, signal);
|
|
237
237
|
if (resolve(text(initialized.codexHome, 4096)) !== this.paths.home) throw new CodexError("PROTOCOL_ERROR");
|
|
238
238
|
client.notify("initialized");
|
|
@@ -52,7 +52,7 @@ const EN_CODEX_MESSAGES: Readonly<Record<CodexErrorCode, string>> = Object.freez
|
|
|
52
52
|
LOGIN_EXPIRED: "The sign-in window expired. Start signing in again.",
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
/** Russian texts for every code;
|
|
55
|
+
/** Russian texts for every code; `@App({ language: "ru" })` turns them on. */
|
|
56
56
|
export const RU_CODEX_MESSAGES: Readonly<Record<CodexErrorCode, string>> = Object.freeze({
|
|
57
57
|
NOT_CONFIGURED: "Подключение ChatGPT не настроено на сервере.",
|
|
58
58
|
UNAVAILABLE: "Codex App Server недоступен. Проверьте его установку и настройки.",
|
|
@@ -73,7 +73,7 @@ let own: Partial<Record<CodexErrorCode, string>> = {};
|
|
|
73
73
|
let messages: Readonly<Record<CodexErrorCode, string>> = EN_CODEX_MESSAGES;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
|
-
* @internal
|
|
76
|
+
* @internal `@App({ language })` sets the language of the texts. It is
|
|
77
77
|
* process-wide, like the texts themselves: the last started application wins.
|
|
78
78
|
*/
|
|
79
79
|
export function useCodexLanguage(name: keyof typeof CODEX_LANGUAGES): void {
|
|
@@ -81,7 +81,7 @@ export function useCodexLanguage(name: keyof typeof CODEX_LANGUAGES): void {
|
|
|
81
81
|
messages = Object.freeze({ ...language, ...own });
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/** `message` is user-facing text for `code`, in the language of
|
|
84
|
+
/** `message` is user-facing text for `code`, in the language of `@App({ language })`. */
|
|
85
85
|
export class CodexError extends Error {
|
|
86
86
|
constructor(readonly code: CodexErrorCode) { super(messages[code]); this.name = "CodexError"; }
|
|
87
87
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AppConfig, ConfigRegistry, Secret } from "../../kernel";
|
|
2
2
|
import { AGENT_MODEL_PROVIDER, AGENT_RUNTIME_OPTIONS, AgentRuntime, type AgentModelProvider } from "../../agent";
|
|
3
|
-
import {
|
|
3
|
+
import { DiError, HOSTED_SERVICE, SERVICE_PROVIDER, type ProviderDefinition } from "../../di";
|
|
4
|
+
import { DI } from "../../di/lowLevel";
|
|
4
5
|
import { LOGGER } from "../../kernel/logging/Logger";
|
|
5
6
|
import { reader, requireValue, settingNames } from "../connectorConfig";
|
|
6
7
|
import { InfraError, type InfraConnector } from "../InfraConnector";
|