bazis 0.100.0 → 0.100.2

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/cli/codegen.ts CHANGED
@@ -86,13 +86,12 @@ export function renderCodegen(ui: CliOutput, report: CodegenReport, cwd: string,
86
86
  commandFailed(ui.err, t.failure.codegen(report.errors.length), report.errors.map((error) => codegenProblem(error, cwd, ui.err)), 1, footer);
87
87
  return;
88
88
  }
89
+ // Files always; the rest only when the project has them.
89
90
  const counts = target === undefined ? [] : [
90
91
  s.num(target.sources, t.noun("file", target.sources)),
91
- s.num(target.deps, t.noun("dep", target.deps)),
92
- s.num(target.controllers, t.noun("controller", target.controllers)),
93
- s.num(target.requestModels, t.noun("requestModel", target.requestModels)),
94
- s.num(target.listModels, t.noun("list", target.listModels)),
95
- s.num(target.tools, t.noun("tool", target.tools)),
92
+ ...([[target.deps, "dep"], [target.controllers, "controller"], [target.requestModels, "requestModel"], [target.listModels, "list"], [target.tools, "tool"]] as const)
93
+ .filter(([value]) => value > 0)
94
+ .map(([value, noun]) => s.num(value, t.noun(noun, value))),
96
95
  ];
97
96
  s.step("ok", t.step.codegen, counts.join(s.dot), time);
98
97
  const directories = new Map<string, string[]>();
@@ -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.100.0" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.100.2" }, 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");
@@ -3,6 +3,7 @@ import type { AppConfig, ConfigRegistry, Secret } from "../../kernel";
3
3
  import { reader, requireValue, settingNames } from "../connectorConfig";
4
4
  import { errorMessage, InfraError, type InfraConnector } from "../InfraConnector";
5
5
  import { redactSensitive, redactSensitiveText } from "../../../library/redaction";
6
+ import { identifyConnector } from "../connectorIdentity";
6
7
 
7
8
  /** Default cluster request timeout (ms): fail fast instead of hanging. */
8
9
  const DEFAULT_OPENSEARCH_TIMEOUT_MS = 5000;
@@ -295,7 +296,7 @@ export function openSearchConnect<T extends OpenSearchConfigShape>(
295
296
  options: OpenSearchConnectorOptions = {},
296
297
  ): InfraConnector<OpenSearchClient> {
297
298
  const settings = new WeakMap<OpenSearchClient, string[]>();
298
- return {
299
+ return identifyConnector({
299
300
  token: options.token ?? IOpenSearch,
300
301
  config,
301
302
  create(configs) {
@@ -334,5 +335,5 @@ export function openSearchConnect<T extends OpenSearchConfigShape>(
334
335
  healthCheck(client, signal) {
335
336
  return client.ping(signal);
336
337
  },
337
- };
338
+ }, "opensearch");
338
339
  }
@@ -3,6 +3,7 @@ import { createToken, type InjectionToken } from "../../di";
3
3
  import type { AppConfig, ConfigRegistry, Secret } from "../../kernel";
4
4
  import { reader, requireValue, settingNames } from "../connectorConfig";
5
5
  import { errorMessage, InfraError, type InfraConnector } from "../InfraConnector";
6
+ import { identifyConnector } from "../connectorIdentity";
6
7
 
7
8
  /**
8
9
  * PostgreSQL client (native Bun `SQL`, no external dependencies) of the
@@ -83,7 +84,7 @@ export function postgres<T extends PostgresConfigShape>(config: AppConfig<T>, op
83
84
  throw new InfraError("postgres shutdownTimeoutMs must be an integer 0..2147483647");
84
85
  }
85
86
  const described = new WeakMap<SQL, { options: { hostname?: string; port?: number; database?: string; username?: string; max?: number }; settings: string[] }>();
86
- return {
87
+ return identifyConnector({
87
88
  token: options.token ?? IPostgres,
88
89
  config,
89
90
  create(configs) {
@@ -118,7 +119,7 @@ export function postgres<T extends PostgresConfigShape>(config: AppConfig<T>, op
118
119
  return false;
119
120
  }
120
121
  },
121
- };
122
+ }, "postgres");
122
123
  }
123
124
 
124
125
  /** `postgres@127.0.0.1:5432/app`: user, host, port and database, never the password. */
@@ -12,6 +12,7 @@ import { RedisDistributedCacheBackend, type RedisDistributedCacheTuning } from "
12
12
  import { reader, requireValue, settingNames } from "../connectorConfig";
13
13
  import { errorMessage, InfraError, type InfraConnector } from "../InfraConnector";
14
14
  import { redactSensitiveText } from "../../../library/redaction";
15
+ import { identifyConnector } from "../connectorIdentity";
15
16
 
16
17
  const DEFAULT_REDIS_CONNECT_TIMEOUT_MS = 5_000;
17
18
 
@@ -115,7 +116,7 @@ export function redisConnect<T extends RedisConfigShape>(
115
116
  ? undefined
116
117
  : distributedCacheProviders(cache === "distributed" ? {} : withoutMode(cache), token);
117
118
 
118
- return {
119
+ return identifyConnector({
119
120
  token,
120
121
  config,
121
122
  providers: cacheProviders,
@@ -166,7 +167,7 @@ export function redisConnect<T extends RedisConfigShape>(
166
167
  return false;
167
168
  }
168
169
  },
169
- };
170
+ }, "redis");
170
171
  }
171
172
 
172
173
  function redisUrl(value: unknown): string {
@@ -113,7 +113,7 @@ export class StartupScreen {
113
113
  if (schemas.length > 0) {
114
114
  const operations = schemas.flatMap((schema) => [...schema.migrated.map((name) => `migration ${name}`), ...schema.applied]);
115
115
  s.section(t.section.schema, s.muted([...new Set(schemas.map((schema) => schema.mode))].join(", ")));
116
- s.step("ok", t.step.schema, operations.length === 0 ? s.muted(t.schema.none) : [s.num(operations.length, t.noun("operation", operations.length)), s.num(schemas.length, t.noun("schema", schemas.length))].join(s.dot), formatDuration(stageMs(kernel, "schema")));
116
+ s.step("ok", t.step.schema, operations.length === 0 ? s.muted(t.schema.none) : [s.num(operations.length, t.noun("operation", operations.length)), s.num(schemas.length, t.noun("context", schemas.length))].join(s.dot), formatDuration(stageMs(kernel, "schema")));
117
117
  const shown = operations.slice(0, 12).map((operation) => ({ text: `${s.strong("+", PALETTE.lime)} ${s.text(operation)}` }));
118
118
  if (operations.length > 12) shown.push({ text: s.muted(`… ${t.common.plus} ${operations.length - 12}`) });
119
119
  s.tree(shown, this.indent);
@@ -1,9 +1,10 @@
1
1
  import { inspect } from "node:util";
2
2
  import { redactSensitive, redactSensitiveText } from "../../library/redaction";
3
+ import { StartupFailuresError } from "./errors";
3
4
 
4
5
  const MAX_CAUSES = 5;
5
6
  /** Properties already printed in the header, the frames or the cause chain. */
6
- const SHOWN = new Set(["name", "message", "stack", "cause", "code", "errno", "errors"]);
7
+ const SHOWN = new Set(["name", "message", "stack", "cause", "code", "errno", "errors", "failures"]);
7
8
  const INSPECT = { depth: null, colors: false, customInspect: false, getters: false, maxArrayLength: null, maxStringLength: null } as const;
8
9
 
9
10
  /**
@@ -28,7 +29,9 @@ export function formatStartupFailure(error: unknown): string {
28
29
  if (current instanceof AggregateError) {
29
30
  for (const inner of current.errors) lines.push(` - ${inner instanceof Error ? `${inner.name}: ${inner.message}` : String(inner)}`);
30
31
  }
31
- lines.push(...(current.stack ?? "").split("\n").filter((line) => /^\s+at /.test(line)));
32
+ // Services that failed together: the list above is the report; the
33
+ // aggregate's own frames are the kernel's, not the application's.
34
+ if (!(current instanceof StartupFailuresError)) lines.push(...(current.stack ?? "").split("\n").filter((line) => /^\s+at /.test(line)));
32
35
  const details = Object.fromEntries(Object.entries(current).filter(([name]) => !SHOWN.has(name)));
33
36
  if (Object.keys(details).length > 0) lines.push(inspect(redactSensitive(details), INSPECT));
34
37
  current = current.cause;
@@ -1,6 +1,6 @@
1
1
  import type { HostedService, HostedServicePlanValidator } from "../di";
2
2
  import { SchemaAdmissionError } from "../../library/orm";
3
- import { isOrmProviderLifecycle, isStrictSchemaPrerequisiteLifecycle } from "./strictSchemaHostedIdentity";
3
+ import { describePhaseConflict, isOrmProviderLifecycle, isStrictSchemaPrerequisiteLifecycle } from "./strictSchemaHostedIdentity";
4
4
 
5
5
  /** Stateless strategy shared by ORM lifecycles; it owns no connection or application state. */
6
6
  export const ormHostedPlanValidator: HostedServicePlanValidator = Object.freeze({ validate: validateOrmHostedPlan });
@@ -18,7 +18,7 @@ function validateOrmHostedPlan(services: readonly HostedService[]): void {
18
18
  || (service as { __bazisOrmOwnedStoreAdmission?: unknown }).__bazisOrmOwnedStoreAdmission === true
19
19
  || (service as { __bazisOrmLegacyLifecycle?: unknown }).__bazisOrmLegacyLifecycle === true
20
20
  || isStrictSchemaPrerequisiteLifecycle(service);
21
- if (!framework && (service.phase ?? 0) < 0) throw new SchemaAdmissionError("ORM_SCHEMA_HOSTED_PHASE_CONFLICT", "Application hosted services must use phase 0 or later with ORM owned-store admission.");
21
+ if (!framework && (service.phase ?? 0) < 0) throw new SchemaAdmissionError("ORM_SCHEMA_HOSTED_PHASE_CONFLICT", `Application hosted services must use phase 0 or later with ORM owned-store admission: ${describePhaseConflict(service)}`);
22
22
  }
23
23
  }
24
24
  const strict = services.filter((service): service is HostedService & { readonly __bazisSchemaAdmission: { readonly owner?: string; readonly unit: readonly string[]; readonly tables: readonly string[]; readonly foreignKeys: readonly { readonly source: string; readonly target: string }[] } } =>
@@ -59,7 +59,7 @@ function validateOrmHostedPlan(services: readonly HostedService[]): void {
59
59
  || (service as { __bazisSchemaAdmission?: unknown }).__bazisSchemaAdmission !== undefined
60
60
  || (service as { __bazisOrmOwnedStoreAdmission?: unknown }).__bazisOrmOwnedStoreAdmission === true;
61
61
  if (!framework && (service.phase ?? 0) < 0) {
62
- throw new SchemaAdmissionError("ORM_SCHEMA_HOSTED_PHASE_CONFLICT", "Application hosted services must use phase 0 or later with schema admission.");
62
+ throw new SchemaAdmissionError("ORM_SCHEMA_HOSTED_PHASE_CONFLICT", `Application hosted services must use phase 0 or later with schema admission: ${describePhaseConflict(service)}`);
63
63
  }
64
64
  }
65
65
  }
@@ -9,9 +9,24 @@ export function isOrmProviderLifecycle(lifecycle: HostedService): boolean {
9
9
  return connector?.token === DATABASE_PROVIDER && lifecycle.phase === -110;
10
10
  }
11
11
 
12
+ /**
13
+ * Unchanged framework connectors that never touch the schema: they may start
14
+ * at phase -100, after schema admission and before application services.
15
+ */
16
+ const PREREQUISITE_ROLES = new Set(["llm", "agent-session-checkpoint-protection", "postgres", "redis", "opensearch"]);
17
+
12
18
  /** Only these unchanged factory-owned resources may precede application services. */
13
19
  export function isStrictSchemaPrerequisiteLifecycle(lifecycle: HostedService): boolean {
14
20
  if ((lifecycle.phase ?? 0) !== -100) return false;
15
21
  const role = getLifecycleConnectorRole(lifecycle);
16
- return role === "llm" || role === "agent-session-checkpoint-protection";
22
+ return role !== undefined && PREREQUISITE_ROLES.has(role);
23
+ }
24
+
25
+ /** The service a phase conflict is about and what to do with it. */
26
+ export function describePhaseConflict(service: HostedService): string {
27
+ const name = service.diagnosticName ?? service.constructor?.name ?? "a hosted service";
28
+ const connector = getLifecycleConnector(service) !== undefined;
29
+ return connector
30
+ ? `${name} (phase ${service.phase ?? 0}) is not a framework connector; a custom or changed connector starts at phase 0 or later: set phase: 0 on it.`
31
+ : `${name} has phase ${service.phase ?? 0}; use phase 0 or later.`;
17
32
  }
@@ -31,6 +31,7 @@ const NOUNS_EN = {
31
31
  test: ["test", "tests", "tests"],
32
32
  change: ["change", "changes", "changes"],
33
33
  part: ["part", "parts", "parts"],
34
+ context: ["database context", "database contexts", "database contexts"],
34
35
  } as const satisfies Record<string, Forms>;
35
36
 
36
37
  export type Noun = keyof typeof NOUNS_EN;
@@ -58,8 +59,19 @@ const NOUNS_RU: Record<Noun, Forms> = {
58
59
  test: ["тест", "теста", "тестов"],
59
60
  change: ["изменение", "изменения", "изменений"],
60
61
  part: ["часть", "части", "частей"],
62
+ context: ["контекст базы данных", "контекста базы данных", "контекстов базы данных"],
61
63
  };
62
64
 
65
+ /** Genitive after «из N»: «из 1 подключения», «из 3 подключений», «из 21 подключения». */
66
+ const GENITIVE_RU: Partial<Record<Noun, readonly [one: string, many: string]>> = {
67
+ connection: ["подключения", "подключений"],
68
+ };
69
+
70
+ function genitiveRu(count: number, noun: Noun): string {
71
+ const forms = GENITIVE_RU[noun] ?? [NOUNS_RU[noun][1], NOUNS_RU[noun][2]];
72
+ return count % 10 === 1 && count % 100 !== 11 ? forms[0] : forms[1];
73
+ }
74
+
63
75
  function formEn(count: number, forms: Forms): string {
64
76
  return count === 1 ? forms[0] : forms[1];
65
77
  }
@@ -316,7 +328,7 @@ const ru: Messages = {
316
328
  },
317
329
  failure: {
318
330
  config: (count) => `конфигурация с ошибками · ${count} ${formRu(count, NOUNS_RU.problem)}`,
319
- infra: (down, total) => `инфраструктура недоступна · ${down} из ${total} ${formRu(total, NOUNS_RU.connection)}`,
331
+ infra: (down, total) => `инфраструктура недоступна · ${down} из ${total} ${genitiveRu(total, "connection")}`,
320
332
  timeout: (seconds) => `запуск не уложился в ${seconds}`,
321
333
  di: "внедрение зависимостей не собрано",
322
334
  codegen: (count) => `кодогенерация не удалась · ${count} ${formRu(count, NOUNS_RU.error)}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.100.0",
3
+ "version": "0.100.2",
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",