@zhin.js/database 1.1.2 → 1.1.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.
package/README.md CHANGED
@@ -108,6 +108,22 @@ await db.start();
108
108
 
109
109
  ### MySQL Example
110
110
 
111
+ 框架初始化验证基线为 **MySQL 8.4 LTS**,驱动为 `mysql2` 3.x。项目配置中的
112
+ `database.dialect: mysql` 用于选择方言,由 CLI 移除后再把连接参数传给驱动。
113
+ 不要为了消除驱动警告而删除项目配置中的 `dialect`。
114
+
115
+ MySQL 方言将 `text` 的非空默认值和 `json` 默认值写成表达式,例如
116
+ `TEXT DEFAULT ('')`;普通 `TEXT DEFAULT ''` 会被 MySQL 拒绝。
117
+ 声明了 `length` 的 `text` 使用 `VARCHAR(length)`;未声明长度的文本主键或唯一键
118
+ 使用 `VARCHAR(768)`,以完整值建立索引;显式声明的索引文本长度超过 768 时直接报错。文本主键和唯一键使用 `utf8mb4_0900_bin`,保留大小写及末尾空格的区别。其余文本仍为 `TEXT`。
119
+ 新表显式使用 InnoDB `ROW_FORMAT=DYNAMIC`。使用 4KB / 8KB InnoDB 页时,启动会先校验文本索引长度,并提示配置更大的页或显式声明更短的键长度。
120
+ 键值应在该长度内;需要更短的业务边界时显式声明 `length`。这里没有使用文本前缀唯一索引,
121
+ 避免将前缀相同的不同键误判为重复。
122
+
123
+ 如果启动报 `message_key can't have a default value`,请升级包含此修复的
124
+ `@zhin.js/database` 与 `@zhin.js/cli` 后重启。该错误来自建表 SQL,无需降级
125
+ MySQL 或 mysql2,也无需删除已有数据库。修复针对新建表,不会缩窄既有文本列。
126
+
111
127
  ```typescript
112
128
  const db = Registry.create('mysql', {
113
129
  host: 'localhost',
@@ -64,4 +64,5 @@ export declare class MySQLDialect<S extends Record<string, object> = Record<stri
64
64
  }
65
65
  export declare class MySQL<S extends Record<string, object> = Record<string, object>> extends RelatedDatabase<MySQLDialectConfig, S> {
66
66
  constructor(config: MySQLDialectConfig, definitions?: Database.DefinitionObj<S>);
67
+ protected initialize(): Promise<void>;
67
68
  }
@@ -187,20 +187,36 @@ export class MySQLDialect extends Dialect {
187
187
  return `LIMIT ${offset}, ${limit}`;
188
188
  }
189
189
  formatCreateTable(tableName, columns) {
190
- return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(String(tableName))} (${columns.join(', ')}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
190
+ return `CREATE TABLE IF NOT EXISTS ${this.quoteIdentifier(String(tableName))} (${columns.join(', ')}) ENGINE=InnoDB ROW_FORMAT=DYNAMIC DEFAULT CHARSET=utf8mb4`;
191
191
  }
192
192
  formatColumnDefinition(field, column) {
193
193
  const name = this.quoteIdentifier(String(field));
194
- const type = this.mapColumnType(column.type);
195
- const length = column.length ? `(${column.length})` : '';
194
+ // TEXT cannot be indexed in full. Bounded keys retain full-value uniqueness
195
+ // within InnoDB's 3072-byte index limit under utf8mb4 (4 bytes per character).
196
+ const boundedText = column.type.toLowerCase() === 'text'
197
+ && (column.length !== undefined || column.primary || column.unique);
198
+ const indexedText = boundedText && (column.primary || column.unique);
199
+ if (boundedText && (!Number.isInteger(column.length ?? 768) || (column.length ?? 768) < 1)) {
200
+ throw new RangeError(`MySQL text column ${field} requires a positive integer length`);
201
+ }
202
+ if (indexedText && (column.length ?? 768) > 768) {
203
+ throw new RangeError(`MySQL indexed text column ${field} exceeds the utf8mb4 index limit of 768 characters`);
204
+ }
205
+ const collation = indexedText ? ' CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_bin' : '';
206
+ const type = boundedText ? 'VARCHAR' : this.mapColumnType(column.type);
207
+ const length = boundedText ? `(${column.length ?? 768})` : column.length ? `(${column.length})` : '';
196
208
  const nullable = column.nullable === false ? ' NOT NULL' : '';
197
209
  const primary = column.primary ? ' PRIMARY KEY' : '';
198
210
  const unique = column.unique ? ' UNIQUE' : '';
199
211
  const autoIncrement = column.autoIncrement ? ' AUTO_INCREMENT' : '';
200
- const defaultVal = column.default !== undefined
201
- ? ` DEFAULT ${this.formatDefaultValue(column.default)}`
202
- : '';
203
- return `${name} ${type}${length}${primary}${unique}${autoIncrement}${nullable}${defaultVal}`;
212
+ let defaultVal = '';
213
+ if (column.default !== undefined) {
214
+ const value = this.formatDefaultValue(column.default);
215
+ // MySQL 8.0.13+ requires even literal TEXT/JSON defaults to be expressions.
216
+ const expression = (type === 'TEXT' || type === 'JSON') && column.default !== null;
217
+ defaultVal = ` DEFAULT ${expression ? `(${value})` : value}`;
218
+ }
219
+ return `${name} ${type}${length}${collation}${primary}${unique}${autoIncrement}${nullable}${defaultVal}`;
204
220
  }
205
221
  async reconcileDefinitions(definitions) {
206
222
  const expected = new Map();
@@ -320,5 +336,20 @@ export class MySQL extends RelatedDatabase {
320
336
  constructor(config, definitions) {
321
337
  super(new MySQLDialect(config), definitions);
322
338
  }
339
+ async initialize() {
340
+ const indexedText = [...this.definitions.values()].flatMap(definition => Object.entries(definition).filter(([, column]) => column.type.toLowerCase() === 'text' && (column.primary || column.unique)));
341
+ if (indexedText.length) {
342
+ const [settings] = await this.dialect.query('SELECT @@innodb_page_size AS page_size');
343
+ const maxLength = Math.min(768, Number(settings?.page_size) * 3 / 64);
344
+ if (!Number.isFinite(maxLength) || maxLength < 1)
345
+ throw new Error('Cannot determine MySQL InnoDB text index limit');
346
+ for (const [name, column] of indexedText) {
347
+ if ((column.length ?? 768) > maxLength) {
348
+ throw new RangeError(`MySQL indexed text column ${name} requires ${column.length ?? 768} characters, but innodb_page_size=${settings.page_size} allows ${maxLength}; use a 16KB-or-larger InnoDB page size or declare a smaller key length`);
349
+ }
350
+ }
351
+ }
352
+ await super.initialize();
353
+ }
323
354
  }
324
355
  Registry.register('mysql', MySQL);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/database",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "Universal database abstraction layer for Zhin.js - supports SQLite, MySQL, PostgreSQL, MongoDB, Redis",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -9,18 +9,17 @@
9
9
  ".": {
10
10
  "types": "./lib/index.d.ts",
11
11
  "import": "./lib/index.js",
12
- "require": "./lib/index.js",
13
- "development": "./src/index.ts"
12
+ "require": "./lib/index.js"
14
13
  }
15
14
  },
16
15
  "files": [
17
16
  "lib"
18
17
  ],
19
18
  "engines": {
20
- "node": ">=22.5.0"
19
+ "node": ">=22.19.0"
21
20
  },
22
21
  "dependencies": {
23
- "@zhin.js/logger": "1.1.1"
22
+ "@zhin.js/logger": "1.1.2"
24
23
  },
25
24
  "peerDependencies": {
26
25
  "mongodb": ">=3.1.13",