bazis 0.98.12 → 0.98.13

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.
@@ -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.98.12" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.98.13" }, 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");
@@ -65,7 +65,10 @@ export class OrmLifecycle implements HostedService {
65
65
  const database = new DatabaseFacade(this.options.provider, this.options.model);
66
66
  try {
67
67
  if (this.ensureCreated) {
68
- await database.ensureCreated();
68
+ const result = await database.ensureCreated();
69
+ for (const warning of result.warnings) {
70
+ console.warn(`[orm:schema] ${warning}`);
71
+ }
69
72
  }
70
73
  if (this.migrateOnStart) {
71
74
  const result = await database.migrate();
@@ -4,7 +4,7 @@ import type { DatabaseProvider, DbExecutor, ExecuteResult, ForeignKeyConstraint,
4
4
  import { Migrator, type MigrationResult } from "./Schema/Migrator";
5
5
  import { MigrationRunner, type Migration, type VersionedMigrationResult } from "./Schema/MigrationRunner";
6
6
  import { SchemaDiffer } from "./Schema/SchemaDiffer";
7
- import { SchemaAdmissionEngine } from "./Schema/SchemaAdmission";
7
+ import { SchemaAdmissionEngine, type EnsureCreatedResult } from "./Schema/SchemaAdmission";
8
8
  import { SchemaAdmissionError } from "./errors";
9
9
  import { physicalColumnTypes } from "./Schema/physicalColumnTypes";
10
10
  import { bindResolvedForeignKey } from "./Providers/resolvedForeignKey";
@@ -26,10 +26,9 @@ export class DatabaseFacade {
26
26
  * whole tables, then exact-verifies again before commit. Existing tables are
27
27
  * never altered or repaired.
28
28
  */
29
- async ensureCreated(): Promise<void> {
29
+ async ensureCreated(): Promise<EnsureCreatedResult> {
30
30
  if (this.provider.name === "postgres") {
31
- await new SchemaAdmissionEngine(this.provider, this.models).ensureCreated();
32
- return;
31
+ return new SchemaAdmissionEngine(this.provider, this.models).ensureCreated();
33
32
  }
34
33
  // The physical default contract belongs exclusively to exact PostgreSQL
35
34
  // admission. Other providers must reject it rather than silently ignore it.
@@ -45,6 +44,7 @@ export class DatabaseFacade {
45
44
  await this.provider.execute(indexSql, []);
46
45
  }
47
46
  }
47
+ return { warnings: [] };
48
48
  }
49
49
 
50
50
  /**
@@ -2,7 +2,7 @@ import type { DatabaseProvider, SchemaAdmissionScope } from "../Providers/types"
2
2
  import { SchemaAdmissionError, SchemaMigrationRequiredError, SchemaVerificationError } from "../errors";
3
3
  import type { OrmModel } from "../Metadata/OrmModel";
4
4
  import { compileExpectedSchema, type OrmExpectedSchema } from "./ExpectedSchema";
5
- import { ExactSchemaVerifier } from "./ExactSchemaVerifier";
5
+ import { ExactSchemaVerifier, type SchemaVerificationResult } from "./ExactSchemaVerifier";
6
6
  import { classifySafeAdditive, renderSafeAdditivePostgres } from "./SafeAdditiveSchema";
7
7
 
8
8
  /** PostgreSQL-private trace boundary; deliberately absent from public capability types. */
@@ -10,10 +10,15 @@ type TraceableSchemaAdmissionScope = SchemaAdmissionScope & {
10
10
  readonly executeSchemaAdmission?: (sql: string, operation: string) => ReturnType<SchemaAdmissionScope["execute"]>;
11
11
  };
12
12
 
13
+ /** Result of `ensureCreated`: tolerated differences the application should know about. */
14
+ export interface EnsureCreatedResult {
15
+ readonly warnings: readonly string[];
16
+ }
17
+
13
18
  /** PostgreSQL-only create-missing-whole-tables and exact admission. */
14
19
  export class SchemaAdmissionEngine {
15
20
  constructor(private readonly provider: DatabaseProvider, private readonly models: OrmModel) {}
16
- async ensureCreated(): Promise<void> {
21
+ async ensureCreated(): Promise<EnsureCreatedResult> {
17
22
  let expected: OrmExpectedSchema;
18
23
  try {
19
24
  expected = compileExpectedSchema(this.models);
@@ -25,16 +30,18 @@ export class SchemaAdmissionEngine {
25
30
  if (capability.version !== 1 || !capability.distributedLock || !capability.transactionalDdl || !capability.exactIntrospection) {
26
31
  throw new SchemaAdmissionError("ORM_SCHEMA_ATOMICITY_UNAVAILABLE", "PostgreSQL schema admission cannot guarantee atomic exact verification.");
27
32
  }
33
+ let warnings: readonly string[] = [];
28
34
  try {
29
- await capability.withSchemaAdmission([...new Set(expected.tables.map((table) => table.schema))], async (scope) => this.admit(scope, expected));
35
+ await capability.withSchemaAdmission([...new Set(expected.tables.map((table) => table.schema))], async (scope) => { warnings = await this.admit(scope, expected); });
30
36
  } catch (error) {
31
37
  if (error instanceof SchemaAdmissionError) throw error;
32
38
  throw new SchemaAdmissionError("ORM_SCHEMA_ATOMICITY_UNAVAILABLE", "PostgreSQL schema admission could not complete safely.");
33
39
  }
40
+ return { warnings };
34
41
  }
35
- private async admit(scope: SchemaAdmissionScope, expected: OrmExpectedSchema): Promise<void> {
42
+ private async admit(scope: SchemaAdmissionScope, expected: OrmExpectedSchema): Promise<readonly string[]> {
36
43
  const before = await scope.introspectExpected(expected);
37
- const initial = new ExactSchemaVerifier().verify(expected, before, true);
44
+ const { verification: initial, warnings } = tolerateNameDrift(new ExactSchemaVerifier().verify(expected, before, true));
38
45
  const preflight = classifySafeAdditive(expected, before, initial);
39
46
  if (preflight.hardDifferences.length) {
40
47
  if (preflight.hardDifferences.some((difference) => difference.code === "catalog.unsupported")) throw new SchemaAdmissionError("ORM_SCHEMA_CATALOG_UNSUPPORTED", "PostgreSQL catalog contains a schema form unsupported by exact admission.");
@@ -49,8 +56,9 @@ export class SchemaAdmissionEngine {
49
56
  }
50
57
  catch (error) { throw this.mapDdlError(operation.kind, error); }
51
58
  }
52
- const final = new ExactSchemaVerifier().verify(expected, await scope.introspectExpected(expected));
59
+ const { verification: final } = tolerateNameDrift(new ExactSchemaVerifier().verify(expected, await scope.introspectExpected(expected)));
53
60
  if (!final.compatible) throw new SchemaVerificationError(final);
61
+ return warnings;
54
62
  }
55
63
  private mapDdlError(kind: string, error: unknown): SchemaAdmissionError {
56
64
  const sqlState = typeof error === "object" && error !== null
@@ -60,3 +68,24 @@ export class SchemaAdmissionEngine {
60
68
  return new SchemaAdmissionError(kind === "createSchema" || kind === "createTable" ? "ORM_SCHEMA_CREATE_FAILED" : "ORM_SCHEMA_ADDITIVE_DDL_FAILED", "PostgreSQL additive schema admission failed.");
61
69
  }
62
70
  }
71
+
72
+ /**
73
+ * A primary key that differs only by name (`tasks_pkey` from an older schema vs
74
+ * the model's `pk_tasks`) enforces the same thing, and queries never reference
75
+ * the name (`ON CONFLICT` names columns). It is reported, not refused; the same
76
+ * table passes `migrateOnStart`. Every other difference stays exact.
77
+ */
78
+ function tolerateNameDrift(verification: SchemaVerificationResult): { readonly verification: SchemaVerificationResult; readonly warnings: readonly string[] } {
79
+ const tolerated = verification.differences.filter((difference) => difference.code === "primaryKey.name");
80
+ if (tolerated.length === 0) return { verification, warnings: [] };
81
+ const differences = verification.differences.filter((difference) => difference.code !== "primaryKey.name");
82
+ const quote = (name: string) => `"${name.replaceAll('"', '""')}"`;
83
+ const warnings = tolerated.map((difference) => {
84
+ const table = `${quote(difference.schema)}.${quote(difference.table)}`;
85
+ const actual = difference.actual?.value ?? "";
86
+ const expected = difference.expected?.value ?? difference.objectName ?? "";
87
+ return `table ${table}: primary key is named ${quote(actual)}, the model expects ${quote(expected)}. `
88
+ + `It works as is; to align the name run: ALTER TABLE ${table} RENAME CONSTRAINT ${quote(actual)} TO ${quote(expected)};`;
89
+ });
90
+ return { verification: { compatible: differences.length === 0, differences }, warnings };
91
+ }
@@ -131,6 +131,7 @@ export type {
131
131
  IntrospectedCheck,
132
132
  } from "./Schema/introspection";
133
133
  export type { SchemaDifferenceCode, SafeSchemaDescriptor, SchemaDifference, SchemaVerificationResult } from "./Schema/ExactSchemaVerifier";
134
+ export type { EnsureCreatedResult } from "./Schema/SchemaAdmission";
134
135
  export { SchemaAdmissionError, SchemaMigrationRequiredError, SchemaVerificationError } from "./errors";
135
136
  export type { SchemaAdmissionErrorCode } from "./errors";
136
137
  export { compileCheck, renderCheck } from "./Schema/CheckExpression";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.98.12",
3
+ "version": "0.98.13",
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",