bazis 0.117.0 → 0.118.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.
@@ -31,7 +31,9 @@ export function holdResponseScope(
31
31
  if (signal.aborted && !body.locked) void body.cancel(signal.reason).catch(() => undefined);
32
32
  void Promise.resolve().then(dispose).then(complete, reject);
33
33
  };
34
- cleanup = finished(body, { signal }, finish);
34
+ // Types only: with the DOM lib (TypeScript's default) Response.body is the
35
+ // DOM ReadableStream, which node:stream's typings do not list.
36
+ cleanup = finished(body as unknown as Parameters<typeof finished>[0], { signal }, finish);
35
37
 
36
38
  // Qualified Bun 1.4 consumes native Blob/file bodies directly: bodyUsed is
37
39
  // true, but no JS reader is acquired and its stream never emits finished.
@@ -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.117.0" }, capabilities: { experimentalApi: true },
235
+ clientInfo: { name: "bazis", title: "Bazis", version: "0.118.0" }, 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");
@@ -1,3 +1,5 @@
1
+ import { rememberSecretValue } from "../../../library/redaction";
2
+
1
3
  const REDACTED = "***";
2
4
 
3
5
  /**
@@ -9,6 +11,8 @@ export class Secret {
9
11
 
10
12
  public constructor(value: string) {
11
13
  this.#value = value;
14
+ // Logs and errors drop the value even where it was pasted as plain text.
15
+ rememberSecretValue(value);
12
16
  }
13
17
 
14
18
  public reveal(): string {
@@ -5,6 +5,8 @@ import { ShutdownTimeoutError, StartupFailuresError, StartupTimeoutError } from
5
5
  const MAX_CAUSES = 5;
6
6
  /** Properties already printed in the header, the frames or the cause chain. */
7
7
  const SHOWN = new Set(["name", "message", "stack", "cause", "code", "errno", "errors", "failures"]);
8
+ /** Where in PostgreSQL's own sources the error was raised: nothing to act on. Hint, detail and the object names stay. */
9
+ const POSTGRES_INTERNALS = new Set(["file", "line", "routine", "severity", "severity_local"]);
8
10
  const INSPECT = { depth: null, colors: false, customInspect: false, getters: false, maxArrayLength: null, maxStringLength: null } as const;
9
11
 
10
12
  /**
@@ -34,7 +36,8 @@ export function formatStartupFailure(error: unknown): string {
34
36
  if (!(current instanceof StartupFailuresError)) lines.push(...(current.stack ?? "").split("\n").filter((line) => /^\s+at /.test(line)));
35
37
  // Timeouts already say their limit and what was pending in the message.
36
38
  const restated = current instanceof StartupTimeoutError || current instanceof ShutdownTimeoutError;
37
- const details = restated ? {} : Object.fromEntries(Object.entries(current).filter(([name]) => !SHOWN.has(name)));
39
+ const internals = current.name === "PostgresError" ? POSTGRES_INTERNALS : undefined;
40
+ const details = restated ? {} : Object.fromEntries(Object.entries(current).filter(([name]) => !SHOWN.has(name) && !internals?.has(name)));
38
41
  if (Object.keys(details).length > 0) lines.push(inspect(redactSensitive(details), INSPECT));
39
42
  current = current.cause;
40
43
  }
@@ -5,7 +5,7 @@ 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
7
  import { SchemaAdmissionEngine, type EnsureCreatedResult } from "./Schema/SchemaAdmission";
8
- import { entityStorageKey } from "./Schema/tableKey";
8
+ import { entityStorageKey, foreignKeyName } from "./Schema/tableKey";
9
9
 
10
10
  /** Result of `ensureCreated` together with versioned migrations. */
11
11
  export interface EnsureCreatedWithMigrationsResult extends EnsureCreatedResult {
@@ -166,7 +166,7 @@ export class DatabaseFacade {
166
166
  const constraint: ForeignKeyConstraint = {
167
167
  column: columns[0]!.columnName,
168
168
  columns: columns.map((column) => column!.columnName),
169
- name: fk.name,
169
+ name: foreignKeyName(model, fk),
170
170
  referencedTable: this.referencedTableName(target),
171
171
  referencedColumn: target.key[0].columnName,
172
172
  referencedColumns: target.key.map((property) => property.columnName),
@@ -2,7 +2,7 @@ import type { ColumnType, EntityModel, IndexModel, PropertyModel } from "../Meta
2
2
  import type { ForeignKeyConstraint, RowLockMode, SqlDialect, SqlParam } from "./types";
3
3
  import { projectCheckAstIdentifiers, renderCheck } from "../Schema/CheckExpression";
4
4
  import { resolvedForeignKey } from "./resolvedForeignKey";
5
- import { physicalTableIdentity } from "../Schema/tableKey";
5
+ import { physicalTableIdentity, primaryKeyName } from "../Schema/tableKey";
6
6
  import { decimalParameter } from "./decimalValue";
7
7
 
8
8
  const jsonScalarParameters = new WeakSet<object>();
@@ -132,17 +132,19 @@ export class PostgresDialect implements SqlDialect {
132
132
  }
133
133
 
134
134
  createTableSql(model: EntityModel, foreignKeys: readonly ForeignKeyConstraint[]): string {
135
+ // The same name ensureCreated expects, not PostgreSQL's "<table>_pkey".
136
+ const primaryKey = `CONSTRAINT ${this.quoteId(primaryKeyName(model))} PRIMARY KEY`;
135
137
  const columns = model.properties.map((property) => {
136
138
  if (model.key.length === 1 && property.isKey && property.generation === "identity") {
137
139
  // BY DEFAULT allows inserting an explicit key when needed.
138
- return `${this.quoteId(property.columnName)} bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY`;
140
+ return `${this.quoteId(property.columnName)} bigint GENERATED BY DEFAULT AS IDENTITY ${primaryKey}`;
139
141
  }
140
142
  if (model.key.length === 1 && property.isKey && property.generation === "uuid") {
141
- return `${this.quoteId(property.columnName)} uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY`;
143
+ return `${this.quoteId(property.columnName)} uuid NOT NULL DEFAULT gen_random_uuid() ${primaryKey}`;
142
144
  }
143
145
  if (model.key.length === 1 && property.isKey && property.generation === "uuidV7") {
144
146
  // The ORM assigns the v7 value before INSERT, so the column has no default.
145
- return `${this.quoteId(property.columnName)} uuid NOT NULL PRIMARY KEY`;
147
+ return `${this.quoteId(property.columnName)} uuid NOT NULL ${primaryKey}`;
146
148
  }
147
149
  const foreignKey = foreignKeys.find((candidate) => (candidate.columns ?? [candidate.column]).includes(property.columnName));
148
150
  const parts = [this.quoteId(property.columnName), this.ddlColumnType(this.foreignKeyColumnType(foreignKey, property), property)];
@@ -152,7 +154,7 @@ export class PostgresDialect implements SqlDialect {
152
154
  return parts.join(" ");
153
155
  });
154
156
  if (model.key.length > 1 || model.key[0].generation === "none") {
155
- columns.push(`PRIMARY KEY (${model.key.map((key) => this.quoteId(key.columnName)).join(", ")})`);
157
+ columns.push(`${primaryKey} (${model.key.map((key) => this.quoteId(key.columnName)).join(", ")})`);
156
158
  }
157
159
  for (const check of model.checks) columns.push(`CONSTRAINT ${this.quoteId(check.name)} CHECK (${renderCheck(projectCheckAstIdentifiers(check.expression, (name) => model.propertyByName(name)!.columnName), (name) => this.quoteId(name))})`);
158
160
  for (const fk of foreignKeys) {
@@ -4,7 +4,7 @@ import type { CanonicalDefault, GenerationStrategy } from "./introspection";
4
4
  import { projectCheckAstIdentifiers, type CheckAst } from "./CheckExpression";
5
5
  import { crossUnitForeignKey, physicalColumnTypes } from "./physicalColumnTypes";
6
6
  import { SchemaAdmissionError } from "../errors";
7
- import { physicalTableIdentity } from "./tableKey";
7
+ import { foreignKeyName, physicalTableIdentity, primaryKeyName } from "./tableKey";
8
8
 
9
9
  export interface ExpectedColumn { readonly property: string; readonly column: string; readonly physicalType: string; readonly nullable: boolean; readonly default: CanonicalDefault; readonly generation: GenerationStrategy }
10
10
  export interface ExpectedTable { readonly schema: string; readonly table: string; readonly columns: readonly ExpectedColumn[]; readonly primaryKey: { readonly name: string; readonly columns: readonly string[] }; readonly indexes: readonly { readonly name: string; readonly columns: readonly string[]; readonly unique: boolean; readonly method: "btree"; readonly predicate?: CheckAst }[]; readonly foreignKeys: readonly { readonly name: string; readonly columns: readonly string[]; readonly target: { readonly schema: string; readonly table: string }; readonly targetColumns: readonly string[]; readonly onDelete: string; readonly onUpdate: string }[]; readonly checks: readonly { readonly name: string; readonly expression: unknown }[] }
@@ -39,9 +39,9 @@ function compileTable(model: EntityModel, byCtor: ReadonlyMap<EntityModel["ctor"
39
39
  const foreignKeys = model.foreignKeys.map((foreignKey) => {
40
40
  const target = byCtor.get(foreignKey.target());
41
41
  if (!target) throw crossUnitForeignKey(model, foreignKey.properties, foreignKey.target());
42
- return { name: foreignKey.name ?? `fk_${table}_${foreignKey.properties.join("_")}`, columns: foreignKey.properties.map((name) => model.propertyByName(name)!.columnName), target: { schema: schemaOf(target), table: physicalTableIdentity(target).table }, targetColumns: target.key.map((property) => property.columnName), onDelete: foreignKey.onDelete, onUpdate: foreignKey.onUpdate };
42
+ return { name: foreignKeyName(model, foreignKey), columns: foreignKey.properties.map((name) => model.propertyByName(name)!.columnName), target: { schema: schemaOf(target), table: physicalTableIdentity(target).table }, targetColumns: target.key.map((property) => property.columnName), onDelete: foreignKey.onDelete, onUpdate: foreignKey.onUpdate };
43
43
  });
44
- return Object.freeze({ schema, table, columns: Object.freeze(columns), primaryKey: Object.freeze({ name: model.keyName ?? `pk_${table}`, columns: Object.freeze(model.key.map((property) => property.columnName)) }), indexes: Object.freeze(model.indexes.map((index) => Object.freeze({ name: index.name, columns: Object.freeze([...index.columns]), unique: index.unique, method: "btree" as const, ...(index.where === undefined ? {} : { predicate: projectCheckAstIdentifiers(index.where, (name) => model.propertyByName(name)!.columnName) }) }))), foreignKeys: Object.freeze(foreignKeys), checks: Object.freeze(model.checks.map((check) => Object.freeze({ name: check.name, expression: projectCheckAstIdentifiers(check.expression, (name) => model.propertyByName(name)!.columnName) }))) });
44
+ return Object.freeze({ schema, table, columns: Object.freeze(columns), primaryKey: Object.freeze({ name: primaryKeyName(model), columns: Object.freeze(model.key.map((property) => property.columnName)) }), indexes: Object.freeze(model.indexes.map((index) => Object.freeze({ name: index.name, columns: Object.freeze([...index.columns]), unique: index.unique, method: "btree" as const, ...(index.where === undefined ? {} : { predicate: projectCheckAstIdentifiers(index.where, (name) => model.propertyByName(name)!.columnName) }) }))), foreignKeys: Object.freeze(foreignKeys), checks: Object.freeze(model.checks.map((check) => Object.freeze({ name: check.name, expression: projectCheckAstIdentifiers(check.expression, (name) => model.propertyByName(name)!.columnName) }))) });
45
45
  }
46
46
 
47
47
  /** `decimal(12,2)` for numeric(12, 2), `decimal` for an unbounded numeric: the catalog reports the same label. */
@@ -607,7 +607,7 @@ function row(v: unknown): OwnedStoreRegistryRowV1 {
607
607
  bad();
608
608
  return Object.freeze(out as OwnedStoreRegistryRowV1);
609
609
  }
610
- export function validateOwnedStoreRegistryV1(rows: unknown, defs: readonly OrmOwnedStoreDefinitionV1[]): readonly OwnedStoreRegistryRowV1[] {
610
+ export function validateOwnedStoreRegistryV1(rows: unknown, _defs: readonly OrmOwnedStoreDefinitionV1[]): readonly OwnedStoreRegistryRowV1[] {
611
611
  const out = dense(rows, row, 4096);
612
612
  const keys = new Set<string>();
613
613
  for (const x of out) {
@@ -619,9 +619,9 @@ export function validateOwnedStoreRegistryV1(rows: unknown, defs: readonly OrmOw
619
619
  if (keys.has(x.storeKey))
620
620
  throw failure("ORM_OWNED_STORE_OWNERSHIP_CONFLICT");
621
621
  keys.add(x.storeKey);
622
- const d = defs.find(y => y.storeKey === x.storeKey);
623
- if (d && (x.formatVersion !== String(d.formatVersion) || x.ownedSchema !== d.ownedScope.schema || x.tablePrefix !== d.ownedScope.tablePrefix))
624
- throw failure("ORM_OWNED_STORE_IDENTITY_MISMATCH");
622
+ // A row that differs from the code (format version, scope) is refused by
623
+ // admission's assertCurrent, after the row is recorded for the explainer,
624
+ // so the message can name what changed.
625
625
  }
626
626
  for (let i = 0; i < out.length; i++)
627
627
  for (let j = i + 1; j < out.length; j++) {
@@ -24,3 +24,13 @@ export function entityStorageKey(model: EntityModel): string {
24
24
  export function introspectedTableKey(schema: string, table: string): string {
25
25
  return schema === "public" ? table : `${schema}.${table}`;
26
26
  }
27
+
28
+ /** `pk_<table>` unless the key is named; ensureCreated and migrate() name the primary key the same way. */
29
+ export function primaryKeyName(model: EntityModel): string {
30
+ return model.keyName ?? `pk_${physicalTableIdentity(model).table}`;
31
+ }
32
+
33
+ /** `fk_<table>_<properties>` unless the foreign key is named, for every way a table is created. */
34
+ export function foreignKeyName(model: EntityModel, foreignKey: { readonly name?: string; readonly properties: readonly string[] }): string {
35
+ return foreignKey.name ?? `fk_${physicalTableIdentity(model).table}_${foreignKey.properties.join("_")}`;
36
+ }
@@ -47,6 +47,42 @@ export function redactTraceValues<T>(values: readonly T[], options: SensitiveRed
47
47
  return values.map(() => replacement);
48
48
  }
49
49
 
50
+ /** Values shorter than this are not remembered: masking them would hide ordinary words ("postgres"). */
51
+ export const MIN_REMEMBERED_SECRET_LENGTH = 10;
52
+ const rememberedSecrets = new Set<string>();
53
+ let longestFirst: readonly string[] = [];
54
+
55
+ /**
56
+ * Remembers a secret value: from now on every redaction removes it wherever it
57
+ * appears, also in plain text without a marker such as `password=`. `Secret`
58
+ * calls it for every configuration secret. Process-wide; values shorter than
59
+ * {@link MIN_REMEMBERED_SECRET_LENGTH} are recognised only by their shape.
60
+ */
61
+ export function rememberSecretValue(value: string): void {
62
+ // A connection URL keeps naming its server: only its password is the secret,
63
+ // and the userinfo pattern already hides user:password@ in the URL itself.
64
+ const password = urlPassword(value);
65
+ if (password !== undefined) {
66
+ if (password !== "") rememberSecretValue(password);
67
+ return;
68
+ }
69
+ if (value.length < MIN_REMEMBERED_SECRET_LENGTH || rememberedSecrets.has(value)) return;
70
+ rememberedSecrets.add(value);
71
+ // A longer value first, so a secret that contains another one goes whole.
72
+ longestFirst = [...rememberedSecrets].sort((left, right) => right.length - left.length);
73
+ }
74
+
75
+ /** The decoded password of a URL with userinfo (`""` for user only), or undefined for other values. */
76
+ function urlPassword(value: string): string | undefined {
77
+ if (!/^[a-z][a-z0-9+.-]*:\/\/[^\s/?#@]+@/i.test(value)) return undefined;
78
+ try {
79
+ const url = new URL(value);
80
+ return decodeURIComponent(url.password);
81
+ } catch {
82
+ return undefined;
83
+ }
84
+ }
85
+
50
86
  export function isSensitiveKey(key: string, options: SensitiveRedactionOptions = {}): boolean {
51
87
  return keyIsSensitive(key, normalizeOptions(options));
52
88
  }
@@ -177,7 +213,9 @@ function normalizeKey(key: string): string {
177
213
  }
178
214
 
179
215
  function redactText(value: string, replacement: string): string {
180
- return value
216
+ let text = value;
217
+ for (const secret of longestFirst) if (text.includes(secret)) text = text.replaceAll(secret, replacement);
218
+ return text
181
219
  .replace(SECRET_TEXT_PATTERN, (_match, name: string, separator: string) => `${name}${separator}${replacement}`)
182
220
  .replace(BEARER_TEXT_PATTERN, (_match, scheme: string) => `${scheme} ${replacement}`)
183
221
  .replace(URL_USERINFO_PATTERN, (_match, scheme: string, userinfo: string) =>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bazis",
3
- "version": "0.117.0",
3
+ "version": "0.118.0",
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",