@mastra/mysql 0.7.1-alpha.0 → 0.7.1-alpha.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/dist/index.js CHANGED
@@ -5,6 +5,46 @@ import { parseSqlIdentifier } from "@mastra/core/utils";
5
5
  import { randomUUID } from "crypto";
6
6
  import { MessageList } from "@mastra/core/agent";
7
7
  import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
8
+ //#region src/storage/db/schema-snapshot.ts
9
+ /** Builds the `table.index` presence key the snapshot uses. */
10
+ function indexKey(table, index) {
11
+ return `${table.toLowerCase()}.${index.toLowerCase()}`;
12
+ }
13
+ const lower = (value) => String(value).toLowerCase();
14
+ /**
15
+ * Reads the catalog for `schemaName` in three queries. Returns null when no
16
+ * schema name is available (a pool with no default database): correctness over
17
+ * optimization, callers fall back to today's per-probe behavior.
18
+ */
19
+ async function loadSchemaSnapshot(pool, schemaName) {
20
+ if (!schemaName) return null;
21
+ const [[tableRows], [columnRows], [indexRows]] = await Promise.all([
22
+ pool.execute(`SELECT table_name FROM information_schema.tables WHERE table_schema = ?`, [schemaName]),
23
+ pool.execute(`SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = ?`, [schemaName]),
24
+ pool.execute(`SELECT DISTINCT table_name, index_name FROM information_schema.statistics WHERE table_schema = ?`, [schemaName])
25
+ ]);
26
+ const tables = /* @__PURE__ */ new Set();
27
+ for (const row of tableRows ?? []) tables.add(lower(row.table_name ?? row.TABLE_NAME));
28
+ const columns = /* @__PURE__ */ new Map();
29
+ for (const row of columnRows ?? []) {
30
+ const table = lower(row.table_name ?? row.TABLE_NAME);
31
+ let set = columns.get(table);
32
+ if (!set) {
33
+ set = /* @__PURE__ */ new Set();
34
+ columns.set(table, set);
35
+ }
36
+ set.add(lower(row.column_name ?? row.COLUMN_NAME));
37
+ }
38
+ const indexes = /* @__PURE__ */ new Set();
39
+ for (const row of indexRows ?? []) indexes.add(indexKey(lower(row.table_name ?? row.TABLE_NAME), lower(row.index_name ?? row.INDEX_NAME)));
40
+ return {
41
+ schemaName,
42
+ tables,
43
+ columns,
44
+ indexes
45
+ };
46
+ }
47
+ //#endregion
8
48
  //#region src/storage/domains/utils.ts
9
49
  function quoteIdentifier(value, context) {
10
50
  return `\`${parseSqlIdentifier(value, context)}\``;
@@ -144,12 +184,38 @@ var StoreOperationsMySQL = class extends StoreOperations {
144
184
  pool;
145
185
  database;
146
186
  resolvedDatabase;
187
+ /**
188
+ * Init-scoped catalog snapshot (see db/schema-snapshot.ts). Installed by
189
+ * MySQLStore.init() for exactly the init window and cleared in its finally;
190
+ * null at runtime so non-init callers keep probing the live catalog.
191
+ */
192
+ schemaSnapshot = null;
147
193
  constructor({ pool, database }) {
148
194
  super();
149
195
  this.pool = pool;
150
196
  this.database = database;
151
197
  this.resolvedDatabase = database ?? null;
152
198
  }
199
+ /**
200
+ * Loads and installs the init-scoped snapshot. A null load (no default
201
+ * database) or a failed load leaves the snapshot uninstalled, so init
202
+ * proceeds with today's per-probe behavior: correctness over optimization.
203
+ */
204
+ async loadInitSchemaSnapshot() {
205
+ try {
206
+ this.schemaSnapshot = await loadSchemaSnapshot(this.pool, await this.getDatabase());
207
+ } catch (error) {
208
+ console.warn("Failed to load init schema snapshot, falling back to per-object probing:", error);
209
+ this.schemaSnapshot = null;
210
+ }
211
+ }
212
+ clearInitSchemaSnapshot() {
213
+ this.schemaSnapshot = null;
214
+ }
215
+ /** Read by sibling domains (memory's raw index DDL) so snapshot state lives in exactly one place. */
216
+ getInitSchemaSnapshot() {
217
+ return this.schemaSnapshot;
218
+ }
153
219
  getPool() {
154
220
  return this.pool;
155
221
  }
@@ -165,6 +231,8 @@ var StoreOperationsMySQL = class extends StoreOperations {
165
231
  return this.resolvedDatabase ?? void 0;
166
232
  }
167
233
  async hasColumn(table, column) {
234
+ const snapshotColumns = this.schemaSnapshot?.columns.get(table.toLowerCase());
235
+ if (snapshotColumns) return snapshotColumns.has(column.toLowerCase());
168
236
  const db = await this.getDatabase();
169
237
  const params = [table, column];
170
238
  let sql = "SELECT COUNT(*) as count FROM information_schema.columns WHERE table_name = ? AND (column_name = ? OR column_name = ? )";
@@ -242,13 +310,23 @@ var StoreOperationsMySQL = class extends StoreOperations {
242
310
  return false;
243
311
  }
244
312
  async createTable({ tableName, schema }) {
313
+ const snapshot = this.schemaSnapshot;
314
+ if (snapshot?.tables.has(tableName.toLowerCase())) return;
245
315
  const connection = await this.pool.getConnection();
246
316
  try {
247
- const db = await this.getDatabase();
248
- const [t_rows] = await connection.query("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = ? AND table_name = ?", [db ?? "", tableName]);
249
- if (Array.isArray(t_rows) && t_rows.length > 0 && t_rows[0].count > 0) return;
317
+ if (!snapshot) {
318
+ const db = await this.getDatabase();
319
+ const [t_rows] = await connection.query("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = ? AND table_name = ?", [db ?? "", tableName]);
320
+ if (Array.isArray(t_rows) && t_rows.length > 0 && t_rows[0].count > 0) return;
321
+ }
250
322
  const sql = this.getCreateTableSQL(tableName, schema);
251
- await connection.execute(sql);
323
+ const [result] = await connection.execute(sql);
324
+ const created = result?.warningStatus === 0;
325
+ if (snapshot && created) {
326
+ const table = tableName.toLowerCase();
327
+ snapshot.tables.add(table);
328
+ snapshot.columns.set(table, new Set(Object.keys(schema).map((column) => column.toLowerCase())));
329
+ }
252
330
  } catch (error) {
253
331
  throw new MastraError({
254
332
  id: "MYSQL_STORE_CREATE_TABLE_FAILED",
@@ -304,17 +382,22 @@ var StoreOperationsMySQL = class extends StoreOperations {
304
382
  const tableName = formatTableName(table, this.database);
305
383
  const indexName = quoteIdentifier(name, "index name");
306
384
  try {
307
- const db = await this.getDatabase() ?? "";
308
- const [existing] = await this.pool.execute(`SELECT 1 FROM information_schema.STATISTICS
385
+ const snapshot = this.schemaSnapshot;
386
+ if (snapshot) {
387
+ if (snapshot.indexes.has(indexKey(table, name))) return;
388
+ } else {
389
+ const db = await this.getDatabase() ?? "";
390
+ const [existing] = await this.pool.execute(`SELECT 1 FROM information_schema.STATISTICS
309
391
  WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND INDEX_NAME = ?
310
392
  LIMIT 1`, [
311
- db,
312
- table,
313
- name
314
- ]);
315
- if (existing.length > 0) return;
393
+ db,
394
+ table,
395
+ name
396
+ ]);
397
+ if (existing.length > 0) return;
398
+ }
316
399
  const [columnMeta] = await this.pool.execute(`SELECT COLUMN_NAME, DATA_TYPE FROM information_schema.COLUMNS
317
- WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`, [db, table]);
400
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`, [await this.getDatabase() ?? "", table]);
318
401
  const dataTypeByColumn = new Map(columnMeta.map((row) => [String(row.COLUMN_NAME).toLowerCase(), String(row.DATA_TYPE).toLowerCase()]));
319
402
  const PREFIX_TYPES = /* @__PURE__ */ new Set([
320
403
  "tinytext",
@@ -340,6 +423,7 @@ var StoreOperationsMySQL = class extends StoreOperations {
340
423
  }).join(", ");
341
424
  const sql = `CREATE ${unique ? "UNIQUE " : ""}INDEX ${indexName} ON ${tableName} (${columnsStr})`;
342
425
  await this.pool.execute(sql);
426
+ this.schemaSnapshot?.indexes.add(indexKey(table, name));
343
427
  } catch (error) {
344
428
  console.warn(`Failed to create index ${name}:`, error);
345
429
  }
@@ -571,17 +655,22 @@ var StoreOperationsMySQL = class extends StoreOperations {
571
655
  }
572
656
  async alterTable({ tableName, schema, ifNotExists }) {
573
657
  if (!ifNotExists.length) return;
574
- const db = await this.getDatabase();
575
- const [tableRows] = await this.pool.execute("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = ? AND table_name = ?", [db ?? "", tableName]);
576
- if (!(Array.isArray(tableRows) && tableRows.length > 0 && tableRows[0].count > 0)) return;
577
- const params = [tableName];
578
- let sql = "SELECT column_name FROM information_schema.columns WHERE table_name = ?";
579
- if (db) {
580
- sql += " AND table_schema = ?";
581
- params.push(db);
658
+ const snapshotColumns = this.schemaSnapshot?.columns.get(tableName.toLowerCase());
659
+ let existing;
660
+ if (snapshotColumns) existing = snapshotColumns;
661
+ else {
662
+ const db = await this.getDatabase();
663
+ const [tableRows] = await this.pool.execute("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = ? AND table_name = ?", [db ?? "", tableName]);
664
+ if (!(Array.isArray(tableRows) && tableRows.length > 0 && tableRows[0].count > 0)) return;
665
+ const params = [tableName];
666
+ let sql = "SELECT column_name FROM information_schema.columns WHERE table_name = ?";
667
+ if (db) {
668
+ sql += " AND table_schema = ?";
669
+ params.push(db);
670
+ }
671
+ const [rows] = await this.pool.execute(sql, params);
672
+ existing = new Set((rows || []).map((row) => String(row.column_name ?? row.COLUMN_NAME).toLowerCase()));
582
673
  }
583
- const [rows] = await this.pool.execute(sql, params);
584
- const existing = new Set((rows || []).map((row) => String(row.column_name ?? row.COLUMN_NAME).toLowerCase()));
585
674
  for (const columnName of ifNotExists) {
586
675
  if (existing.has(columnName.toLowerCase())) continue;
587
676
  const column = schema[columnName];
@@ -603,6 +692,7 @@ var StoreOperationsMySQL = class extends StoreOperations {
603
692
  const alterSql = `ALTER TABLE ${formatTableName(tableName, this.database)} ADD COLUMN ${parts.join(" ")}`;
604
693
  try {
605
694
  await this.pool.execute(alterSql);
695
+ existing.add(columnName.toLowerCase());
606
696
  } catch (error) {
607
697
  if (error?.code === "ER_DUP_FIELDNAME") continue;
608
698
  throw new MastraError({
@@ -759,7 +849,8 @@ var AgentsMySQL = class AgentsMySQL extends AgentsStorage {
759
849
  "requestContextSchema",
760
850
  "workspace",
761
851
  "skills",
762
- "skillsFormat"
852
+ "skillsFormat",
853
+ "durable"
763
854
  ]
764
855
  });
765
856
  await this.createDefaultIndexes();
@@ -1051,6 +1142,7 @@ var AgentsMySQL = class AgentsMySQL extends AgentsStorage {
1051
1142
  workspace: input.workspace ?? null,
1052
1143
  skills: input.skills ?? null,
1053
1144
  skillsFormat: input.skillsFormat ?? null,
1145
+ durable: input.durable ?? null,
1054
1146
  changedFields: input.changedFields ?? null,
1055
1147
  changeMessage: input.changeMessage ?? null,
1056
1148
  createdAt: now
@@ -1266,6 +1358,7 @@ var AgentsMySQL = class AgentsMySQL extends AgentsStorage {
1266
1358
  workspace: this.safeParseJSON(row.workspace),
1267
1359
  skills: this.safeParseJSON(row.skills),
1268
1360
  skillsFormat: row.skillsFormat,
1361
+ durable: this.safeParseJSON(row.durable),
1269
1362
  changedFields: this.safeParseJSON(row.changedFields),
1270
1363
  changeMessage: row.changeMessage ?? void 0,
1271
1364
  createdAt: row.createdAt instanceof Date ? row.createdAt : new Date(row.createdAt)
@@ -4832,10 +4925,14 @@ var MemoryMySQL = class MemoryMySQL extends MemoryStorage {
4832
4925
  schema: TABLE_SCHEMAS[TABLE_MESSAGES],
4833
4926
  ifNotExists: ["resourceId"]
4834
4927
  });
4835
- if (omSchema) try {
4836
- await this.pool.execute(`CREATE INDEX idx_om_lookup_key ON ${OM_TABLE_QUOTED} (${quoteIdentifier("lookupKey", "column name")}(191))`);
4837
- } catch (err) {
4838
- if (err?.errno !== 1061) throw err;
4928
+ if (omSchema) {
4929
+ const snapshot = this.operations.getInitSchemaSnapshot();
4930
+ if (!snapshot?.indexes.has(indexKey(OM_TABLE, "idx_om_lookup_key"))) try {
4931
+ await this.pool.execute(`CREATE INDEX idx_om_lookup_key ON ${OM_TABLE_QUOTED} (${quoteIdentifier("lookupKey", "column name")}(191))`);
4932
+ snapshot?.indexes.add(indexKey(OM_TABLE, "idx_om_lookup_key"));
4933
+ } catch (err) {
4934
+ if (err?.errno !== 1061) throw err;
4935
+ }
4839
4936
  }
4840
4937
  await this.createDefaultIndexes();
4841
4938
  await this.createCustomIndexes();
@@ -10354,6 +10451,7 @@ function parseConnectionString(connectionString, overrides) {
10354
10451
  }
10355
10452
  var MySQLStore = class extends MastraCompositeStore {
10356
10453
  pool;
10454
+ operations;
10357
10455
  stores;
10358
10456
  constructor(config) {
10359
10457
  super({
@@ -10368,6 +10466,7 @@ var MySQLStore = class extends MastraCompositeStore {
10368
10466
  pool: this.pool,
10369
10467
  database
10370
10468
  });
10469
+ this.operations = operations;
10371
10470
  const memory = new MemoryMySQL({
10372
10471
  pool: this.pool,
10373
10472
  operations,
@@ -10510,6 +10609,7 @@ var MySQLStore = class extends MastraCompositeStore {
10510
10609
  async init() {
10511
10610
  try {
10512
10611
  (await this.pool.getConnection()).release();
10612
+ await this.operations.loadInitSchemaSnapshot();
10513
10613
  await super.init();
10514
10614
  } catch (error) {
10515
10615
  throw new MastraError({
@@ -10517,6 +10617,8 @@ var MySQLStore = class extends MastraCompositeStore {
10517
10617
  domain: ErrorDomain.STORAGE,
10518
10618
  category: ErrorCategory.THIRD_PARTY
10519
10619
  }, error);
10620
+ } finally {
10621
+ this.operations.clearInitSchemaSnapshot();
10520
10622
  }
10521
10623
  }
10522
10624
  async close() {