@atscript/db-mysql 0.1.39 → 0.1.40

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/dist/index.mjs CHANGED
@@ -1,14 +1,19 @@
1
+ import { t as MysqlPlugin } from "./plugin-3jLlPI4f.mjs";
1
2
  import { AtscriptDbView, BaseDbAdapter, DbError, DbSpace } from "@atscript/db";
2
3
  import { buildAggregateCount, buildAggregateSelect, buildCreateView, buildDelete, buildInsert, buildSelect, buildUpdate, buildWhere as buildWhere$1, defaultValueForType, defaultValueToSqlLiteral, refActionToSql, toSqlValue } from "@atscript/db-sql-tools";
3
- import { AnnotationSpec } from "@atscript/core";
4
-
5
- //#region packages/db-mysql/src/sql-builder.ts
4
+ //#region src/sql-builder.ts
5
+ /** Escapes a MySQL identifier by doubling backticks. */
6
6
  function esc(name) {
7
7
  return name.replace(/`/g, "``");
8
8
  }
9
+ /** Backtick-quotes a single identifier. */
9
10
  function qi(name) {
10
11
  return `\`${esc(name)}\``;
11
12
  }
13
+ /**
14
+ * Backtick-quotes a table name, handling `schema.table` format.
15
+ * Input is a raw name like `mydb.users` or just `users`.
16
+ */
12
17
  function quoteTableName(name) {
13
18
  const dot = name.indexOf(".");
14
19
  if (dot >= 0) return `${qi(name.slice(0, dot))}.${qi(name.slice(dot + 1))}`;
@@ -24,7 +29,7 @@ const mysqlDialect = {
24
29
  unlimitedLimit: "18446744073709551615",
25
30
  toValue: toSqlValue,
26
31
  toParam(value) {
27
- if (value === undefined) return null;
32
+ if (value === void 0) return null;
28
33
  return typeof value === "boolean" ? value ? 1 : 0 : value;
29
34
  },
30
35
  regex(quotedCol, value) {
@@ -36,27 +41,51 @@ const mysqlDialect = {
36
41
  },
37
42
  createViewPrefix: "CREATE OR REPLACE VIEW"
38
43
  };
44
+ /**
45
+ * Builds an INSERT statement.
46
+ */
39
47
  function buildInsert$1(table, data) {
40
48
  return buildInsert(mysqlDialect, table, data);
41
49
  }
50
+ /**
51
+ * Builds a SELECT statement with optional sort, limit, offset, projection.
52
+ */
42
53
  function buildSelect$1(table, where, controls) {
43
54
  return buildSelect(mysqlDialect, table, where, controls);
44
55
  }
45
- function buildUpdate$1(table, data, where, limit) {
46
- return buildUpdate(mysqlDialect, table, data, where, limit);
56
+ /**
57
+ * Builds an UPDATE ... SET ... WHERE statement with optional LIMIT.
58
+ */
59
+ function buildUpdate$1(table, data, where, limit, ops) {
60
+ return buildUpdate(mysqlDialect, table, data, where, limit, ops);
47
61
  }
62
+ /**
63
+ * Builds a DELETE ... WHERE statement with optional LIMIT.
64
+ */
48
65
  function buildDelete$1(table, where, limit) {
49
66
  return buildDelete(mysqlDialect, table, where, limit);
50
67
  }
68
+ /**
69
+ * Builds a CREATE OR REPLACE VIEW statement from a view plan and column mappings.
70
+ */
51
71
  function buildCreateView$1(viewName, plan, columns, resolveFieldRef) {
52
72
  return buildCreateView(mysqlDialect, viewName, plan, columns, resolveFieldRef);
53
73
  }
74
+ /**
75
+ * Builds a SELECT ... GROUP BY statement with aggregate functions.
76
+ */
54
77
  function buildAggregateSelect$1(table, where, controls) {
55
78
  return buildAggregateSelect(mysqlDialect, table, where, controls);
56
79
  }
80
+ /**
81
+ * Builds a COUNT query for the number of distinct groups.
82
+ */
57
83
  function buildAggregateCount$1(table, where, controls) {
58
84
  return buildAggregateCount(mysqlDialect, table, where, controls);
59
85
  }
86
+ /**
87
+ * Maps portable collation values to MySQL collation names.
88
+ */
60
89
  function collationToMysql(collation) {
61
90
  switch (collation) {
62
91
  case "binary": return "utf8mb4_bin";
@@ -74,7 +103,8 @@ function collationToMysql(collation) {
74
103
  * For FK fields, delegates to the target PK's type via `field.fkTargetField`
75
104
  * so the FK column type always matches the referenced column.
76
105
  */
77
- /** Maps integer primitive tags to MySQL integer types. */ function intTypeFromTags(tags, unsigned) {
106
+ /** Maps integer primitive tags to MySQL integer types. */
107
+ function intTypeFromTags(tags, unsigned) {
78
108
  if (tags?.has("int8")) return unsigned ? "TINYINT UNSIGNED" : "TINYINT";
79
109
  if (tags?.has("uint8") || tags?.has("byte")) return "TINYINT UNSIGNED";
80
110
  if (tags?.has("int16")) return unsigned ? "SMALLINT UNSIGNED" : "SMALLINT";
@@ -94,36 +124,36 @@ function mysqlTypeFromField(field) {
94
124
  const unsigned = metadata?.has("db.mysql.unsigned") ?? false;
95
125
  const precision = metadata?.get("db.column.precision");
96
126
  switch (field.designType) {
97
- case "number": {
127
+ case "number":
98
128
  if (precision) return `DECIMAL(${precision.precision},${precision.scale})`;
99
129
  if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "increment") return unsigned ? "BIGINT UNSIGNED" : "BIGINT";
100
130
  if (field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return "TIMESTAMP";
101
131
  if (tags?.has("int")) return intTypeFromTags(tags, unsigned);
102
132
  return "DOUBLE";
103
- }
104
133
  case "integer": return intTypeFromTags(tags, unsigned);
105
- case "decimal": {
134
+ case "decimal":
106
135
  if (precision) return `DECIMAL(${precision.precision},${precision.scale})`;
107
136
  return "DECIMAL(10,2)";
108
- }
109
137
  case "boolean": return "TINYINT(1)";
110
138
  case "string": {
111
139
  if (tags?.has("char")) return "CHAR(1)";
112
- const maxLen = metadata?.get("expect.maxLength")?.length;
113
- if (maxLen !== undefined && maxLen <= 65535) return `VARCHAR(${maxLen})`;
114
- if (maxLen !== undefined && maxLen > 65535) return "LONGTEXT";
140
+ const maxLen = (metadata?.get("expect.maxLength"))?.length;
141
+ if (maxLen !== void 0 && maxLen <= 65535) return `VARCHAR(${maxLen})`;
142
+ if (maxLen !== void 0 && maxLen > 65535) return "LONGTEXT";
115
143
  if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
116
144
  return "TEXT";
117
145
  }
118
146
  case "json":
119
147
  case "object":
120
148
  case "array": return "JSON";
121
- default: {
149
+ default:
122
150
  if (field.isPrimaryKey || field.defaultValue) return "VARCHAR(255)";
123
151
  return "TEXT";
124
- }
125
152
  }
126
153
  }
154
+ /**
155
+ * Builds a CREATE TABLE IF NOT EXISTS statement with MySQL options.
156
+ */
127
157
  function buildCreateTable(table, fields, foreignKeys, options) {
128
158
  const colDefs = [];
129
159
  const primaryKeys = fields.filter((f) => f.isPrimaryKey);
@@ -134,13 +164,13 @@ function buildCreateTable(table, fields, foreignKeys, options) {
134
164
  if (options?.incrementFields?.has(field.physicalName)) def += " AUTO_INCREMENT";
135
165
  if (!field.optional && !field.isPrimaryKey && !options?.incrementFields?.has(field.physicalName)) def += " NOT NULL";
136
166
  if (field.defaultValue?.kind === "value") def += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
137
- else if (field.defaultValue?.kind === "fn") {
167
+ else if (field.defaultValue?.kind === "fn") {
138
168
  if (field.defaultValue.fn === "uuid") def += " DEFAULT (UUID())";
139
- else if (field.defaultValue.fn === "now") def += " DEFAULT CURRENT_TIMESTAMP";
169
+ else if (field.defaultValue.fn === "now") def += " DEFAULT CURRENT_TIMESTAMP";
140
170
  }
141
171
  const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
142
172
  if (nativeCollate) def += ` COLLATE ${nativeCollate}`;
143
- else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
173
+ else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
144
174
  const onUpdate = options?.onUpdateFields?.get(field.physicalName);
145
175
  if (onUpdate) def += ` ON UPDATE ${onUpdate}`;
146
176
  colDefs.push(def);
@@ -168,28 +198,23 @@ else if (field.collate) def += ` COLLATE ${collationToMysql(field.collate)}`;
168
198
  const charset = options?.charset ?? "utf8mb4";
169
199
  const collation = options?.collation ?? "utf8mb4_unicode_ci";
170
200
  sql += ` ENGINE=${engine} DEFAULT CHARSET=${charset} COLLATE=${collation}`;
171
- if (options?.autoIncrementStart !== undefined) sql += ` AUTO_INCREMENT=${options.autoIncrementStart}`;
201
+ if (options?.autoIncrementStart !== void 0) sql += ` AUTO_INCREMENT=${options.autoIncrementStart}`;
172
202
  return sql;
173
203
  }
174
-
175
204
  //#endregion
176
- //#region packages/db-mysql/src/filter-builder.ts
205
+ //#region src/filter-builder.ts
206
+ /**
207
+ * Translates a uniqu filter expression into a parameterized MySQL WHERE clause.
208
+ *
209
+ * @returns `{ sql, params }` — the WHERE clause (without "WHERE") and bound params.
210
+ * Returns `{ sql: '1=1', params: [] }` for empty/null filters.
211
+ */
177
212
  function buildWhere(filter) {
178
213
  return buildWhere$1(mysqlDialect, filter);
179
214
  }
180
-
181
215
  //#endregion
182
- //#region packages/db-mysql/src/mysql-adapter.ts
183
- function _define_property$1(obj, key, value) {
184
- if (key in obj) Object.defineProperty(obj, key, {
185
- value,
186
- enumerable: true,
187
- configurable: true,
188
- writable: true
189
- });
190
- else obj[key] = value;
191
- return obj;
192
- }
216
+ //#region src/mysql-adapter.ts
217
+ /** Parses a MySQL UTC datetime string ('YYYY-MM-DD HH:MM:SS') to epoch ms. Returns the original value if parsing fails. */
193
218
  function utcDatetimeToEpochMs(value) {
194
219
  if (typeof value === "number") return value;
195
220
  if (value instanceof Date) return value.getTime();
@@ -199,14 +224,50 @@ function utcDatetimeToEpochMs(value) {
199
224
  }
200
225
  return value;
201
226
  }
202
- /** Formats epoch ms as 'YYYY-MM-DD HH:MM:SS' in UTC for MySQL TIMESTAMP columns. */ function epochMsToUtcDatetime(ms) {
227
+ /** Formats epoch ms as 'YYYY-MM-DD HH:MM:SS' in UTC for MySQL TIMESTAMP columns. */
228
+ function epochMsToUtcDatetime(ms) {
203
229
  const d = new Date(ms);
204
230
  return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")} ${String(d.getUTCHours()).padStart(2, "0")}:${String(d.getUTCMinutes()).padStart(2, "0")}:${String(d.getUTCSeconds()).padStart(2, "0")}`;
205
231
  }
232
+ /**
233
+ * MySQL adapter for {@link AtscriptDbTable}.
234
+ *
235
+ * Accepts any {@link TMysqlDriver} implementation — the actual MySQL driver
236
+ * is fully swappable (mysql2/promise pool, custom implementations, etc.).
237
+ *
238
+ * Usage:
239
+ * ```typescript
240
+ * import { Mysql2Driver, MysqlAdapter } from '@atscript/db-mysql'
241
+ * import { DbSpace } from '@atscript/db'
242
+ *
243
+ * const driver = new Mysql2Driver('mysql://root@localhost:3306/mydb')
244
+ * const space = new DbSpace(() => new MysqlAdapter(driver))
245
+ * const users = space.getTable(UsersType)
246
+ * ```
247
+ */
206
248
  var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
207
- /** Schema name for INFORMATION_SCHEMA queries (null falls through to DATABASE()). */ get _schema() {
249
+ supportsColumnModify = true;
250
+ static NATIVE_DEFAULT_FNS = new Set(["now", "increment"]);
251
+ _engine = "InnoDB";
252
+ _charset = "utf8mb4";
253
+ _collation = "utf8mb4_unicode_ci";
254
+ _autoIncrementStart;
255
+ _incrementFields = /* @__PURE__ */ new Set();
256
+ _onUpdateFields = /* @__PURE__ */ new Map();
257
+ /** Whether the connected MySQL instance supports native VECTOR type (MySQL 9.0+). */
258
+ _supportsVector;
259
+ /** Vector fields: physical field name → { dimensions, similarity, indexName }. */
260
+ _vectorFields = /* @__PURE__ */ new Map();
261
+ /** Default similarity thresholds per vector field (from @db.search.vector.threshold). */
262
+ _vectorThresholds = /* @__PURE__ */ new Map();
263
+ /** Schema name for INFORMATION_SCHEMA queries (null falls through to DATABASE()). */
264
+ get _schema() {
208
265
  return this._table.schema ?? null;
209
266
  }
267
+ constructor(driver) {
268
+ super();
269
+ this.driver = driver;
270
+ }
210
271
  async _beginTransaction() {
211
272
  const conn = await this.driver.getConnection();
212
273
  await conn.exec("START TRANSACTION");
@@ -234,11 +295,12 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
234
295
  /**
235
296
  * Returns the active executor: dedicated connection if inside a transaction,
236
297
  * otherwise the pool-based driver.
237
- */ _exec() {
238
- const txState = this._getTransactionState();
239
- return txState ?? this.driver;
298
+ */
299
+ _exec() {
300
+ return this._getTransactionState() ?? this.driver;
240
301
  }
241
- /** MySQL InnoDB enforces FK constraints natively. */ supportsNativeForeignKeys() {
302
+ /** MySQL InnoDB enforces FK constraints natively. */
303
+ supportsNativeForeignKeys() {
242
304
  return true;
243
305
  }
244
306
  prepareId(id, _fieldType) {
@@ -250,8 +312,8 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
250
312
  nativeDefaultFns() {
251
313
  return MysqlAdapter.NATIVE_DEFAULT_FNS;
252
314
  }
253
- onBeforeFlatten(type) {
254
- const meta = type.metadata;
315
+ onBeforeFlatten(_type) {
316
+ const meta = _type.metadata;
255
317
  const engine = meta.get("db.mysql.engine");
256
318
  if (engine) this._engine = engine;
257
319
  const charset = meta.get("db.mysql.charset");
@@ -276,7 +338,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
276
338
  indexName
277
339
  });
278
340
  const threshold = metadata.get("db.search.vector.threshold");
279
- if (threshold !== undefined) this._vectorThresholds.set(indexName, threshold);
341
+ if (threshold !== void 0) this._vectorThresholds.set(indexName, threshold);
280
342
  }
281
343
  }
282
344
  getDesiredTableOptions() {
@@ -320,18 +382,15 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
320
382
  const tableName = this.resolveTableName();
321
383
  const clauses = [];
322
384
  for (const change of changes) switch (change.key) {
323
- case "engine": {
385
+ case "engine":
324
386
  clauses.push(`ENGINE = ${change.newValue}`);
325
387
  break;
326
- }
327
- case "charset": {
388
+ case "charset":
328
389
  clauses.push(`CHARACTER SET = ${change.newValue}`);
329
390
  break;
330
- }
331
- case "collation": {
391
+ case "collation":
332
392
  clauses.push(`COLLATE = ${change.newValue}`);
333
393
  break;
334
- }
335
394
  }
336
395
  if (clauses.length > 0) {
337
396
  const ddl = `ALTER TABLE ${quoteTableName(tableName)} ${clauses.join(", ")}`;
@@ -343,12 +402,12 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
343
402
  * Returns a value formatter for TIMESTAMP-mapped fields.
344
403
  * Number fields with @db.default.now map to MySQL TIMESTAMP — the formatter
345
404
  * converts epoch ms to a UTC datetime string for the wire protocol.
346
- */ formatValue(field) {
405
+ */
406
+ formatValue(field) {
347
407
  if (field.designType === "number" && field.defaultValue?.kind === "fn" && field.defaultValue.fn === "now") return {
348
408
  toStorage: (value) => typeof value === "number" ? epochMsToUtcDatetime(value) : value,
349
409
  fromStorage: utcDatetimeToEpochMs
350
410
  };
351
- return undefined;
352
411
  }
353
412
  /**
354
413
  * Wraps an async write operation to catch MySQL constraint errors
@@ -358,24 +417,18 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
358
417
  * - 1062 = ER_DUP_ENTRY (unique constraint violation)
359
418
  * - 1451 = ER_ROW_IS_REFERENCED_2 (FK violation on delete)
360
419
  * - 1452 = ER_NO_REFERENCED_ROW_2 (FK violation on insert/update)
361
- */ async _wrapConstraintError(fn) {
420
+ */
421
+ async _wrapConstraintError(fn) {
362
422
  try {
363
423
  return await fn();
364
424
  } catch (error) {
365
425
  if (error && typeof error === "object" && "errno" in error) {
366
426
  const err = error;
367
- if (err.errno === 1062) {
368
- const match = err.message?.match(/for key '(?:\w+\.)?(\w+)'/);
369
- const field = match?.[1] ?? "";
370
- throw new DbError("CONFLICT", [{
371
- path: field,
372
- message: err.sqlMessage ?? err.message
373
- }]);
374
- }
375
- if (err.errno === 1451 || err.errno === 1452) {
376
- const errors = this._mapFkError(err.message);
377
- throw new DbError("FK_VIOLATION", errors);
378
- }
427
+ if (err.errno === 1062) throw new DbError("CONFLICT", [{
428
+ path: (err.message?.match(/for key '(?:\w+\.)?(\w+)'/))?.[1] ?? "",
429
+ message: err.sqlMessage ?? err.message
430
+ }]);
431
+ if (err.errno === 1451 || err.errno === 1452) throw new DbError("FK_VIOLATION", this._mapFkError(err.message));
379
432
  }
380
433
  throw error;
381
434
  }
@@ -384,9 +437,8 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
384
437
  const fkMatch = message.match(/FOREIGN KEY \(`(\w+)`\)/);
385
438
  if (fkMatch) {
386
439
  const physicalCol = fkMatch[1];
387
- const field = this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol);
388
440
  return [{
389
- path: field?.path ?? physicalCol,
441
+ path: this._table.fieldDescriptors.find((f) => f.physicalName === physicalCol)?.path ?? physicalCol,
390
442
  message
391
443
  }];
392
444
  }
@@ -453,28 +505,25 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
453
505
  }
454
506
  async count(query) {
455
507
  const where = buildWhere(query.filter);
456
- const tableName = this.resolveTableName();
457
- const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${where.sql}`;
508
+ const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(this.resolveTableName())} WHERE ${where.sql}`;
458
509
  this._log(sql, where.params);
459
- const row = await this._exec().get(sql, where.params);
460
- return row?.cnt ?? 0;
510
+ return (await this._exec().get(sql, where.params))?.cnt ?? 0;
461
511
  }
462
512
  async aggregate(query) {
463
513
  const where = buildWhere(query.filter);
464
514
  const tableName = this.resolveTableName();
465
515
  if (query.controls.$count) {
466
- const { sql: sql$1, params: params$1 } = buildAggregateCount$1(tableName, where, query.controls);
467
- this._log(sql$1, params$1);
468
- const row = await this._exec().get(sql$1, params$1);
469
- return [{ count: row?.count ?? 0 }];
516
+ const { sql, params } = buildAggregateCount$1(tableName, where, query.controls);
517
+ this._log(sql, params);
518
+ return [{ count: (await this._exec().get(sql, params))?.count ?? 0 }];
470
519
  }
471
520
  const { sql, params } = buildAggregateSelect$1(tableName, where, query.controls);
472
521
  this._log(sql, params);
473
522
  return this._exec().all(sql, params);
474
523
  }
475
- async updateOne(filter, data) {
524
+ async updateOne(filter, data, ops) {
476
525
  const where = buildWhere(filter);
477
- const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, 1);
526
+ const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, 1, ops);
478
527
  this._log(sql, params);
479
528
  const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
480
529
  return {
@@ -482,9 +531,9 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
482
531
  modifiedCount: result.changedRows
483
532
  };
484
533
  }
485
- async updateMany(filter, data) {
534
+ async updateMany(filter, data, ops) {
486
535
  const where = buildWhere(filter);
487
- const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where);
536
+ const { sql, params } = buildUpdate$1(this.resolveTableName(), data, where, void 0, ops);
488
537
  this._log(sql, params);
489
538
  const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
490
539
  return {
@@ -516,18 +565,16 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
516
565
  const where = buildWhere(filter);
517
566
  const { sql, params } = buildDelete$1(this.resolveTableName(), where, 1);
518
567
  this._log(sql, params);
519
- const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
520
- return { deletedCount: result.affectedRows };
568
+ return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
521
569
  }
522
570
  async deleteMany(filter) {
523
571
  const where = buildWhere(filter);
524
572
  const { sql, params } = buildDelete$1(this.resolveTableName(), where);
525
573
  this._log(sql, params);
526
- const result = await this._wrapConstraintError(() => this._exec().run(sql, params));
527
- return { deletedCount: result.affectedRows };
574
+ return { deletedCount: (await this._wrapConstraintError(() => this._exec().run(sql, params))).affectedRows };
528
575
  }
529
576
  async ensureTable() {
530
- if (this._supportsVector === undefined && this._vectorFields.size > 0) await this._detectVectorSupport();
577
+ if (this._supportsVector === void 0 && this._vectorFields.size > 0) await this._detectVectorSupport();
531
578
  if (this._table instanceof AtscriptDbView) return this._ensureView();
532
579
  const sql = buildCreateTable(this.resolveTableName(), this._table.fieldDescriptors, this._table.foreignKeys, {
533
580
  engine: this._engine,
@@ -551,11 +598,10 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
551
598
  }
552
599
  async getExistingColumnsForTable(tableName) {
553
600
  const schema = this._schema;
554
- const rows = await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT
601
+ return (await this._exec().all(`SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_KEY, COLUMN_DEFAULT
555
602
  FROM INFORMATION_SCHEMA.COLUMNS
556
603
  WHERE TABLE_NAME = ? AND TABLE_SCHEMA = COALESCE(?, DATABASE())
557
- ORDER BY ORDINAL_POSITION`, [tableName, schema]);
558
- return rows.map((r) => ({
604
+ ORDER BY ORDINAL_POSITION`, [tableName, schema])).map((r) => ({
559
605
  name: r.COLUMN_NAME,
560
606
  type: r.COLUMN_TYPE.toUpperCase(),
561
607
  notnull: r.IS_NULLABLE === "NO",
@@ -578,7 +624,7 @@ var MysqlAdapter = class MysqlAdapter extends BaseDbAdapter {
578
624
  let ddl = `ALTER TABLE ${quoteTableName(tableName)} ADD COLUMN ${qi(field.physicalName)} ${sqlType}`;
579
625
  if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
580
626
  if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
581
- else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
627
+ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValueForType(field.designType)}`;
582
628
  if (field.collate) {
583
629
  const nativeCollate = field.type?.metadata?.get("db.mysql.collate");
584
630
  ddl += ` COLLATE ${nativeCollate ?? collationToMysql(field.collate)}`;
@@ -606,8 +652,8 @@ else if (!field.optional && !field.isPrimaryKey) ddl += ` DEFAULT ${defaultValue
606
652
  let ddl = `ALTER TABLE ${quoteTableName(tableName)} MODIFY COLUMN ${qi(field.physicalName)} ${sqlType}`;
607
653
  if (!field.optional && !field.isPrimaryKey) ddl += " NOT NULL";
608
654
  if (field.defaultValue?.kind === "value") ddl += ` DEFAULT ${defaultValueToSqlLiteral(field.designType, field.defaultValue.value)}`;
609
- else if (field.defaultValue?.kind === "fn") ddl += ` DEFAULT ${field.defaultValue.fn === "now" ? "CURRENT_TIMESTAMP" : field.defaultValue.fn === "uuid" ? "(UUID())" : `(${field.defaultValue.fn}())`}`;
610
- else ddl += " DEFAULT NULL";
655
+ else if (field.defaultValue?.kind === "fn") ddl += ` DEFAULT ${field.defaultValue.fn === "now" ? "CURRENT_TIMESTAMP" : field.defaultValue.fn === "uuid" ? "(UUID())" : `(${field.defaultValue.fn}())`}`;
656
+ else ddl += " DEFAULT NULL";
611
657
  this._log(ddl);
612
658
  await this._exec().exec(ddl);
613
659
  }
@@ -717,10 +763,7 @@ else ddl += " DEFAULT NULL";
717
763
  const fulltext = index.type === "fulltext" ? "FULLTEXT " : "";
718
764
  const isFulltext = index.type === "fulltext";
719
765
  const cols = index.fields.map((f) => {
720
- const col = qi(f.name);
721
- const prefix = !isFulltext && stringFields.has(f.name) ? "(255)" : "";
722
- const order = isFulltext ? "" : ` ${f.sort === "desc" ? "DESC" : "ASC"}`;
723
- return `${col}${prefix}${order}`;
766
+ return `${qi(f.name)}${!isFulltext && stringFields.has(f.name) ? "(255)" : ""}${isFulltext ? "" : ` ${f.sort === "desc" ? "DESC" : "ASC"}`}`;
724
767
  }).join(", ");
725
768
  const sql = `CREATE ${fulltext}${unique}INDEX ${qi(index.key)} ON ${quoteTableName(this.resolveTableName())} (${cols})`;
726
769
  this._log(sql);
@@ -735,19 +778,19 @@ else ddl += " DEFAULT NULL";
735
778
  }
736
779
  async syncForeignKeys() {
737
780
  const existingByName = await this._getExistingFkConstraints();
738
- const desiredFkKeys = new Set();
739
- for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].sort().join(","));
781
+ const desiredFkKeys = /* @__PURE__ */ new Set();
782
+ for (const fk of this._table.foreignKeys.values()) desiredFkKeys.add([...fk.fields].toSorted().join(","));
740
783
  for (const [constraintName, columns] of existingByName) {
741
- const key = columns.sort().join(",");
784
+ const key = columns.toSorted().join(",");
742
785
  if (!desiredFkKeys.has(key)) {
743
786
  const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP FOREIGN KEY ${qi(constraintName)}`;
744
787
  this._log(ddl);
745
788
  await this._exec().exec(ddl);
746
789
  }
747
790
  }
748
- const existingKeys = new Set([...existingByName.values()].map((cols) => cols.sort().join(",")));
791
+ const existingKeys = new Set([...existingByName.values()].map((cols) => cols.toSorted().join(",")));
749
792
  for (const fk of this._table.foreignKeys.values()) {
750
- const key = [...fk.fields].sort().join(",");
793
+ const key = [...fk.fields].toSorted().join(",");
751
794
  if (!existingKeys.has(key)) {
752
795
  const localCols = fk.fields.map((f) => qi(f)).join(", ");
753
796
  const targetCols = fk.targetFields.map((f) => qi(f)).join(", ");
@@ -764,7 +807,7 @@ else ddl += " DEFAULT NULL";
764
807
  const keySet = new Set(fkFieldKeys);
765
808
  const existingByName = await this._getExistingFkConstraints();
766
809
  for (const [constraintName, cols] of existingByName) {
767
- const key = cols.sort().join(",");
810
+ const key = cols.toSorted().join(",");
768
811
  if (keySet.has(key)) {
769
812
  const ddl = `ALTER TABLE ${quoteTableName(this.resolveTableName())} DROP FOREIGN KEY ${qi(constraintName)}`;
770
813
  this._log(ddl);
@@ -772,12 +815,13 @@ else ddl += " DEFAULT NULL";
772
815
  }
773
816
  }
774
817
  }
775
- /** Queries INFORMATION_SCHEMA for existing FK constraints, grouped by constraint name → column names. */ async _getExistingFkConstraints() {
818
+ /** Queries INFORMATION_SCHEMA for existing FK constraints, grouped by constraint name → column names. */
819
+ async _getExistingFkConstraints() {
776
820
  const rows = await this._exec().all(`SELECT kcu.CONSTRAINT_NAME, kcu.COLUMN_NAME
777
821
  FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu
778
822
  WHERE kcu.TABLE_NAME = ? AND kcu.TABLE_SCHEMA = COALESCE(?, DATABASE())
779
823
  AND kcu.REFERENCED_TABLE_NAME IS NOT NULL`, [this._table.tableName, this._schema]);
780
- const byName = new Map();
824
+ const byName = /* @__PURE__ */ new Map();
781
825
  for (const row of rows) {
782
826
  let cols = byName.get(row.CONSTRAINT_NAME);
783
827
  if (!cols) {
@@ -819,8 +863,7 @@ else ddl += " DEFAULT NULL";
819
863
  const countPromise = (async () => {
820
864
  const sql = `SELECT COUNT(*) as cnt FROM ${quoteTableName(tableName)} WHERE ${combinedWhere.sql}`;
821
865
  this._log(sql, combinedWhere.params);
822
- const row = await this._exec().get(sql, combinedWhere.params);
823
- return row?.cnt ?? 0;
866
+ return (await this._exec().get(sql, combinedWhere.params))?.cnt ?? 0;
824
867
  })();
825
868
  const [data, count] = await Promise.all([selectPromise, countPromise]);
826
869
  return {
@@ -843,14 +886,14 @@ else ddl += " DEFAULT NULL";
843
886
  for (const index of this._table.indexes.values()) if (index.type === "fulltext") {
844
887
  if (!indexName || index.key === indexName) return index;
845
888
  }
846
- return undefined;
847
889
  }
848
890
  /**
849
891
  * Detects native VECTOR type support by inspecting the server version.
850
892
  * MySQL 9.0+ supports the VECTOR column type natively.
851
893
  * Caches the result for the lifetime of this adapter instance.
852
- */ async _detectVectorSupport() {
853
- if (this._supportsVector !== undefined) return this._supportsVector;
894
+ */
895
+ async _detectVectorSupport() {
896
+ if (this._supportsVector !== void 0) return this._supportsVector;
854
897
  try {
855
898
  const row = await this.driver.get("SELECT VERSION() as v", []);
856
899
  if (row?.v) {
@@ -885,7 +928,8 @@ else ddl += " DEFAULT NULL";
885
928
  count: countRow?.cnt ?? 0
886
929
  };
887
930
  }
888
- /** Resolves vector field and computes shared context for vector search SQL builders. */ _prepareVectorSearch(vector, query, indexName) {
931
+ /** Resolves vector field and computes shared context for vector search SQL builders. */
932
+ _prepareVectorSearch(vector, query, indexName) {
889
933
  let field;
890
934
  let vec;
891
935
  if (indexName) {
@@ -923,13 +967,13 @@ else ddl += " DEFAULT NULL";
923
967
  const inner = `SELECT *, ${ctx.distanceFn}(${qi(ctx.field)}, STRING_TO_VECTOR(?)) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${ctx.where.sql}`;
924
968
  const params = [ctx.vectorStr, ...ctx.where.params];
925
969
  let sql = `SELECT * FROM (${inner}) _v`;
926
- if (ctx.threshold !== undefined) {
970
+ if (ctx.threshold !== void 0) {
927
971
  sql += ` WHERE _distance <= ?`;
928
972
  params.push(2 * (1 - ctx.threshold));
929
973
  }
930
974
  sql += ` ORDER BY _distance ASC`;
931
- if (ctx.controls.$skip) sql += ` LIMIT ${ctx.controls.$limit || 1e3} OFFSET ${ctx.controls.$skip}`;
932
- else sql += ` LIMIT ${ctx.controls.$limit || 20}`;
975
+ if (ctx.controls.$skip) sql += ` LIMIT ${Number(ctx.controls.$limit) || 1e3} OFFSET ${Number(ctx.controls.$skip)}`;
976
+ else sql += ` LIMIT ${Number(ctx.controls.$limit) || 20}`;
933
977
  return {
934
978
  sql,
935
979
  params
@@ -940,7 +984,7 @@ else sql += ` LIMIT ${ctx.controls.$limit || 20}`;
940
984
  const inner = `SELECT ${ctx.distanceFn}(${qi(ctx.field)}, STRING_TO_VECTOR(?)) AS _distance FROM ${quoteTableName(ctx.tableName)} WHERE ${ctx.where.sql}`;
941
985
  const params = [ctx.vectorStr, ...ctx.where.params];
942
986
  let sql = `SELECT COUNT(*) AS cnt FROM (${inner}) _v`;
943
- if (ctx.threshold !== undefined) {
987
+ if (ctx.threshold !== void 0) {
944
988
  sql += ` WHERE _distance <= ?`;
945
989
  params.push(2 * (1 - ctx.threshold));
946
990
  }
@@ -949,16 +993,13 @@ else sql += ` LIMIT ${ctx.controls.$limit || 20}`;
949
993
  params
950
994
  };
951
995
  }
952
- /** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */ _resolveVectorThreshold(controls, indexName) {
996
+ /** Resolves threshold: query-time $threshold > schema-level @db.search.vector.threshold. */
997
+ _resolveVectorThreshold(controls, indexName) {
953
998
  const queryThreshold = controls.$threshold;
954
- if (queryThreshold !== undefined) return queryThreshold;
999
+ if (queryThreshold !== void 0) return queryThreshold;
955
1000
  return this._vectorThresholds.get(indexName);
956
1001
  }
957
- constructor(driver) {
958
- super(), _define_property$1(this, "driver", void 0), _define_property$1(this, "supportsColumnModify", void 0), _define_property$1(this, "_engine", void 0), _define_property$1(this, "_charset", void 0), _define_property$1(this, "_collation", void 0), _define_property$1(this, "_autoIncrementStart", void 0), _define_property$1(this, "_incrementFields", void 0), _define_property$1(this, "_onUpdateFields", void 0), _define_property$1(this, "_supportsVector", void 0), _define_property$1(this, "_vectorFields", void 0), _define_property$1(this, "_vectorThresholds", void 0), this.driver = driver, this.supportsColumnModify = true, this._engine = "InnoDB", this._charset = "utf8mb4", this._collation = "utf8mb4_unicode_ci", this._incrementFields = new Set(), this._onUpdateFields = new Map(), this._vectorFields = new Map(), this._vectorThresholds = new Map();
959
- }
960
1002
  };
961
- _define_property$1(MysqlAdapter, "NATIVE_DEFAULT_FNS", new Set(["now", "increment"]));
962
1003
  /**
963
1004
  * Normalizes MySQL INFORMATION_SCHEMA.COLUMNS.COLUMN_DEFAULT values
964
1005
  * to match the format produced by `serializeDefaultValue()`.
@@ -967,47 +1008,41 @@ _define_property$1(MysqlAdapter, "NATIVE_DEFAULT_FNS", new Set(["now", "incremen
967
1008
  * `uuid()`), but the diff engine compares against serialized form (`fn:now`,
968
1009
  * `fn:uuid`). Without normalization, every table with function defaults
969
1010
  * produces phantom ALTER diffs on re-plan.
970
- */ function normalizeMysqlDefault(value) {
971
- if (value === null) return undefined;
1011
+ */
1012
+ function normalizeMysqlDefault(value) {
1013
+ if (value === null) return;
972
1014
  const lower = value.toLowerCase();
973
1015
  if (lower === "current_timestamp" || lower === "current_timestamp()") return "fn:now";
974
1016
  if (lower === "uuid()") return "fn:uuid";
975
1017
  if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1).replace(/''/g, "'");
976
1018
  return value;
977
1019
  }
978
- /** Maps generic similarity metric to MySQL 9+ distance function name. */ function similarityToMysqlFn(similarity) {
1020
+ /** Maps generic similarity metric to MySQL 9+ distance function name. */
1021
+ function similarityToMysqlFn(similarity) {
979
1022
  switch (similarity) {
980
1023
  case "euclidean": return "VEC_DISTANCE_EUCLIDEAN";
981
1024
  case "dotProduct": return "VEC_DISTANCE_DOT";
982
1025
  default: return "VEC_DISTANCE_COSINE";
983
1026
  }
984
1027
  }
985
- /** Formats a number[] vector as MySQL's STRING_TO_VECTOR input: '[1.0, 2.0, ...]'. */ function vectorToString(vector) {
1028
+ /** Formats a number[] vector as MySQL's STRING_TO_VECTOR input: '[1.0, 2.0, ...]'. */
1029
+ function vectorToString(vector) {
986
1030
  return `[${vector.join(",")}]`;
987
1031
  }
988
-
989
1032
  //#endregion
990
- //#region packages/db-mysql/src/mysql2-driver.ts
991
- function _define_property(obj, key, value) {
992
- if (key in obj) Object.defineProperty(obj, key, {
993
- value,
994
- enumerable: true,
995
- configurable: true,
996
- writable: true
997
- });
998
- else obj[key] = value;
999
- return obj;
1000
- }
1001
- /** mysql2 rejects `undefined` in bind arrays — coerce to `null`. */ function sanitizeParams(params) {
1033
+ //#region src/mysql2-driver.ts
1034
+ /** mysql2 rejects `undefined` in bind arrays — coerce to `null`. */
1035
+ function sanitizeParams(params) {
1002
1036
  if (!params) return [];
1003
- return params.map((v) => v === undefined ? null : v);
1037
+ return params.map((v) => v === void 0 ? null : v);
1004
1038
  }
1005
1039
  /**
1006
1040
  * Custom type-casting for mysql2 result columns to maintain cross-adapter consistency.
1007
1041
  *
1008
1042
  * - TIMESTAMP/DATETIME → epoch milliseconds (number) instead of Date objects
1009
1043
  * - DECIMAL/NEWDECIMAL → number instead of string
1010
- */ function atscriptTypeCast(field, next) {
1044
+ */
1045
+ function atscriptTypeCast(field, next) {
1011
1046
  if (field.type === "TIMESTAMP" || field.type === "DATETIME") {
1012
1047
  const str = field.string();
1013
1048
  if (str === null) return null;
@@ -1019,13 +1054,66 @@ else obj[key] = value;
1019
1054
  }
1020
1055
  return next();
1021
1056
  }
1057
+ /**
1058
+ * {@link TMysqlDriver} implementation backed by `mysql2/promise`.
1059
+ *
1060
+ * Accepts a connection URI string, a `PoolOptions` object, or a pre-created
1061
+ * `Pool` instance from `mysql2/promise`.
1062
+ *
1063
+ * ```typescript
1064
+ * import { Mysql2Driver } from '@atscript/db-mysql'
1065
+ *
1066
+ * // Connection URI
1067
+ * const driver = new Mysql2Driver('mysql://root:pass@localhost:3306/mydb')
1068
+ *
1069
+ * // Pool options
1070
+ * const driver = new Mysql2Driver({
1071
+ * host: 'localhost',
1072
+ * user: 'root',
1073
+ * database: 'mydb',
1074
+ * waitForConnections: true,
1075
+ * connectionLimit: 10,
1076
+ * })
1077
+ *
1078
+ * // Pre-created pool
1079
+ * import mysql from 'mysql2/promise'
1080
+ * const pool = mysql.createPool({ host: 'localhost', database: 'mydb' })
1081
+ * const driver = new Mysql2Driver(pool)
1082
+ * ```
1083
+ *
1084
+ * Requires `mysql2` to be installed:
1085
+ * ```bash
1086
+ * pnpm add mysql2
1087
+ * ```
1088
+ */
1022
1089
  var Mysql2Driver = class {
1090
+ pool;
1091
+ poolInit;
1092
+ constructor(poolOrConfig) {
1093
+ if (typeof poolOrConfig === "object" && "execute" in poolOrConfig) this.pool = poolOrConfig;
1094
+ else this.poolInit = import("mysql2/promise").then((mysql) => {
1095
+ if (typeof poolOrConfig === "string") this.pool = mysql.createPool({
1096
+ uri: poolOrConfig,
1097
+ timezone: "+00:00",
1098
+ supportBigNumbers: true,
1099
+ bigNumberStrings: false,
1100
+ typeCast: atscriptTypeCast
1101
+ });
1102
+ else this.pool = mysql.createPool({
1103
+ ...poolOrConfig,
1104
+ timezone: "+00:00",
1105
+ supportBigNumbers: true,
1106
+ bigNumberStrings: false,
1107
+ typeCast: atscriptTypeCast
1108
+ });
1109
+ return this.pool;
1110
+ });
1111
+ }
1023
1112
  getPool() {
1024
1113
  return this.pool || this.poolInit;
1025
1114
  }
1026
1115
  async run(sql, params) {
1027
- const pool = await this.getPool();
1028
- const [result] = await pool.query(sql, sanitizeParams(params));
1116
+ const [result] = await (await this.getPool()).query(sql, sanitizeParams(params));
1029
1117
  const header = result;
1030
1118
  return {
1031
1119
  affectedRows: header.affectedRows ?? 0,
@@ -1034,22 +1122,18 @@ var Mysql2Driver = class {
1034
1122
  };
1035
1123
  }
1036
1124
  async all(sql, params) {
1037
- const pool = await this.getPool();
1038
- const [rows] = await pool.query(sql, sanitizeParams(params));
1125
+ const [rows] = await (await this.getPool()).query(sql, sanitizeParams(params));
1039
1126
  return rows;
1040
1127
  }
1041
1128
  async get(sql, params) {
1042
- const pool = await this.getPool();
1043
- const [rows] = await pool.query(sql, sanitizeParams(params));
1129
+ const [rows] = await (await this.getPool()).query(sql, sanitizeParams(params));
1044
1130
  return rows[0] ?? null;
1045
1131
  }
1046
1132
  async exec(sql) {
1047
- const pool = await this.getPool();
1048
- await pool.query(sql);
1133
+ await (await this.getPool()).query(sql);
1049
1134
  }
1050
1135
  async getConnection() {
1051
- const pool = await this.getPool();
1052
- const conn = await pool.getConnection();
1136
+ const conn = await (await this.getPool()).getConnection();
1053
1137
  return {
1054
1138
  async run(sql, params) {
1055
1139
  const [result] = await conn.query(sql, sanitizeParams(params));
@@ -1077,119 +1161,18 @@ var Mysql2Driver = class {
1077
1161
  };
1078
1162
  }
1079
1163
  async close() {
1080
- const pool = await this.getPool();
1081
- await pool.end();
1082
- }
1083
- constructor(poolOrConfig) {
1084
- _define_property(this, "pool", void 0);
1085
- _define_property(this, "poolInit", void 0);
1086
- if (typeof poolOrConfig === "object" && "execute" in poolOrConfig) this.pool = poolOrConfig;
1087
- else this.poolInit = import("mysql2/promise").then((mysql) => {
1088
- if (typeof poolOrConfig === "string") this.pool = mysql.createPool({
1089
- uri: poolOrConfig,
1090
- timezone: "+00:00",
1091
- supportBigNumbers: true,
1092
- bigNumberStrings: false,
1093
- typeCast: atscriptTypeCast
1094
- });
1095
- else this.pool = mysql.createPool({
1096
- ...poolOrConfig,
1097
- timezone: "+00:00",
1098
- supportBigNumbers: true,
1099
- bigNumberStrings: false,
1100
- typeCast: atscriptTypeCast
1101
- });
1102
- return this.pool;
1103
- });
1164
+ await (await this.getPool()).end();
1104
1165
  }
1105
1166
  };
1106
-
1107
1167
  //#endregion
1108
- //#region packages/db-mysql/src/plugin/annotations.ts
1109
- const annotations = {
1110
- engine: new AnnotationSpec({
1111
- description: "Specifies the MySQL storage engine.\n\n**Default:** `\"InnoDB\"`\n\n```atscript\n@db.mysql.engine \"MyISAM\"\nexport interface Logs { ... }\n```",
1112
- nodeType: ["interface"],
1113
- multiple: false,
1114
- argument: {
1115
- name: "engine",
1116
- type: "string",
1117
- values: [
1118
- "InnoDB",
1119
- "MyISAM",
1120
- "MEMORY",
1121
- "CSV",
1122
- "ARCHIVE"
1123
- ],
1124
- description: "MySQL storage engine name."
1125
- }
1126
- }),
1127
- charset: new AnnotationSpec({
1128
- description: "Specifies the character set for the table or column.\n\n**Default:** `\"utf8mb4\"`\n\n```atscript\n@db.mysql.charset \"latin1\"\nexport interface Legacy { ... }\n```",
1129
- nodeType: ["interface", "prop"],
1130
- multiple: false,
1131
- argument: {
1132
- name: "charset",
1133
- type: "string",
1134
- values: [
1135
- "utf8mb4",
1136
- "utf8",
1137
- "latin1",
1138
- "ascii",
1139
- "binary"
1140
- ],
1141
- description: "MySQL character set name."
1142
- }
1143
- }),
1144
- collate: new AnnotationSpec({
1145
- description: "Specifies a native MySQL collation (overrides portable `@db.column.collate`).\n\n```atscript\n@db.mysql.collate \"utf8mb4_turkish_ci\"\nname: string\n```",
1146
- nodeType: ["interface", "prop"],
1147
- multiple: false,
1148
- argument: {
1149
- name: "collation",
1150
- type: "string",
1151
- description: "Native MySQL collation name (e.g., \"utf8mb4_turkish_ci\")."
1152
- }
1153
- }),
1154
- unsigned: new AnnotationSpec({
1155
- description: "Adds the UNSIGNED modifier to an integer column.\n\n```atscript\n@db.mysql.unsigned\nage: number.int\n```",
1156
- nodeType: ["prop"],
1157
- multiple: false
1158
- }),
1159
- type: new AnnotationSpec({
1160
- description: "Overrides the native MySQL column type.\n\n```atscript\n@db.mysql.type \"MEDIUMTEXT\"\nbio: string\n```",
1161
- nodeType: ["prop"],
1162
- multiple: false,
1163
- argument: {
1164
- name: "type",
1165
- type: "string",
1166
- description: "Native MySQL column type (e.g., \"MEDIUMTEXT\", \"TINYTEXT\")."
1167
- }
1168
- }),
1169
- onUpdate: new AnnotationSpec({
1170
- description: "Sets the MySQL ON UPDATE clause for a column.\n\n```atscript\n@db.mysql.onUpdate \"CURRENT_TIMESTAMP\"\nupdatedAt: number.timestamp\n```",
1171
- nodeType: ["prop"],
1172
- multiple: false,
1173
- argument: {
1174
- name: "expression",
1175
- type: "string",
1176
- values: ["CURRENT_TIMESTAMP"],
1177
- description: "Expression to evaluate on row update."
1178
- }
1179
- })
1180
- };
1181
-
1182
- //#endregion
1183
- //#region packages/db-mysql/src/plugin/index.ts
1184
- const MysqlPlugin = () => ({
1185
- name: "mysql",
1186
- config() {
1187
- return { annotations: { db: { mysql: annotations } } };
1188
- }
1189
- });
1190
-
1191
- //#endregion
1192
- //#region packages/db-mysql/src/index.ts
1168
+ //#region src/index.ts
1169
+ /**
1170
+ * Creates a {@link DbSpace} backed by a MySQL connection pool.
1171
+ *
1172
+ * @param uri - MySQL connection URI (e.g., `mysql://root@localhost:3306/mydb`)
1173
+ * @param options - Additional pool options passed to mysql2.
1174
+ * @returns A `DbSpace` that creates `MysqlAdapter` instances per table.
1175
+ */
1193
1176
  function createAdapter(uri, options) {
1194
1177
  const driver = new Mysql2Driver({
1195
1178
  uri,
@@ -1197,6 +1180,5 @@ function createAdapter(uri, options) {
1197
1180
  });
1198
1181
  return new DbSpace(() => new MysqlAdapter(driver));
1199
1182
  }
1200
-
1201
1183
  //#endregion
1202
- export { Mysql2Driver, MysqlAdapter, MysqlPlugin, buildWhere, createAdapter };
1184
+ export { Mysql2Driver, MysqlAdapter, MysqlPlugin, buildWhere, createAdapter };