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
package/core/app/runApp.ts
CHANGED
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
collectModuleUiProfiles,
|
|
3
3
|
collectModuleControllers,
|
|
4
4
|
collectModuleGateways,
|
|
5
|
-
DI,
|
|
6
5
|
Global,
|
|
7
6
|
Module,
|
|
8
7
|
singletonValue,
|
|
9
8
|
CLASS_PROVIDER_HOOK,
|
|
10
9
|
type BazisModuleRef,
|
|
11
10
|
} from "../di";
|
|
11
|
+
import { DI } from "../di/lowLevel";
|
|
12
12
|
import { loadBazisGeneratedRuntime } from "../generatedRuntime";
|
|
13
13
|
import { missingCacheModuleCheck } from "../cache/http/missingCacheModuleCheck";
|
|
14
14
|
import { cacheableClassProviderHook } from "../cache/di/classProviderHook";
|
|
@@ -35,14 +35,16 @@ import { resolveUiProfileAuthoringV1, UiProfileV1Registry, type UiLabels } from
|
|
|
35
35
|
import { UiSurfaceDocumentProvider, UiSurfaceHttpController } from "./ui/uiSurfaceHttp";
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
* starts as a worker/CLI (kernel and hosted
|
|
38
|
+
* What an application is made of besides its modules. Everything is optional:
|
|
39
|
+
* without `http` and `grpc` the app starts as a worker/CLI (kernel and hosted
|
|
40
|
+
* services only).
|
|
40
41
|
*/
|
|
41
|
-
export interface
|
|
42
|
+
export interface AppSettings {
|
|
42
43
|
/**
|
|
43
44
|
* Start the HTTP server. Controllers are collected from the whole module tree
|
|
44
|
-
* automatically, no need to list them. Pass `{}` for the defaults
|
|
45
|
-
*
|
|
45
|
+
* automatically, no need to list them. Pass `{}` for the defaults or an
|
|
46
|
+
* object with settings (prefix/cors/...). The address comes from the `http`
|
|
47
|
+
* settings: `BAZIS_HTTP__HOST`, `BAZIS_HTTP__PORT` (port 3000 by default).
|
|
46
48
|
*/
|
|
47
49
|
readonly http?: HttpModuleOptions;
|
|
48
50
|
/** gRPC server; discovers feature-module grpcControllers, independently of HTTP. */
|
|
@@ -67,7 +69,7 @@ export interface RunAppOptions {
|
|
|
67
69
|
* (`new NotFoundError("Книга не найдена")`) are never changed.
|
|
68
70
|
*
|
|
69
71
|
* ```ts
|
|
70
|
-
*
|
|
72
|
+
* @App({ modules: [UsersModule], language: "ru", http: {} })
|
|
71
73
|
* ```
|
|
72
74
|
*
|
|
73
75
|
* Several languages — each request gets the best match of its
|
|
@@ -75,7 +77,7 @@ export interface RunAppOptions {
|
|
|
75
77
|
* and `Vary: Accept-Language`, and `ctx.language` names the choice:
|
|
76
78
|
*
|
|
77
79
|
* ```ts
|
|
78
|
-
*
|
|
80
|
+
* @App({ modules: [UsersModule], language: { default: "ru", supported: ["ru", "en"] } })
|
|
79
81
|
* ```
|
|
80
82
|
*/
|
|
81
83
|
readonly language?: AppLanguage | AppLanguageOptions;
|
|
@@ -100,7 +102,7 @@ export interface RunAppOptions {
|
|
|
100
102
|
* their tokens, connections open before the servers and close after them.
|
|
101
103
|
*
|
|
102
104
|
* ```ts
|
|
103
|
-
*
|
|
105
|
+
* @App({ modules: [UsersModule], infra: AppInfra, http: {} })
|
|
104
106
|
* ```
|
|
105
107
|
*/
|
|
106
108
|
readonly infra?: BazisModuleRef;
|
|
@@ -112,7 +114,7 @@ export interface RunAppOptions {
|
|
|
112
114
|
*
|
|
113
115
|
* ```ts
|
|
114
116
|
* import { memory } from "@/core/cache";
|
|
115
|
-
*
|
|
117
|
+
* @App({ modules: [UsersModule], cache: memory({ maxEntries: 1000 }), http: {} })
|
|
116
118
|
* ```
|
|
117
119
|
*/
|
|
118
120
|
readonly cache?: BazisModuleRef;
|
|
@@ -126,11 +128,71 @@ export interface RunAppOptions {
|
|
|
126
128
|
readonly ui?: RunAppUiOptions;
|
|
127
129
|
}
|
|
128
130
|
|
|
131
|
+
/** What `@App` takes: the modules of the application and its {@link AppSettings}. */
|
|
132
|
+
export interface AppOptions extends AppSettings {
|
|
133
|
+
/** The modules of the application. Their controllers, gateways and background work are found by themselves. */
|
|
134
|
+
readonly modules: readonly BazisModuleRef[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** A class marked `@App`. */
|
|
138
|
+
export type AppClass = abstract new (...args: never) => unknown;
|
|
139
|
+
|
|
140
|
+
const APP_OPTIONS = Symbol.for("bazis/core/app/options");
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Declares the application: its modules, its infrastructure and the servers it
|
|
144
|
+
* starts. One declaration is read by `runApp` and by the tests, so a test starts
|
|
145
|
+
* the same application as production does.
|
|
146
|
+
*
|
|
147
|
+
* ```ts
|
|
148
|
+
* // src/app/App.ts
|
|
149
|
+
* @App({
|
|
150
|
+
* modules: [ProjectModule, TaskModule],
|
|
151
|
+
* infra: AppInfra,
|
|
152
|
+
* http: { health: true },
|
|
153
|
+
* })
|
|
154
|
+
* export class TodoApp {}
|
|
155
|
+
*
|
|
156
|
+
* // src/index.ts
|
|
157
|
+
* await registerBazisGeneratedRuntime();
|
|
158
|
+
* await runApp(TodoApp);
|
|
159
|
+
*
|
|
160
|
+
* // a test
|
|
161
|
+
* const app = await startTestApp(TodoApp);
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* The class is also the root module: its `imports` are `modules`.
|
|
165
|
+
*/
|
|
166
|
+
export function App(options: AppOptions) {
|
|
167
|
+
return (target: AppClass, context: ClassDecoratorContext): void => {
|
|
168
|
+
if (options === null || typeof options !== "object" || !Array.isArray(options.modules)) {
|
|
169
|
+
throw new KernelError(`@App on ${target.name || "a class"}: modules must be an array of the application's modules.`);
|
|
170
|
+
}
|
|
171
|
+
Module({ imports: options.modules })(target, context);
|
|
172
|
+
Object.defineProperty(target, APP_OPTIONS, { configurable: false, enumerable: false, writable: false, value: Object.freeze({ ...options }) });
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** @internal What `@App` declared on a class; `undefined` for anything else. */
|
|
177
|
+
export function appOptionsOf(value: unknown): AppOptions | undefined {
|
|
178
|
+
return typeof value === "function" && Object.hasOwn(value, APP_OPTIONS) ? (Reflect.get(value, APP_OPTIONS) as AppOptions) : undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** @internal The settings of an `@App` class without its modules: they are the class's own imports. */
|
|
182
|
+
export function appSettingsOf(app: AppClass): AppSettings {
|
|
183
|
+
const options = appOptionsOf(app);
|
|
184
|
+
if (options === undefined) {
|
|
185
|
+
throw new KernelError(`${typeof app === "function" && app.name ? app.name : "The value"} is not an application: mark the class with @App({ modules: [...] }).`);
|
|
186
|
+
}
|
|
187
|
+
const { modules: _modules, ...settings } = options;
|
|
188
|
+
return settings;
|
|
189
|
+
}
|
|
190
|
+
|
|
129
191
|
/** Adds the WebSocket module when a module declares gateways; settings alone without gateways are a mistake. */
|
|
130
192
|
function withWebSocket(root: BazisModuleRef, options: WebSocketOptions | undefined): BazisModuleRef {
|
|
131
193
|
if (collectModuleGateways([root]).length === 0) {
|
|
132
194
|
if (options !== undefined) {
|
|
133
|
-
throw new KernelError("
|
|
195
|
+
throw new KernelError("@App: websocket options are set, but no module declares gateways. Add gateways: [ChatGateway] to the module that owns the gateway.");
|
|
134
196
|
}
|
|
135
197
|
return root;
|
|
136
198
|
}
|
|
@@ -139,7 +201,7 @@ function withWebSocket(root: BazisModuleRef, options: WebSocketOptions | undefin
|
|
|
139
201
|
|
|
140
202
|
function withUiRuntime(
|
|
141
203
|
root: BazisModuleRef,
|
|
142
|
-
option:
|
|
204
|
+
option: AppSettings["ui"],
|
|
143
205
|
http: HttpModuleOptions | undefined,
|
|
144
206
|
): BazisModuleRef {
|
|
145
207
|
const declarations = collectModuleUiProfiles([root]);
|
|
@@ -234,7 +296,7 @@ function declarationName(value: unknown): string {
|
|
|
234
296
|
|
|
235
297
|
/** @internal Exported for hosting contract tests. */
|
|
236
298
|
export function normalizeUiSurfaceHostingOptions(
|
|
237
|
-
option:
|
|
299
|
+
option: AppSettings["ui"],
|
|
238
300
|
http: HttpModuleOptions,
|
|
239
301
|
): UiSurfaceHostingOptions | undefined {
|
|
240
302
|
if (option === undefined) {
|
|
@@ -271,34 +333,36 @@ function normalizeAbsolutePath(path: string): string {
|
|
|
271
333
|
}
|
|
272
334
|
|
|
273
335
|
/**
|
|
274
|
-
* The single application entry point
|
|
336
|
+
* The single application entry point: starts what `@App` declares.
|
|
275
337
|
*
|
|
276
338
|
* ```ts
|
|
277
|
-
* //
|
|
278
|
-
* import { runApp } from "
|
|
279
|
-
* import {
|
|
339
|
+
* // src/index.ts
|
|
340
|
+
* import { runApp } from "bazis/core/app";
|
|
341
|
+
* import { TodoApp } from "./app/App";
|
|
342
|
+
* import { registerBazisGeneratedRuntime } from "./generated/bazis/runtime";
|
|
280
343
|
*
|
|
281
|
-
* await
|
|
344
|
+
* await registerBazisGeneratedRuntime();
|
|
345
|
+
* await runApp(TodoApp);
|
|
282
346
|
* ```
|
|
283
347
|
*
|
|
284
348
|
* It does three things you would otherwise write by hand:
|
|
285
349
|
* 1. starts the HTTP server and collects controllers from the whole module tree;
|
|
286
350
|
* 2. plugs in the request body validator (DIP glue between the kernel and the library);
|
|
287
351
|
* 3. starts the kernel with graceful shutdown on SIGINT/SIGTERM.
|
|
288
|
-
*
|
|
289
|
-
* The root module stays clean: `@Module({ imports: [UsersModule] })`.
|
|
290
352
|
*/
|
|
291
|
-
export async function runApp(
|
|
353
|
+
export async function runApp(app: AppClass): Promise<number> {
|
|
292
354
|
await loadBazisGeneratedRuntime();
|
|
293
|
-
let
|
|
355
|
+
let options: AppSettings;
|
|
356
|
+
let composed: BazisModuleRef;
|
|
294
357
|
try {
|
|
295
|
-
|
|
358
|
+
options = appSettingsOf(app);
|
|
359
|
+
composed = composeApp(app, options);
|
|
296
360
|
} catch (error) {
|
|
297
361
|
// Composing already reads routes and options: report its errors like the kernel build does.
|
|
298
362
|
return reportBuildFailure(error);
|
|
299
363
|
}
|
|
300
|
-
return Bazis.run(
|
|
301
|
-
builder.useApplicationRoot(
|
|
364
|
+
return Bazis.run(composed, options.kernel, (builder) => {
|
|
365
|
+
builder.useApplicationRoot(app);
|
|
302
366
|
options.configure?.(builder);
|
|
303
367
|
});
|
|
304
368
|
}
|
|
@@ -308,7 +372,7 @@ export async function runApp(root: BazisModuleRef, options: RunAppOptions = {}):
|
|
|
308
372
|
* configs next to the feature root, wrapped in the HTTP/gRPC/UI hosts the
|
|
309
373
|
* options ask for. Shared with `startTestApp`, so tests run the same graph.
|
|
310
374
|
*/
|
|
311
|
-
export function composeApp(root: BazisModuleRef, options:
|
|
375
|
+
export function composeApp(root: BazisModuleRef, options: AppSettings = {}): BazisModuleRef {
|
|
312
376
|
// Checked even without HTTP: an unknown language is a startup error.
|
|
313
377
|
const languages = createLanguages(options.language, options.messages);
|
|
314
378
|
useCodexLanguage(typeof options.language === "object" ? options.language.default : options.language ?? "en");
|
package/core/cache/ICache.ts
CHANGED
|
@@ -67,7 +67,7 @@ export interface ICache<TValue = unknown> {
|
|
|
67
67
|
export const ICache = createToken<ICache>("ICache") as InjectionToken<ICache<unknown>>;
|
|
68
68
|
|
|
69
69
|
registerNamedDependencyHint((name) => name === "ICache" || name.startsWith("ICache<")
|
|
70
|
-
? "The cache is provided by a cache module: add cache: memory() to
|
|
70
|
+
? "The cache is provided by a cache module: add cache: memory() to @App (startTestApp takes the same option)."
|
|
71
71
|
: undefined);
|
|
72
72
|
|
|
73
73
|
/** Typed token for a specific value type. */
|
package/core/cache/README.md
CHANGED
|
@@ -46,7 +46,8 @@ import { memory } from "@/core/cache";
|
|
|
46
46
|
|
|
47
47
|
// A self-installing value: the output-cache composer is published through the
|
|
48
48
|
// `ROUTE_MIDDLEWARE_COMPOSER` DI token, and `httpModule` picks it up automatically.
|
|
49
|
-
|
|
49
|
+
@App({ modules: [AppModule], cache: memory({ maxEntries: 1000 }), http: {} })
|
|
50
|
+
export class Application {}
|
|
50
51
|
|
|
51
52
|
// Named policies/tuning go through the advanced builder:
|
|
52
53
|
import { buildCacheModule } from "@/core/cache";
|
|
@@ -70,7 +71,8 @@ import { redisConfig } from "../config/redis.config"; // defineConfig("redis", {
|
|
|
70
71
|
})
|
|
71
72
|
export class AppInfra {}
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
@App({ modules: [AppModule], cache: memory(), infra: AppInfra, http: {} })
|
|
75
|
+
export class Application {}
|
|
74
76
|
```
|
|
75
77
|
|
|
76
78
|
`@CacheableRedis` / `@OutputRedisCache` find the backend through DI automatically.
|
package/core/cache/SPEC.md
CHANGED
|
@@ -163,10 +163,12 @@ flowchart TB
|
|
|
163
163
|
import { Cacheable, OutputCache, memory } from "@/core/cache";
|
|
164
164
|
import { runApp } from "@/core/app";
|
|
165
165
|
|
|
166
|
-
|
|
166
|
+
@App({
|
|
167
|
+
modules: [AppModule],
|
|
167
168
|
cache: memory(),
|
|
168
169
|
http: { prefix: "api" },
|
|
169
|
-
})
|
|
170
|
+
})
|
|
171
|
+
export class Application {}
|
|
170
172
|
```
|
|
171
173
|
|
|
172
174
|
Controller:
|
|
@@ -214,7 +216,8 @@ import { AppModule } from "@/app/modules/App.module";
|
|
|
214
216
|
})
|
|
215
217
|
class AppInfra {}
|
|
216
218
|
|
|
217
|
-
|
|
219
|
+
@App({ modules: [AppModule], cache: memory(), infra: AppInfra, http: {} })
|
|
220
|
+
export class Application {}
|
|
218
221
|
```
|
|
219
222
|
|
|
220
223
|
```ts
|
|
@@ -662,7 +665,7 @@ scoped(IUserStore, UserService, deps);
|
|
|
662
665
|
|
|
663
666
|
### 10.2. Module encapsulation
|
|
664
667
|
|
|
665
|
-
The application must install the cache module once with
|
|
668
|
+
The application must install the cache module once with `@App({ cache: memory() })`
|
|
666
669
|
or a root import of `memory()`. A feature module registers only its own service:
|
|
667
670
|
|
|
668
671
|
```ts
|
|
@@ -795,10 +798,12 @@ The order on a route with `@Authorize` and an output cache:
|
|
|
795
798
|
Connection: installing the cache module is enough. `memory()` (or `buildCacheModule(...)`) registers the output cache composer through the `ROUTE_MIDDLEWARE_COMPOSER` DI token, and `httpModule` picks it up:
|
|
796
799
|
|
|
797
800
|
```ts
|
|
798
|
-
|
|
801
|
+
@App({
|
|
802
|
+
modules: [AppModule],
|
|
799
803
|
cache: memory(), // registers the output cache ROUTE_MIDDLEWARE_COMPOSER
|
|
800
804
|
http: {},
|
|
801
|
-
})
|
|
805
|
+
})
|
|
806
|
+
export class Application {}
|
|
802
807
|
```
|
|
803
808
|
|
|
804
809
|
No manual `routeMiddlewareComposer` wiring is needed; an explicit
|
|
@@ -1040,11 +1045,13 @@ import { redisConfig } from "@/app/config/redis.config";
|
|
|
1040
1045
|
})
|
|
1041
1046
|
class AppInfra {}
|
|
1042
1047
|
|
|
1043
|
-
|
|
1048
|
+
@App({
|
|
1049
|
+
modules: [AppModule],
|
|
1044
1050
|
infra: AppInfra,
|
|
1045
1051
|
cache: memory(),
|
|
1046
|
-
http: {
|
|
1047
|
-
})
|
|
1052
|
+
http: { prefix: "api" },
|
|
1053
|
+
})
|
|
1054
|
+
export class Application {}
|
|
1048
1055
|
```
|
|
1049
1056
|
|
|
1050
1057
|
**Service** (one and the same provider for memory and distributed):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type InjectionToken, type ProviderDefinition } from "../../di";
|
|
2
|
+
import { DI } from "../../di/lowLevel";
|
|
2
3
|
import { ROUTE_MIDDLEWARE_COMPOSER, type RouteMiddlewareComposerRegistration } from "../../http";
|
|
3
4
|
import { LOGGER } from "../../kernel";
|
|
4
5
|
import { outputCacheMetaOf, resolveOutputCacheRequirement } from "../decorators/outputCacheMetadata";
|
|
@@ -22,7 +23,7 @@ const OUTPUT_CACHE_COMPOSER_ORDER = 100;
|
|
|
22
23
|
* decorator has no effect.
|
|
23
24
|
*/
|
|
24
25
|
export function missingCacheModuleCheck(): ProviderDefinition {
|
|
25
|
-
return
|
|
26
|
+
return DI.singleton(DI.factoryProviderWithResolver(ROUTE_MIDDLEWARE_COMPOSER, [], (resolver): RouteMiddlewareComposerRegistration => {
|
|
26
27
|
const cacheInstalled = resolver.tryResolve(ICache) !== undefined;
|
|
27
28
|
const logger = resolver.tryResolve(LOGGER);
|
|
28
29
|
const redis = createRouteOutputCacheComposerWithOptions({
|
|
@@ -43,11 +44,11 @@ export function missingCacheModuleCheck(): ProviderDefinition {
|
|
|
43
44
|
if (requirement === undefined || requirement.enabled === false) return [];
|
|
44
45
|
const message = `[cache] @OutputCache on ${controllerClass.name}.${String(methodName)} has no effect: `
|
|
45
46
|
+ "no cache module is installed, so every request runs the action. "
|
|
46
|
-
+ "Add `cache: memory()` to
|
|
47
|
+
+ "Add `cache: memory()` to @App.";
|
|
47
48
|
if (logger) logger.warn(message);
|
|
48
49
|
else console.warn(message);
|
|
49
50
|
return [];
|
|
50
51
|
},
|
|
51
52
|
};
|
|
52
|
-
});
|
|
53
|
+
}));
|
|
53
54
|
}
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DI,
|
|
3
2
|
CLASS_PROVIDER_HOOK,
|
|
4
3
|
OptionsValidationError,
|
|
5
4
|
singleton,
|
|
6
|
-
singletonFactoryWithResolver,
|
|
7
5
|
singletonValue,
|
|
8
6
|
type InjectionToken,
|
|
9
7
|
type Options,
|
|
10
8
|
type BazisModuleRef,
|
|
11
9
|
type ProviderDefinition,
|
|
12
10
|
} from "../../di";
|
|
11
|
+
import { DI } from "../../di/lowLevel";
|
|
13
12
|
import { ROUTE_MIDDLEWARE_COMPOSER, type RouteMiddlewareComposerRegistration } from "../../http";
|
|
14
13
|
import { HEALTH_CHECK } from "../../kernel";
|
|
15
14
|
import { cacheableClassProviderHook } from "../di/classProviderHook";
|
|
@@ -82,7 +81,7 @@ function buildCacheProviders(config: CacheModuleConfig): ProviderDefinition[] {
|
|
|
82
81
|
singletonValue(CLASS_PROVIDER_HOOK, cacheableClassProviderHook),
|
|
83
82
|
cacheOptionsProvider(config),
|
|
84
83
|
DI.singleton(DI.valueProvider(CACHE_POLICIES, policies)),
|
|
85
|
-
singleton(token, MemoryCache, [CACHE_OPTIONS] as const),
|
|
84
|
+
DI.singleton(DI.classProvider(token, MemoryCache, [CACHE_OPTIONS] as const)),
|
|
86
85
|
outputCacheComposerProvider(config),
|
|
87
86
|
];
|
|
88
87
|
|
|
@@ -115,7 +114,7 @@ function cacheOptionsProvider(config: CacheModuleConfig): ProviderDefinition {
|
|
|
115
114
|
/** Registers the `@OutputCache` / `@OutputRedisCache` middleware via the HTTP DI token. */
|
|
116
115
|
function outputCacheComposerProvider(config: CacheModuleConfig): ProviderDefinition {
|
|
117
116
|
const token = config.token ?? ICache;
|
|
118
|
-
return
|
|
117
|
+
return DI.singleton(DI.factoryProviderWithResolver(ROUTE_MIDDLEWARE_COMPOSER, [], (resolver) => ({
|
|
119
118
|
order: OUTPUT_CACHE_COMPOSER_ORDER,
|
|
120
119
|
compose: createRouteOutputCacheComposerWithOptions({
|
|
121
120
|
policies: config.policies ?? {},
|
|
@@ -129,7 +128,7 @@ function outputCacheComposerProvider(config: CacheModuleConfig): ProviderDefinit
|
|
|
129
128
|
// start fails and names the route instead of every request failing.
|
|
130
129
|
distributedEnabled: resolver.has(DISTRIBUTED_OUTPUT_CACHE),
|
|
131
130
|
}),
|
|
132
|
-
} satisfies RouteMiddlewareComposerRegistration));
|
|
131
|
+
} satisfies RouteMiddlewareComposerRegistration)));
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
function outputCacheSecurityBehavior(options: CacheOutputCacheOptions | undefined): "throw" | "warn" | "ignore" {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
DI,
|
|
3
2
|
type Class,
|
|
4
3
|
type ProviderDefinition,
|
|
5
4
|
type ProviderDependencyList,
|
|
6
5
|
type ServiceResolver,
|
|
7
6
|
type Token,
|
|
8
7
|
} from "../../di";
|
|
8
|
+
import { DI } from "../../di/lowLevel";
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
import { ICache } from "../ICache";
|
|
11
11
|
import { CACHE_POLICIES } from "../tokens/CACHE_POLICIES";
|
package/core/di/index.ts
CHANGED
|
@@ -32,7 +32,6 @@ export {
|
|
|
32
32
|
Global,
|
|
33
33
|
markGlobal,
|
|
34
34
|
Module,
|
|
35
|
-
DI,
|
|
36
35
|
registerModuleMetadataExpander,
|
|
37
36
|
registerModuleOwnedContributionValidator,
|
|
38
37
|
registerModuleOwnedProviderContributor,
|
|
@@ -43,9 +42,6 @@ export {
|
|
|
43
42
|
scoped,
|
|
44
43
|
singleton,
|
|
45
44
|
singletonAsyncFactory,
|
|
46
|
-
singletonAsyncFactoryWithResolver,
|
|
47
|
-
singletonFactory,
|
|
48
|
-
singletonFactoryWithResolver,
|
|
49
45
|
singletonValue,
|
|
50
46
|
transient,
|
|
51
47
|
type DiRegistrar,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level provider forms: providers built by hand, factories, dependency
|
|
3
|
+
* lists, classes bound to their dependencies. The framework's own modules and
|
|
4
|
+
* packages that extend it (a connector, a host) use them.
|
|
5
|
+
*
|
|
6
|
+
* An application does not need this entry point: it registers its services in
|
|
7
|
+
* the agreed forms of `bazis/core/di` — `scoped(IService, Service)`,
|
|
8
|
+
* `singleton(...)`, `transient(...)`, `singletonValue(TOKEN, value)`,
|
|
9
|
+
* `singletonAsyncFactory(TOKEN, [deps], factory)` — and codegen refuses the
|
|
10
|
+
* forms of this file in `@Module({ providers })` of an application.
|
|
11
|
+
*/
|
|
12
|
+
export { DI } from "./module/DI";
|
package/core/di/module/index.ts
CHANGED
|
@@ -1,95 +1,47 @@
|
|
|
1
1
|
import type { Class, Token } from "../token";
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
ProviderDefinition,
|
|
5
|
-
ProviderDependencyList,
|
|
6
|
-
ResolvedDeps,
|
|
7
|
-
} from "../provider";
|
|
2
|
+
import type { ProviderDefinition, ProviderDependencyList, ResolvedDeps } from "../provider";
|
|
8
3
|
import { DI } from "./DI";
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const factory = lifetime === "singleton" ? DI.singleton : lifetime === "scoped" ? DI.scoped : DI.transient;
|
|
17
|
-
return factory(DI.classProvider(provide, useClass, deps));
|
|
5
|
+
type Lifetime = "singleton" | "scoped" | "transient";
|
|
6
|
+
|
|
7
|
+
/** A class under its contract, or under itself. Its constructor dependencies come from codegen. */
|
|
8
|
+
function classProvider<T>(lifetime: Lifetime, provide: Token<T>, useClass: Class<T> | undefined): ProviderDefinition<T> {
|
|
9
|
+
const register = lifetime === "singleton" ? DI.singleton : lifetime === "scoped" ? DI.scoped : DI.transient;
|
|
10
|
+
return register(DI.classProvider(provide, useClass ?? (provide as Class<T>)));
|
|
18
11
|
}
|
|
19
12
|
|
|
13
|
+
/**
|
|
14
|
+
* One instance for the whole application.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* singleton(IClock, SystemClock) // under a contract
|
|
18
|
+
* singleton(Metrics) // the class is its own contract
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
20
21
|
export function singleton<T>(useClass: Class<T>): ProviderDefinition<T>;
|
|
21
|
-
export function singleton<T
|
|
22
|
-
|
|
23
|
-
useClass
|
|
24
|
-
deps?: D,
|
|
25
|
-
): ProviderDefinition<T>;
|
|
26
|
-
export function singleton<T, D extends ProviderDependencyList = []>(
|
|
27
|
-
provide: Token<T>,
|
|
28
|
-
useClass?: Class<T>,
|
|
29
|
-
deps?: D,
|
|
30
|
-
): ProviderDefinition<T> {
|
|
31
|
-
if (!useClass) {
|
|
32
|
-
const classOnly = provide as Class<T>;
|
|
33
|
-
return registerClassProvider("singleton", classOnly, classOnly);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return registerClassProvider("singleton", provide, useClass, deps);
|
|
22
|
+
export function singleton<T>(provide: Token<T>, useClass: Class<T>): ProviderDefinition<T>;
|
|
23
|
+
export function singleton<T>(provide: Token<T>, useClass?: Class<T>): ProviderDefinition<T> {
|
|
24
|
+
return classProvider("singleton", provide, useClass);
|
|
37
25
|
}
|
|
38
26
|
|
|
27
|
+
/** One instance per scope: an HTTP request, a job, a message. */
|
|
39
28
|
export function scoped<T>(useClass: Class<T>): ProviderDefinition<T>;
|
|
40
|
-
export function scoped<T
|
|
41
|
-
|
|
42
|
-
useClass
|
|
43
|
-
deps?: D,
|
|
44
|
-
): ProviderDefinition<T>;
|
|
45
|
-
export function scoped<T, D extends ProviderDependencyList = []>(
|
|
46
|
-
provide: Token<T>,
|
|
47
|
-
useClass?: Class<T>,
|
|
48
|
-
deps?: D,
|
|
49
|
-
): ProviderDefinition<T> {
|
|
50
|
-
if (!useClass) {
|
|
51
|
-
const classOnly = provide as Class<T>;
|
|
52
|
-
return registerClassProvider("scoped", classOnly, classOnly);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return registerClassProvider("scoped", provide, useClass, deps);
|
|
29
|
+
export function scoped<T>(provide: Token<T>, useClass: Class<T>): ProviderDefinition<T>;
|
|
30
|
+
export function scoped<T>(provide: Token<T>, useClass?: Class<T>): ProviderDefinition<T> {
|
|
31
|
+
return classProvider("scoped", provide, useClass);
|
|
56
32
|
}
|
|
57
33
|
|
|
34
|
+
/** A new instance each time it is asked for. */
|
|
58
35
|
export function transient<T>(useClass: Class<T>): ProviderDefinition<T>;
|
|
59
|
-
export function transient<T
|
|
60
|
-
|
|
61
|
-
useClass
|
|
62
|
-
deps?: D,
|
|
63
|
-
): ProviderDefinition<T>;
|
|
64
|
-
export function transient<T, D extends ProviderDependencyList = []>(
|
|
65
|
-
provide: Token<T>,
|
|
66
|
-
useClass?: Class<T>,
|
|
67
|
-
deps?: D,
|
|
68
|
-
): ProviderDefinition<T> {
|
|
69
|
-
if (!useClass) {
|
|
70
|
-
const classOnly = provide as Class<T>;
|
|
71
|
-
return registerClassProvider("transient", classOnly, classOnly);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return registerClassProvider("transient", provide, useClass, deps);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function singletonFactory<T, D extends ProviderDependencyList>(
|
|
78
|
-
provide: Token<T>,
|
|
79
|
-
deps: D,
|
|
80
|
-
useFactory: (...args: ResolvedDeps<D>) => T,
|
|
81
|
-
): ProviderDefinition<T> {
|
|
82
|
-
return DI.singleton(DI.factoryProvider(provide, deps, useFactory));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function singletonFactoryWithResolver<T, D extends ProviderDependencyList>(
|
|
86
|
-
provide: Token<T>,
|
|
87
|
-
deps: D,
|
|
88
|
-
useFactory: (resolver: ServiceResolver, ...args: ResolvedDeps<D>) => T,
|
|
89
|
-
): ProviderDefinition<T> {
|
|
90
|
-
return DI.singleton(DI.factoryProviderWithResolver(provide, deps, useFactory));
|
|
36
|
+
export function transient<T>(provide: Token<T>, useClass: Class<T>): ProviderDefinition<T>;
|
|
37
|
+
export function transient<T>(provide: Token<T>, useClass?: Class<T>): ProviderDefinition<T> {
|
|
38
|
+
return classProvider("transient", provide, useClass);
|
|
91
39
|
}
|
|
92
40
|
|
|
41
|
+
/**
|
|
42
|
+
* A resource that is created asynchronously, once, at startup: a connection, a
|
|
43
|
+
* loaded key. Everything else is a class with a constructor.
|
|
44
|
+
*/
|
|
93
45
|
export function singletonAsyncFactory<T, D extends ProviderDependencyList>(
|
|
94
46
|
provide: Token<T>,
|
|
95
47
|
deps: D,
|
|
@@ -98,14 +50,7 @@ export function singletonAsyncFactory<T, D extends ProviderDependencyList>(
|
|
|
98
50
|
return DI.singleton(DI.asyncFactoryProvider(provide, deps, useAsyncFactory));
|
|
99
51
|
}
|
|
100
52
|
|
|
101
|
-
|
|
102
|
-
provide: Token<T>,
|
|
103
|
-
deps: D,
|
|
104
|
-
useAsyncFactory: (resolver: ServiceResolver, ...args: ResolvedDeps<D>) => Promise<T>,
|
|
105
|
-
): ProviderDefinition<T> {
|
|
106
|
-
return DI.singleton(DI.asyncFactoryProviderWithResolver(provide, deps, useAsyncFactory));
|
|
107
|
-
}
|
|
108
|
-
|
|
53
|
+
/** A ready value under a token: a constant, a test double. */
|
|
109
54
|
export function singletonValue<T>(provide: Token<T>, useValue: T): ProviderDefinition<T> {
|
|
110
55
|
return DI.singleton(DI.valueProvider(provide, useValue));
|
|
111
56
|
}
|
package/core/generatedRuntime.ts
CHANGED
|
@@ -139,8 +139,14 @@ async function loadRuntimeOnce(): Promise<void> {
|
|
|
139
139
|
* (`bazis new --link-framework`) would otherwise find that checkout's own
|
|
140
140
|
* `src/generated` next to its sources. Framework sources inside the project
|
|
141
141
|
* (`src/bazis`) find the same directory either way.
|
|
142
|
+
*
|
|
143
|
+
* A compiled binary looks nowhere: its generated code is inside it and comes
|
|
144
|
+
* through `registerBazisGeneratedRuntime()` of the entry point. Reading
|
|
145
|
+
* `src/generated` of whatever directory the binary was started in would run
|
|
146
|
+
* another project's code.
|
|
142
147
|
*/
|
|
143
148
|
async function importGeneratedModule<T>(name: string): Promise<T | undefined> {
|
|
149
|
+
if (isCompiledBinary()) return undefined;
|
|
144
150
|
const candidates = [path.join(process.cwd(), "src/generated/bazis", name), `../../generated/bazis/${name}`];
|
|
145
151
|
for (const candidate of candidates) {
|
|
146
152
|
try {
|
|
@@ -152,6 +158,11 @@ async function importGeneratedModule<T>(name: string): Promise<T | undefined> {
|
|
|
152
158
|
return undefined;
|
|
153
159
|
}
|
|
154
160
|
|
|
161
|
+
/** Whether this code runs from a `bun build --compile` binary: its modules live in Bun's embedded file system. */
|
|
162
|
+
function isCompiledBinary(): boolean {
|
|
163
|
+
return import.meta.url.includes("/$bunfs/") || import.meta.url.includes("~BUN");
|
|
164
|
+
}
|
|
165
|
+
|
|
155
166
|
function isOptionalGeneratedModuleMissing(error: unknown, generatedPath: string): boolean {
|
|
156
167
|
const message = error instanceof Error ? error.message : String(error);
|
|
157
168
|
return message.includes("Cannot find module") && message.includes(generatedPath);
|
package/core/grpc/Grpc.module.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DiContainer, HOSTED_SERVICE, Module, SERVICE_PROVIDER, type BazisModule } from "../di";
|
|
2
|
+
import { DI } from "../di/lowLevel";
|
|
2
3
|
import type { GrpcModuleOptions } from "./contracts";
|
|
3
4
|
import { GrpcServer } from "./GrpcServer";
|
|
4
5
|
import { collectGrpcControllers, GRPC_CONTROLLERS } from "./moduleControllers";
|
package/core/grpc/grpcClients.ts
CHANGED
|
@@ -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 type { ConfigDefinition } from "../kernel";
|
|
3
4
|
import type { GrpcClientOptions } from "./clientContracts";
|
|
4
5
|
import { GrpcClientBase } from "./GrpcClientBase";
|
|
@@ -208,7 +208,7 @@ export function resolveAuthorizeMeta(ctor: object, methodName: string | symbol):
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
* The checks a route runs: the server's default checks (
|
|
211
|
+
* The checks a route runs: the server's default checks (`@App({ http:
|
|
212
212
|
* { authorize } })`) first, then the route's own `@Authorize`; each function
|
|
213
213
|
* once. `undefined` — the route is open: `@AllowAnonymous`, or no checks at all.
|
|
214
214
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Class } from "../../di";
|
|
2
2
|
import type { ActionFilterHooks, HttpMiddleware } from "../Middleware/types";
|
|
3
|
+
import type { HttpResult } from "../Results/HttpResult";
|
|
3
4
|
|
|
4
5
|
// Same one-line polyfill as the validation module: Bun executes TC39
|
|
5
6
|
// decorators natively, Symbol.metadata may be missing in the runtime.
|
|
@@ -26,6 +27,8 @@ export interface CatchDeclaration {
|
|
|
26
27
|
export interface ActionMeta {
|
|
27
28
|
routes: RouteDeclaration[];
|
|
28
29
|
httpCode?: number;
|
|
30
|
+
/** `nullAs` of the route options: the result that stands for `null`. */
|
|
31
|
+
nullAs?: () => HttpResult;
|
|
29
32
|
produces?: string;
|
|
30
33
|
consumes?: string;
|
|
31
34
|
version?: string;
|