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.
Files changed (132) hide show
  1. package/README.md +7 -4
  2. package/cli/generateProject.ts +11 -18
  3. package/cli/main.ts +9 -1
  4. package/cli/parseCli.ts +9 -0
  5. package/cli/templates/module.ts +16 -19
  6. package/cli/templates/passport.ts +2 -2
  7. package/cli/upgrade/markColumns.ts +151 -0
  8. package/cli/upgrade/runUpgrade.ts +57 -0
  9. package/core/access/Access.module.ts +1 -1
  10. package/core/access/catalog/moduleFields.ts +41 -4
  11. package/core/access/catalog/permissions.ts +10 -0
  12. package/core/access/check/Access.ts +5 -5
  13. package/core/access/check/can.ts +2 -1
  14. package/core/access/index.ts +11 -1
  15. package/core/access/roles/roles.ts +4 -2
  16. package/core/access/testing/index.ts +26 -4
  17. package/core/agent/ToolRunner.ts +7 -1
  18. package/core/agent/defineTool.ts +109 -0
  19. package/core/agent/index.ts +8 -3
  20. package/core/agent/moduleContributions-v1.ts +3 -10
  21. package/core/agent/session/entities.ts +45 -45
  22. package/core/app/index.ts +1 -1
  23. package/core/app/messages.ts +3 -3
  24. package/core/app/runApp.ts +90 -26
  25. package/core/cache/ICache.ts +1 -1
  26. package/core/cache/README.md +4 -2
  27. package/core/cache/SPEC.md +16 -9
  28. package/core/cache/http/missingCacheModuleCheck.ts +5 -4
  29. package/core/cache/internal/buildCacheProviders.ts +4 -5
  30. package/core/cache/providers/cachedProviders.ts +1 -1
  31. package/core/di/index.ts +0 -4
  32. package/core/di/lowLevel.ts +12 -0
  33. package/core/di/module/index.ts +0 -3
  34. package/core/di/module/shortcuts.ts +31 -86
  35. package/core/generatedRuntime.ts +11 -0
  36. package/core/grpc/Grpc.module.ts +2 -1
  37. package/core/grpc/grpcClients.ts +2 -1
  38. package/core/http/Authorization/metadata.ts +1 -1
  39. package/core/http/Decorators/metadata.ts +3 -0
  40. package/core/http/Decorators/routes.ts +12 -0
  41. package/core/http/Errors/HttpError.ts +16 -2
  42. package/core/http/Http.config.ts +22 -0
  43. package/core/http/HttpContext/HttpContext.ts +1 -1
  44. package/core/http/HttpServer.ts +29 -9
  45. package/core/http/Middleware/errorHandler.ts +16 -1
  46. package/core/http/OpenApi/openApiDocument.ts +44 -5
  47. package/core/http/Results/normalize.ts +38 -1
  48. package/core/http/Results/results.ts +5 -2
  49. package/core/http/Routing/RouterBuilder.ts +27 -2
  50. package/core/http/httpModule.ts +10 -8
  51. package/core/http/index.ts +4 -0
  52. package/core/http/middlewareTokens.ts +12 -0
  53. package/core/http/options.ts +5 -1
  54. package/core/http-client/httpClients.ts +2 -1
  55. package/core/infra/Infra.ts +3 -3
  56. package/core/infra/connectors/codex/CodexAppServerClient.ts +1 -1
  57. package/core/infra/connectors/codex/contracts.ts +3 -3
  58. package/core/infra/connectors/llm.ts +2 -1
  59. package/core/infra/connectors/postgres.ts +4 -2
  60. package/core/infra/connectors/redis.ts +2 -1
  61. package/core/infra/index.ts +1 -1
  62. package/core/kernel/KernelBuilder.ts +2 -1
  63. package/core/kernel/config/ConfigRegistry.ts +2 -1
  64. package/core/kernel/config/addConfigOptions.ts +2 -1
  65. package/core/kernel/config/defineConfig.ts +56 -20
  66. package/core/kernel/events/EventBus.ts +1 -0
  67. package/core/kernel/events/eventSubscribers.ts +1 -1
  68. package/core/kernel/events/onEvent.ts +2 -1
  69. package/core/kernel/index.ts +3 -0
  70. package/core/orm/buildOrmProviders.ts +4 -2
  71. package/core/orm/contextEntities.ts +36 -0
  72. package/core/orm/databaseConnector.ts +9 -3
  73. package/core/orm/databaseHttpError.ts +21 -0
  74. package/core/orm/encryptedWith.ts +1 -1
  75. package/core/orm/index.ts +12 -1
  76. package/core/orm/ormModule.ts +19 -8
  77. package/core/orm/ownedStoreContributions.ts +2 -1
  78. package/core/orm/registerRepositories.ts +2 -1
  79. package/core/scripts/di-generate.ts +113 -12
  80. package/core/testing/index.ts +51 -11
  81. package/core/tools/StandardTools.module.ts +1 -1
  82. package/core/tools/contracts/Tools.config.ts +13 -7
  83. package/core/tools/generated/deps.ts +3 -3
  84. package/core/tools/http/HttpRequest.tool.ts +1 -2
  85. package/core/tools/http/publicAddress.ts +2 -2
  86. package/core/tools/index.ts +1 -1
  87. package/core/tools/orm/OrmEntity.references.ts +7 -8
  88. package/core/tools/orm/ormEntityTools.ts +54 -28
  89. package/core/websocket/DESIGN.md +5 -3
  90. package/core/websocket/middleware.ts +1 -1
  91. package/core/websocket/websocketModule.ts +3 -3
  92. package/core/workflow/contracts/Workflow.config.ts +5 -5
  93. package/core/workflow/definitions_module/WorkflowDefinitions.module.ts +3 -4
  94. package/core/workflow/definitions_module/code/defineProcess.ts +213 -11
  95. package/core/workflow/definitions_module/code/inspectProcesses.ts +12 -2
  96. package/core/workflow/definitions_module/http/Processes.controller.ts +2 -2
  97. package/core/workflow/definitions_module/internal/PublishedVersions.ts +39 -0
  98. package/core/workflow/definitions_module/model/WfProcess.model.ts +24 -11
  99. package/core/workflow/definitions_module/model/WfVersion.model.ts +7 -1
  100. package/core/workflow/definitions_module/services/IWorkflowDefinitions.service.ts +4 -1
  101. package/core/workflow/definitions_module/services/WorkflowDefinitions.service.ts +17 -7
  102. package/core/workflow/generated/deps.ts +2 -1
  103. package/core/workflow/index.ts +23 -1
  104. package/core/workflow/internal/WorkflowLanguages.ts +1 -1
  105. package/core/workflow/internal/http.ts +2 -2
  106. package/core/workflow/runtime_module/background/WorkflowWorker.ts +6 -2
  107. package/core/workflow/runtime_module/http/Instances.controller.ts +1 -1
  108. package/core/workflow/runtime_module/internal/WorkflowJobs.ts +43 -1
  109. package/core/workflow/runtime_module/model/WfHistory.model.ts +13 -6
  110. package/core/workflow/runtime_module/model/WfInstance.model.ts +30 -10
  111. package/core/workflow/runtime_module/model/WfJob.model.ts +22 -10
  112. package/core/workflow/runtime_module/model/WfSubscription.model.ts +8 -3
  113. package/core/workflow/runtime_module/model/WfToken.model.ts +16 -7
  114. package/core/workflow/runtime_module/services/IWorkflowRuntime.service.ts +10 -6
  115. package/core/workflow/runtime_module/services/WorkflowRuntime.service.ts +5 -2
  116. package/core/workflow/tasks_module/model/WfTask.model.ts +25 -9
  117. package/core/workflow/testing/index.ts +46 -4
  118. package/index.ts +4 -3
  119. package/library/openapi/codegen.ts +2 -2
  120. package/library/orm/DbContext.ts +37 -1
  121. package/library/orm/Metadata/ModelBuilder.ts +23 -7
  122. package/library/orm/Metadata/decorators.ts +23 -10
  123. package/library/orm/Metadata/types.ts +2 -0
  124. package/library/orm/Transactions/TransactionScopeCoordinator.ts +3 -3
  125. package/library/orm/errors.ts +39 -1
  126. package/library/orm/index.ts +1 -0
  127. package/library/terminal/messages.ts +6 -6
  128. package/library/validation/README.md +1 -1
  129. package/library/validation/SPEC.md +1 -1
  130. package/library/workflow/index.ts +1 -0
  131. package/library/workflow/nodeConfigs.ts +130 -0
  132. package/package.json +2 -1
@@ -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
- * Application start options. Everything is optional: without `http` and `grpc` the app
39
- * starts as a worker/CLI (kernel and hosted services only).
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 RunAppOptions {
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
- * (port 3000) or an object with settings (port/prefix/cors/...).
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
- * await runApp(AppModule, { language: "ru", http: { port: 3000 } });
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
- * await runApp(AppModule, { language: { default: "ru", supported: ["ru", "en"] } });
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
- * await runApp(AppModule, { infra: AppInfra, http: { port: 3000 } });
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
- * await runApp(AppModule, { cache: memory({ maxEntries: 1000 }), http: {} });
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("runApp: websocket options are set, but no module declares gateways. Add gateways: [ChatGateway] to the module that owns the gateway.");
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: RunAppOptions["ui"],
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: RunAppOptions["ui"],
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, simpler than `NestFactory`.
336
+ * The single application entry point: starts what `@App` declares.
275
337
  *
276
338
  * ```ts
277
- * // main.ts
278
- * import { runApp } from "@/core/app";
279
- * import { AppModule } from "./modules/App.module";
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 runApp(AppModule, { http: { port: 3000, prefix: "api" } });
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(root: BazisModuleRef, options: RunAppOptions = {}): Promise<number> {
353
+ export async function runApp(app: AppClass): Promise<number> {
292
354
  await loadBazisGeneratedRuntime();
293
- let app: BazisModuleRef;
355
+ let options: AppSettings;
356
+ let composed: BazisModuleRef;
294
357
  try {
295
- app = composeApp(root, options);
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(app, options.kernel, (builder) => {
301
- builder.useApplicationRoot(root);
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: RunAppOptions = {}): BazisModuleRef {
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");
@@ -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 the runApp options (startTestApp takes the same options)."
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. */
@@ -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
- await runApp(AppModule, { cache: memory({ maxEntries: 1000 }), http: {} });
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
- await runApp(AppModule, { cache: memory(), infra: AppInfra, http: {} });
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.
@@ -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
- await runApp(AppModule, {
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
- await runApp(AppModule, { cache: memory(), infra: AppInfra, http: {} });
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 `runApp(AppModule, { cache: memory() })`
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
- await runApp(AppModule, {
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
- await runApp(AppModule, {
1048
+ @App({
1049
+ modules: [AppModule],
1044
1050
  infra: AppInfra,
1045
1051
  cache: memory(),
1046
- http: { port: 3000, prefix: "api" },
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 { singletonFactoryWithResolver, type InjectionToken, type ProviderDefinition } from "../../di";
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 singletonFactoryWithResolver(ROUTE_MIDDLEWARE_COMPOSER, [], (resolver): RouteMiddlewareComposerRegistration => {
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 the runApp options.";
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 singletonFactoryWithResolver(ROUTE_MIDDLEWARE_COMPOSER, [], (resolver) => ({
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";
@@ -34,9 +34,6 @@ export {
34
34
  scoped,
35
35
  singleton,
36
36
  singletonAsyncFactory,
37
- singletonAsyncFactoryWithResolver,
38
- singletonFactory,
39
- singletonFactoryWithResolver,
40
37
  singletonValue,
41
38
  transient,
42
39
  } from "./shortcuts";
@@ -1,95 +1,47 @@
1
1
  import type { Class, Token } from "../token";
2
- import type { ServiceResolver } from "../types";
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
- function registerClassProvider<T, D extends ProviderDependencyList>(
11
- lifetime: "singleton" | "scoped" | "transient",
12
- provide: Token<T>,
13
- useClass: Class<T>,
14
- deps?: D,
15
- ): ProviderDefinition<T> {
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, D extends ProviderDependencyList = []>(
22
- provide: Token<T>,
23
- useClass: Class<T>,
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, D extends ProviderDependencyList = []>(
41
- provide: Token<T>,
42
- useClass: Class<T>,
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, D extends ProviderDependencyList = []>(
60
- provide: Token<T>,
61
- useClass: Class<T>,
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
- export function singletonAsyncFactoryWithResolver<T, D extends ProviderDependencyList>(
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
  }
@@ -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);
@@ -1,4 +1,5 @@
1
- import { DI, DiContainer, HOSTED_SERVICE, Module, SERVICE_PROVIDER, type BazisModule } from "../di";
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";
@@ -1,4 +1,5 @@
1
- import { DI, registerModuleMetadataExpander, type BazisModuleMetadata, type BazisModuleRef, type ServiceResolver } from "../di";
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 (`runApp({ http:
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;