bazis 0.97.3 → 0.97.4

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.
@@ -112,6 +112,23 @@ export class ServiceProvider implements ServiceResolver {
112
112
  return this.resolveSingleAsync(token, key, this.lifetime.root, []);
113
113
  }
114
114
 
115
+ /**
116
+ * Creates every singleton registered with an async factory (keyed ones
117
+ * included), in registration order. Afterwards `resolve` and constructor
118
+ * injection return the cached instances. The kernel calls it at startup, so a
119
+ * failing factory stops the start instead of failing every request.
120
+ */
121
+ public async initializeAsyncSingletons(): Promise<void> {
122
+ this.lifetime.assertLive(this.lifetime.root);
123
+ for (const group of this.registry.groups()) {
124
+ for (const registration of group) {
125
+ if (registration.lifetime === "singleton" && isAsyncFactoryProvider(registration.provider)) {
126
+ await this.resolveRegistrationAsync(registration, registration.key, this.lifetime.root, [], undefined);
127
+ }
128
+ }
129
+ }
130
+ }
131
+
115
132
  public tryResolve<T>(token: Token<T>, key?: ServiceKey): T | undefined {
116
133
  return this.tryResolveForScope(token, key, this.lifetime.root);
117
134
  }
@@ -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.97.3" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.97.4" }, 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");
@@ -13,6 +13,7 @@ import type { UnhandledErrorPolicy } from "./types";
13
13
  import { redactSensitive } from "../../library/redaction";
14
14
  import { validateTimeout } from "./internal/validateTimeout";
15
15
  import { awaitShutdown } from "./internal/awaitShutdown";
16
+ import { awaitAbortable } from "./internal/awaitAbortable";
16
17
  import { reportDiagnosticFailure } from "./internal/reportDiagnosticFailure";
17
18
 
18
19
  export interface KernelTimings {
@@ -97,6 +98,9 @@ export class Kernel {
97
98
  );
98
99
  }
99
100
  try {
101
+ // Async singletons first: hosted services and controllers take them
102
+ // through constructors, which resolve synchronously.
103
+ await awaitAbortable(this.container.initializeAsyncSingletons(), abortController.signal);
100
104
  await this.coordinator.start(abortController.signal, this.options.shutdownTimeoutMs);
101
105
  coordinatorStarted = true;
102
106
  this.startupMs = performance.now() - this.timings.buildStartedAt;
@@ -156,6 +156,8 @@ interface CollectedDependency { readonly name: string; readonly target?: ts.Clas
156
156
  interface ConstructorDependencies {
157
157
  readonly deps: CollectedDependency[];
158
158
  readonly unsupportedInheritedParameter?: string;
159
+ /** An own constructor parameter whose type cannot name a DI dependency (`string`, `number`, an inline type). */
160
+ readonly unsupportedParameter?: { readonly index: number; readonly name: string; readonly type: string };
159
161
  }
160
162
  let classDeps = new Map<ts.ClassDeclaration, readonly CollectedDependency[]>();
161
163
  let inferredDiClasses = new Set<ts.ClassDeclaration>();
@@ -796,6 +798,10 @@ function collectClassDeps(source: ts.SourceFile): void {
796
798
  const visit = (node: ts.Node): void => {
797
799
  if (ts.isClassDeclaration(node) && node.name) {
798
800
  const result = readConstructorTypeNames(node);
801
+ if (result.unsupportedParameter !== undefined && needsInferredDiDeps(node)) {
802
+ const { index, name, type } = result.unsupportedParameter;
803
+ fatalErrors.push(`BAZIS_DI_DEPENDENCY_UNKNOWN: ${sourceLocation(node)}: constructor parameter ${index + 1} "${name}" of "${node.name.text}" has type "${type}", which cannot be injected: DI resolves dependencies by class, contract or token, and a plain type does not say which value to inject. Declare a token with the same name as a type alias (export type Prefix = string; export const Prefix = createToken<Prefix>("Prefix")) and register a value, or pass the deps explicitly: scoped(${node.name.text}, ${node.name.text}, [TOKEN] as const).`);
804
+ }
799
805
  if (result.unsupportedInheritedParameter !== undefined && needsInferredDiDeps(node)) {
800
806
  fatalErrors.push(`BAZIS_DI_CONSTRUCTOR_UNRESOLVED: ${sourceLocation(node)}: cannot infer inherited constructor parameter "${result.unsupportedInheritedParameter}" for "${node.name.text}". The parameter needs a runtime DI token or an explicit value/factory binding.`);
801
807
  }
@@ -984,7 +990,7 @@ function readConstructorTypeNames(node: ts.ClassDeclaration): ConstructorDepende
984
990
  const paramType = param.type;
985
991
  const refName = paramType ? getDependencyTypeName(paramType) : undefined;
986
992
  if (!refName || BUILTIN_DEPENDENCY_NAMES.has(refName)) {
987
- return { deps: [] };
993
+ return { deps: [], unsupportedParameter: { index, name: param.name.getText(), type: paramType?.getText() ?? "(no type)" } };
988
994
  }
989
995
 
990
996
  // `Lazy<X>` is a deferred dependency on X: encode it with a marker prefix
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.97.3",
3
+ "version": "0.97.4",
4
4
  "description": "Modular backend framework for Bun in TypeScript: DI wired by codegen, HTTP, a PostgreSQL ORM, JWT, WebSocket, agents and a CLI.",
5
5
  "license": "MIT",
6
6
  "author": "Alexander Alenin",