@mastra/mssql 0.0.0-netlify-no-bundle-20251127120354 → 0.0.0-new-button-export-20251219130424

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.cjs CHANGED
@@ -2,15 +2,16 @@
2
2
 
3
3
  var error = require('@mastra/core/error');
4
4
  var storage = require('@mastra/core/storage');
5
- var sql2 = require('mssql');
6
- var agent = require('@mastra/core/agent');
5
+ var sql = require('mssql');
6
+ var base = require('@mastra/core/base');
7
7
  var utils = require('@mastra/core/utils');
8
+ var agent = require('@mastra/core/agent');
8
9
  var crypto = require('crypto');
9
10
  var evals = require('@mastra/core/evals');
10
11
 
11
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
13
 
13
- var sql2__default = /*#__PURE__*/_interopDefault(sql2);
14
+ var sql__default = /*#__PURE__*/_interopDefault(sql);
14
15
 
15
16
  // src/storage/index.ts
16
17
  function getSchemaName(schema) {
@@ -22,259 +23,271 @@ function getTableName({ indexName, schemaName }) {
22
23
  const quotedSchemaName = schemaName;
23
24
  return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
24
25
  }
25
- function buildDateRangeFilter(dateRange, fieldName) {
26
- const filters = {};
27
- if (dateRange?.start) {
28
- filters[`${fieldName}_gte`] = dateRange.start;
29
- }
30
- if (dateRange?.end) {
31
- filters[`${fieldName}_lte`] = dateRange.end;
32
- }
33
- return filters;
34
- }
35
- function prepareWhereClause(filters, _schema) {
36
- const conditions = [];
37
- const params = {};
38
- let paramIndex = 1;
39
- Object.entries(filters).forEach(([key, value]) => {
40
- if (value === void 0) return;
41
- const paramName = `p${paramIndex++}`;
42
- if (key.endsWith("_gte")) {
43
- const fieldName = key.slice(0, -4);
44
- conditions.push(`[${utils.parseSqlIdentifier(fieldName, "field name")}] >= @${paramName}`);
45
- params[paramName] = value instanceof Date ? value.toISOString() : value;
46
- } else if (key.endsWith("_lte")) {
47
- const fieldName = key.slice(0, -4);
48
- conditions.push(`[${utils.parseSqlIdentifier(fieldName, "field name")}] <= @${paramName}`);
49
- params[paramName] = value instanceof Date ? value.toISOString() : value;
50
- } else if (value === null) {
51
- conditions.push(`[${utils.parseSqlIdentifier(key, "field name")}] IS NULL`);
52
- } else {
53
- conditions.push(`[${utils.parseSqlIdentifier(key, "field name")}] = @${paramName}`);
54
- params[paramName] = value instanceof Date ? value.toISOString() : value;
55
- }
56
- });
57
- return {
58
- sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
59
- params
60
- };
61
- }
62
- function transformFromSqlRow({
63
- tableName,
64
- sqlRow
65
- }) {
66
- const schema = storage.TABLE_SCHEMAS[tableName];
67
- const result = {};
68
- Object.entries(sqlRow).forEach(([key, value]) => {
69
- const columnSchema = schema?.[key];
70
- if (columnSchema?.type === "jsonb" && typeof value === "string") {
71
- try {
72
- result[key] = JSON.parse(value);
73
- } catch {
74
- result[key] = value;
75
- }
76
- } else if (columnSchema?.type === "timestamp" && value && typeof value === "string") {
77
- result[key] = new Date(value);
78
- } else if (columnSchema?.type === "timestamp" && value instanceof Date) {
79
- result[key] = value;
80
- } else if (columnSchema?.type === "boolean") {
81
- result[key] = Boolean(value);
82
- } else {
83
- result[key] = value;
84
- }
26
+
27
+ // src/storage/db/index.ts
28
+ function resolveMssqlConfig(config) {
29
+ if ("pool" in config && "db" in config) {
30
+ return { pool: config.pool, db: config.db, schema: config.schema, needsConnect: false };
31
+ }
32
+ const pool = new sql__default.default.ConnectionPool({
33
+ server: config.server,
34
+ database: config.database,
35
+ user: config.user,
36
+ password: config.password,
37
+ port: config.port,
38
+ options: config.options || { encrypt: true, trustServerCertificate: true }
85
39
  });
86
- return result;
40
+ const db = new MssqlDB({ pool, schemaName: config.schemaName });
41
+ return { pool, db, schema: config.schemaName, needsConnect: true };
87
42
  }
88
-
89
- // src/storage/domains/memory/index.ts
90
- var MemoryMSSQL = class extends storage.MemoryStorage {
43
+ var MssqlDB = class extends base.MastraBase {
91
44
  pool;
92
- schema;
93
- operations;
94
- _parseAndFormatMessages(messages, format) {
95
- const messagesWithParsedContent = messages.map((message) => {
96
- if (typeof message.content === "string") {
45
+ schemaName;
46
+ setupSchemaPromise = null;
47
+ schemaSetupComplete = void 0;
48
+ getSqlType(type, isPrimaryKey = false, useLargeStorage = false) {
49
+ switch (type) {
50
+ case "text":
51
+ if (useLargeStorage) {
52
+ return "NVARCHAR(MAX)";
53
+ }
54
+ return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(400)";
55
+ case "timestamp":
56
+ return "DATETIME2(7)";
57
+ case "uuid":
58
+ return "UNIQUEIDENTIFIER";
59
+ case "jsonb":
60
+ return "NVARCHAR(MAX)";
61
+ case "integer":
62
+ return "INT";
63
+ case "bigint":
64
+ return "BIGINT";
65
+ case "float":
66
+ return "FLOAT";
67
+ case "boolean":
68
+ return "BIT";
69
+ default:
70
+ throw new error.MastraError({
71
+ id: storage.createStorageErrorId("MSSQL", "TYPE", "NOT_SUPPORTED"),
72
+ domain: error.ErrorDomain.STORAGE,
73
+ category: error.ErrorCategory.THIRD_PARTY
74
+ });
75
+ }
76
+ }
77
+ constructor({ pool, schemaName }) {
78
+ super({ component: "STORAGE", name: "MssqlDB" });
79
+ this.pool = pool;
80
+ this.schemaName = schemaName;
81
+ }
82
+ async hasColumn(table, column) {
83
+ const schema = this.schemaName || "dbo";
84
+ const request = this.pool.request();
85
+ request.input("schema", schema);
86
+ request.input("table", table);
87
+ request.input("column", column);
88
+ request.input("columnLower", column.toLowerCase());
89
+ const result = await request.query(
90
+ `SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
91
+ );
92
+ return result.recordset.length > 0;
93
+ }
94
+ async setupSchema() {
95
+ if (!this.schemaName || this.schemaSetupComplete) {
96
+ return;
97
+ }
98
+ if (!this.setupSchemaPromise) {
99
+ this.setupSchemaPromise = (async () => {
97
100
  try {
98
- return { ...message, content: JSON.parse(message.content) };
99
- } catch {
100
- return message;
101
+ const checkRequest = this.pool.request();
102
+ checkRequest.input("schemaName", this.schemaName);
103
+ const checkResult = await checkRequest.query(`
104
+ SELECT 1 AS found FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = @schemaName
105
+ `);
106
+ const schemaExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
107
+ if (!schemaExists) {
108
+ try {
109
+ await this.pool.request().query(`CREATE SCHEMA [${this.schemaName}]`);
110
+ this.logger?.info?.(`Schema "${this.schemaName}" created successfully`);
111
+ } catch (error) {
112
+ this.logger?.error?.(`Failed to create schema "${this.schemaName}"`, { error });
113
+ throw new Error(
114
+ `Unable to create schema "${this.schemaName}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
115
+ );
116
+ }
117
+ }
118
+ this.schemaSetupComplete = true;
119
+ this.logger?.debug?.(`Schema "${this.schemaName}" is ready for use`);
120
+ } catch (error) {
121
+ this.schemaSetupComplete = void 0;
122
+ this.setupSchemaPromise = null;
123
+ throw error;
124
+ } finally {
125
+ this.setupSchemaPromise = null;
101
126
  }
102
- }
103
- return message;
104
- });
105
- const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
106
- const list = new agent.MessageList().add(cleanMessages, "memory");
107
- return format === "v2" ? list.get.all.db() : list.get.all.v1();
127
+ })();
128
+ }
129
+ await this.setupSchemaPromise;
108
130
  }
109
- constructor({
110
- pool,
111
- schema,
112
- operations
131
+ async insert({
132
+ tableName,
133
+ record,
134
+ transaction
113
135
  }) {
114
- super();
115
- this.pool = pool;
116
- this.schema = schema;
117
- this.operations = operations;
136
+ try {
137
+ const columns = Object.keys(record);
138
+ const parsedColumns = columns.map((col) => utils.parseSqlIdentifier(col, "column name"));
139
+ const paramNames = columns.map((_, i) => `@param${i}`);
140
+ const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${parsedColumns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
141
+ const request = transaction ? transaction.request() : this.pool.request();
142
+ columns.forEach((col, i) => {
143
+ const value = record[col];
144
+ const preparedValue = this.prepareValue(value, col, tableName);
145
+ if (preparedValue instanceof Date) {
146
+ request.input(`param${i}`, sql__default.default.DateTime2, preparedValue);
147
+ } else if (preparedValue === null || preparedValue === void 0) {
148
+ request.input(`param${i}`, this.getMssqlType(tableName, col), null);
149
+ } else {
150
+ request.input(`param${i}`, preparedValue);
151
+ }
152
+ });
153
+ await request.query(insertSql);
154
+ } catch (error$1) {
155
+ throw new error.MastraError(
156
+ {
157
+ id: storage.createStorageErrorId("MSSQL", "INSERT", "FAILED"),
158
+ domain: error.ErrorDomain.STORAGE,
159
+ category: error.ErrorCategory.THIRD_PARTY,
160
+ details: {
161
+ tableName
162
+ }
163
+ },
164
+ error$1
165
+ );
166
+ }
118
167
  }
119
- async getThreadById({ threadId }) {
168
+ async clearTable({ tableName }) {
169
+ const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
120
170
  try {
121
- const sql5 = `SELECT
122
- id,
123
- [resourceId],
124
- title,
125
- metadata,
126
- [createdAt],
127
- [updatedAt]
128
- FROM ${getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) })}
129
- WHERE id = @threadId`;
130
- const request = this.pool.request();
131
- request.input("threadId", threadId);
132
- const resultSet = await request.query(sql5);
133
- const thread = resultSet.recordset[0] || null;
134
- if (!thread) {
135
- return null;
171
+ try {
172
+ await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
173
+ } catch (truncateError) {
174
+ if (truncateError?.number === 4712) {
175
+ await this.pool.request().query(`DELETE FROM ${fullTableName}`);
176
+ } else {
177
+ throw truncateError;
178
+ }
136
179
  }
137
- return {
138
- ...thread,
139
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
140
- createdAt: thread.createdAt,
141
- updatedAt: thread.updatedAt
142
- };
143
180
  } catch (error$1) {
144
181
  throw new error.MastraError(
145
182
  {
146
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_THREAD_BY_ID_FAILED",
183
+ id: storage.createStorageErrorId("MSSQL", "CLEAR_TABLE", "FAILED"),
147
184
  domain: error.ErrorDomain.STORAGE,
148
185
  category: error.ErrorCategory.THIRD_PARTY,
149
186
  details: {
150
- threadId
187
+ tableName
151
188
  }
152
189
  },
153
190
  error$1
154
191
  );
155
192
  }
156
193
  }
157
- async listThreadsByResourceId(args) {
158
- const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
159
- if (page < 0) {
160
- throw new error.MastraError({
161
- id: "MASTRA_STORAGE_MSSQL_STORE_INVALID_PAGE",
162
- domain: error.ErrorDomain.STORAGE,
163
- category: error.ErrorCategory.USER,
164
- text: "Page number must be non-negative",
165
- details: {
166
- resourceId,
167
- page
168
- }
169
- });
194
+ getDefaultValue(type) {
195
+ switch (type) {
196
+ case "timestamp":
197
+ return "DEFAULT SYSUTCDATETIME()";
198
+ case "jsonb":
199
+ return "DEFAULT N'{}'";
200
+ case "boolean":
201
+ return "DEFAULT 0";
202
+ default:
203
+ return storage.getDefaultValue(type);
170
204
  }
171
- const perPage = storage.normalizePerPage(perPageInput, 100);
172
- const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
173
- const { field, direction } = this.parseOrderBy(orderBy);
174
- try {
175
- const baseQuery = `FROM ${getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
176
- const countQuery = `SELECT COUNT(*) as count ${baseQuery}`;
177
- const countRequest = this.pool.request();
178
- countRequest.input("resourceId", resourceId);
179
- const countResult = await countRequest.query(countQuery);
180
- const total = parseInt(countResult.recordset[0]?.count ?? "0", 10);
181
- if (total === 0) {
182
- return {
183
- threads: [],
184
- total: 0,
185
- page,
186
- perPage: perPageForResponse,
187
- hasMore: false
188
- };
205
+ }
206
+ async createTable({
207
+ tableName,
208
+ schema
209
+ }) {
210
+ try {
211
+ const uniqueConstraintColumns = tableName === storage.TABLE_WORKFLOW_SNAPSHOT ? ["workflow_name", "run_id"] : [];
212
+ const largeDataColumns = [
213
+ "workingMemory",
214
+ "snapshot",
215
+ "metadata",
216
+ "content",
217
+ // messages.content - can be very long conversation content
218
+ "input",
219
+ // evals.input - test input data
220
+ "output",
221
+ // evals.output - test output data
222
+ "instructions",
223
+ // evals.instructions - evaluation instructions
224
+ "other"
225
+ // traces.other - additional trace data
226
+ ];
227
+ const columns = Object.entries(schema).map(([name, def]) => {
228
+ const parsedName = utils.parseSqlIdentifier(name, "column name");
229
+ const constraints = [];
230
+ if (def.primaryKey) constraints.push("PRIMARY KEY");
231
+ if (!def.nullable) constraints.push("NOT NULL");
232
+ const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
233
+ const useLargeStorage = largeDataColumns.includes(name);
234
+ return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
235
+ }).join(",\n");
236
+ if (this.schemaName) {
237
+ await this.setupSchema();
189
238
  }
190
- const orderByField = field === "createdAt" ? "[createdAt]" : "[updatedAt]";
191
- const dir = (direction || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
192
- const limitValue = perPageInput === false ? total : perPage;
193
- const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
194
- const dataRequest = this.pool.request();
195
- dataRequest.input("resourceId", resourceId);
196
- dataRequest.input("offset", offset);
197
- if (limitValue > 2147483647) {
198
- dataRequest.input("perPage", sql2__default.default.BigInt, limitValue);
199
- } else {
200
- dataRequest.input("perPage", limitValue);
239
+ const checkTableRequest = this.pool.request();
240
+ checkTableRequest.input(
241
+ "tableName",
242
+ getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
243
+ );
244
+ const checkTableSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName`;
245
+ checkTableRequest.input("schema", this.schemaName || "dbo");
246
+ const checkTableResult = await checkTableRequest.query(checkTableSql);
247
+ const tableExists = Array.isArray(checkTableResult.recordset) && checkTableResult.recordset.length > 0;
248
+ if (!tableExists) {
249
+ const createSql = `CREATE TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (
250
+ ${columns}
251
+ )`;
252
+ await this.pool.request().query(createSql);
201
253
  }
202
- const rowsResult = await dataRequest.query(dataQuery);
203
- const rows = rowsResult.recordset || [];
204
- const threads = rows.map((thread) => ({
205
- ...thread,
206
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
207
- createdAt: thread.createdAt,
208
- updatedAt: thread.updatedAt
209
- }));
210
- return {
211
- threads,
212
- total,
213
- page,
214
- perPage: perPageForResponse,
215
- hasMore: perPageInput === false ? false : offset + perPage < total
216
- };
217
- } catch (error$1) {
218
- const mastraError = new error.MastraError(
219
- {
220
- id: "MASTRA_STORAGE_MSSQL_STORE_LIST_THREADS_BY_RESOURCE_ID_FAILED",
221
- domain: error.ErrorDomain.STORAGE,
222
- category: error.ErrorCategory.THIRD_PARTY,
223
- details: {
224
- resourceId,
225
- page
226
- }
227
- },
228
- error$1
254
+ const columnCheckSql = `
255
+ SELECT 1 AS found
256
+ FROM INFORMATION_SCHEMA.COLUMNS
257
+ WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = 'seq_id'
258
+ `;
259
+ const checkColumnRequest = this.pool.request();
260
+ checkColumnRequest.input("schema", this.schemaName || "dbo");
261
+ checkColumnRequest.input(
262
+ "tableName",
263
+ getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
229
264
  );
230
- this.logger?.error?.(mastraError.toString());
231
- this.logger?.trackException?.(mastraError);
232
- return {
233
- threads: [],
234
- total: 0,
235
- page,
236
- perPage: perPageForResponse,
237
- hasMore: false
238
- };
239
- }
240
- }
241
- async saveThread({ thread }) {
242
- try {
243
- const table = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
244
- const mergeSql = `MERGE INTO ${table} WITH (HOLDLOCK) AS target
245
- USING (SELECT @id AS id) AS source
246
- ON (target.id = source.id)
247
- WHEN MATCHED THEN
248
- UPDATE SET
249
- [resourceId] = @resourceId,
250
- title = @title,
251
- metadata = @metadata,
252
- [updatedAt] = @updatedAt
253
- WHEN NOT MATCHED THEN
254
- INSERT (id, [resourceId], title, metadata, [createdAt], [updatedAt])
255
- VALUES (@id, @resourceId, @title, @metadata, @createdAt, @updatedAt);`;
256
- const req = this.pool.request();
257
- req.input("id", thread.id);
258
- req.input("resourceId", thread.resourceId);
259
- req.input("title", thread.title);
260
- const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
261
- if (metadata === null) {
262
- req.input("metadata", sql2__default.default.NVarChar, null);
263
- } else {
264
- req.input("metadata", metadata);
265
+ const columnResult = await checkColumnRequest.query(columnCheckSql);
266
+ const columnExists = Array.isArray(columnResult.recordset) && columnResult.recordset.length > 0;
267
+ if (!columnExists) {
268
+ const alterSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD seq_id BIGINT IDENTITY(1,1)`;
269
+ await this.pool.request().query(alterSql);
270
+ }
271
+ if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
272
+ const constraintName = "mastra_workflow_snapshot_workflow_name_run_id_key";
273
+ const checkConstraintSql = `SELECT 1 AS found FROM sys.key_constraints WHERE name = @constraintName`;
274
+ const checkConstraintRequest = this.pool.request();
275
+ checkConstraintRequest.input("constraintName", constraintName);
276
+ const constraintResult = await checkConstraintRequest.query(checkConstraintSql);
277
+ const constraintExists = Array.isArray(constraintResult.recordset) && constraintResult.recordset.length > 0;
278
+ if (!constraintExists) {
279
+ const addConstraintSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD CONSTRAINT ${constraintName} UNIQUE ([workflow_name], [run_id])`;
280
+ await this.pool.request().query(addConstraintSql);
281
+ }
265
282
  }
266
- req.input("createdAt", sql2__default.default.DateTime2, thread.createdAt);
267
- req.input("updatedAt", sql2__default.default.DateTime2, thread.updatedAt);
268
- await req.query(mergeSql);
269
- return thread;
270
283
  } catch (error$1) {
271
284
  throw new error.MastraError(
272
285
  {
273
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_THREAD_FAILED",
286
+ id: storage.createStorageErrorId("MSSQL", "CREATE_TABLE", "FAILED"),
274
287
  domain: error.ErrorDomain.STORAGE,
275
288
  category: error.ErrorCategory.THIRD_PARTY,
276
289
  details: {
277
- threadId: thread.id
290
+ tableName
278
291
  }
279
292
  },
280
293
  error$1
@@ -282,1978 +295,2049 @@ var MemoryMSSQL = class extends storage.MemoryStorage {
282
295
  }
283
296
  }
284
297
  /**
285
- * Updates a thread's title and metadata, merging with existing metadata. Returns the updated thread.
298
+ * Alters table schema to add columns if they don't exist
299
+ * @param tableName Name of the table
300
+ * @param schema Schema of the table
301
+ * @param ifNotExists Array of column names to add if they don't exist
286
302
  */
287
- async updateThread({
288
- id,
289
- title,
290
- metadata
303
+ async alterTable({
304
+ tableName,
305
+ schema,
306
+ ifNotExists
291
307
  }) {
292
- const existingThread = await this.getThreadById({ threadId: id });
293
- if (!existingThread) {
294
- throw new error.MastraError({
295
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_THREAD_FAILED",
296
- domain: error.ErrorDomain.STORAGE,
297
- category: error.ErrorCategory.USER,
298
- text: `Thread ${id} not found`,
299
- details: {
300
- threadId: id,
301
- title
302
- }
303
- });
304
- }
305
- const mergedMetadata = {
306
- ...existingThread.metadata,
307
- ...metadata
308
- };
308
+ const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
309
309
  try {
310
- const table = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
311
- const sql5 = `UPDATE ${table}
312
- SET title = @title,
313
- metadata = @metadata,
314
- [updatedAt] = @updatedAt
315
- OUTPUT INSERTED.*
316
- WHERE id = @id`;
317
- const req = this.pool.request();
318
- req.input("id", id);
319
- req.input("title", title);
320
- req.input("metadata", JSON.stringify(mergedMetadata));
321
- req.input("updatedAt", /* @__PURE__ */ new Date());
322
- const result = await req.query(sql5);
323
- let thread = result.recordset && result.recordset[0];
324
- if (thread && "seq_id" in thread) {
325
- const { seq_id, ...rest } = thread;
326
- thread = rest;
327
- }
328
- if (!thread) {
329
- throw new error.MastraError({
330
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_THREAD_FAILED",
331
- domain: error.ErrorDomain.STORAGE,
332
- category: error.ErrorCategory.USER,
333
- text: `Thread ${id} not found after update`,
334
- details: {
335
- threadId: id,
336
- title
310
+ for (const columnName of ifNotExists) {
311
+ if (schema[columnName]) {
312
+ const columnCheckRequest = this.pool.request();
313
+ columnCheckRequest.input("tableName", fullTableName.replace(/[[\]]/g, "").split(".").pop());
314
+ columnCheckRequest.input("columnName", columnName);
315
+ columnCheckRequest.input("schema", this.schemaName || "dbo");
316
+ const checkSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = @columnName`;
317
+ const checkResult = await columnCheckRequest.query(checkSql);
318
+ const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
319
+ if (!columnExists) {
320
+ const columnDef = schema[columnName];
321
+ const largeDataColumns = [
322
+ "workingMemory",
323
+ "snapshot",
324
+ "metadata",
325
+ "content",
326
+ "input",
327
+ "output",
328
+ "instructions",
329
+ "other"
330
+ ];
331
+ const useLargeStorage = largeDataColumns.includes(columnName);
332
+ const isIndexed = !!columnDef.primaryKey;
333
+ const sqlType = this.getSqlType(columnDef.type, isIndexed, useLargeStorage);
334
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
335
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
336
+ const parsedColumnName = utils.parseSqlIdentifier(columnName, "column name");
337
+ const alterSql = `ALTER TABLE ${fullTableName} ADD [${parsedColumnName}] ${sqlType} ${nullable} ${defaultValue}`.trim();
338
+ await this.pool.request().query(alterSql);
339
+ this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
337
340
  }
338
- });
341
+ }
339
342
  }
340
- return {
341
- ...thread,
342
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
343
- createdAt: thread.createdAt,
344
- updatedAt: thread.updatedAt
345
- };
346
343
  } catch (error$1) {
347
344
  throw new error.MastraError(
348
345
  {
349
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_THREAD_FAILED",
346
+ id: storage.createStorageErrorId("MSSQL", "ALTER_TABLE", "FAILED"),
350
347
  domain: error.ErrorDomain.STORAGE,
351
348
  category: error.ErrorCategory.THIRD_PARTY,
352
349
  details: {
353
- threadId: id,
354
- title
350
+ tableName
355
351
  }
356
352
  },
357
353
  error$1
358
354
  );
359
355
  }
360
356
  }
361
- async deleteThread({ threadId }) {
362
- const messagesTable = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
363
- const threadsTable = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
364
- const deleteMessagesSql = `DELETE FROM ${messagesTable} WHERE [thread_id] = @threadId`;
365
- const deleteThreadSql = `DELETE FROM ${threadsTable} WHERE id = @threadId`;
366
- const tx = this.pool.transaction();
357
+ async load({ tableName, keys }) {
367
358
  try {
368
- await tx.begin();
369
- const req = tx.request();
370
- req.input("threadId", threadId);
371
- await req.query(deleteMessagesSql);
372
- await req.query(deleteThreadSql);
373
- await tx.commit();
374
- } catch (error$1) {
375
- await tx.rollback().catch(() => {
359
+ const keyEntries = Object.entries(keys).map(([key, value]) => [utils.parseSqlIdentifier(key, "column name"), value]);
360
+ const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
361
+ const sqlQuery = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
362
+ const request = this.pool.request();
363
+ keyEntries.forEach(([key, value], i) => {
364
+ const preparedValue = this.prepareValue(value, key, tableName);
365
+ if (preparedValue === null || preparedValue === void 0) {
366
+ request.input(`param${i}`, this.getMssqlType(tableName, key), null);
367
+ } else {
368
+ request.input(`param${i}`, preparedValue);
369
+ }
376
370
  });
371
+ const resultSet = await request.query(sqlQuery);
372
+ const result = resultSet.recordset[0] || null;
373
+ if (!result) {
374
+ return null;
375
+ }
376
+ if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
377
+ const snapshot = result;
378
+ if (typeof snapshot.snapshot === "string") {
379
+ snapshot.snapshot = JSON.parse(snapshot.snapshot);
380
+ }
381
+ return snapshot;
382
+ }
383
+ return result;
384
+ } catch (error$1) {
377
385
  throw new error.MastraError(
378
386
  {
379
- id: "MASTRA_STORAGE_MSSQL_STORE_DELETE_THREAD_FAILED",
387
+ id: storage.createStorageErrorId("MSSQL", "LOAD", "FAILED"),
380
388
  domain: error.ErrorDomain.STORAGE,
381
389
  category: error.ErrorCategory.THIRD_PARTY,
382
390
  details: {
383
- threadId
391
+ tableName
384
392
  }
385
393
  },
386
394
  error$1
387
395
  );
388
396
  }
389
397
  }
390
- async _getIncludedMessages({
391
- threadId,
392
- include
393
- }) {
394
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
395
- if (!include) return null;
396
- const unionQueries = [];
397
- const paramValues = [];
398
- let paramIdx = 1;
399
- const paramNames = [];
400
- for (const inc of include) {
401
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
402
- const searchId = inc.threadId || threadId;
403
- const pThreadId = `@p${paramIdx}`;
404
- const pId = `@p${paramIdx + 1}`;
405
- const pPrev = `@p${paramIdx + 2}`;
406
- const pNext = `@p${paramIdx + 3}`;
407
- unionQueries.push(
408
- `
409
- SELECT
410
- m.id,
411
- m.content,
412
- m.role,
413
- m.type,
414
- m.[createdAt],
415
- m.thread_id AS threadId,
416
- m.[resourceId],
417
- m.seq_id
418
- FROM (
419
- SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
420
- FROM ${getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}
421
- WHERE [thread_id] = ${pThreadId}
422
- ) AS m
423
- WHERE m.id = ${pId}
424
- OR EXISTS (
425
- SELECT 1
426
- FROM (
427
- SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
428
- FROM ${getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}
429
- WHERE [thread_id] = ${pThreadId}
430
- ) AS target
431
- WHERE target.id = ${pId}
432
- AND (
433
- -- Get previous messages (messages that come BEFORE the target)
434
- (m.row_num < target.row_num AND m.row_num >= target.row_num - ${pPrev})
435
- OR
436
- -- Get next messages (messages that come AFTER the target)
437
- (m.row_num > target.row_num AND m.row_num <= target.row_num + ${pNext})
438
- )
439
- )
440
- `
441
- );
442
- paramValues.push(searchId, id, withPreviousMessages, withNextMessages);
443
- paramNames.push(`p${paramIdx}`, `p${paramIdx + 1}`, `p${paramIdx + 2}`, `p${paramIdx + 3}`);
444
- paramIdx += 4;
445
- }
446
- const finalQuery = `
447
- SELECT * FROM (
448
- ${unionQueries.join(" UNION ALL ")}
449
- ) AS union_result
450
- ORDER BY [seq_id] ASC
451
- `;
452
- const req = this.pool.request();
453
- for (let i = 0; i < paramValues.length; ++i) {
454
- req.input(paramNames[i], paramValues[i]);
455
- }
456
- const result = await req.query(finalQuery);
457
- const includedRows = result.recordset || [];
458
- const seen = /* @__PURE__ */ new Set();
459
- const dedupedRows = includedRows.filter((row) => {
460
- if (seen.has(row.id)) return false;
461
- seen.add(row.id);
462
- return true;
463
- });
464
- return dedupedRows;
465
- }
466
- async listMessagesById({ messageIds }) {
467
- if (messageIds.length === 0) return { messages: [] };
468
- const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
469
- const orderByStatement = `ORDER BY [seq_id] DESC`;
398
+ async batchInsert({ tableName, records }) {
399
+ const transaction = this.pool.transaction();
470
400
  try {
471
- let rows = [];
472
- let query = `${selectStatement} FROM ${getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE [id] IN (${messageIds.map((_, i) => `@id${i}`).join(", ")})`;
473
- const request = this.pool.request();
474
- messageIds.forEach((id, i) => request.input(`id${i}`, id));
475
- query += ` ${orderByStatement}`;
476
- const result = await request.query(query);
477
- const remainingRows = result.recordset || [];
478
- rows.push(...remainingRows);
479
- rows.sort((a, b) => {
480
- const timeDiff = a.seq_id - b.seq_id;
481
- return timeDiff;
482
- });
483
- const messagesWithParsedContent = rows.map((row) => {
484
- if (typeof row.content === "string") {
485
- try {
486
- return { ...row, content: JSON.parse(row.content) };
487
- } catch {
488
- return row;
489
- }
490
- }
491
- return row;
492
- });
493
- const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
494
- const list = new agent.MessageList().add(cleanMessages, "memory");
495
- return { messages: list.get.all.db() };
401
+ await transaction.begin();
402
+ for (const record of records) {
403
+ await this.insert({ tableName, record, transaction });
404
+ }
405
+ await transaction.commit();
496
406
  } catch (error$1) {
497
- const mastraError = new error.MastraError(
407
+ await transaction.rollback();
408
+ throw new error.MastraError(
498
409
  {
499
- id: "MASTRA_STORAGE_MSSQL_STORE_LIST_MESSAGES_BY_ID_FAILED",
410
+ id: storage.createStorageErrorId("MSSQL", "BATCH_INSERT", "FAILED"),
500
411
  domain: error.ErrorDomain.STORAGE,
501
412
  category: error.ErrorCategory.THIRD_PARTY,
502
413
  details: {
503
- messageIds: JSON.stringify(messageIds)
414
+ tableName,
415
+ numberOfRecords: records.length
504
416
  }
505
417
  },
506
418
  error$1
507
419
  );
508
- this.logger?.error?.(mastraError.toString());
509
- this.logger?.trackException?.(mastraError);
510
- return { messages: [] };
511
420
  }
512
421
  }
513
- async listMessages(args) {
514
- const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
515
- if (!threadId.trim()) {
516
- throw new error.MastraError(
517
- {
518
- id: "STORAGE_MSSQL_LIST_MESSAGES_INVALID_THREAD_ID",
519
- domain: error.ErrorDomain.STORAGE,
520
- category: error.ErrorCategory.THIRD_PARTY,
521
- details: { threadId }
522
- },
523
- new Error("threadId must be a non-empty string")
524
- );
525
- }
526
- if (page < 0) {
527
- throw new error.MastraError({
528
- id: "MASTRA_STORAGE_MSSQL_STORE_INVALID_PAGE",
529
- domain: error.ErrorDomain.STORAGE,
530
- category: error.ErrorCategory.USER,
531
- text: "Page number must be non-negative",
532
- details: {
533
- threadId,
534
- page
535
- }
536
- });
537
- }
538
- const perPage = storage.normalizePerPage(perPageInput, 40);
539
- const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
422
+ async dropTable({ tableName }) {
540
423
  try {
541
- const { field, direction } = this.parseOrderBy(orderBy, "ASC");
542
- const orderByStatement = `ORDER BY [${field}] ${direction}, [seq_id] ${direction}`;
543
- const tableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
544
- const baseQuery = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId FROM ${tableName}`;
545
- const filters = {
546
- thread_id: threadId,
547
- ...resourceId ? { resourceId } : {},
548
- ...buildDateRangeFilter(filter?.dateRange, "createdAt")
549
- };
550
- const { sql: actualWhereClause = "", params: whereParams } = prepareWhereClause(
551
- filters);
552
- const bindWhereParams = (req) => {
553
- Object.entries(whereParams).forEach(([paramName, paramValue]) => req.input(paramName, paramValue));
554
- };
555
- const countRequest = this.pool.request();
556
- bindWhereParams(countRequest);
557
- const countResult = await countRequest.query(`SELECT COUNT(*) as total FROM ${tableName}${actualWhereClause}`);
558
- const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
559
- const fetchBaseMessages = async () => {
560
- const request = this.pool.request();
561
- bindWhereParams(request);
562
- if (perPageInput === false) {
563
- const result2 = await request.query(`${baseQuery}${actualWhereClause} ${orderByStatement}`);
564
- return result2.recordset || [];
565
- }
566
- request.input("offset", offset);
567
- request.input("limit", perPage > 2147483647 ? sql2__default.default.BigInt : sql2__default.default.Int, perPage);
568
- const result = await request.query(
569
- `${baseQuery}${actualWhereClause} ${orderByStatement} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
570
- );
571
- return result.recordset || [];
572
- };
573
- const baseRows = perPage === 0 ? [] : await fetchBaseMessages();
574
- const messages = [...baseRows];
575
- const seqById = /* @__PURE__ */ new Map();
576
- messages.forEach((msg) => {
577
- if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
578
- });
579
- if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
580
- return {
581
- messages: [],
582
- total: 0,
583
- page,
584
- perPage: perPageForResponse,
585
- hasMore: false
586
- };
587
- }
588
- if (include?.length) {
589
- const messageIds = new Set(messages.map((m) => m.id));
590
- const includeMessages = await this._getIncludedMessages({ threadId, include });
591
- includeMessages?.forEach((msg) => {
592
- if (!messageIds.has(msg.id)) {
593
- messages.push(msg);
594
- messageIds.add(msg.id);
595
- if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
596
- }
597
- });
598
- }
599
- const parsed = this._parseAndFormatMessages(messages, "v2");
600
- const mult = direction === "ASC" ? 1 : -1;
601
- const finalMessages = parsed.sort((a, b) => {
602
- const aVal = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
603
- const bVal = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
604
- if (aVal == null || bVal == null) {
605
- return aVal == null && bVal == null ? a.id.localeCompare(b.id) : aVal == null ? 1 : -1;
606
- }
607
- const diff = (typeof aVal === "number" && typeof bVal === "number" ? aVal - bVal : String(aVal).localeCompare(String(bVal))) * mult;
608
- if (diff !== 0) return diff;
609
- const seqA = seqById.get(a.id);
610
- const seqB = seqById.get(b.id);
611
- return seqA != null && seqB != null ? (seqA - seqB) * mult : a.id.localeCompare(b.id);
612
- });
613
- const returnedThreadMessageCount = finalMessages.filter((m) => m.threadId === threadId).length;
614
- const hasMore = perPageInput !== false && returnedThreadMessageCount < total && offset + perPage < total;
615
- return {
616
- messages: finalMessages,
617
- total,
618
- page,
619
- perPage: perPageForResponse,
620
- hasMore
621
- };
424
+ const tableNameWithSchema = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
425
+ await this.pool.request().query(`DROP TABLE IF EXISTS ${tableNameWithSchema}`);
622
426
  } catch (error$1) {
623
- const mastraError = new error.MastraError(
427
+ throw new error.MastraError(
624
428
  {
625
- id: "MASTRA_STORAGE_MSSQL_STORE_LIST_MESSAGES_FAILED",
429
+ id: storage.createStorageErrorId("MSSQL", "DROP_TABLE", "FAILED"),
626
430
  domain: error.ErrorDomain.STORAGE,
627
431
  category: error.ErrorCategory.THIRD_PARTY,
628
432
  details: {
629
- threadId,
630
- resourceId: resourceId ?? ""
433
+ tableName
631
434
  }
632
435
  },
633
436
  error$1
634
437
  );
635
- this.logger?.error?.(mastraError.toString());
636
- this.logger?.trackException?.(mastraError);
637
- return {
638
- messages: [],
639
- total: 0,
640
- page,
641
- perPage: perPageForResponse,
642
- hasMore: false
643
- };
644
438
  }
645
439
  }
646
- async saveMessages({ messages }) {
647
- if (messages.length === 0) return { messages: [] };
648
- const threadId = messages[0]?.threadId;
649
- if (!threadId) {
650
- throw new error.MastraError({
651
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
652
- domain: error.ErrorDomain.STORAGE,
653
- category: error.ErrorCategory.THIRD_PARTY,
654
- text: `Thread ID is required`
655
- });
440
+ /**
441
+ * Prepares a value for database operations, handling Date objects and JSON serialization
442
+ */
443
+ prepareValue(value, columnName, tableName) {
444
+ if (value === null || value === void 0) {
445
+ return value;
656
446
  }
657
- const thread = await this.getThreadById({ threadId });
658
- if (!thread) {
659
- throw new error.MastraError({
660
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
661
- domain: error.ErrorDomain.STORAGE,
662
- category: error.ErrorCategory.THIRD_PARTY,
663
- text: `Thread ${threadId} not found`,
664
- details: { threadId }
665
- });
447
+ if (value instanceof Date) {
448
+ return value;
666
449
  }
667
- const tableMessages = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
668
- const tableThreads = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
669
- try {
670
- const transaction = this.pool.transaction();
671
- await transaction.begin();
672
- try {
673
- for (const message of messages) {
674
- if (!message.threadId) {
675
- throw new Error(
676
- `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
677
- );
678
- }
679
- if (!message.resourceId) {
680
- throw new Error(
681
- `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
682
- );
683
- }
684
- const request = transaction.request();
685
- request.input("id", message.id);
686
- request.input("thread_id", message.threadId);
687
- request.input(
688
- "content",
689
- typeof message.content === "string" ? message.content : JSON.stringify(message.content)
690
- );
691
- request.input("createdAt", sql2__default.default.DateTime2, message.createdAt);
692
- request.input("role", message.role);
693
- request.input("type", message.type || "v2");
694
- request.input("resourceId", message.resourceId);
695
- const mergeSql = `MERGE INTO ${tableMessages} AS target
696
- USING (SELECT @id AS id) AS src
697
- ON target.id = src.id
698
- WHEN MATCHED THEN UPDATE SET
699
- thread_id = @thread_id,
700
- content = @content,
701
- [createdAt] = @createdAt,
702
- role = @role,
703
- type = @type,
704
- resourceId = @resourceId
705
- WHEN NOT MATCHED THEN INSERT (id, thread_id, content, [createdAt], role, type, resourceId)
706
- VALUES (@id, @thread_id, @content, @createdAt, @role, @type, @resourceId);`;
707
- await request.query(mergeSql);
708
- }
709
- const threadReq = transaction.request();
710
- threadReq.input("updatedAt", sql2__default.default.DateTime2, /* @__PURE__ */ new Date());
711
- threadReq.input("id", threadId);
712
- await threadReq.query(`UPDATE ${tableThreads} SET [updatedAt] = @updatedAt WHERE id = @id`);
713
- await transaction.commit();
714
- } catch (error) {
715
- await transaction.rollback();
716
- throw error;
717
- }
718
- const messagesWithParsedContent = messages.map((message) => {
719
- if (typeof message.content === "string") {
450
+ const schema = storage.TABLE_SCHEMAS[tableName];
451
+ const columnSchema = schema?.[columnName];
452
+ if (columnSchema?.type === "boolean") {
453
+ return value ? 1 : 0;
454
+ }
455
+ if (columnSchema?.type === "jsonb") {
456
+ if (typeof value === "string") {
457
+ const trimmed = value.trim();
458
+ if (trimmed.length > 0) {
720
459
  try {
721
- return { ...message, content: JSON.parse(message.content) };
460
+ JSON.parse(trimmed);
461
+ return trimmed;
722
462
  } catch {
723
- return message;
724
463
  }
725
464
  }
726
- return message;
465
+ return JSON.stringify(value);
466
+ }
467
+ if (typeof value === "bigint") {
468
+ return value.toString();
469
+ }
470
+ return JSON.stringify(value);
471
+ }
472
+ if (typeof value === "object") {
473
+ return JSON.stringify(value);
474
+ }
475
+ return value;
476
+ }
477
+ /**
478
+ * Maps TABLE_SCHEMAS types to mssql param types (used when value is null)
479
+ */
480
+ getMssqlType(tableName, columnName) {
481
+ const col = storage.TABLE_SCHEMAS[tableName]?.[columnName];
482
+ switch (col?.type) {
483
+ case "text":
484
+ return sql__default.default.NVarChar;
485
+ case "timestamp":
486
+ return sql__default.default.DateTime2;
487
+ case "uuid":
488
+ return sql__default.default.UniqueIdentifier;
489
+ case "jsonb":
490
+ return sql__default.default.NVarChar;
491
+ case "integer":
492
+ return sql__default.default.Int;
493
+ case "bigint":
494
+ return sql__default.default.BigInt;
495
+ case "float":
496
+ return sql__default.default.Float;
497
+ case "boolean":
498
+ return sql__default.default.Bit;
499
+ default:
500
+ return sql__default.default.NVarChar;
501
+ }
502
+ }
503
+ /**
504
+ * Update a single record in the database
505
+ */
506
+ async update({
507
+ tableName,
508
+ keys,
509
+ data,
510
+ transaction
511
+ }) {
512
+ try {
513
+ if (!data || Object.keys(data).length === 0) {
514
+ throw new error.MastraError({
515
+ id: storage.createStorageErrorId("MSSQL", "UPDATE", "EMPTY_DATA"),
516
+ domain: error.ErrorDomain.STORAGE,
517
+ category: error.ErrorCategory.USER,
518
+ text: "Cannot update with empty data payload"
519
+ });
520
+ }
521
+ if (!keys || Object.keys(keys).length === 0) {
522
+ throw new error.MastraError({
523
+ id: storage.createStorageErrorId("MSSQL", "UPDATE", "EMPTY_KEYS"),
524
+ domain: error.ErrorDomain.STORAGE,
525
+ category: error.ErrorCategory.USER,
526
+ text: "Cannot update without keys to identify records"
527
+ });
528
+ }
529
+ const setClauses = [];
530
+ const request = transaction ? transaction.request() : this.pool.request();
531
+ let paramIndex = 0;
532
+ Object.entries(data).forEach(([key, value]) => {
533
+ const parsedKey = utils.parseSqlIdentifier(key, "column name");
534
+ const paramName = `set${paramIndex++}`;
535
+ setClauses.push(`[${parsedKey}] = @${paramName}`);
536
+ const preparedValue = this.prepareValue(value, key, tableName);
537
+ if (preparedValue === null || preparedValue === void 0) {
538
+ request.input(paramName, this.getMssqlType(tableName, key), null);
539
+ } else {
540
+ request.input(paramName, preparedValue);
541
+ }
727
542
  });
728
- const list = new agent.MessageList().add(messagesWithParsedContent, "memory");
729
- return { messages: list.get.all.db() };
543
+ const whereConditions = [];
544
+ Object.entries(keys).forEach(([key, value]) => {
545
+ const parsedKey = utils.parseSqlIdentifier(key, "column name");
546
+ const paramName = `where${paramIndex++}`;
547
+ whereConditions.push(`[${parsedKey}] = @${paramName}`);
548
+ const preparedValue = this.prepareValue(value, key, tableName);
549
+ if (preparedValue === null || preparedValue === void 0) {
550
+ request.input(paramName, this.getMssqlType(tableName, key), null);
551
+ } else {
552
+ request.input(paramName, preparedValue);
553
+ }
554
+ });
555
+ const tableName_ = getTableName({
556
+ indexName: tableName,
557
+ schemaName: getSchemaName(this.schemaName)
558
+ });
559
+ const updateSql = `UPDATE ${tableName_} SET ${setClauses.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
560
+ await request.query(updateSql);
730
561
  } catch (error$1) {
731
562
  throw new error.MastraError(
732
563
  {
733
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
564
+ id: storage.createStorageErrorId("MSSQL", "UPDATE", "FAILED"),
734
565
  domain: error.ErrorDomain.STORAGE,
735
566
  category: error.ErrorCategory.THIRD_PARTY,
736
- details: { threadId }
567
+ details: {
568
+ tableName
569
+ }
737
570
  },
738
571
  error$1
739
572
  );
740
573
  }
741
574
  }
742
- async updateMessages({
743
- messages
575
+ /**
576
+ * Update multiple records in a single batch transaction
577
+ */
578
+ async batchUpdate({
579
+ tableName,
580
+ updates
744
581
  }) {
745
- if (!messages || messages.length === 0) {
746
- return [];
747
- }
748
- const messageIds = messages.map((m) => m.id);
749
- const idParams = messageIds.map((_, i) => `@id${i}`).join(", ");
750
- let selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}`;
751
- if (idParams.length > 0) {
752
- selectQuery += ` WHERE id IN (${idParams})`;
753
- } else {
754
- return [];
755
- }
756
- const selectReq = this.pool.request();
757
- messageIds.forEach((id, i) => selectReq.input(`id${i}`, id));
758
- const existingMessagesDb = (await selectReq.query(selectQuery)).recordset;
759
- if (!existingMessagesDb || existingMessagesDb.length === 0) {
760
- return [];
761
- }
762
- const existingMessages = existingMessagesDb.map((msg) => {
763
- if (typeof msg.content === "string") {
764
- try {
765
- msg.content = JSON.parse(msg.content);
766
- } catch {
767
- }
768
- }
769
- return msg;
770
- });
771
- const threadIdsToUpdate = /* @__PURE__ */ new Set();
772
582
  const transaction = this.pool.transaction();
773
583
  try {
774
584
  await transaction.begin();
775
- for (const existingMessage of existingMessages) {
776
- const updatePayload = messages.find((m) => m.id === existingMessage.id);
777
- if (!updatePayload) continue;
778
- const { id, ...fieldsToUpdate } = updatePayload;
779
- if (Object.keys(fieldsToUpdate).length === 0) continue;
780
- threadIdsToUpdate.add(existingMessage.threadId);
781
- if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
782
- threadIdsToUpdate.add(updatePayload.threadId);
783
- }
784
- const setClauses = [];
785
- const req = transaction.request();
786
- req.input("id", id);
787
- const columnMapping = { threadId: "thread_id" };
788
- const updatableFields = { ...fieldsToUpdate };
789
- if (updatableFields.content) {
790
- const newContent = {
791
- ...existingMessage.content,
792
- ...updatableFields.content,
793
- ...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: { ...existingMessage.content.metadata, ...updatableFields.content.metadata } } : {}
794
- };
795
- setClauses.push(`content = @content`);
796
- req.input("content", JSON.stringify(newContent));
797
- delete updatableFields.content;
798
- }
799
- for (const key in updatableFields) {
800
- if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
801
- const dbColumn = columnMapping[key] || key;
802
- setClauses.push(`[${dbColumn}] = @${dbColumn}`);
803
- req.input(dbColumn, updatableFields[key]);
804
- }
805
- }
806
- if (setClauses.length > 0) {
807
- const updateSql = `UPDATE ${getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} SET ${setClauses.join(", ")} WHERE id = @id`;
808
- await req.query(updateSql);
809
- }
810
- }
811
- if (threadIdsToUpdate.size > 0) {
812
- const threadIdParams = Array.from(threadIdsToUpdate).map((_, i) => `@tid${i}`).join(", ");
813
- const threadReq = transaction.request();
814
- Array.from(threadIdsToUpdate).forEach((tid, i) => threadReq.input(`tid${i}`, tid));
815
- threadReq.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
816
- const threadSql = `UPDATE ${getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) })} SET updatedAt = @updatedAt WHERE id IN (${threadIdParams})`;
817
- await threadReq.query(threadSql);
585
+ for (const { keys, data } of updates) {
586
+ await this.update({ tableName, keys, data, transaction });
818
587
  }
819
588
  await transaction.commit();
820
589
  } catch (error$1) {
821
590
  await transaction.rollback();
822
591
  throw new error.MastraError(
823
592
  {
824
- id: "MASTRA_STORAGE_MSSQL_UPDATE_MESSAGES_FAILED",
593
+ id: storage.createStorageErrorId("MSSQL", "BATCH_UPDATE", "FAILED"),
825
594
  domain: error.ErrorDomain.STORAGE,
826
- category: error.ErrorCategory.THIRD_PARTY
827
- },
828
- error$1
595
+ category: error.ErrorCategory.THIRD_PARTY,
596
+ details: {
597
+ tableName,
598
+ numberOfRecords: updates.length
599
+ }
600
+ },
601
+ error$1
829
602
  );
830
603
  }
831
- const refetchReq = this.pool.request();
832
- messageIds.forEach((id, i) => refetchReq.input(`id${i}`, id));
833
- const updatedMessages = (await refetchReq.query(selectQuery)).recordset;
834
- return (updatedMessages || []).map((message) => {
835
- if (typeof message.content === "string") {
836
- try {
837
- message.content = JSON.parse(message.content);
838
- } catch {
839
- }
840
- }
841
- return message;
842
- });
843
604
  }
844
- async deleteMessages(messageIds) {
845
- if (!messageIds || messageIds.length === 0) {
605
+ /**
606
+ * Delete multiple records by keys
607
+ */
608
+ async batchDelete({ tableName, keys }) {
609
+ if (keys.length === 0) {
846
610
  return;
847
611
  }
612
+ const tableName_ = getTableName({
613
+ indexName: tableName,
614
+ schemaName: getSchemaName(this.schemaName)
615
+ });
616
+ const transaction = this.pool.transaction();
848
617
  try {
849
- const messageTableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
850
- const threadTableName = getTableName({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName(this.schema) });
851
- const placeholders = messageIds.map((_, idx) => `@p${idx + 1}`).join(",");
852
- const request = this.pool.request();
853
- messageIds.forEach((id, idx) => {
854
- request.input(`p${idx + 1}`, id);
855
- });
856
- const messages = await request.query(
857
- `SELECT DISTINCT [thread_id] FROM ${messageTableName} WHERE [id] IN (${placeholders})`
858
- );
859
- const threadIds = messages.recordset?.map((msg) => msg.thread_id).filter(Boolean) || [];
860
- const transaction = this.pool.transaction();
861
618
  await transaction.begin();
862
- try {
863
- const deleteRequest = transaction.request();
864
- messageIds.forEach((id, idx) => {
865
- deleteRequest.input(`p${idx + 1}`, id);
866
- });
867
- await deleteRequest.query(`DELETE FROM ${messageTableName} WHERE [id] IN (${placeholders})`);
868
- if (threadIds.length > 0) {
869
- for (const threadId of threadIds) {
870
- const updateRequest = transaction.request();
871
- updateRequest.input("p1", threadId);
872
- await updateRequest.query(`UPDATE ${threadTableName} SET [updatedAt] = GETDATE() WHERE [id] = @p1`);
619
+ for (const keySet of keys) {
620
+ const conditions = [];
621
+ const request = transaction.request();
622
+ let paramIndex = 0;
623
+ Object.entries(keySet).forEach(([key, value]) => {
624
+ const parsedKey = utils.parseSqlIdentifier(key, "column name");
625
+ const paramName = `p${paramIndex++}`;
626
+ conditions.push(`[${parsedKey}] = @${paramName}`);
627
+ const preparedValue = this.prepareValue(value, key, tableName);
628
+ if (preparedValue === null || preparedValue === void 0) {
629
+ request.input(paramName, this.getMssqlType(tableName, key), null);
630
+ } else {
631
+ request.input(paramName, preparedValue);
873
632
  }
874
- }
875
- await transaction.commit();
876
- } catch (error) {
877
- try {
878
- await transaction.rollback();
879
- } catch {
880
- }
881
- throw error;
633
+ });
634
+ const deleteSql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
635
+ await request.query(deleteSql);
882
636
  }
637
+ await transaction.commit();
883
638
  } catch (error$1) {
639
+ await transaction.rollback();
884
640
  throw new error.MastraError(
885
641
  {
886
- id: "MASTRA_STORAGE_MSSQL_STORE_DELETE_MESSAGES_FAILED",
642
+ id: storage.createStorageErrorId("MSSQL", "BATCH_DELETE", "FAILED"),
887
643
  domain: error.ErrorDomain.STORAGE,
888
644
  category: error.ErrorCategory.THIRD_PARTY,
889
- details: { messageIds: messageIds.join(", ") }
645
+ details: {
646
+ tableName,
647
+ numberOfRecords: keys.length
648
+ }
890
649
  },
891
650
  error$1
892
651
  );
893
652
  }
894
653
  }
895
- async getResourceById({ resourceId }) {
896
- const tableName = getTableName({ indexName: storage.TABLE_RESOURCES, schemaName: getSchemaName(this.schema) });
654
+ /**
655
+ * Create a new index on a table
656
+ */
657
+ async createIndex(options) {
897
658
  try {
898
- const req = this.pool.request();
899
- req.input("resourceId", resourceId);
900
- const result = (await req.query(`SELECT * FROM ${tableName} WHERE id = @resourceId`)).recordset[0];
901
- if (!result) {
902
- return null;
659
+ const { name, table, columns, unique = false, where } = options;
660
+ const schemaName = this.schemaName || "dbo";
661
+ const fullTableName = getTableName({
662
+ indexName: table,
663
+ schemaName: getSchemaName(this.schemaName)
664
+ });
665
+ const indexNameSafe = utils.parseSqlIdentifier(name, "index name");
666
+ const checkRequest = this.pool.request();
667
+ checkRequest.input("indexName", indexNameSafe);
668
+ checkRequest.input("schemaName", schemaName);
669
+ checkRequest.input("tableName", table);
670
+ const indexExists = await checkRequest.query(`
671
+ SELECT 1 as found
672
+ FROM sys.indexes i
673
+ INNER JOIN sys.tables t ON i.object_id = t.object_id
674
+ INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
675
+ WHERE i.name = @indexName
676
+ AND s.name = @schemaName
677
+ AND t.name = @tableName
678
+ `);
679
+ if (indexExists.recordset && indexExists.recordset.length > 0) {
680
+ return;
903
681
  }
904
- return {
905
- id: result.id,
906
- createdAt: result.createdAt,
907
- updatedAt: result.updatedAt,
908
- workingMemory: result.workingMemory,
909
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
910
- };
682
+ const uniqueStr = unique ? "UNIQUE " : "";
683
+ const columnsStr = columns.map((col) => {
684
+ if (col.includes(" DESC") || col.includes(" ASC")) {
685
+ const [colName, ...modifiers] = col.split(" ");
686
+ if (!colName) {
687
+ throw new Error(`Invalid column specification: ${col}`);
688
+ }
689
+ return `[${utils.parseSqlIdentifier(colName, "column name")}] ${modifiers.join(" ")}`;
690
+ }
691
+ return `[${utils.parseSqlIdentifier(col, "column name")}]`;
692
+ }).join(", ");
693
+ const whereStr = where ? ` WHERE ${where}` : "";
694
+ const createIndexSql = `CREATE ${uniqueStr}INDEX [${indexNameSafe}] ON ${fullTableName} (${columnsStr})${whereStr}`;
695
+ await this.pool.request().query(createIndexSql);
911
696
  } catch (error$1) {
912
- const mastraError = new error.MastraError(
697
+ throw new error.MastraError(
913
698
  {
914
- id: "MASTRA_STORAGE_MSSQL_GET_RESOURCE_BY_ID_FAILED",
699
+ id: storage.createStorageErrorId("MSSQL", "INDEX_CREATE", "FAILED"),
915
700
  domain: error.ErrorDomain.STORAGE,
916
701
  category: error.ErrorCategory.THIRD_PARTY,
917
- details: { resourceId }
702
+ details: {
703
+ indexName: options.name,
704
+ tableName: options.table
705
+ }
918
706
  },
919
707
  error$1
920
708
  );
921
- this.logger?.error?.(mastraError.toString());
922
- this.logger?.trackException?.(mastraError);
923
- throw mastraError;
924
709
  }
925
710
  }
926
- async saveResource({ resource }) {
927
- await this.operations.insert({
928
- tableName: storage.TABLE_RESOURCES,
929
- record: {
930
- ...resource,
931
- metadata: resource.metadata
932
- }
933
- });
934
- return resource;
935
- }
936
- async updateResource({
937
- resourceId,
938
- workingMemory,
939
- metadata
940
- }) {
711
+ /**
712
+ * Drop an existing index
713
+ */
714
+ async dropIndex(indexName) {
941
715
  try {
942
- const existingResource = await this.getResourceById({ resourceId });
943
- if (!existingResource) {
944
- const newResource = {
945
- id: resourceId,
946
- workingMemory,
947
- metadata: metadata || {},
948
- createdAt: /* @__PURE__ */ new Date(),
949
- updatedAt: /* @__PURE__ */ new Date()
950
- };
951
- return this.saveResource({ resource: newResource });
952
- }
953
- const updatedResource = {
954
- ...existingResource,
955
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
956
- metadata: {
957
- ...existingResource.metadata,
958
- ...metadata
959
- },
960
- updatedAt: /* @__PURE__ */ new Date()
961
- };
962
- const tableName = getTableName({ indexName: storage.TABLE_RESOURCES, schemaName: getSchemaName(this.schema) });
963
- const updates = [];
964
- const req = this.pool.request();
965
- if (workingMemory !== void 0) {
966
- updates.push("workingMemory = @workingMemory");
967
- req.input("workingMemory", workingMemory);
716
+ const schemaName = this.schemaName || "dbo";
717
+ const indexNameSafe = utils.parseSqlIdentifier(indexName, "index name");
718
+ const checkRequest = this.pool.request();
719
+ checkRequest.input("indexName", indexNameSafe);
720
+ checkRequest.input("schemaName", schemaName);
721
+ const result = await checkRequest.query(`
722
+ SELECT t.name as table_name
723
+ FROM sys.indexes i
724
+ INNER JOIN sys.tables t ON i.object_id = t.object_id
725
+ INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
726
+ WHERE i.name = @indexName
727
+ AND s.name = @schemaName
728
+ `);
729
+ if (!result.recordset || result.recordset.length === 0) {
730
+ return;
968
731
  }
969
- if (metadata) {
970
- updates.push("metadata = @metadata");
971
- req.input("metadata", JSON.stringify(updatedResource.metadata));
732
+ if (result.recordset.length > 1) {
733
+ const tables = result.recordset.map((r) => r.table_name).join(", ");
734
+ throw new error.MastraError({
735
+ id: storage.createStorageErrorId("MSSQL", "INDEX", "AMBIGUOUS"),
736
+ domain: error.ErrorDomain.STORAGE,
737
+ category: error.ErrorCategory.USER,
738
+ text: `Index "${indexNameSafe}" exists on multiple tables (${tables}) in schema "${schemaName}". Please drop indexes manually or ensure unique index names.`
739
+ });
972
740
  }
973
- updates.push("updatedAt = @updatedAt");
974
- req.input("updatedAt", updatedResource.updatedAt.toISOString());
975
- req.input("id", resourceId);
976
- await req.query(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = @id`);
977
- return updatedResource;
741
+ const tableName = result.recordset[0].table_name;
742
+ const fullTableName = getTableName({
743
+ indexName: tableName,
744
+ schemaName: getSchemaName(this.schemaName)
745
+ });
746
+ const dropSql = `DROP INDEX [${indexNameSafe}] ON ${fullTableName}`;
747
+ await this.pool.request().query(dropSql);
978
748
  } catch (error$1) {
979
- const mastraError = new error.MastraError(
749
+ throw new error.MastraError(
980
750
  {
981
- id: "MASTRA_STORAGE_MSSQL_UPDATE_RESOURCE_FAILED",
751
+ id: storage.createStorageErrorId("MSSQL", "INDEX_DROP", "FAILED"),
982
752
  domain: error.ErrorDomain.STORAGE,
983
753
  category: error.ErrorCategory.THIRD_PARTY,
984
- details: { resourceId }
754
+ details: {
755
+ indexName
756
+ }
985
757
  },
986
758
  error$1
987
759
  );
988
- this.logger?.error?.(mastraError.toString());
989
- this.logger?.trackException?.(mastraError);
990
- throw mastraError;
991
760
  }
992
761
  }
993
- };
994
- var ObservabilityMSSQL = class extends storage.ObservabilityStorage {
995
- pool;
996
- operations;
997
- schema;
998
- constructor({
999
- pool,
1000
- operations,
1001
- schema
1002
- }) {
1003
- super();
1004
- this.pool = pool;
1005
- this.operations = operations;
1006
- this.schema = schema;
1007
- }
1008
- get tracingStrategy() {
1009
- return {
1010
- preferred: "batch-with-updates",
1011
- supported: ["batch-with-updates", "insert-only"]
1012
- };
1013
- }
1014
- async createSpan(span) {
762
+ /**
763
+ * List indexes for a specific table or all tables
764
+ */
765
+ async listIndexes(tableName) {
1015
766
  try {
1016
- const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt;
1017
- const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt;
1018
- const record = {
1019
- ...span,
1020
- startedAt,
1021
- endedAt
1022
- // Note: createdAt/updatedAt will be set by default values
1023
- };
1024
- return this.operations.insert({ tableName: storage.TABLE_SPANS, record });
767
+ const schemaName = this.schemaName || "dbo";
768
+ let query;
769
+ const request = this.pool.request();
770
+ request.input("schemaName", schemaName);
771
+ if (tableName) {
772
+ query = `
773
+ SELECT
774
+ i.name as name,
775
+ o.name as [table],
776
+ i.is_unique as is_unique,
777
+ CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
778
+ FROM sys.indexes i
779
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
780
+ INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
781
+ LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
782
+ WHERE sch.name = @schemaName
783
+ AND o.name = @tableName
784
+ AND i.name IS NOT NULL
785
+ GROUP BY i.name, o.name, i.is_unique
786
+ `;
787
+ request.input("tableName", tableName);
788
+ } else {
789
+ query = `
790
+ SELECT
791
+ i.name as name,
792
+ o.name as [table],
793
+ i.is_unique as is_unique,
794
+ CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
795
+ FROM sys.indexes i
796
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
797
+ INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
798
+ LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
799
+ WHERE sch.name = @schemaName
800
+ AND i.name IS NOT NULL
801
+ GROUP BY i.name, o.name, i.is_unique
802
+ `;
803
+ }
804
+ const result = await request.query(query);
805
+ const indexes = [];
806
+ for (const row of result.recordset) {
807
+ const colRequest = this.pool.request();
808
+ colRequest.input("indexName", row.name);
809
+ colRequest.input("schemaName", schemaName);
810
+ const colResult = await colRequest.query(`
811
+ SELECT c.name as column_name
812
+ FROM sys.indexes i
813
+ INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
814
+ INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
815
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
816
+ INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
817
+ WHERE i.name = @indexName
818
+ AND s.name = @schemaName
819
+ ORDER BY ic.key_ordinal
820
+ `);
821
+ indexes.push({
822
+ name: row.name,
823
+ table: row.table,
824
+ columns: colResult.recordset.map((c) => c.column_name),
825
+ unique: row.is_unique || false,
826
+ size: row.size || "0 MB",
827
+ definition: ""
828
+ // MSSQL doesn't store definition like PG
829
+ });
830
+ }
831
+ return indexes;
1025
832
  } catch (error$1) {
1026
833
  throw new error.MastraError(
1027
834
  {
1028
- id: "MSSQL_STORE_CREATE_SPAN_FAILED",
835
+ id: storage.createStorageErrorId("MSSQL", "INDEX_LIST", "FAILED"),
1029
836
  domain: error.ErrorDomain.STORAGE,
1030
- category: error.ErrorCategory.USER,
1031
- details: {
1032
- spanId: span.spanId,
1033
- traceId: span.traceId,
1034
- spanType: span.spanType,
1035
- spanName: span.name
1036
- }
837
+ category: error.ErrorCategory.THIRD_PARTY,
838
+ details: tableName ? {
839
+ tableName
840
+ } : {}
1037
841
  },
1038
842
  error$1
1039
843
  );
1040
844
  }
1041
845
  }
1042
- async getTrace(traceId) {
846
+ /**
847
+ * Get detailed statistics for a specific index
848
+ */
849
+ async describeIndex(indexName) {
1043
850
  try {
1044
- const tableName = getTableName({
1045
- indexName: storage.TABLE_SPANS,
1046
- schemaName: getSchemaName(this.schema)
1047
- });
851
+ const schemaName = this.schemaName || "dbo";
1048
852
  const request = this.pool.request();
1049
- request.input("traceId", traceId);
1050
- const result = await request.query(
1051
- `SELECT
1052
- [traceId], [spanId], [parentSpanId], [name], [scope], [spanType],
1053
- [attributes], [metadata], [links], [input], [output], [error], [isEvent],
1054
- [startedAt], [endedAt], [createdAt], [updatedAt]
1055
- FROM ${tableName}
1056
- WHERE [traceId] = @traceId
1057
- ORDER BY [startedAt] DESC`
1058
- );
853
+ request.input("indexName", indexName);
854
+ request.input("schemaName", schemaName);
855
+ const query = `
856
+ SELECT
857
+ i.name as name,
858
+ o.name as [table],
859
+ i.is_unique as is_unique,
860
+ CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size,
861
+ i.type_desc as method,
862
+ ISNULL(us.user_scans, 0) as scans,
863
+ ISNULL(us.user_seeks + us.user_scans, 0) as tuples_read,
864
+ ISNULL(us.user_lookups, 0) as tuples_fetched
865
+ FROM sys.indexes i
866
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
867
+ INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
868
+ LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
869
+ LEFT JOIN sys.dm_db_index_usage_stats us ON i.object_id = us.object_id AND i.index_id = us.index_id
870
+ WHERE i.name = @indexName
871
+ AND sch.name = @schemaName
872
+ GROUP BY i.name, o.name, i.is_unique, i.type_desc, us.user_seeks, us.user_scans, us.user_lookups
873
+ `;
874
+ const result = await request.query(query);
1059
875
  if (!result.recordset || result.recordset.length === 0) {
1060
- return null;
876
+ throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
1061
877
  }
878
+ const row = result.recordset[0];
879
+ const colRequest = this.pool.request();
880
+ colRequest.input("indexName", indexName);
881
+ colRequest.input("schemaName", schemaName);
882
+ const colResult = await colRequest.query(`
883
+ SELECT c.name as column_name
884
+ FROM sys.indexes i
885
+ INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
886
+ INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
887
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
888
+ INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
889
+ WHERE i.name = @indexName
890
+ AND s.name = @schemaName
891
+ ORDER BY ic.key_ordinal
892
+ `);
1062
893
  return {
1063
- traceId,
1064
- spans: result.recordset.map(
1065
- (span) => transformFromSqlRow({
1066
- tableName: storage.TABLE_SPANS,
1067
- sqlRow: span
1068
- })
1069
- )
894
+ name: row.name,
895
+ table: row.table,
896
+ columns: colResult.recordset.map((c) => c.column_name),
897
+ unique: row.is_unique || false,
898
+ size: row.size || "0 MB",
899
+ definition: "",
900
+ method: row.method?.toLowerCase() || "nonclustered",
901
+ scans: Number(row.scans) || 0,
902
+ tuples_read: Number(row.tuples_read) || 0,
903
+ tuples_fetched: Number(row.tuples_fetched) || 0
1070
904
  };
1071
905
  } catch (error$1) {
1072
906
  throw new error.MastraError(
1073
907
  {
1074
- id: "MSSQL_STORE_GET_TRACE_FAILED",
908
+ id: storage.createStorageErrorId("MSSQL", "INDEX_DESCRIBE", "FAILED"),
1075
909
  domain: error.ErrorDomain.STORAGE,
1076
- category: error.ErrorCategory.USER,
910
+ category: error.ErrorCategory.THIRD_PARTY,
1077
911
  details: {
1078
- traceId
912
+ indexName
1079
913
  }
1080
914
  },
1081
915
  error$1
1082
916
  );
1083
917
  }
1084
918
  }
1085
- async updateSpan({
1086
- spanId,
1087
- traceId,
1088
- updates
1089
- }) {
1090
- try {
1091
- const data = { ...updates };
1092
- if (data.endedAt instanceof Date) {
1093
- data.endedAt = data.endedAt.toISOString();
919
+ /**
920
+ * Returns definitions for automatic performance indexes
921
+ * IMPORTANT: Uses seq_id DESC instead of createdAt DESC for MSSQL due to millisecond accuracy limitations
922
+ * NOTE: Using NVARCHAR(400) for text columns (800 bytes) leaves room for composite indexes
923
+ */
924
+ getAutomaticIndexDefinitions() {
925
+ const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
926
+ return [
927
+ // Composite indexes for optimal filtering + sorting performance
928
+ // NVARCHAR(400) = 800 bytes, plus BIGINT (8 bytes) = 808 bytes total (under 900-byte limit)
929
+ {
930
+ name: `${schemaPrefix}mastra_threads_resourceid_seqid_idx`,
931
+ table: storage.TABLE_THREADS,
932
+ columns: ["resourceId", "seq_id DESC"]
933
+ },
934
+ {
935
+ name: `${schemaPrefix}mastra_messages_thread_id_seqid_idx`,
936
+ table: storage.TABLE_MESSAGES,
937
+ columns: ["thread_id", "seq_id DESC"]
938
+ },
939
+ {
940
+ name: `${schemaPrefix}mastra_traces_name_seqid_idx`,
941
+ table: storage.TABLE_TRACES,
942
+ columns: ["name", "seq_id DESC"]
943
+ },
944
+ {
945
+ name: `${schemaPrefix}mastra_scores_trace_id_span_id_seqid_idx`,
946
+ table: storage.TABLE_SCORERS,
947
+ columns: ["traceId", "spanId", "seq_id DESC"]
948
+ },
949
+ // Spans indexes for optimal trace querying
950
+ {
951
+ name: `${schemaPrefix}mastra_ai_spans_traceid_startedat_idx`,
952
+ table: storage.TABLE_SPANS,
953
+ columns: ["traceId", "startedAt DESC"]
954
+ },
955
+ {
956
+ name: `${schemaPrefix}mastra_ai_spans_parentspanid_startedat_idx`,
957
+ table: storage.TABLE_SPANS,
958
+ columns: ["parentSpanId", "startedAt DESC"]
959
+ },
960
+ {
961
+ name: `${schemaPrefix}mastra_ai_spans_name_idx`,
962
+ table: storage.TABLE_SPANS,
963
+ columns: ["name"]
964
+ },
965
+ {
966
+ name: `${schemaPrefix}mastra_ai_spans_spantype_startedat_idx`,
967
+ table: storage.TABLE_SPANS,
968
+ columns: ["spanType", "startedAt DESC"]
1094
969
  }
1095
- if (data.startedAt instanceof Date) {
1096
- data.startedAt = data.startedAt.toISOString();
970
+ ];
971
+ }
972
+ /**
973
+ * Creates automatic indexes for optimal query performance
974
+ * Uses getAutomaticIndexDefinitions() to determine which indexes to create
975
+ */
976
+ async createAutomaticIndexes() {
977
+ try {
978
+ const indexes = this.getAutomaticIndexDefinitions();
979
+ for (const indexOptions of indexes) {
980
+ try {
981
+ await this.createIndex(indexOptions);
982
+ } catch (error) {
983
+ this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
984
+ }
1097
985
  }
1098
- await this.operations.update({
1099
- tableName: storage.TABLE_SPANS,
1100
- keys: { spanId, traceId },
1101
- data
1102
- });
1103
986
  } catch (error$1) {
1104
987
  throw new error.MastraError(
1105
988
  {
1106
- id: "MSSQL_STORE_UPDATE_SPAN_FAILED",
989
+ id: storage.createStorageErrorId("MSSQL", "CREATE_PERFORMANCE_INDEXES", "FAILED"),
1107
990
  domain: error.ErrorDomain.STORAGE,
1108
- category: error.ErrorCategory.USER,
1109
- details: {
1110
- spanId,
1111
- traceId
1112
- }
991
+ category: error.ErrorCategory.THIRD_PARTY
1113
992
  },
1114
993
  error$1
1115
994
  );
1116
995
  }
1117
996
  }
1118
- async getTracesPaginated({
1119
- filters,
1120
- pagination
1121
- }) {
1122
- const page = pagination?.page ?? 0;
1123
- const perPage = pagination?.perPage ?? 10;
1124
- const { entityId, entityType, ...actualFilters } = filters || {};
1125
- const filtersWithDateRange = {
1126
- ...actualFilters,
1127
- ...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
1128
- parentSpanId: null
1129
- // Only get root spans for traces
1130
- };
1131
- const whereClause = prepareWhereClause(filtersWithDateRange);
1132
- let actualWhereClause = whereClause.sql;
1133
- const params = { ...whereClause.params };
1134
- let currentParamIndex = Object.keys(params).length + 1;
1135
- if (entityId && entityType) {
1136
- let name = "";
1137
- if (entityType === "workflow") {
1138
- name = `workflow run: '${entityId}'`;
1139
- } else if (entityType === "agent") {
1140
- name = `agent run: '${entityId}'`;
997
+ };
998
+ function getSchemaName2(schema) {
999
+ return schema ? `[${utils.parseSqlIdentifier(schema, "schema name")}]` : void 0;
1000
+ }
1001
+ function getTableName2({ indexName, schemaName }) {
1002
+ const parsedIndexName = utils.parseSqlIdentifier(indexName, "index name");
1003
+ const quotedIndexName = `[${parsedIndexName}]`;
1004
+ const quotedSchemaName = schemaName;
1005
+ return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
1006
+ }
1007
+ function buildDateRangeFilter(dateRange, fieldName) {
1008
+ const filters = {};
1009
+ if (dateRange?.start) {
1010
+ filters[`${fieldName}_gte`] = dateRange.start;
1011
+ }
1012
+ if (dateRange?.end) {
1013
+ filters[`${fieldName}_lte`] = dateRange.end;
1014
+ }
1015
+ return filters;
1016
+ }
1017
+ function isInOperator(value) {
1018
+ return typeof value === "object" && value !== null && "$in" in value && Array.isArray(value.$in);
1019
+ }
1020
+ function prepareWhereClause(filters, _schema) {
1021
+ const conditions = [];
1022
+ const params = {};
1023
+ let paramIndex = 1;
1024
+ Object.entries(filters).forEach(([key, value]) => {
1025
+ if (value === void 0) return;
1026
+ if (key.endsWith("_gte")) {
1027
+ const paramName = `p${paramIndex++}`;
1028
+ const fieldName = key.slice(0, -4);
1029
+ conditions.push(`[${utils.parseSqlIdentifier(fieldName, "field name")}] >= @${paramName}`);
1030
+ params[paramName] = value instanceof Date ? value.toISOString() : value;
1031
+ } else if (key.endsWith("_lte")) {
1032
+ const paramName = `p${paramIndex++}`;
1033
+ const fieldName = key.slice(0, -4);
1034
+ conditions.push(`[${utils.parseSqlIdentifier(fieldName, "field name")}] <= @${paramName}`);
1035
+ params[paramName] = value instanceof Date ? value.toISOString() : value;
1036
+ } else if (value === null) {
1037
+ conditions.push(`[${utils.parseSqlIdentifier(key, "field name")}] IS NULL`);
1038
+ } else if (isInOperator(value)) {
1039
+ const inValues = value.$in;
1040
+ if (inValues.length === 0) {
1041
+ conditions.push("1 = 0");
1042
+ } else if (inValues.length === 1) {
1043
+ const paramName = `p${paramIndex++}`;
1044
+ conditions.push(`[${utils.parseSqlIdentifier(key, "field name")}] = @${paramName}`);
1045
+ params[paramName] = inValues[0] instanceof Date ? inValues[0].toISOString() : inValues[0];
1141
1046
  } else {
1142
- const error$1 = new error.MastraError({
1143
- id: "MSSQL_STORE_GET_TRACES_PAGINATED_FAILED",
1144
- domain: error.ErrorDomain.STORAGE,
1145
- category: error.ErrorCategory.USER,
1146
- details: {
1147
- entityType
1148
- },
1149
- text: `Cannot filter by entity type: ${entityType}`
1150
- });
1151
- throw error$1;
1152
- }
1153
- const entityParam = `p${currentParamIndex++}`;
1154
- if (actualWhereClause) {
1155
- actualWhereClause += ` AND [name] = @${entityParam}`;
1047
+ const inParamNames = [];
1048
+ for (const item of inValues) {
1049
+ const paramName = `p${paramIndex++}`;
1050
+ inParamNames.push(`@${paramName}`);
1051
+ params[paramName] = item instanceof Date ? item.toISOString() : item;
1052
+ }
1053
+ conditions.push(`[${utils.parseSqlIdentifier(key, "field name")}] IN (${inParamNames.join(", ")})`);
1054
+ }
1055
+ } else if (Array.isArray(value)) {
1056
+ if (value.length === 0) {
1057
+ conditions.push("1 = 0");
1058
+ } else if (value.length === 1) {
1059
+ const paramName = `p${paramIndex++}`;
1060
+ conditions.push(`[${utils.parseSqlIdentifier(key, "field name")}] = @${paramName}`);
1061
+ params[paramName] = value[0] instanceof Date ? value[0].toISOString() : value[0];
1156
1062
  } else {
1157
- actualWhereClause = ` WHERE [name] = @${entityParam}`;
1063
+ const inParamNames = [];
1064
+ for (const item of value) {
1065
+ const paramName = `p${paramIndex++}`;
1066
+ inParamNames.push(`@${paramName}`);
1067
+ params[paramName] = item instanceof Date ? item.toISOString() : item;
1068
+ }
1069
+ conditions.push(`[${utils.parseSqlIdentifier(key, "field name")}] IN (${inParamNames.join(", ")})`);
1158
1070
  }
1159
- params[entityParam] = name;
1071
+ } else {
1072
+ const paramName = `p${paramIndex++}`;
1073
+ conditions.push(`[${utils.parseSqlIdentifier(key, "field name")}] = @${paramName}`);
1074
+ params[paramName] = value instanceof Date ? value.toISOString() : value;
1160
1075
  }
1161
- const tableName = getTableName({
1162
- indexName: storage.TABLE_SPANS,
1163
- schemaName: getSchemaName(this.schema)
1164
- });
1165
- try {
1166
- const countRequest = this.pool.request();
1167
- Object.entries(params).forEach(([key, value]) => {
1168
- countRequest.input(key, value);
1169
- });
1170
- const countResult = await countRequest.query(
1171
- `SELECT COUNT(*) as count FROM ${tableName}${actualWhereClause}`
1172
- );
1173
- const total = countResult.recordset[0]?.count ?? 0;
1174
- if (total === 0) {
1175
- return {
1176
- pagination: {
1177
- total: 0,
1178
- page,
1179
- perPage,
1180
- hasMore: false
1181
- },
1182
- spans: []
1183
- };
1076
+ });
1077
+ return {
1078
+ sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
1079
+ params
1080
+ };
1081
+ }
1082
+ function transformFromSqlRow({
1083
+ tableName,
1084
+ sqlRow
1085
+ }) {
1086
+ const schema = storage.TABLE_SCHEMAS[tableName];
1087
+ const result = {};
1088
+ Object.entries(sqlRow).forEach(([key, value]) => {
1089
+ const columnSchema = schema?.[key];
1090
+ if (columnSchema?.type === "jsonb" && typeof value === "string") {
1091
+ try {
1092
+ result[key] = JSON.parse(value);
1093
+ } catch {
1094
+ result[key] = value;
1184
1095
  }
1185
- const dataRequest = this.pool.request();
1186
- Object.entries(params).forEach(([key, value]) => {
1187
- dataRequest.input(key, value);
1188
- });
1189
- dataRequest.input("offset", page * perPage);
1190
- dataRequest.input("limit", perPage);
1191
- const dataResult = await dataRequest.query(
1192
- `SELECT * FROM ${tableName}${actualWhereClause} ORDER BY [startedAt] DESC OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
1193
- );
1194
- const spans = dataResult.recordset.map(
1195
- (row) => transformFromSqlRow({
1196
- tableName: storage.TABLE_SPANS,
1197
- sqlRow: row
1198
- })
1199
- );
1200
- return {
1201
- pagination: {
1202
- total,
1203
- page,
1204
- perPage,
1205
- hasMore: (page + 1) * perPage < total
1206
- },
1207
- spans
1208
- };
1209
- } catch (error$1) {
1210
- throw new error.MastraError(
1211
- {
1212
- id: "MSSQL_STORE_GET_TRACES_PAGINATED_FAILED",
1213
- domain: error.ErrorDomain.STORAGE,
1214
- category: error.ErrorCategory.USER
1215
- },
1216
- error$1
1217
- );
1096
+ } else if (columnSchema?.type === "timestamp" && value && typeof value === "string") {
1097
+ result[key] = new Date(value);
1098
+ } else if (columnSchema?.type === "timestamp" && value instanceof Date) {
1099
+ result[key] = value;
1100
+ } else if (columnSchema?.type === "boolean") {
1101
+ result[key] = Boolean(value);
1102
+ } else {
1103
+ result[key] = value;
1218
1104
  }
1105
+ });
1106
+ return result;
1107
+ }
1108
+
1109
+ // src/storage/domains/memory/index.ts
1110
+ var MemoryMSSQL = class extends storage.MemoryStorage {
1111
+ pool;
1112
+ schema;
1113
+ db;
1114
+ needsConnect;
1115
+ _parseAndFormatMessages(messages, format) {
1116
+ const messagesWithParsedContent = messages.map((message) => {
1117
+ if (typeof message.content === "string") {
1118
+ try {
1119
+ return { ...message, content: JSON.parse(message.content) };
1120
+ } catch {
1121
+ return message;
1122
+ }
1123
+ }
1124
+ return message;
1125
+ });
1126
+ const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
1127
+ const list = new agent.MessageList().add(cleanMessages, "memory");
1128
+ return format === "v2" ? list.get.all.db() : list.get.all.v1();
1219
1129
  }
1220
- async batchCreateSpans(args) {
1221
- if (!args.records || args.records.length === 0) {
1222
- return;
1223
- }
1224
- try {
1225
- await this.operations.batchInsert({
1226
- tableName: storage.TABLE_SPANS,
1227
- records: args.records.map((span) => ({
1228
- ...span,
1229
- startedAt: span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt,
1230
- endedAt: span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt
1231
- }))
1232
- });
1233
- } catch (error$1) {
1234
- throw new error.MastraError(
1235
- {
1236
- id: "MSSQL_STORE_BATCH_CREATE_SPANS_FAILED",
1237
- domain: error.ErrorDomain.STORAGE,
1238
- category: error.ErrorCategory.USER,
1239
- details: {
1240
- count: args.records.length
1241
- }
1242
- },
1243
- error$1
1244
- );
1245
- }
1130
+ constructor(config) {
1131
+ super();
1132
+ const { pool, db, schema, needsConnect } = resolveMssqlConfig(config);
1133
+ this.pool = pool;
1134
+ this.schema = schema;
1135
+ this.db = db;
1136
+ this.needsConnect = needsConnect;
1246
1137
  }
1247
- async batchUpdateSpans(args) {
1248
- if (!args.records || args.records.length === 0) {
1249
- return;
1250
- }
1251
- try {
1252
- const updates = args.records.map(({ traceId, spanId, updates: data }) => {
1253
- const processedData = { ...data };
1254
- if (processedData.endedAt instanceof Date) {
1255
- processedData.endedAt = processedData.endedAt.toISOString();
1256
- }
1257
- if (processedData.startedAt instanceof Date) {
1258
- processedData.startedAt = processedData.startedAt.toISOString();
1259
- }
1260
- return {
1261
- keys: { spanId, traceId },
1262
- data: processedData
1263
- };
1264
- });
1265
- await this.operations.batchUpdate({
1266
- tableName: storage.TABLE_SPANS,
1267
- updates
1268
- });
1269
- } catch (error$1) {
1270
- throw new error.MastraError(
1271
- {
1272
- id: "MSSQL_STORE_BATCH_UPDATE_SPANS_FAILED",
1273
- domain: error.ErrorDomain.STORAGE,
1274
- category: error.ErrorCategory.USER,
1275
- details: {
1276
- count: args.records.length
1277
- }
1278
- },
1279
- error$1
1280
- );
1138
+ async init() {
1139
+ if (this.needsConnect) {
1140
+ await this.pool.connect();
1141
+ this.needsConnect = false;
1281
1142
  }
1143
+ await this.db.createTable({ tableName: storage.TABLE_THREADS, schema: storage.TABLE_SCHEMAS[storage.TABLE_THREADS] });
1144
+ await this.db.createTable({ tableName: storage.TABLE_MESSAGES, schema: storage.TABLE_SCHEMAS[storage.TABLE_MESSAGES] });
1145
+ await this.db.createTable({ tableName: storage.TABLE_RESOURCES, schema: storage.TABLE_SCHEMAS[storage.TABLE_RESOURCES] });
1282
1146
  }
1283
- async batchDeleteTraces(args) {
1284
- if (!args.traceIds || args.traceIds.length === 0) {
1285
- return;
1286
- }
1147
+ async dangerouslyClearAll() {
1148
+ await this.db.clearTable({ tableName: storage.TABLE_MESSAGES });
1149
+ await this.db.clearTable({ tableName: storage.TABLE_THREADS });
1150
+ await this.db.clearTable({ tableName: storage.TABLE_RESOURCES });
1151
+ }
1152
+ async getThreadById({ threadId }) {
1287
1153
  try {
1288
- const keys = args.traceIds.map((traceId) => ({ traceId }));
1289
- await this.operations.batchDelete({
1290
- tableName: storage.TABLE_SPANS,
1291
- keys
1292
- });
1154
+ const sql5 = `SELECT
1155
+ id,
1156
+ [resourceId],
1157
+ title,
1158
+ metadata,
1159
+ [createdAt],
1160
+ [updatedAt]
1161
+ FROM ${getTableName2({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName2(this.schema) })}
1162
+ WHERE id = @threadId`;
1163
+ const request = this.pool.request();
1164
+ request.input("threadId", threadId);
1165
+ const resultSet = await request.query(sql5);
1166
+ const thread = resultSet.recordset[0] || null;
1167
+ if (!thread) {
1168
+ return null;
1169
+ }
1170
+ return {
1171
+ ...thread,
1172
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1173
+ createdAt: thread.createdAt,
1174
+ updatedAt: thread.updatedAt
1175
+ };
1293
1176
  } catch (error$1) {
1294
1177
  throw new error.MastraError(
1295
1178
  {
1296
- id: "MSSQL_STORE_BATCH_DELETE_TRACES_FAILED",
1179
+ id: storage.createStorageErrorId("MSSQL", "GET_THREAD_BY_ID", "FAILED"),
1297
1180
  domain: error.ErrorDomain.STORAGE,
1298
- category: error.ErrorCategory.USER,
1181
+ category: error.ErrorCategory.THIRD_PARTY,
1299
1182
  details: {
1300
- count: args.traceIds.length
1183
+ threadId
1301
1184
  }
1302
1185
  },
1303
1186
  error$1
1304
1187
  );
1305
1188
  }
1306
1189
  }
1307
- };
1308
- var StoreOperationsMSSQL = class extends storage.StoreOperations {
1309
- pool;
1310
- schemaName;
1311
- setupSchemaPromise = null;
1312
- schemaSetupComplete = void 0;
1313
- getSqlType(type, isPrimaryKey = false, useLargeStorage = false) {
1314
- switch (type) {
1315
- case "text":
1316
- if (useLargeStorage) {
1317
- return "NVARCHAR(MAX)";
1318
- }
1319
- return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(400)";
1320
- case "timestamp":
1321
- return "DATETIME2(7)";
1322
- case "uuid":
1323
- return "UNIQUEIDENTIFIER";
1324
- case "jsonb":
1325
- return "NVARCHAR(MAX)";
1326
- case "integer":
1327
- return "INT";
1328
- case "bigint":
1329
- return "BIGINT";
1330
- case "float":
1331
- return "FLOAT";
1332
- case "boolean":
1333
- return "BIT";
1334
- default:
1335
- throw new error.MastraError({
1336
- id: "MASTRA_STORAGE_MSSQL_STORE_TYPE_NOT_SUPPORTED",
1337
- domain: error.ErrorDomain.STORAGE,
1338
- category: error.ErrorCategory.THIRD_PARTY
1339
- });
1340
- }
1341
- }
1342
- constructor({ pool, schemaName }) {
1343
- super();
1344
- this.pool = pool;
1345
- this.schemaName = schemaName;
1346
- }
1347
- async hasColumn(table, column) {
1348
- const schema = this.schemaName || "dbo";
1349
- const request = this.pool.request();
1350
- request.input("schema", schema);
1351
- request.input("table", table);
1352
- request.input("column", column);
1353
- request.input("columnLower", column.toLowerCase());
1354
- const result = await request.query(
1355
- `SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
1356
- );
1357
- return result.recordset.length > 0;
1358
- }
1359
- async setupSchema() {
1360
- if (!this.schemaName || this.schemaSetupComplete) {
1361
- return;
1362
- }
1363
- if (!this.setupSchemaPromise) {
1364
- this.setupSchemaPromise = (async () => {
1365
- try {
1366
- const checkRequest = this.pool.request();
1367
- checkRequest.input("schemaName", this.schemaName);
1368
- const checkResult = await checkRequest.query(`
1369
- SELECT 1 AS found FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = @schemaName
1370
- `);
1371
- const schemaExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
1372
- if (!schemaExists) {
1373
- try {
1374
- await this.pool.request().query(`CREATE SCHEMA [${this.schemaName}]`);
1375
- this.logger?.info?.(`Schema "${this.schemaName}" created successfully`);
1376
- } catch (error) {
1377
- this.logger?.error?.(`Failed to create schema "${this.schemaName}"`, { error });
1378
- throw new Error(
1379
- `Unable to create schema "${this.schemaName}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
1380
- );
1381
- }
1382
- }
1383
- this.schemaSetupComplete = true;
1384
- this.logger?.debug?.(`Schema "${this.schemaName}" is ready for use`);
1385
- } catch (error) {
1386
- this.schemaSetupComplete = void 0;
1387
- this.setupSchemaPromise = null;
1388
- throw error;
1389
- } finally {
1390
- this.setupSchemaPromise = null;
1391
- }
1392
- })();
1393
- }
1394
- await this.setupSchemaPromise;
1395
- }
1396
- async insert({
1397
- tableName,
1398
- record,
1399
- transaction
1400
- }) {
1401
- try {
1402
- const columns = Object.keys(record);
1403
- const parsedColumns = columns.map((col) => utils.parseSqlIdentifier(col, "column name"));
1404
- const paramNames = columns.map((_, i) => `@param${i}`);
1405
- const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${parsedColumns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
1406
- const request = transaction ? transaction.request() : this.pool.request();
1407
- columns.forEach((col, i) => {
1408
- const value = record[col];
1409
- const preparedValue = this.prepareValue(value, col, tableName);
1410
- if (preparedValue instanceof Date) {
1411
- request.input(`param${i}`, sql2__default.default.DateTime2, preparedValue);
1412
- } else if (preparedValue === null || preparedValue === void 0) {
1413
- request.input(`param${i}`, this.getMssqlType(tableName, col), null);
1414
- } else {
1415
- request.input(`param${i}`, preparedValue);
1190
+ async listThreadsByResourceId(args) {
1191
+ const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
1192
+ if (page < 0) {
1193
+ throw new error.MastraError({
1194
+ id: storage.createStorageErrorId("MSSQL", "LIST_THREADS_BY_RESOURCE_ID", "INVALID_PAGE"),
1195
+ domain: error.ErrorDomain.STORAGE,
1196
+ category: error.ErrorCategory.USER,
1197
+ text: "Page number must be non-negative",
1198
+ details: {
1199
+ resourceId,
1200
+ page
1416
1201
  }
1417
1202
  });
1418
- await request.query(insertSql);
1203
+ }
1204
+ const perPage = storage.normalizePerPage(perPageInput, 100);
1205
+ const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
1206
+ const { field, direction } = this.parseOrderBy(orderBy);
1207
+ try {
1208
+ const baseQuery = `FROM ${getTableName2({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName2(this.schema) })} WHERE [resourceId] = @resourceId`;
1209
+ const countQuery = `SELECT COUNT(*) as count ${baseQuery}`;
1210
+ const countRequest = this.pool.request();
1211
+ countRequest.input("resourceId", resourceId);
1212
+ const countResult = await countRequest.query(countQuery);
1213
+ const total = parseInt(countResult.recordset[0]?.count ?? "0", 10);
1214
+ if (total === 0) {
1215
+ return {
1216
+ threads: [],
1217
+ total: 0,
1218
+ page,
1219
+ perPage: perPageForResponse,
1220
+ hasMore: false
1221
+ };
1222
+ }
1223
+ const orderByField = field === "createdAt" ? "[createdAt]" : "[updatedAt]";
1224
+ const dir = (direction || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
1225
+ const limitValue = perPageInput === false ? total : perPage;
1226
+ const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
1227
+ const dataRequest = this.pool.request();
1228
+ dataRequest.input("resourceId", resourceId);
1229
+ dataRequest.input("offset", offset);
1230
+ if (limitValue > 2147483647) {
1231
+ dataRequest.input("perPage", sql__default.default.BigInt, limitValue);
1232
+ } else {
1233
+ dataRequest.input("perPage", limitValue);
1234
+ }
1235
+ const rowsResult = await dataRequest.query(dataQuery);
1236
+ const rows = rowsResult.recordset || [];
1237
+ const threads = rows.map((thread) => ({
1238
+ ...thread,
1239
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1240
+ createdAt: thread.createdAt,
1241
+ updatedAt: thread.updatedAt
1242
+ }));
1243
+ return {
1244
+ threads,
1245
+ total,
1246
+ page,
1247
+ perPage: perPageForResponse,
1248
+ hasMore: perPageInput === false ? false : offset + perPage < total
1249
+ };
1419
1250
  } catch (error$1) {
1420
- throw new error.MastraError(
1251
+ const mastraError = new error.MastraError(
1421
1252
  {
1422
- id: "MASTRA_STORAGE_MSSQL_STORE_INSERT_FAILED",
1253
+ id: storage.createStorageErrorId("MSSQL", "LIST_THREADS_BY_RESOURCE_ID", "FAILED"),
1423
1254
  domain: error.ErrorDomain.STORAGE,
1424
1255
  category: error.ErrorCategory.THIRD_PARTY,
1425
1256
  details: {
1426
- tableName
1257
+ resourceId,
1258
+ page
1427
1259
  }
1428
1260
  },
1429
1261
  error$1
1430
1262
  );
1263
+ this.logger?.error?.(mastraError.toString());
1264
+ this.logger?.trackException?.(mastraError);
1265
+ return {
1266
+ threads: [],
1267
+ total: 0,
1268
+ page,
1269
+ perPage: perPageForResponse,
1270
+ hasMore: false
1271
+ };
1431
1272
  }
1432
1273
  }
1433
- async clearTable({ tableName }) {
1434
- const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1274
+ async saveThread({ thread }) {
1435
1275
  try {
1436
- try {
1437
- await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
1438
- } catch (truncateError) {
1439
- if (truncateError?.number === 4712) {
1440
- await this.pool.request().query(`DELETE FROM ${fullTableName}`);
1441
- } else {
1442
- throw truncateError;
1443
- }
1276
+ const table = getTableName2({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName2(this.schema) });
1277
+ const mergeSql = `MERGE INTO ${table} WITH (HOLDLOCK) AS target
1278
+ USING (SELECT @id AS id) AS source
1279
+ ON (target.id = source.id)
1280
+ WHEN MATCHED THEN
1281
+ UPDATE SET
1282
+ [resourceId] = @resourceId,
1283
+ title = @title,
1284
+ metadata = @metadata,
1285
+ [updatedAt] = @updatedAt
1286
+ WHEN NOT MATCHED THEN
1287
+ INSERT (id, [resourceId], title, metadata, [createdAt], [updatedAt])
1288
+ VALUES (@id, @resourceId, @title, @metadata, @createdAt, @updatedAt);`;
1289
+ const req = this.pool.request();
1290
+ req.input("id", thread.id);
1291
+ req.input("resourceId", thread.resourceId);
1292
+ req.input("title", thread.title);
1293
+ const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
1294
+ if (metadata === null) {
1295
+ req.input("metadata", sql__default.default.NVarChar, null);
1296
+ } else {
1297
+ req.input("metadata", metadata);
1444
1298
  }
1299
+ req.input("createdAt", sql__default.default.DateTime2, thread.createdAt);
1300
+ req.input("updatedAt", sql__default.default.DateTime2, thread.updatedAt);
1301
+ await req.query(mergeSql);
1302
+ return thread;
1445
1303
  } catch (error$1) {
1446
1304
  throw new error.MastraError(
1447
1305
  {
1448
- id: "MASTRA_STORAGE_MSSQL_STORE_CLEAR_TABLE_FAILED",
1306
+ id: storage.createStorageErrorId("MSSQL", "SAVE_THREAD", "FAILED"),
1449
1307
  domain: error.ErrorDomain.STORAGE,
1450
1308
  category: error.ErrorCategory.THIRD_PARTY,
1451
1309
  details: {
1452
- tableName
1310
+ threadId: thread.id
1453
1311
  }
1454
1312
  },
1455
1313
  error$1
1456
1314
  );
1457
1315
  }
1458
1316
  }
1459
- getDefaultValue(type) {
1460
- switch (type) {
1461
- case "timestamp":
1462
- return "DEFAULT SYSUTCDATETIME()";
1463
- case "jsonb":
1464
- return "DEFAULT N'{}'";
1465
- case "boolean":
1466
- return "DEFAULT 0";
1467
- default:
1468
- return super.getDefaultValue(type);
1469
- }
1470
- }
1471
- async createTable({
1472
- tableName,
1473
- schema
1317
+ /**
1318
+ * Updates a thread's title and metadata, merging with existing metadata. Returns the updated thread.
1319
+ */
1320
+ async updateThread({
1321
+ id,
1322
+ title,
1323
+ metadata
1474
1324
  }) {
1325
+ const existingThread = await this.getThreadById({ threadId: id });
1326
+ if (!existingThread) {
1327
+ throw new error.MastraError({
1328
+ id: storage.createStorageErrorId("MSSQL", "UPDATE_THREAD", "NOT_FOUND"),
1329
+ domain: error.ErrorDomain.STORAGE,
1330
+ category: error.ErrorCategory.USER,
1331
+ text: `Thread ${id} not found`,
1332
+ details: {
1333
+ threadId: id,
1334
+ title
1335
+ }
1336
+ });
1337
+ }
1338
+ const mergedMetadata = {
1339
+ ...existingThread.metadata,
1340
+ ...metadata
1341
+ };
1475
1342
  try {
1476
- const uniqueConstraintColumns = tableName === storage.TABLE_WORKFLOW_SNAPSHOT ? ["workflow_name", "run_id"] : [];
1477
- const largeDataColumns = [
1478
- "workingMemory",
1479
- "snapshot",
1480
- "metadata",
1481
- "content",
1482
- // messages.content - can be very long conversation content
1483
- "input",
1484
- // evals.input - test input data
1485
- "output",
1486
- // evals.output - test output data
1487
- "instructions",
1488
- // evals.instructions - evaluation instructions
1489
- "other"
1490
- // traces.other - additional trace data
1491
- ];
1492
- const columns = Object.entries(schema).map(([name, def]) => {
1493
- const parsedName = utils.parseSqlIdentifier(name, "column name");
1494
- const constraints = [];
1495
- if (def.primaryKey) constraints.push("PRIMARY KEY");
1496
- if (!def.nullable) constraints.push("NOT NULL");
1497
- const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
1498
- const useLargeStorage = largeDataColumns.includes(name);
1499
- return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
1500
- }).join(",\n");
1501
- if (this.schemaName) {
1502
- await this.setupSchema();
1343
+ const table = getTableName2({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName2(this.schema) });
1344
+ const sql5 = `UPDATE ${table}
1345
+ SET title = @title,
1346
+ metadata = @metadata,
1347
+ [updatedAt] = @updatedAt
1348
+ OUTPUT INSERTED.*
1349
+ WHERE id = @id`;
1350
+ const req = this.pool.request();
1351
+ req.input("id", id);
1352
+ req.input("title", title);
1353
+ req.input("metadata", JSON.stringify(mergedMetadata));
1354
+ req.input("updatedAt", /* @__PURE__ */ new Date());
1355
+ const result = await req.query(sql5);
1356
+ let thread = result.recordset && result.recordset[0];
1357
+ if (thread && "seq_id" in thread) {
1358
+ const { seq_id, ...rest } = thread;
1359
+ thread = rest;
1503
1360
  }
1504
- const checkTableRequest = this.pool.request();
1505
- checkTableRequest.input(
1506
- "tableName",
1507
- getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
1508
- );
1509
- const checkTableSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName`;
1510
- checkTableRequest.input("schema", this.schemaName || "dbo");
1511
- const checkTableResult = await checkTableRequest.query(checkTableSql);
1512
- const tableExists = Array.isArray(checkTableResult.recordset) && checkTableResult.recordset.length > 0;
1513
- if (!tableExists) {
1514
- const createSql = `CREATE TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (
1515
- ${columns}
1516
- )`;
1517
- await this.pool.request().query(createSql);
1361
+ if (!thread) {
1362
+ throw new error.MastraError({
1363
+ id: storage.createStorageErrorId("MSSQL", "UPDATE_THREAD", "NOT_FOUND"),
1364
+ domain: error.ErrorDomain.STORAGE,
1365
+ category: error.ErrorCategory.USER,
1366
+ text: `Thread ${id} not found after update`,
1367
+ details: {
1368
+ threadId: id,
1369
+ title
1370
+ }
1371
+ });
1518
1372
  }
1519
- const columnCheckSql = `
1520
- SELECT 1 AS found
1521
- FROM INFORMATION_SCHEMA.COLUMNS
1522
- WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = 'seq_id'
1523
- `;
1524
- const checkColumnRequest = this.pool.request();
1525
- checkColumnRequest.input("schema", this.schemaName || "dbo");
1526
- checkColumnRequest.input(
1527
- "tableName",
1528
- getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
1373
+ return {
1374
+ ...thread,
1375
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
1376
+ createdAt: thread.createdAt,
1377
+ updatedAt: thread.updatedAt
1378
+ };
1379
+ } catch (error$1) {
1380
+ throw new error.MastraError(
1381
+ {
1382
+ id: storage.createStorageErrorId("MSSQL", "UPDATE_THREAD", "FAILED"),
1383
+ domain: error.ErrorDomain.STORAGE,
1384
+ category: error.ErrorCategory.THIRD_PARTY,
1385
+ details: {
1386
+ threadId: id,
1387
+ title
1388
+ }
1389
+ },
1390
+ error$1
1529
1391
  );
1530
- const columnResult = await checkColumnRequest.query(columnCheckSql);
1531
- const columnExists = Array.isArray(columnResult.recordset) && columnResult.recordset.length > 0;
1532
- if (!columnExists) {
1533
- const alterSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD seq_id BIGINT IDENTITY(1,1)`;
1534
- await this.pool.request().query(alterSql);
1535
- }
1536
- if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
1537
- const constraintName = "mastra_workflow_snapshot_workflow_name_run_id_key";
1538
- const checkConstraintSql = `SELECT 1 AS found FROM sys.key_constraints WHERE name = @constraintName`;
1539
- const checkConstraintRequest = this.pool.request();
1540
- checkConstraintRequest.input("constraintName", constraintName);
1541
- const constraintResult = await checkConstraintRequest.query(checkConstraintSql);
1542
- const constraintExists = Array.isArray(constraintResult.recordset) && constraintResult.recordset.length > 0;
1543
- if (!constraintExists) {
1544
- const addConstraintSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD CONSTRAINT ${constraintName} UNIQUE ([workflow_name], [run_id])`;
1545
- await this.pool.request().query(addConstraintSql);
1546
- }
1547
- }
1392
+ }
1393
+ }
1394
+ async deleteThread({ threadId }) {
1395
+ const messagesTable = getTableName2({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName2(this.schema) });
1396
+ const threadsTable = getTableName2({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName2(this.schema) });
1397
+ const deleteMessagesSql = `DELETE FROM ${messagesTable} WHERE [thread_id] = @threadId`;
1398
+ const deleteThreadSql = `DELETE FROM ${threadsTable} WHERE id = @threadId`;
1399
+ const tx = this.pool.transaction();
1400
+ try {
1401
+ await tx.begin();
1402
+ const req = tx.request();
1403
+ req.input("threadId", threadId);
1404
+ await req.query(deleteMessagesSql);
1405
+ await req.query(deleteThreadSql);
1406
+ await tx.commit();
1548
1407
  } catch (error$1) {
1408
+ await tx.rollback().catch(() => {
1409
+ });
1549
1410
  throw new error.MastraError(
1550
1411
  {
1551
- id: "MASTRA_STORAGE_MSSQL_STORE_CREATE_TABLE_FAILED",
1412
+ id: storage.createStorageErrorId("MSSQL", "DELETE_THREAD", "FAILED"),
1552
1413
  domain: error.ErrorDomain.STORAGE,
1553
1414
  category: error.ErrorCategory.THIRD_PARTY,
1554
1415
  details: {
1555
- tableName
1416
+ threadId
1556
1417
  }
1557
1418
  },
1558
1419
  error$1
1559
1420
  );
1560
1421
  }
1561
1422
  }
1562
- /**
1563
- * Alters table schema to add columns if they don't exist
1564
- * @param tableName Name of the table
1565
- * @param schema Schema of the table
1566
- * @param ifNotExists Array of column names to add if they don't exist
1567
- */
1568
- async alterTable({
1569
- tableName,
1570
- schema,
1571
- ifNotExists
1572
- }) {
1573
- const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1423
+ async _getIncludedMessages({ include }) {
1424
+ if (!include || include.length === 0) return null;
1425
+ const unionQueries = [];
1426
+ const paramValues = [];
1427
+ let paramIdx = 1;
1428
+ const paramNames = [];
1429
+ const tableName = getTableName2({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName2(this.schema) });
1430
+ for (const inc of include) {
1431
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
1432
+ const pId = `@p${paramIdx}`;
1433
+ const pPrev = `@p${paramIdx + 1}`;
1434
+ const pNext = `@p${paramIdx + 2}`;
1435
+ unionQueries.push(
1436
+ `
1437
+ SELECT
1438
+ m.id,
1439
+ m.content,
1440
+ m.role,
1441
+ m.type,
1442
+ m.[createdAt],
1443
+ m.thread_id AS threadId,
1444
+ m.[resourceId],
1445
+ m.seq_id
1446
+ FROM (
1447
+ SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
1448
+ FROM ${tableName}
1449
+ WHERE [thread_id] = (SELECT thread_id FROM ${tableName} WHERE id = ${pId})
1450
+ ) AS m
1451
+ WHERE m.id = ${pId}
1452
+ OR EXISTS (
1453
+ SELECT 1
1454
+ FROM (
1455
+ SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
1456
+ FROM ${tableName}
1457
+ WHERE [thread_id] = (SELECT thread_id FROM ${tableName} WHERE id = ${pId})
1458
+ ) AS target
1459
+ WHERE target.id = ${pId}
1460
+ AND (
1461
+ -- Get previous messages (messages that come BEFORE the target)
1462
+ (m.row_num < target.row_num AND m.row_num >= target.row_num - ${pPrev})
1463
+ OR
1464
+ -- Get next messages (messages that come AFTER the target)
1465
+ (m.row_num > target.row_num AND m.row_num <= target.row_num + ${pNext})
1466
+ )
1467
+ )
1468
+ `
1469
+ );
1470
+ paramValues.push(id, withPreviousMessages, withNextMessages);
1471
+ paramNames.push(`p${paramIdx}`, `p${paramIdx + 1}`, `p${paramIdx + 2}`);
1472
+ paramIdx += 3;
1473
+ }
1474
+ const finalQuery = `
1475
+ SELECT * FROM (
1476
+ ${unionQueries.join(" UNION ALL ")}
1477
+ ) AS union_result
1478
+ ORDER BY [seq_id] ASC
1479
+ `;
1480
+ const req = this.pool.request();
1481
+ for (let i = 0; i < paramValues.length; ++i) {
1482
+ req.input(paramNames[i], paramValues[i]);
1483
+ }
1484
+ const result = await req.query(finalQuery);
1485
+ const includedRows = result.recordset || [];
1486
+ const seen = /* @__PURE__ */ new Set();
1487
+ const dedupedRows = includedRows.filter((row) => {
1488
+ if (seen.has(row.id)) return false;
1489
+ seen.add(row.id);
1490
+ return true;
1491
+ });
1492
+ return dedupedRows;
1493
+ }
1494
+ async listMessagesById({ messageIds }) {
1495
+ if (messageIds.length === 0) return { messages: [] };
1496
+ const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
1497
+ const orderByStatement = `ORDER BY [seq_id] DESC`;
1574
1498
  try {
1575
- for (const columnName of ifNotExists) {
1576
- if (schema[columnName]) {
1577
- const columnCheckRequest = this.pool.request();
1578
- columnCheckRequest.input("tableName", fullTableName.replace(/[[\]]/g, "").split(".").pop());
1579
- columnCheckRequest.input("columnName", columnName);
1580
- columnCheckRequest.input("schema", this.schemaName || "dbo");
1581
- const checkSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = @columnName`;
1582
- const checkResult = await columnCheckRequest.query(checkSql);
1583
- const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
1584
- if (!columnExists) {
1585
- const columnDef = schema[columnName];
1586
- const largeDataColumns = [
1587
- "workingMemory",
1588
- "snapshot",
1589
- "metadata",
1590
- "content",
1591
- "input",
1592
- "output",
1593
- "instructions",
1594
- "other"
1595
- ];
1596
- const useLargeStorage = largeDataColumns.includes(columnName);
1597
- const isIndexed = !!columnDef.primaryKey;
1598
- const sqlType = this.getSqlType(columnDef.type, isIndexed, useLargeStorage);
1599
- const nullable = columnDef.nullable === false ? "NOT NULL" : "";
1600
- const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1601
- const parsedColumnName = utils.parseSqlIdentifier(columnName, "column name");
1602
- const alterSql = `ALTER TABLE ${fullTableName} ADD [${parsedColumnName}] ${sqlType} ${nullable} ${defaultValue}`.trim();
1603
- await this.pool.request().query(alterSql);
1604
- this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
1499
+ let rows = [];
1500
+ let query = `${selectStatement} FROM ${getTableName2({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName2(this.schema) })} WHERE [id] IN (${messageIds.map((_, i) => `@id${i}`).join(", ")})`;
1501
+ const request = this.pool.request();
1502
+ messageIds.forEach((id, i) => request.input(`id${i}`, id));
1503
+ query += ` ${orderByStatement}`;
1504
+ const result = await request.query(query);
1505
+ const remainingRows = result.recordset || [];
1506
+ rows.push(...remainingRows);
1507
+ rows.sort((a, b) => {
1508
+ const timeDiff = a.seq_id - b.seq_id;
1509
+ return timeDiff;
1510
+ });
1511
+ const messagesWithParsedContent = rows.map((row) => {
1512
+ if (typeof row.content === "string") {
1513
+ try {
1514
+ return { ...row, content: JSON.parse(row.content) };
1515
+ } catch {
1516
+ return row;
1605
1517
  }
1606
1518
  }
1607
- }
1519
+ return row;
1520
+ });
1521
+ const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
1522
+ const list = new agent.MessageList().add(cleanMessages, "memory");
1523
+ return { messages: list.get.all.db() };
1608
1524
  } catch (error$1) {
1609
- throw new error.MastraError(
1525
+ const mastraError = new error.MastraError(
1610
1526
  {
1611
- id: "MASTRA_STORAGE_MSSQL_STORE_ALTER_TABLE_FAILED",
1527
+ id: storage.createStorageErrorId("MSSQL", "LIST_MESSAGES_BY_ID", "FAILED"),
1612
1528
  domain: error.ErrorDomain.STORAGE,
1613
1529
  category: error.ErrorCategory.THIRD_PARTY,
1614
1530
  details: {
1615
- tableName
1531
+ messageIds: JSON.stringify(messageIds)
1616
1532
  }
1617
1533
  },
1618
1534
  error$1
1619
1535
  );
1536
+ this.logger?.error?.(mastraError.toString());
1537
+ this.logger?.trackException?.(mastraError);
1538
+ return { messages: [] };
1620
1539
  }
1621
1540
  }
1622
- async load({ tableName, keys }) {
1541
+ async listMessages(args) {
1542
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
1543
+ const threadIds = Array.isArray(threadId) ? threadId : [threadId];
1544
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
1545
+ throw new error.MastraError(
1546
+ {
1547
+ id: storage.createStorageErrorId("MSSQL", "LIST_MESSAGES", "INVALID_THREAD_ID"),
1548
+ domain: error.ErrorDomain.STORAGE,
1549
+ category: error.ErrorCategory.THIRD_PARTY,
1550
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
1551
+ },
1552
+ new Error("threadId must be a non-empty string or array of non-empty strings")
1553
+ );
1554
+ }
1555
+ if (page < 0) {
1556
+ throw new error.MastraError({
1557
+ id: storage.createStorageErrorId("MSSQL", "LIST_MESSAGES", "INVALID_PAGE"),
1558
+ domain: error.ErrorDomain.STORAGE,
1559
+ category: error.ErrorCategory.USER,
1560
+ text: "Page number must be non-negative",
1561
+ details: {
1562
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
1563
+ page
1564
+ }
1565
+ });
1566
+ }
1567
+ const perPage = storage.normalizePerPage(perPageInput, 40);
1568
+ const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
1623
1569
  try {
1624
- const keyEntries = Object.entries(keys).map(([key, value]) => [utils.parseSqlIdentifier(key, "column name"), value]);
1625
- const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
1626
- const sql5 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
1627
- const request = this.pool.request();
1628
- keyEntries.forEach(([key, value], i) => {
1629
- const preparedValue = this.prepareValue(value, key, tableName);
1630
- if (preparedValue === null || preparedValue === void 0) {
1631
- request.input(`param${i}`, this.getMssqlType(tableName, key), null);
1632
- } else {
1633
- request.input(`param${i}`, preparedValue);
1570
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
1571
+ const orderByStatement = `ORDER BY [${field}] ${direction}, [seq_id] ${direction}`;
1572
+ const tableName = getTableName2({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName2(this.schema) });
1573
+ const baseQuery = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId FROM ${tableName}`;
1574
+ const filters = {
1575
+ thread_id: threadIds.length === 1 ? threadIds[0] : { $in: threadIds },
1576
+ ...resourceId ? { resourceId } : {},
1577
+ ...buildDateRangeFilter(filter?.dateRange, "createdAt")
1578
+ };
1579
+ const { sql: actualWhereClause = "", params: whereParams } = prepareWhereClause(
1580
+ filters);
1581
+ const bindWhereParams = (req) => {
1582
+ Object.entries(whereParams).forEach(([paramName, paramValue]) => req.input(paramName, paramValue));
1583
+ };
1584
+ const countRequest = this.pool.request();
1585
+ bindWhereParams(countRequest);
1586
+ const countResult = await countRequest.query(`SELECT COUNT(*) as total FROM ${tableName}${actualWhereClause}`);
1587
+ const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
1588
+ const fetchBaseMessages = async () => {
1589
+ const request = this.pool.request();
1590
+ bindWhereParams(request);
1591
+ if (perPageInput === false) {
1592
+ const result2 = await request.query(`${baseQuery}${actualWhereClause} ${orderByStatement}`);
1593
+ return result2.recordset || [];
1634
1594
  }
1595
+ request.input("offset", offset);
1596
+ request.input("limit", perPage > 2147483647 ? sql__default.default.BigInt : sql__default.default.Int, perPage);
1597
+ const result = await request.query(
1598
+ `${baseQuery}${actualWhereClause} ${orderByStatement} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
1599
+ );
1600
+ return result.recordset || [];
1601
+ };
1602
+ const baseRows = perPage === 0 ? [] : await fetchBaseMessages();
1603
+ const messages = [...baseRows];
1604
+ const seqById = /* @__PURE__ */ new Map();
1605
+ messages.forEach((msg) => {
1606
+ if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
1635
1607
  });
1636
- const resultSet = await request.query(sql5);
1637
- const result = resultSet.recordset[0] || null;
1638
- if (!result) {
1639
- return null;
1608
+ if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
1609
+ return {
1610
+ messages: [],
1611
+ total: 0,
1612
+ page,
1613
+ perPage: perPageForResponse,
1614
+ hasMore: false
1615
+ };
1640
1616
  }
1641
- if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
1642
- const snapshot = result;
1643
- if (typeof snapshot.snapshot === "string") {
1644
- snapshot.snapshot = JSON.parse(snapshot.snapshot);
1645
- }
1646
- return snapshot;
1617
+ if (include?.length) {
1618
+ const messageIds = new Set(messages.map((m) => m.id));
1619
+ const includeMessages = await this._getIncludedMessages({ include });
1620
+ includeMessages?.forEach((msg) => {
1621
+ if (!messageIds.has(msg.id)) {
1622
+ messages.push(msg);
1623
+ messageIds.add(msg.id);
1624
+ if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
1625
+ }
1626
+ });
1647
1627
  }
1648
- return result;
1628
+ const parsed = this._parseAndFormatMessages(messages, "v2");
1629
+ const mult = direction === "ASC" ? 1 : -1;
1630
+ const finalMessages = parsed.sort((a, b) => {
1631
+ const aVal = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
1632
+ const bVal = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
1633
+ if (aVal == null || bVal == null) {
1634
+ return aVal == null && bVal == null ? a.id.localeCompare(b.id) : aVal == null ? 1 : -1;
1635
+ }
1636
+ const diff = (typeof aVal === "number" && typeof bVal === "number" ? aVal - bVal : String(aVal).localeCompare(String(bVal))) * mult;
1637
+ if (diff !== 0) return diff;
1638
+ const seqA = seqById.get(a.id);
1639
+ const seqB = seqById.get(b.id);
1640
+ return seqA != null && seqB != null ? (seqA - seqB) * mult : a.id.localeCompare(b.id);
1641
+ });
1642
+ const threadIdSet = new Set(threadIds);
1643
+ const returnedThreadMessageCount = finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).length;
1644
+ const hasMore = perPageInput !== false && returnedThreadMessageCount < total && offset + perPage < total;
1645
+ return {
1646
+ messages: finalMessages,
1647
+ total,
1648
+ page,
1649
+ perPage: perPageForResponse,
1650
+ hasMore
1651
+ };
1649
1652
  } catch (error$1) {
1650
- throw new error.MastraError(
1653
+ const mastraError = new error.MastraError(
1651
1654
  {
1652
- id: "MASTRA_STORAGE_MSSQL_STORE_LOAD_FAILED",
1655
+ id: storage.createStorageErrorId("MSSQL", "LIST_MESSAGES", "FAILED"),
1653
1656
  domain: error.ErrorDomain.STORAGE,
1654
1657
  category: error.ErrorCategory.THIRD_PARTY,
1655
1658
  details: {
1656
- tableName
1659
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
1660
+ resourceId: resourceId ?? ""
1657
1661
  }
1658
1662
  },
1659
1663
  error$1
1660
1664
  );
1665
+ this.logger?.error?.(mastraError.toString());
1666
+ this.logger?.trackException?.(mastraError);
1667
+ return {
1668
+ messages: [],
1669
+ total: 0,
1670
+ page,
1671
+ perPage: perPageForResponse,
1672
+ hasMore: false
1673
+ };
1661
1674
  }
1662
1675
  }
1663
- async batchInsert({ tableName, records }) {
1664
- const transaction = this.pool.transaction();
1676
+ async saveMessages({ messages }) {
1677
+ if (messages.length === 0) return { messages: [] };
1678
+ const threadId = messages[0]?.threadId;
1679
+ if (!threadId) {
1680
+ throw new error.MastraError({
1681
+ id: storage.createStorageErrorId("MSSQL", "SAVE_MESSAGES", "INVALID_THREAD_ID"),
1682
+ domain: error.ErrorDomain.STORAGE,
1683
+ category: error.ErrorCategory.THIRD_PARTY,
1684
+ text: `Thread ID is required`
1685
+ });
1686
+ }
1687
+ const thread = await this.getThreadById({ threadId });
1688
+ if (!thread) {
1689
+ throw new error.MastraError({
1690
+ id: storage.createStorageErrorId("MSSQL", "SAVE_MESSAGES", "THREAD_NOT_FOUND"),
1691
+ domain: error.ErrorDomain.STORAGE,
1692
+ category: error.ErrorCategory.THIRD_PARTY,
1693
+ text: `Thread ${threadId} not found`,
1694
+ details: { threadId }
1695
+ });
1696
+ }
1697
+ const tableMessages = getTableName2({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName2(this.schema) });
1698
+ const tableThreads = getTableName2({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName2(this.schema) });
1665
1699
  try {
1700
+ const transaction = this.pool.transaction();
1666
1701
  await transaction.begin();
1667
- for (const record of records) {
1668
- await this.insert({ tableName, record, transaction });
1702
+ try {
1703
+ for (const message of messages) {
1704
+ if (!message.threadId) {
1705
+ throw new Error(
1706
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1707
+ );
1708
+ }
1709
+ if (!message.resourceId) {
1710
+ throw new Error(
1711
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1712
+ );
1713
+ }
1714
+ const request = transaction.request();
1715
+ request.input("id", message.id);
1716
+ request.input("thread_id", message.threadId);
1717
+ request.input(
1718
+ "content",
1719
+ typeof message.content === "string" ? message.content : JSON.stringify(message.content)
1720
+ );
1721
+ request.input("createdAt", sql__default.default.DateTime2, message.createdAt);
1722
+ request.input("role", message.role);
1723
+ request.input("type", message.type || "v2");
1724
+ request.input("resourceId", message.resourceId);
1725
+ const mergeSql = `MERGE INTO ${tableMessages} AS target
1726
+ USING (SELECT @id AS id) AS src
1727
+ ON target.id = src.id
1728
+ WHEN MATCHED THEN UPDATE SET
1729
+ thread_id = @thread_id,
1730
+ content = @content,
1731
+ [createdAt] = @createdAt,
1732
+ role = @role,
1733
+ type = @type,
1734
+ resourceId = @resourceId
1735
+ WHEN NOT MATCHED THEN INSERT (id, thread_id, content, [createdAt], role, type, resourceId)
1736
+ VALUES (@id, @thread_id, @content, @createdAt, @role, @type, @resourceId);`;
1737
+ await request.query(mergeSql);
1738
+ }
1739
+ const threadReq = transaction.request();
1740
+ threadReq.input("updatedAt", sql__default.default.DateTime2, /* @__PURE__ */ new Date());
1741
+ threadReq.input("id", threadId);
1742
+ await threadReq.query(`UPDATE ${tableThreads} SET [updatedAt] = @updatedAt WHERE id = @id`);
1743
+ await transaction.commit();
1744
+ } catch (error) {
1745
+ await transaction.rollback();
1746
+ throw error;
1669
1747
  }
1670
- await transaction.commit();
1748
+ const messagesWithParsedContent = messages.map((message) => {
1749
+ if (typeof message.content === "string") {
1750
+ try {
1751
+ return { ...message, content: JSON.parse(message.content) };
1752
+ } catch {
1753
+ return message;
1754
+ }
1755
+ }
1756
+ return message;
1757
+ });
1758
+ const list = new agent.MessageList().add(messagesWithParsedContent, "memory");
1759
+ return { messages: list.get.all.db() };
1671
1760
  } catch (error$1) {
1672
- await transaction.rollback();
1673
1761
  throw new error.MastraError(
1674
1762
  {
1675
- id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_INSERT_FAILED",
1763
+ id: storage.createStorageErrorId("MSSQL", "SAVE_MESSAGES", "FAILED"),
1676
1764
  domain: error.ErrorDomain.STORAGE,
1677
1765
  category: error.ErrorCategory.THIRD_PARTY,
1678
- details: {
1679
- tableName,
1680
- numberOfRecords: records.length
1681
- }
1766
+ details: { threadId }
1682
1767
  },
1683
1768
  error$1
1684
1769
  );
1685
1770
  }
1686
1771
  }
1687
- async dropTable({ tableName }) {
1772
+ async updateMessages({
1773
+ messages
1774
+ }) {
1775
+ if (!messages || messages.length === 0) {
1776
+ return [];
1777
+ }
1778
+ const messageIds = messages.map((m) => m.id);
1779
+ const idParams = messageIds.map((_, i) => `@id${i}`).join(", ");
1780
+ let selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${getTableName2({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName2(this.schema) })}`;
1781
+ if (idParams.length > 0) {
1782
+ selectQuery += ` WHERE id IN (${idParams})`;
1783
+ } else {
1784
+ return [];
1785
+ }
1786
+ const selectReq = this.pool.request();
1787
+ messageIds.forEach((id, i) => selectReq.input(`id${i}`, id));
1788
+ const existingMessagesDb = (await selectReq.query(selectQuery)).recordset;
1789
+ if (!existingMessagesDb || existingMessagesDb.length === 0) {
1790
+ return [];
1791
+ }
1792
+ const existingMessages = existingMessagesDb.map((msg) => {
1793
+ if (typeof msg.content === "string") {
1794
+ try {
1795
+ msg.content = JSON.parse(msg.content);
1796
+ } catch {
1797
+ }
1798
+ }
1799
+ return msg;
1800
+ });
1801
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
1802
+ const transaction = this.pool.transaction();
1688
1803
  try {
1689
- const tableNameWithSchema = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1690
- await this.pool.request().query(`DROP TABLE IF EXISTS ${tableNameWithSchema}`);
1804
+ await transaction.begin();
1805
+ for (const existingMessage of existingMessages) {
1806
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
1807
+ if (!updatePayload) continue;
1808
+ const { id, ...fieldsToUpdate } = updatePayload;
1809
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
1810
+ threadIdsToUpdate.add(existingMessage.threadId);
1811
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1812
+ threadIdsToUpdate.add(updatePayload.threadId);
1813
+ }
1814
+ const setClauses = [];
1815
+ const req = transaction.request();
1816
+ req.input("id", id);
1817
+ const columnMapping = { threadId: "thread_id" };
1818
+ const updatableFields = { ...fieldsToUpdate };
1819
+ if (updatableFields.content) {
1820
+ const newContent = {
1821
+ ...existingMessage.content,
1822
+ ...updatableFields.content,
1823
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: { ...existingMessage.content.metadata, ...updatableFields.content.metadata } } : {}
1824
+ };
1825
+ setClauses.push(`content = @content`);
1826
+ req.input("content", JSON.stringify(newContent));
1827
+ delete updatableFields.content;
1828
+ }
1829
+ for (const key in updatableFields) {
1830
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1831
+ const dbColumn = columnMapping[key] || key;
1832
+ setClauses.push(`[${dbColumn}] = @${dbColumn}`);
1833
+ req.input(dbColumn, updatableFields[key]);
1834
+ }
1835
+ }
1836
+ if (setClauses.length > 0) {
1837
+ const updateSql = `UPDATE ${getTableName2({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName2(this.schema) })} SET ${setClauses.join(", ")} WHERE id = @id`;
1838
+ await req.query(updateSql);
1839
+ }
1840
+ }
1841
+ if (threadIdsToUpdate.size > 0) {
1842
+ const threadIdParams = Array.from(threadIdsToUpdate).map((_, i) => `@tid${i}`).join(", ");
1843
+ const threadReq = transaction.request();
1844
+ Array.from(threadIdsToUpdate).forEach((tid, i) => threadReq.input(`tid${i}`, tid));
1845
+ threadReq.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
1846
+ const threadSql = `UPDATE ${getTableName2({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName2(this.schema) })} SET updatedAt = @updatedAt WHERE id IN (${threadIdParams})`;
1847
+ await threadReq.query(threadSql);
1848
+ }
1849
+ await transaction.commit();
1691
1850
  } catch (error$1) {
1851
+ await transaction.rollback();
1692
1852
  throw new error.MastraError(
1693
1853
  {
1694
- id: "MASTRA_STORAGE_MSSQL_STORE_DROP_TABLE_FAILED",
1854
+ id: storage.createStorageErrorId("MSSQL", "UPDATE_MESSAGES", "FAILED"),
1695
1855
  domain: error.ErrorDomain.STORAGE,
1696
- category: error.ErrorCategory.THIRD_PARTY,
1697
- details: {
1698
- tableName
1699
- }
1856
+ category: error.ErrorCategory.THIRD_PARTY
1700
1857
  },
1701
1858
  error$1
1702
1859
  );
1703
1860
  }
1704
- }
1705
- /**
1706
- * Prepares a value for database operations, handling Date objects and JSON serialization
1707
- */
1708
- prepareValue(value, columnName, tableName) {
1709
- if (value === null || value === void 0) {
1710
- return value;
1711
- }
1712
- if (value instanceof Date) {
1713
- return value;
1714
- }
1715
- const schema = storage.TABLE_SCHEMAS[tableName];
1716
- const columnSchema = schema?.[columnName];
1717
- if (columnSchema?.type === "boolean") {
1718
- return value ? 1 : 0;
1719
- }
1720
- if (columnSchema?.type === "jsonb") {
1721
- if (typeof value === "string") {
1722
- const trimmed = value.trim();
1723
- if (trimmed.length > 0) {
1724
- try {
1725
- JSON.parse(trimmed);
1726
- return trimmed;
1727
- } catch {
1728
- }
1861
+ const refetchReq = this.pool.request();
1862
+ messageIds.forEach((id, i) => refetchReq.input(`id${i}`, id));
1863
+ const updatedMessages = (await refetchReq.query(selectQuery)).recordset;
1864
+ return (updatedMessages || []).map((message) => {
1865
+ if (typeof message.content === "string") {
1866
+ try {
1867
+ message.content = JSON.parse(message.content);
1868
+ } catch {
1729
1869
  }
1730
- return JSON.stringify(value);
1731
- }
1732
- if (typeof value === "bigint") {
1733
- return value.toString();
1734
1870
  }
1735
- return JSON.stringify(value);
1736
- }
1737
- if (typeof value === "object") {
1738
- return JSON.stringify(value);
1739
- }
1740
- return value;
1871
+ return message;
1872
+ });
1741
1873
  }
1742
- /**
1743
- * Maps TABLE_SCHEMAS types to mssql param types (used when value is null)
1744
- */
1745
- getMssqlType(tableName, columnName) {
1746
- const col = storage.TABLE_SCHEMAS[tableName]?.[columnName];
1747
- switch (col?.type) {
1748
- case "text":
1749
- return sql2__default.default.NVarChar;
1750
- case "timestamp":
1751
- return sql2__default.default.DateTime2;
1752
- case "uuid":
1753
- return sql2__default.default.UniqueIdentifier;
1754
- case "jsonb":
1755
- return sql2__default.default.NVarChar;
1756
- case "integer":
1757
- return sql2__default.default.Int;
1758
- case "bigint":
1759
- return sql2__default.default.BigInt;
1760
- case "float":
1761
- return sql2__default.default.Float;
1762
- case "boolean":
1763
- return sql2__default.default.Bit;
1764
- default:
1765
- return sql2__default.default.NVarChar;
1874
+ async deleteMessages(messageIds) {
1875
+ if (!messageIds || messageIds.length === 0) {
1876
+ return;
1766
1877
  }
1767
- }
1768
- /**
1769
- * Update a single record in the database
1770
- */
1771
- async update({
1772
- tableName,
1773
- keys,
1774
- data,
1775
- transaction
1776
- }) {
1777
1878
  try {
1778
- if (!data || Object.keys(data).length === 0) {
1779
- throw new error.MastraError({
1780
- id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_DATA",
1781
- domain: error.ErrorDomain.STORAGE,
1782
- category: error.ErrorCategory.USER,
1783
- text: "Cannot update with empty data payload"
1784
- });
1785
- }
1786
- if (!keys || Object.keys(keys).length === 0) {
1787
- throw new error.MastraError({
1788
- id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_KEYS",
1789
- domain: error.ErrorDomain.STORAGE,
1790
- category: error.ErrorCategory.USER,
1791
- text: "Cannot update without keys to identify records"
1879
+ const messageTableName = getTableName2({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName2(this.schema) });
1880
+ const threadTableName = getTableName2({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName2(this.schema) });
1881
+ const placeholders = messageIds.map((_, idx) => `@p${idx + 1}`).join(",");
1882
+ const request = this.pool.request();
1883
+ messageIds.forEach((id, idx) => {
1884
+ request.input(`p${idx + 1}`, id);
1885
+ });
1886
+ const messages = await request.query(
1887
+ `SELECT DISTINCT [thread_id] FROM ${messageTableName} WHERE [id] IN (${placeholders})`
1888
+ );
1889
+ const threadIds = messages.recordset?.map((msg) => msg.thread_id).filter(Boolean) || [];
1890
+ const transaction = this.pool.transaction();
1891
+ await transaction.begin();
1892
+ try {
1893
+ const deleteRequest = transaction.request();
1894
+ messageIds.forEach((id, idx) => {
1895
+ deleteRequest.input(`p${idx + 1}`, id);
1792
1896
  });
1793
- }
1794
- const setClauses = [];
1795
- const request = transaction ? transaction.request() : this.pool.request();
1796
- let paramIndex = 0;
1797
- Object.entries(data).forEach(([key, value]) => {
1798
- const parsedKey = utils.parseSqlIdentifier(key, "column name");
1799
- const paramName = `set${paramIndex++}`;
1800
- setClauses.push(`[${parsedKey}] = @${paramName}`);
1801
- const preparedValue = this.prepareValue(value, key, tableName);
1802
- if (preparedValue === null || preparedValue === void 0) {
1803
- request.input(paramName, this.getMssqlType(tableName, key), null);
1804
- } else {
1805
- request.input(paramName, preparedValue);
1897
+ await deleteRequest.query(`DELETE FROM ${messageTableName} WHERE [id] IN (${placeholders})`);
1898
+ if (threadIds.length > 0) {
1899
+ for (const threadId of threadIds) {
1900
+ const updateRequest = transaction.request();
1901
+ updateRequest.input("p1", threadId);
1902
+ await updateRequest.query(`UPDATE ${threadTableName} SET [updatedAt] = GETDATE() WHERE [id] = @p1`);
1903
+ }
1806
1904
  }
1807
- });
1808
- const whereConditions = [];
1809
- Object.entries(keys).forEach(([key, value]) => {
1810
- const parsedKey = utils.parseSqlIdentifier(key, "column name");
1811
- const paramName = `where${paramIndex++}`;
1812
- whereConditions.push(`[${parsedKey}] = @${paramName}`);
1813
- const preparedValue = this.prepareValue(value, key, tableName);
1814
- if (preparedValue === null || preparedValue === void 0) {
1815
- request.input(paramName, this.getMssqlType(tableName, key), null);
1816
- } else {
1817
- request.input(paramName, preparedValue);
1905
+ await transaction.commit();
1906
+ } catch (error) {
1907
+ try {
1908
+ await transaction.rollback();
1909
+ } catch {
1818
1910
  }
1819
- });
1820
- const tableName_ = getTableName({
1821
- indexName: tableName,
1822
- schemaName: getSchemaName(this.schemaName)
1823
- });
1824
- const updateSql = `UPDATE ${tableName_} SET ${setClauses.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
1825
- await request.query(updateSql);
1911
+ throw error;
1912
+ }
1826
1913
  } catch (error$1) {
1827
1914
  throw new error.MastraError(
1828
1915
  {
1829
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_FAILED",
1916
+ id: storage.createStorageErrorId("MSSQL", "DELETE_MESSAGES", "FAILED"),
1830
1917
  domain: error.ErrorDomain.STORAGE,
1831
1918
  category: error.ErrorCategory.THIRD_PARTY,
1832
- details: {
1833
- tableName
1834
- }
1919
+ details: { messageIds: messageIds.join(", ") }
1835
1920
  },
1836
1921
  error$1
1837
1922
  );
1838
1923
  }
1839
1924
  }
1840
- /**
1841
- * Update multiple records in a single batch transaction
1842
- */
1843
- async batchUpdate({
1844
- tableName,
1845
- updates
1846
- }) {
1847
- const transaction = this.pool.transaction();
1925
+ async getResourceById({ resourceId }) {
1926
+ const tableName = getTableName2({ indexName: storage.TABLE_RESOURCES, schemaName: getSchemaName2(this.schema) });
1848
1927
  try {
1849
- await transaction.begin();
1850
- for (const { keys, data } of updates) {
1851
- await this.update({ tableName, keys, data, transaction });
1928
+ const req = this.pool.request();
1929
+ req.input("resourceId", resourceId);
1930
+ const result = (await req.query(`SELECT * FROM ${tableName} WHERE id = @resourceId`)).recordset[0];
1931
+ if (!result) {
1932
+ return null;
1852
1933
  }
1853
- await transaction.commit();
1934
+ return {
1935
+ id: result.id,
1936
+ createdAt: result.createdAt,
1937
+ updatedAt: result.updatedAt,
1938
+ workingMemory: result.workingMemory,
1939
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
1940
+ };
1854
1941
  } catch (error$1) {
1855
- await transaction.rollback();
1856
- throw new error.MastraError(
1942
+ const mastraError = new error.MastraError(
1857
1943
  {
1858
- id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_UPDATE_FAILED",
1944
+ id: storage.createStorageErrorId("MSSQL", "GET_RESOURCE_BY_ID", "FAILED"),
1859
1945
  domain: error.ErrorDomain.STORAGE,
1860
1946
  category: error.ErrorCategory.THIRD_PARTY,
1861
- details: {
1862
- tableName,
1863
- numberOfRecords: updates.length
1864
- }
1947
+ details: { resourceId }
1865
1948
  },
1866
1949
  error$1
1867
1950
  );
1951
+ this.logger?.error?.(mastraError.toString());
1952
+ this.logger?.trackException?.(mastraError);
1953
+ throw mastraError;
1868
1954
  }
1869
1955
  }
1870
- /**
1871
- * Delete multiple records by keys
1872
- */
1873
- async batchDelete({ tableName, keys }) {
1874
- if (keys.length === 0) {
1875
- return;
1876
- }
1877
- const tableName_ = getTableName({
1878
- indexName: tableName,
1879
- schemaName: getSchemaName(this.schemaName)
1956
+ async saveResource({ resource }) {
1957
+ await this.db.insert({
1958
+ tableName: storage.TABLE_RESOURCES,
1959
+ record: {
1960
+ ...resource,
1961
+ metadata: resource.metadata
1962
+ }
1880
1963
  });
1881
- const transaction = this.pool.transaction();
1964
+ return resource;
1965
+ }
1966
+ async updateResource({
1967
+ resourceId,
1968
+ workingMemory,
1969
+ metadata
1970
+ }) {
1882
1971
  try {
1883
- await transaction.begin();
1884
- for (const keySet of keys) {
1885
- const conditions = [];
1886
- const request = transaction.request();
1887
- let paramIndex = 0;
1888
- Object.entries(keySet).forEach(([key, value]) => {
1889
- const parsedKey = utils.parseSqlIdentifier(key, "column name");
1890
- const paramName = `p${paramIndex++}`;
1891
- conditions.push(`[${parsedKey}] = @${paramName}`);
1892
- const preparedValue = this.prepareValue(value, key, tableName);
1893
- if (preparedValue === null || preparedValue === void 0) {
1894
- request.input(paramName, this.getMssqlType(tableName, key), null);
1895
- } else {
1896
- request.input(paramName, preparedValue);
1897
- }
1898
- });
1899
- const deleteSql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
1900
- await request.query(deleteSql);
1972
+ const existingResource = await this.getResourceById({ resourceId });
1973
+ if (!existingResource) {
1974
+ const newResource = {
1975
+ id: resourceId,
1976
+ workingMemory,
1977
+ metadata: metadata || {},
1978
+ createdAt: /* @__PURE__ */ new Date(),
1979
+ updatedAt: /* @__PURE__ */ new Date()
1980
+ };
1981
+ return this.saveResource({ resource: newResource });
1901
1982
  }
1902
- await transaction.commit();
1983
+ const updatedResource = {
1984
+ ...existingResource,
1985
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1986
+ metadata: {
1987
+ ...existingResource.metadata,
1988
+ ...metadata
1989
+ },
1990
+ updatedAt: /* @__PURE__ */ new Date()
1991
+ };
1992
+ const tableName = getTableName2({ indexName: storage.TABLE_RESOURCES, schemaName: getSchemaName2(this.schema) });
1993
+ const updates = [];
1994
+ const req = this.pool.request();
1995
+ if (workingMemory !== void 0) {
1996
+ updates.push("workingMemory = @workingMemory");
1997
+ req.input("workingMemory", workingMemory);
1998
+ }
1999
+ if (metadata) {
2000
+ updates.push("metadata = @metadata");
2001
+ req.input("metadata", JSON.stringify(updatedResource.metadata));
2002
+ }
2003
+ updates.push("updatedAt = @updatedAt");
2004
+ req.input("updatedAt", updatedResource.updatedAt.toISOString());
2005
+ req.input("id", resourceId);
2006
+ await req.query(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = @id`);
2007
+ return updatedResource;
1903
2008
  } catch (error$1) {
1904
- await transaction.rollback();
1905
- throw new error.MastraError(
2009
+ const mastraError = new error.MastraError(
1906
2010
  {
1907
- id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_DELETE_FAILED",
2011
+ id: storage.createStorageErrorId("MSSQL", "UPDATE_RESOURCE", "FAILED"),
1908
2012
  domain: error.ErrorDomain.STORAGE,
1909
2013
  category: error.ErrorCategory.THIRD_PARTY,
1910
- details: {
1911
- tableName,
1912
- numberOfRecords: keys.length
1913
- }
2014
+ details: { resourceId }
1914
2015
  },
1915
2016
  error$1
1916
2017
  );
2018
+ this.logger?.error?.(mastraError.toString());
2019
+ this.logger?.trackException?.(mastraError);
2020
+ throw mastraError;
1917
2021
  }
1918
2022
  }
1919
- /**
1920
- * Create a new index on a table
1921
- */
1922
- async createIndex(options) {
1923
- try {
1924
- const { name, table, columns, unique = false, where } = options;
1925
- const schemaName = this.schemaName || "dbo";
1926
- const fullTableName = getTableName({
1927
- indexName: table,
1928
- schemaName: getSchemaName(this.schemaName)
1929
- });
1930
- const indexNameSafe = utils.parseSqlIdentifier(name, "index name");
1931
- const checkRequest = this.pool.request();
1932
- checkRequest.input("indexName", indexNameSafe);
1933
- checkRequest.input("schemaName", schemaName);
1934
- checkRequest.input("tableName", table);
1935
- const indexExists = await checkRequest.query(`
1936
- SELECT 1 as found
1937
- FROM sys.indexes i
1938
- INNER JOIN sys.tables t ON i.object_id = t.object_id
1939
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
1940
- WHERE i.name = @indexName
1941
- AND s.name = @schemaName
1942
- AND t.name = @tableName
1943
- `);
1944
- if (indexExists.recordset && indexExists.recordset.length > 0) {
1945
- return;
1946
- }
1947
- const uniqueStr = unique ? "UNIQUE " : "";
1948
- const columnsStr = columns.map((col) => {
1949
- if (col.includes(" DESC") || col.includes(" ASC")) {
1950
- const [colName, ...modifiers] = col.split(" ");
1951
- if (!colName) {
1952
- throw new Error(`Invalid column specification: ${col}`);
1953
- }
1954
- return `[${utils.parseSqlIdentifier(colName, "column name")}] ${modifiers.join(" ")}`;
1955
- }
1956
- return `[${utils.parseSqlIdentifier(col, "column name")}]`;
1957
- }).join(", ");
1958
- const whereStr = where ? ` WHERE ${where}` : "";
1959
- const createIndexSql = `CREATE ${uniqueStr}INDEX [${indexNameSafe}] ON ${fullTableName} (${columnsStr})${whereStr}`;
1960
- await this.pool.request().query(createIndexSql);
2023
+ };
2024
+ var ObservabilityMSSQL = class extends storage.ObservabilityStorage {
2025
+ pool;
2026
+ db;
2027
+ schema;
2028
+ needsConnect;
2029
+ constructor(config) {
2030
+ super();
2031
+ const { pool, db, schema, needsConnect } = resolveMssqlConfig(config);
2032
+ this.pool = pool;
2033
+ this.db = db;
2034
+ this.schema = schema;
2035
+ this.needsConnect = needsConnect;
2036
+ }
2037
+ async init() {
2038
+ if (this.needsConnect) {
2039
+ await this.pool.connect();
2040
+ this.needsConnect = false;
2041
+ }
2042
+ await this.db.createTable({ tableName: storage.TABLE_SPANS, schema: storage.SPAN_SCHEMA });
2043
+ }
2044
+ async dangerouslyClearAll() {
2045
+ await this.db.clearTable({ tableName: storage.TABLE_SPANS });
2046
+ }
2047
+ get tracingStrategy() {
2048
+ return {
2049
+ preferred: "batch-with-updates",
2050
+ supported: ["batch-with-updates", "insert-only"]
2051
+ };
2052
+ }
2053
+ async createSpan(span) {
2054
+ try {
2055
+ const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt;
2056
+ const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt;
2057
+ const record = {
2058
+ ...span,
2059
+ startedAt,
2060
+ endedAt
2061
+ // Note: createdAt/updatedAt will be set by default values
2062
+ };
2063
+ return this.db.insert({ tableName: storage.TABLE_SPANS, record });
1961
2064
  } catch (error$1) {
1962
2065
  throw new error.MastraError(
1963
2066
  {
1964
- id: "MASTRA_STORAGE_MSSQL_INDEX_CREATE_FAILED",
2067
+ id: storage.createStorageErrorId("MSSQL", "CREATE_SPAN", "FAILED"),
1965
2068
  domain: error.ErrorDomain.STORAGE,
1966
- category: error.ErrorCategory.THIRD_PARTY,
2069
+ category: error.ErrorCategory.USER,
1967
2070
  details: {
1968
- indexName: options.name,
1969
- tableName: options.table
2071
+ spanId: span.spanId,
2072
+ traceId: span.traceId,
2073
+ spanType: span.spanType,
2074
+ spanName: span.name
1970
2075
  }
1971
2076
  },
1972
2077
  error$1
1973
2078
  );
1974
2079
  }
1975
2080
  }
1976
- /**
1977
- * Drop an existing index
1978
- */
1979
- async dropIndex(indexName) {
2081
+ async getTrace(traceId) {
1980
2082
  try {
1981
- const schemaName = this.schemaName || "dbo";
1982
- const indexNameSafe = utils.parseSqlIdentifier(indexName, "index name");
1983
- const checkRequest = this.pool.request();
1984
- checkRequest.input("indexName", indexNameSafe);
1985
- checkRequest.input("schemaName", schemaName);
1986
- const result = await checkRequest.query(`
1987
- SELECT t.name as table_name
1988
- FROM sys.indexes i
1989
- INNER JOIN sys.tables t ON i.object_id = t.object_id
1990
- INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
1991
- WHERE i.name = @indexName
1992
- AND s.name = @schemaName
1993
- `);
2083
+ const tableName = getTableName2({
2084
+ indexName: storage.TABLE_SPANS,
2085
+ schemaName: getSchemaName2(this.schema)
2086
+ });
2087
+ const request = this.pool.request();
2088
+ request.input("traceId", traceId);
2089
+ const result = await request.query(
2090
+ `SELECT
2091
+ [traceId], [spanId], [parentSpanId], [name], [scope], [spanType],
2092
+ [attributes], [metadata], [links], [input], [output], [error], [isEvent],
2093
+ [startedAt], [endedAt], [createdAt], [updatedAt]
2094
+ FROM ${tableName}
2095
+ WHERE [traceId] = @traceId
2096
+ ORDER BY [startedAt] DESC`
2097
+ );
1994
2098
  if (!result.recordset || result.recordset.length === 0) {
1995
- return;
2099
+ return null;
1996
2100
  }
1997
- if (result.recordset.length > 1) {
1998
- const tables = result.recordset.map((r) => r.table_name).join(", ");
1999
- throw new error.MastraError({
2000
- id: "MASTRA_STORAGE_MSSQL_INDEX_AMBIGUOUS",
2101
+ return {
2102
+ traceId,
2103
+ spans: result.recordset.map(
2104
+ (span) => transformFromSqlRow({
2105
+ tableName: storage.TABLE_SPANS,
2106
+ sqlRow: span
2107
+ })
2108
+ )
2109
+ };
2110
+ } catch (error$1) {
2111
+ throw new error.MastraError(
2112
+ {
2113
+ id: storage.createStorageErrorId("MSSQL", "GET_TRACE", "FAILED"),
2001
2114
  domain: error.ErrorDomain.STORAGE,
2002
2115
  category: error.ErrorCategory.USER,
2003
- text: `Index "${indexNameSafe}" exists on multiple tables (${tables}) in schema "${schemaName}". Please drop indexes manually or ensure unique index names.`
2004
- });
2116
+ details: {
2117
+ traceId
2118
+ }
2119
+ },
2120
+ error$1
2121
+ );
2122
+ }
2123
+ }
2124
+ async updateSpan({
2125
+ spanId,
2126
+ traceId,
2127
+ updates
2128
+ }) {
2129
+ try {
2130
+ const data = { ...updates };
2131
+ if (data.endedAt instanceof Date) {
2132
+ data.endedAt = data.endedAt.toISOString();
2005
2133
  }
2006
- const tableName = result.recordset[0].table_name;
2007
- const fullTableName = getTableName({
2008
- indexName: tableName,
2009
- schemaName: getSchemaName(this.schemaName)
2134
+ if (data.startedAt instanceof Date) {
2135
+ data.startedAt = data.startedAt.toISOString();
2136
+ }
2137
+ await this.db.update({
2138
+ tableName: storage.TABLE_SPANS,
2139
+ keys: { spanId, traceId },
2140
+ data
2010
2141
  });
2011
- const dropSql = `DROP INDEX [${indexNameSafe}] ON ${fullTableName}`;
2012
- await this.pool.request().query(dropSql);
2013
2142
  } catch (error$1) {
2014
2143
  throw new error.MastraError(
2015
2144
  {
2016
- id: "MASTRA_STORAGE_MSSQL_INDEX_DROP_FAILED",
2145
+ id: storage.createStorageErrorId("MSSQL", "UPDATE_SPAN", "FAILED"),
2017
2146
  domain: error.ErrorDomain.STORAGE,
2018
- category: error.ErrorCategory.THIRD_PARTY,
2147
+ category: error.ErrorCategory.USER,
2019
2148
  details: {
2020
- indexName
2149
+ spanId,
2150
+ traceId
2021
2151
  }
2022
2152
  },
2023
2153
  error$1
2024
2154
  );
2025
2155
  }
2026
2156
  }
2027
- /**
2028
- * List indexes for a specific table or all tables
2029
- */
2030
- async listIndexes(tableName) {
2031
- try {
2032
- const schemaName = this.schemaName || "dbo";
2033
- let query;
2034
- const request = this.pool.request();
2035
- request.input("schemaName", schemaName);
2036
- if (tableName) {
2037
- query = `
2038
- SELECT
2039
- i.name as name,
2040
- o.name as [table],
2041
- i.is_unique as is_unique,
2042
- CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
2043
- FROM sys.indexes i
2044
- INNER JOIN sys.objects o ON i.object_id = o.object_id
2045
- INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
2046
- LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
2047
- WHERE sch.name = @schemaName
2048
- AND o.name = @tableName
2049
- AND i.name IS NOT NULL
2050
- GROUP BY i.name, o.name, i.is_unique
2051
- `;
2052
- request.input("tableName", tableName);
2157
+ async getTracesPaginated({
2158
+ filters,
2159
+ pagination
2160
+ }) {
2161
+ const page = pagination?.page ?? 0;
2162
+ const perPage = pagination?.perPage ?? 10;
2163
+ const { entityId, entityType, ...actualFilters } = filters || {};
2164
+ const filtersWithDateRange = {
2165
+ ...actualFilters,
2166
+ ...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
2167
+ parentSpanId: null
2168
+ // Only get root spans for traces
2169
+ };
2170
+ const whereClause = prepareWhereClause(filtersWithDateRange);
2171
+ let actualWhereClause = whereClause.sql;
2172
+ const params = { ...whereClause.params };
2173
+ let currentParamIndex = Object.keys(params).length + 1;
2174
+ if (entityId && entityType) {
2175
+ let name = "";
2176
+ if (entityType === "workflow") {
2177
+ name = `workflow run: '${entityId}'`;
2178
+ } else if (entityType === "agent") {
2179
+ name = `agent run: '${entityId}'`;
2053
2180
  } else {
2054
- query = `
2055
- SELECT
2056
- i.name as name,
2057
- o.name as [table],
2058
- i.is_unique as is_unique,
2059
- CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
2060
- FROM sys.indexes i
2061
- INNER JOIN sys.objects o ON i.object_id = o.object_id
2062
- INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
2063
- LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
2064
- WHERE sch.name = @schemaName
2065
- AND i.name IS NOT NULL
2066
- GROUP BY i.name, o.name, i.is_unique
2067
- `;
2068
- }
2069
- const result = await request.query(query);
2070
- const indexes = [];
2071
- for (const row of result.recordset) {
2072
- const colRequest = this.pool.request();
2073
- colRequest.input("indexName", row.name);
2074
- colRequest.input("schemaName", schemaName);
2075
- const colResult = await colRequest.query(`
2076
- SELECT c.name as column_name
2077
- FROM sys.indexes i
2078
- INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
2079
- INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
2080
- INNER JOIN sys.objects o ON i.object_id = o.object_id
2081
- INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
2082
- WHERE i.name = @indexName
2083
- AND s.name = @schemaName
2084
- ORDER BY ic.key_ordinal
2085
- `);
2086
- indexes.push({
2087
- name: row.name,
2088
- table: row.table,
2089
- columns: colResult.recordset.map((c) => c.column_name),
2090
- unique: row.is_unique || false,
2091
- size: row.size || "0 MB",
2092
- definition: ""
2093
- // MSSQL doesn't store definition like PG
2181
+ const error$1 = new error.MastraError({
2182
+ id: storage.createStorageErrorId("MSSQL", "GET_TRACES_PAGINATED", "INVALID_ENTITY_TYPE"),
2183
+ domain: error.ErrorDomain.STORAGE,
2184
+ category: error.ErrorCategory.USER,
2185
+ details: {
2186
+ entityType
2187
+ },
2188
+ text: `Cannot filter by entity type: ${entityType}`
2094
2189
  });
2190
+ throw error$1;
2095
2191
  }
2096
- return indexes;
2192
+ const entityParam = `p${currentParamIndex++}`;
2193
+ if (actualWhereClause) {
2194
+ actualWhereClause += ` AND [name] = @${entityParam}`;
2195
+ } else {
2196
+ actualWhereClause = ` WHERE [name] = @${entityParam}`;
2197
+ }
2198
+ params[entityParam] = name;
2199
+ }
2200
+ const tableName = getTableName2({
2201
+ indexName: storage.TABLE_SPANS,
2202
+ schemaName: getSchemaName2(this.schema)
2203
+ });
2204
+ try {
2205
+ const countRequest = this.pool.request();
2206
+ Object.entries(params).forEach(([key, value]) => {
2207
+ countRequest.input(key, value);
2208
+ });
2209
+ const countResult = await countRequest.query(
2210
+ `SELECT COUNT(*) as count FROM ${tableName}${actualWhereClause}`
2211
+ );
2212
+ const total = countResult.recordset[0]?.count ?? 0;
2213
+ if (total === 0) {
2214
+ return {
2215
+ pagination: {
2216
+ total: 0,
2217
+ page,
2218
+ perPage,
2219
+ hasMore: false
2220
+ },
2221
+ spans: []
2222
+ };
2223
+ }
2224
+ const dataRequest = this.pool.request();
2225
+ Object.entries(params).forEach(([key, value]) => {
2226
+ dataRequest.input(key, value);
2227
+ });
2228
+ dataRequest.input("offset", page * perPage);
2229
+ dataRequest.input("limit", perPage);
2230
+ const dataResult = await dataRequest.query(
2231
+ `SELECT * FROM ${tableName}${actualWhereClause} ORDER BY [startedAt] DESC OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
2232
+ );
2233
+ const spans = dataResult.recordset.map(
2234
+ (row) => transformFromSqlRow({
2235
+ tableName: storage.TABLE_SPANS,
2236
+ sqlRow: row
2237
+ })
2238
+ );
2239
+ return {
2240
+ pagination: {
2241
+ total,
2242
+ page,
2243
+ perPage,
2244
+ hasMore: (page + 1) * perPage < total
2245
+ },
2246
+ spans
2247
+ };
2097
2248
  } catch (error$1) {
2098
2249
  throw new error.MastraError(
2099
2250
  {
2100
- id: "MASTRA_STORAGE_MSSQL_INDEX_LIST_FAILED",
2251
+ id: storage.createStorageErrorId("MSSQL", "GET_TRACES_PAGINATED", "FAILED"),
2101
2252
  domain: error.ErrorDomain.STORAGE,
2102
- category: error.ErrorCategory.THIRD_PARTY,
2103
- details: tableName ? {
2104
- tableName
2105
- } : {}
2253
+ category: error.ErrorCategory.USER
2106
2254
  },
2107
2255
  error$1
2108
2256
  );
2109
2257
  }
2110
2258
  }
2111
- /**
2112
- * Get detailed statistics for a specific index
2113
- */
2114
- async describeIndex(indexName) {
2259
+ async batchCreateSpans(args) {
2260
+ if (!args.records || args.records.length === 0) {
2261
+ return;
2262
+ }
2115
2263
  try {
2116
- const schemaName = this.schemaName || "dbo";
2117
- const request = this.pool.request();
2118
- request.input("indexName", indexName);
2119
- request.input("schemaName", schemaName);
2120
- const query = `
2121
- SELECT
2122
- i.name as name,
2123
- o.name as [table],
2124
- i.is_unique as is_unique,
2125
- CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size,
2126
- i.type_desc as method,
2127
- ISNULL(us.user_scans, 0) as scans,
2128
- ISNULL(us.user_seeks + us.user_scans, 0) as tuples_read,
2129
- ISNULL(us.user_lookups, 0) as tuples_fetched
2130
- FROM sys.indexes i
2131
- INNER JOIN sys.objects o ON i.object_id = o.object_id
2132
- INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
2133
- LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
2134
- LEFT JOIN sys.dm_db_index_usage_stats us ON i.object_id = us.object_id AND i.index_id = us.index_id
2135
- WHERE i.name = @indexName
2136
- AND sch.name = @schemaName
2137
- GROUP BY i.name, o.name, i.is_unique, i.type_desc, us.user_seeks, us.user_scans, us.user_lookups
2138
- `;
2139
- const result = await request.query(query);
2140
- if (!result.recordset || result.recordset.length === 0) {
2141
- throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
2142
- }
2143
- const row = result.recordset[0];
2144
- const colRequest = this.pool.request();
2145
- colRequest.input("indexName", indexName);
2146
- colRequest.input("schemaName", schemaName);
2147
- const colResult = await colRequest.query(`
2148
- SELECT c.name as column_name
2149
- FROM sys.indexes i
2150
- INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
2151
- INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
2152
- INNER JOIN sys.objects o ON i.object_id = o.object_id
2153
- INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
2154
- WHERE i.name = @indexName
2155
- AND s.name = @schemaName
2156
- ORDER BY ic.key_ordinal
2157
- `);
2158
- return {
2159
- name: row.name,
2160
- table: row.table,
2161
- columns: colResult.recordset.map((c) => c.column_name),
2162
- unique: row.is_unique || false,
2163
- size: row.size || "0 MB",
2164
- definition: "",
2165
- method: row.method?.toLowerCase() || "nonclustered",
2166
- scans: Number(row.scans) || 0,
2167
- tuples_read: Number(row.tuples_read) || 0,
2168
- tuples_fetched: Number(row.tuples_fetched) || 0
2169
- };
2264
+ await this.db.batchInsert({
2265
+ tableName: storage.TABLE_SPANS,
2266
+ records: args.records.map((span) => ({
2267
+ ...span,
2268
+ startedAt: span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt,
2269
+ endedAt: span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt
2270
+ }))
2271
+ });
2170
2272
  } catch (error$1) {
2171
2273
  throw new error.MastraError(
2172
2274
  {
2173
- id: "MASTRA_STORAGE_MSSQL_INDEX_DESCRIBE_FAILED",
2275
+ id: storage.createStorageErrorId("MSSQL", "BATCH_CREATE_SPANS", "FAILED"),
2174
2276
  domain: error.ErrorDomain.STORAGE,
2175
- category: error.ErrorCategory.THIRD_PARTY,
2277
+ category: error.ErrorCategory.USER,
2176
2278
  details: {
2177
- indexName
2279
+ count: args.records.length
2178
2280
  }
2179
2281
  },
2180
2282
  error$1
2181
2283
  );
2182
2284
  }
2183
2285
  }
2184
- /**
2185
- * Returns definitions for automatic performance indexes
2186
- * IMPORTANT: Uses seq_id DESC instead of createdAt DESC for MSSQL due to millisecond accuracy limitations
2187
- * NOTE: Using NVARCHAR(400) for text columns (800 bytes) leaves room for composite indexes
2188
- */
2189
- getAutomaticIndexDefinitions() {
2190
- const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
2191
- return [
2192
- // Composite indexes for optimal filtering + sorting performance
2193
- // NVARCHAR(400) = 800 bytes, plus BIGINT (8 bytes) = 808 bytes total (under 900-byte limit)
2194
- {
2195
- name: `${schemaPrefix}mastra_threads_resourceid_seqid_idx`,
2196
- table: storage.TABLE_THREADS,
2197
- columns: ["resourceId", "seq_id DESC"]
2198
- },
2199
- {
2200
- name: `${schemaPrefix}mastra_messages_thread_id_seqid_idx`,
2201
- table: storage.TABLE_MESSAGES,
2202
- columns: ["thread_id", "seq_id DESC"]
2203
- },
2204
- {
2205
- name: `${schemaPrefix}mastra_traces_name_seqid_idx`,
2206
- table: storage.TABLE_TRACES,
2207
- columns: ["name", "seq_id DESC"]
2208
- },
2209
- {
2210
- name: `${schemaPrefix}mastra_scores_trace_id_span_id_seqid_idx`,
2211
- table: storage.TABLE_SCORERS,
2212
- columns: ["traceId", "spanId", "seq_id DESC"]
2213
- },
2214
- // Spans indexes for optimal trace querying
2215
- {
2216
- name: `${schemaPrefix}mastra_ai_spans_traceid_startedat_idx`,
2217
- table: storage.TABLE_SPANS,
2218
- columns: ["traceId", "startedAt DESC"]
2219
- },
2220
- {
2221
- name: `${schemaPrefix}mastra_ai_spans_parentspanid_startedat_idx`,
2222
- table: storage.TABLE_SPANS,
2223
- columns: ["parentSpanId", "startedAt DESC"]
2224
- },
2225
- {
2226
- name: `${schemaPrefix}mastra_ai_spans_name_idx`,
2227
- table: storage.TABLE_SPANS,
2228
- columns: ["name"]
2229
- },
2230
- {
2231
- name: `${schemaPrefix}mastra_ai_spans_spantype_startedat_idx`,
2232
- table: storage.TABLE_SPANS,
2233
- columns: ["spanType", "startedAt DESC"]
2234
- }
2235
- ];
2236
- }
2237
- /**
2238
- * Creates automatic indexes for optimal query performance
2239
- * Uses getAutomaticIndexDefinitions() to determine which indexes to create
2240
- */
2241
- async createAutomaticIndexes() {
2286
+ async batchUpdateSpans(args) {
2287
+ if (!args.records || args.records.length === 0) {
2288
+ return;
2289
+ }
2242
2290
  try {
2243
- const indexes = this.getAutomaticIndexDefinitions();
2244
- for (const indexOptions of indexes) {
2245
- try {
2246
- await this.createIndex(indexOptions);
2247
- } catch (error) {
2248
- this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
2291
+ const updates = args.records.map(({ traceId, spanId, updates: data }) => {
2292
+ const processedData = { ...data };
2293
+ if (processedData.endedAt instanceof Date) {
2294
+ processedData.endedAt = processedData.endedAt.toISOString();
2249
2295
  }
2250
- }
2296
+ if (processedData.startedAt instanceof Date) {
2297
+ processedData.startedAt = processedData.startedAt.toISOString();
2298
+ }
2299
+ return {
2300
+ keys: { spanId, traceId },
2301
+ data: processedData
2302
+ };
2303
+ });
2304
+ await this.db.batchUpdate({
2305
+ tableName: storage.TABLE_SPANS,
2306
+ updates
2307
+ });
2251
2308
  } catch (error$1) {
2252
2309
  throw new error.MastraError(
2253
2310
  {
2254
- id: "MASTRA_STORAGE_MSSQL_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
2311
+ id: storage.createStorageErrorId("MSSQL", "BATCH_UPDATE_SPANS", "FAILED"),
2255
2312
  domain: error.ErrorDomain.STORAGE,
2256
- category: error.ErrorCategory.THIRD_PARTY
2313
+ category: error.ErrorCategory.USER,
2314
+ details: {
2315
+ count: args.records.length
2316
+ }
2317
+ },
2318
+ error$1
2319
+ );
2320
+ }
2321
+ }
2322
+ async batchDeleteTraces(args) {
2323
+ if (!args.traceIds || args.traceIds.length === 0) {
2324
+ return;
2325
+ }
2326
+ try {
2327
+ const keys = args.traceIds.map((traceId) => ({ traceId }));
2328
+ await this.db.batchDelete({
2329
+ tableName: storage.TABLE_SPANS,
2330
+ keys
2331
+ });
2332
+ } catch (error$1) {
2333
+ throw new error.MastraError(
2334
+ {
2335
+ id: storage.createStorageErrorId("MSSQL", "BATCH_DELETE_TRACES", "FAILED"),
2336
+ domain: error.ErrorDomain.STORAGE,
2337
+ category: error.ErrorCategory.USER,
2338
+ details: {
2339
+ count: args.traceIds.length
2340
+ }
2257
2341
  },
2258
2342
  error$1
2259
2343
  );
@@ -2261,41 +2345,39 @@ ${columns}
2261
2345
  }
2262
2346
  };
2263
2347
  function transformScoreRow(row) {
2264
- return {
2265
- ...row,
2266
- input: storage.safelyParseJSON(row.input),
2267
- scorer: storage.safelyParseJSON(row.scorer),
2268
- preprocessStepResult: storage.safelyParseJSON(row.preprocessStepResult),
2269
- analyzeStepResult: storage.safelyParseJSON(row.analyzeStepResult),
2270
- metadata: storage.safelyParseJSON(row.metadata),
2271
- output: storage.safelyParseJSON(row.output),
2272
- additionalContext: storage.safelyParseJSON(row.additionalContext),
2273
- requestContext: storage.safelyParseJSON(row.requestContext),
2274
- entity: storage.safelyParseJSON(row.entity),
2275
- createdAt: new Date(row.createdAt),
2276
- updatedAt: new Date(row.updatedAt)
2277
- };
2348
+ return storage.transformScoreRow(row, {
2349
+ convertTimestamps: true
2350
+ });
2278
2351
  }
2279
2352
  var ScoresMSSQL = class extends storage.ScoresStorage {
2280
2353
  pool;
2281
- operations;
2354
+ db;
2282
2355
  schema;
2283
- constructor({
2284
- pool,
2285
- operations,
2286
- schema
2287
- }) {
2356
+ needsConnect;
2357
+ constructor(config) {
2288
2358
  super();
2359
+ const { pool, db, schema, needsConnect } = resolveMssqlConfig(config);
2289
2360
  this.pool = pool;
2290
- this.operations = operations;
2361
+ this.db = db;
2291
2362
  this.schema = schema;
2363
+ this.needsConnect = needsConnect;
2364
+ }
2365
+ async init() {
2366
+ if (this.needsConnect) {
2367
+ await this.pool.connect();
2368
+ this.needsConnect = false;
2369
+ }
2370
+ await this.db.createTable({ tableName: storage.TABLE_SCORERS, schema: storage.TABLE_SCHEMAS[storage.TABLE_SCORERS] });
2371
+ }
2372
+ async dangerouslyClearAll() {
2373
+ await this.db.clearTable({ tableName: storage.TABLE_SCORERS });
2292
2374
  }
2293
2375
  async getScoreById({ id }) {
2294
2376
  try {
2295
2377
  const request = this.pool.request();
2296
2378
  request.input("p1", id);
2297
2379
  const result = await request.query(
2298
- `SELECT * FROM ${getTableName({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE id = @p1`
2380
+ `SELECT * FROM ${getTableName2({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE id = @p1`
2299
2381
  );
2300
2382
  if (result.recordset.length === 0) {
2301
2383
  return null;
@@ -2304,7 +2386,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2304
2386
  } catch (error$1) {
2305
2387
  throw new error.MastraError(
2306
2388
  {
2307
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORE_BY_ID_FAILED",
2389
+ id: storage.createStorageErrorId("MSSQL", "GET_SCORE_BY_ID", "FAILED"),
2308
2390
  domain: error.ErrorDomain.STORAGE,
2309
2391
  category: error.ErrorCategory.THIRD_PARTY,
2310
2392
  details: { id }
@@ -2320,15 +2402,23 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2320
2402
  } catch (error$1) {
2321
2403
  throw new error.MastraError(
2322
2404
  {
2323
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_SCORE_VALIDATION_FAILED",
2405
+ id: storage.createStorageErrorId("MSSQL", "SAVE_SCORE", "VALIDATION_FAILED"),
2324
2406
  domain: error.ErrorDomain.STORAGE,
2325
- category: error.ErrorCategory.THIRD_PARTY
2407
+ category: error.ErrorCategory.USER,
2408
+ details: {
2409
+ scorer: score.scorer?.id ?? "unknown",
2410
+ entityId: score.entityId ?? "unknown",
2411
+ entityType: score.entityType ?? "unknown",
2412
+ traceId: score.traceId ?? "",
2413
+ spanId: score.spanId ?? ""
2414
+ }
2326
2415
  },
2327
2416
  error$1
2328
2417
  );
2329
2418
  }
2330
2419
  try {
2331
2420
  const scoreId = crypto.randomUUID();
2421
+ const now = /* @__PURE__ */ new Date();
2332
2422
  const {
2333
2423
  scorer,
2334
2424
  preprocessStepResult,
@@ -2341,7 +2431,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2341
2431
  entity,
2342
2432
  ...rest
2343
2433
  } = validatedScore;
2344
- await this.operations.insert({
2434
+ await this.db.insert({
2345
2435
  tableName: storage.TABLE_SCORERS,
2346
2436
  record: {
2347
2437
  id: scoreId,
@@ -2355,16 +2445,15 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2355
2445
  requestContext: requestContext || null,
2356
2446
  entity: entity || null,
2357
2447
  scorer: scorer || null,
2358
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2359
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2448
+ createdAt: now.toISOString(),
2449
+ updatedAt: now.toISOString()
2360
2450
  }
2361
2451
  });
2362
- const scoreFromDb = await this.getScoreById({ id: scoreId });
2363
- return { score: scoreFromDb };
2452
+ return { score: { ...validatedScore, id: scoreId, createdAt: now, updatedAt: now } };
2364
2453
  } catch (error$1) {
2365
2454
  throw new error.MastraError(
2366
2455
  {
2367
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_SCORE_FAILED",
2456
+ id: storage.createStorageErrorId("MSSQL", "SAVE_SCORE", "FAILED"),
2368
2457
  domain: error.ErrorDomain.STORAGE,
2369
2458
  category: error.ErrorCategory.THIRD_PARTY
2370
2459
  },
@@ -2399,7 +2488,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2399
2488
  paramIndex++;
2400
2489
  }
2401
2490
  const whereClause = conditions.join(" AND ");
2402
- const tableName = getTableName({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName(this.schema) });
2491
+ const tableName = getTableName2({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName2(this.schema) });
2403
2492
  const countRequest = this.pool.request();
2404
2493
  Object.entries(params).forEach(([key, value]) => {
2405
2494
  countRequest.input(key, value);
@@ -2442,7 +2531,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2442
2531
  } catch (error$1) {
2443
2532
  throw new error.MastraError(
2444
2533
  {
2445
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORES_BY_SCORER_ID_FAILED",
2534
+ id: storage.createStorageErrorId("MSSQL", "LIST_SCORES_BY_SCORER_ID", "FAILED"),
2446
2535
  domain: error.ErrorDomain.STORAGE,
2447
2536
  category: error.ErrorCategory.THIRD_PARTY,
2448
2537
  details: { scorerId }
@@ -2459,7 +2548,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2459
2548
  const request = this.pool.request();
2460
2549
  request.input("p1", runId);
2461
2550
  const totalResult = await request.query(
2462
- `SELECT COUNT(*) as count FROM ${getTableName({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1`
2551
+ `SELECT COUNT(*) as count FROM ${getTableName2({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE [runId] = @p1`
2463
2552
  );
2464
2553
  const total = totalResult.recordset[0]?.count || 0;
2465
2554
  const { page, perPage: perPageInput } = pagination;
@@ -2483,7 +2572,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2483
2572
  dataRequest.input("p2", limitValue);
2484
2573
  dataRequest.input("p3", start);
2485
2574
  const result = await dataRequest.query(
2486
- `SELECT * FROM ${getTableName({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1 ORDER BY [createdAt] DESC OFFSET @p3 ROWS FETCH NEXT @p2 ROWS ONLY`
2575
+ `SELECT * FROM ${getTableName2({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE [runId] = @p1 ORDER BY [createdAt] DESC OFFSET @p3 ROWS FETCH NEXT @p2 ROWS ONLY`
2487
2576
  );
2488
2577
  return {
2489
2578
  pagination: {
@@ -2497,7 +2586,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2497
2586
  } catch (error$1) {
2498
2587
  throw new error.MastraError(
2499
2588
  {
2500
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORES_BY_RUN_ID_FAILED",
2589
+ id: storage.createStorageErrorId("MSSQL", "LIST_SCORES_BY_RUN_ID", "FAILED"),
2501
2590
  domain: error.ErrorDomain.STORAGE,
2502
2591
  category: error.ErrorCategory.THIRD_PARTY,
2503
2592
  details: { runId }
@@ -2516,7 +2605,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2516
2605
  request.input("p1", entityId);
2517
2606
  request.input("p2", entityType);
2518
2607
  const totalResult = await request.query(
2519
- `SELECT COUNT(*) as count FROM ${getTableName({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2`
2608
+ `SELECT COUNT(*) as count FROM ${getTableName2({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2`
2520
2609
  );
2521
2610
  const total = totalResult.recordset[0]?.count || 0;
2522
2611
  const { page, perPage: perPageInput } = pagination;
@@ -2541,7 +2630,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2541
2630
  dataRequest.input("p3", limitValue);
2542
2631
  dataRequest.input("p4", start);
2543
2632
  const result = await dataRequest.query(
2544
- `SELECT * FROM ${getTableName({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
2633
+ `SELECT * FROM ${getTableName2({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
2545
2634
  );
2546
2635
  return {
2547
2636
  pagination: {
@@ -2555,7 +2644,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2555
2644
  } catch (error$1) {
2556
2645
  throw new error.MastraError(
2557
2646
  {
2558
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORES_BY_ENTITY_ID_FAILED",
2647
+ id: storage.createStorageErrorId("MSSQL", "LIST_SCORES_BY_ENTITY_ID", "FAILED"),
2559
2648
  domain: error.ErrorDomain.STORAGE,
2560
2649
  category: error.ErrorCategory.THIRD_PARTY,
2561
2650
  details: { entityId, entityType }
@@ -2574,7 +2663,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2574
2663
  request.input("p1", traceId);
2575
2664
  request.input("p2", spanId);
2576
2665
  const totalResult = await request.query(
2577
- `SELECT COUNT(*) as count FROM ${getTableName({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2`
2666
+ `SELECT COUNT(*) as count FROM ${getTableName2({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2`
2578
2667
  );
2579
2668
  const total = totalResult.recordset[0]?.count || 0;
2580
2669
  const { page, perPage: perPageInput } = pagination;
@@ -2599,7 +2688,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2599
2688
  dataRequest.input("p3", limitValue);
2600
2689
  dataRequest.input("p4", start);
2601
2690
  const result = await dataRequest.query(
2602
- `SELECT * FROM ${getTableName({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
2691
+ `SELECT * FROM ${getTableName2({ indexName: storage.TABLE_SCORERS, schemaName: getSchemaName2(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
2603
2692
  );
2604
2693
  return {
2605
2694
  pagination: {
@@ -2613,7 +2702,7 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2613
2702
  } catch (error$1) {
2614
2703
  throw new error.MastraError(
2615
2704
  {
2616
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORES_BY_SPAN_FAILED",
2705
+ id: storage.createStorageErrorId("MSSQL", "LIST_SCORES_BY_SPAN", "FAILED"),
2617
2706
  domain: error.ErrorDomain.STORAGE,
2618
2707
  category: error.ErrorCategory.THIRD_PARTY,
2619
2708
  details: { traceId, spanId }
@@ -2625,17 +2714,26 @@ var ScoresMSSQL = class extends storage.ScoresStorage {
2625
2714
  };
2626
2715
  var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2627
2716
  pool;
2628
- operations;
2717
+ db;
2629
2718
  schema;
2630
- constructor({
2631
- pool,
2632
- operations,
2633
- schema
2634
- }) {
2719
+ needsConnect;
2720
+ constructor(config) {
2635
2721
  super();
2722
+ const { pool, db, schema, needsConnect } = resolveMssqlConfig(config);
2636
2723
  this.pool = pool;
2637
- this.operations = operations;
2724
+ this.db = db;
2638
2725
  this.schema = schema;
2726
+ this.needsConnect = needsConnect;
2727
+ }
2728
+ async init() {
2729
+ if (this.needsConnect) {
2730
+ await this.pool.connect();
2731
+ this.needsConnect = false;
2732
+ }
2733
+ await this.db.createTable({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT, schema: storage.TABLE_SCHEMAS[storage.TABLE_WORKFLOW_SNAPSHOT] });
2734
+ }
2735
+ async dangerouslyClearAll() {
2736
+ await this.db.clearTable({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT });
2639
2737
  }
2640
2738
  parseWorkflowRun(row) {
2641
2739
  let parsedSnapshot = row.snapshot;
@@ -2662,11 +2760,11 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2662
2760
  result,
2663
2761
  requestContext
2664
2762
  }) {
2665
- const table = getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
2763
+ const table = getTableName2({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
2666
2764
  const transaction = this.pool.transaction();
2667
2765
  try {
2668
2766
  await transaction.begin();
2669
- const selectRequest = new sql2__default.default.Request(transaction);
2767
+ const selectRequest = new sql__default.default.Request(transaction);
2670
2768
  selectRequest.input("workflow_name", workflowName);
2671
2769
  selectRequest.input("run_id", runId);
2672
2770
  const existingSnapshotResult = await selectRequest.query(
@@ -2694,12 +2792,12 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2694
2792
  }
2695
2793
  snapshot.context[stepId] = result;
2696
2794
  snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
2697
- const upsertReq = new sql2__default.default.Request(transaction);
2795
+ const upsertReq = new sql__default.default.Request(transaction);
2698
2796
  upsertReq.input("workflow_name", workflowName);
2699
2797
  upsertReq.input("run_id", runId);
2700
2798
  upsertReq.input("snapshot", JSON.stringify(snapshot));
2701
- upsertReq.input("createdAt", sql2__default.default.DateTime2, /* @__PURE__ */ new Date());
2702
- upsertReq.input("updatedAt", sql2__default.default.DateTime2, /* @__PURE__ */ new Date());
2799
+ upsertReq.input("createdAt", sql__default.default.DateTime2, /* @__PURE__ */ new Date());
2800
+ upsertReq.input("updatedAt", sql__default.default.DateTime2, /* @__PURE__ */ new Date());
2703
2801
  await upsertReq.query(
2704
2802
  `MERGE ${table} AS target
2705
2803
  USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
@@ -2717,7 +2815,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2717
2815
  }
2718
2816
  throw new error.MastraError(
2719
2817
  {
2720
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_RESULTS_FAILED",
2818
+ id: storage.createStorageErrorId("MSSQL", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
2721
2819
  domain: error.ErrorDomain.STORAGE,
2722
2820
  category: error.ErrorCategory.THIRD_PARTY,
2723
2821
  details: {
@@ -2735,11 +2833,11 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2735
2833
  runId,
2736
2834
  opts
2737
2835
  }) {
2738
- const table = getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
2836
+ const table = getTableName2({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
2739
2837
  const transaction = this.pool.transaction();
2740
2838
  try {
2741
2839
  await transaction.begin();
2742
- const selectRequest = new sql2__default.default.Request(transaction);
2840
+ const selectRequest = new sql__default.default.Request(transaction);
2743
2841
  selectRequest.input("workflow_name", workflowName);
2744
2842
  selectRequest.input("run_id", runId);
2745
2843
  const existingSnapshotResult = await selectRequest.query(
@@ -2755,7 +2853,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2755
2853
  await transaction.rollback();
2756
2854
  throw new error.MastraError(
2757
2855
  {
2758
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_SNAPSHOT_NOT_FOUND",
2856
+ id: storage.createStorageErrorId("MSSQL", "UPDATE_WORKFLOW_STATE", "SNAPSHOT_NOT_FOUND"),
2759
2857
  domain: error.ErrorDomain.STORAGE,
2760
2858
  category: error.ErrorCategory.SYSTEM,
2761
2859
  details: {
@@ -2767,11 +2865,11 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2767
2865
  );
2768
2866
  }
2769
2867
  const updatedSnapshot = { ...snapshot, ...opts };
2770
- const updateRequest = new sql2__default.default.Request(transaction);
2868
+ const updateRequest = new sql__default.default.Request(transaction);
2771
2869
  updateRequest.input("snapshot", JSON.stringify(updatedSnapshot));
2772
2870
  updateRequest.input("workflow_name", workflowName);
2773
2871
  updateRequest.input("run_id", runId);
2774
- updateRequest.input("updatedAt", sql2__default.default.DateTime2, /* @__PURE__ */ new Date());
2872
+ updateRequest.input("updatedAt", sql__default.default.DateTime2, /* @__PURE__ */ new Date());
2775
2873
  await updateRequest.query(
2776
2874
  `UPDATE ${table} SET snapshot = @snapshot, [updatedAt] = @updatedAt WHERE workflow_name = @workflow_name AND run_id = @run_id`
2777
2875
  );
@@ -2782,9 +2880,10 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2782
2880
  await transaction.rollback();
2783
2881
  } catch {
2784
2882
  }
2883
+ if (error$1 instanceof error.MastraError) throw error$1;
2785
2884
  throw new error.MastraError(
2786
2885
  {
2787
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_FAILED",
2886
+ id: storage.createStorageErrorId("MSSQL", "UPDATE_WORKFLOW_STATE", "FAILED"),
2788
2887
  domain: error.ErrorDomain.STORAGE,
2789
2888
  category: error.ErrorCategory.THIRD_PARTY,
2790
2889
  details: {
@@ -2802,7 +2901,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2802
2901
  resourceId,
2803
2902
  snapshot
2804
2903
  }) {
2805
- const table = getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
2904
+ const table = getTableName2({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
2806
2905
  const now = (/* @__PURE__ */ new Date()).toISOString();
2807
2906
  try {
2808
2907
  const request = this.pool.request();
@@ -2810,8 +2909,8 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2810
2909
  request.input("run_id", runId);
2811
2910
  request.input("resourceId", resourceId);
2812
2911
  request.input("snapshot", JSON.stringify(snapshot));
2813
- request.input("createdAt", sql2__default.default.DateTime2, new Date(now));
2814
- request.input("updatedAt", sql2__default.default.DateTime2, new Date(now));
2912
+ request.input("createdAt", sql__default.default.DateTime2, new Date(now));
2913
+ request.input("updatedAt", sql__default.default.DateTime2, new Date(now));
2815
2914
  const mergeSql = `MERGE INTO ${table} AS target
2816
2915
  USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
2817
2916
  ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
@@ -2825,7 +2924,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2825
2924
  } catch (error$1) {
2826
2925
  throw new error.MastraError(
2827
2926
  {
2828
- id: "MASTRA_STORAGE_MSSQL_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
2927
+ id: storage.createStorageErrorId("MSSQL", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
2829
2928
  domain: error.ErrorDomain.STORAGE,
2830
2929
  category: error.ErrorCategory.THIRD_PARTY,
2831
2930
  details: {
@@ -2842,7 +2941,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2842
2941
  runId
2843
2942
  }) {
2844
2943
  try {
2845
- const result = await this.operations.load({
2944
+ const result = await this.db.load({
2846
2945
  tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
2847
2946
  keys: {
2848
2947
  workflow_name: workflowName,
@@ -2856,7 +2955,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2856
2955
  } catch (error$1) {
2857
2956
  throw new error.MastraError(
2858
2957
  {
2859
- id: "MASTRA_STORAGE_MSSQL_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
2958
+ id: storage.createStorageErrorId("MSSQL", "LOAD_WORKFLOW_SNAPSHOT", "FAILED"),
2860
2959
  domain: error.ErrorDomain.STORAGE,
2861
2960
  category: error.ErrorCategory.THIRD_PARTY,
2862
2961
  details: {
@@ -2884,7 +2983,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2884
2983
  paramMap["workflowName"] = workflowName;
2885
2984
  }
2886
2985
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2887
- const tableName = getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
2986
+ const tableName = getTableName2({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
2888
2987
  const query = `SELECT * FROM ${tableName} ${whereClause}`;
2889
2988
  const request = this.pool.request();
2890
2989
  Object.entries(paramMap).forEach(([key, value]) => request.input(key, value));
@@ -2896,7 +2995,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2896
2995
  } catch (error$1) {
2897
2996
  throw new error.MastraError(
2898
2997
  {
2899
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
2998
+ id: storage.createStorageErrorId("MSSQL", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
2900
2999
  domain: error.ErrorDomain.STORAGE,
2901
3000
  category: error.ErrorCategory.THIRD_PARTY,
2902
3001
  details: {
@@ -2908,6 +3007,35 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2908
3007
  );
2909
3008
  }
2910
3009
  }
3010
+ async deleteWorkflowRunById({ runId, workflowName }) {
3011
+ const table = getTableName2({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
3012
+ const transaction = this.pool.transaction();
3013
+ try {
3014
+ await transaction.begin();
3015
+ const deleteRequest = new sql__default.default.Request(transaction);
3016
+ deleteRequest.input("workflow_name", workflowName);
3017
+ deleteRequest.input("run_id", runId);
3018
+ await deleteRequest.query(`DELETE FROM ${table} WHERE workflow_name = @workflow_name AND run_id = @run_id`);
3019
+ await transaction.commit();
3020
+ } catch (error$1) {
3021
+ try {
3022
+ await transaction.rollback();
3023
+ } catch {
3024
+ }
3025
+ throw new error.MastraError(
3026
+ {
3027
+ id: storage.createStorageErrorId("MSSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
3028
+ domain: error.ErrorDomain.STORAGE,
3029
+ category: error.ErrorCategory.THIRD_PARTY,
3030
+ details: {
3031
+ runId,
3032
+ workflowName
3033
+ }
3034
+ },
3035
+ error$1
3036
+ );
3037
+ }
3038
+ }
2911
3039
  async listWorkflowRuns({
2912
3040
  workflowName,
2913
3041
  fromDate,
@@ -2929,7 +3057,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2929
3057
  paramMap["status"] = status;
2930
3058
  }
2931
3059
  if (resourceId) {
2932
- const hasResourceId = await this.operations.hasColumn(storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId");
3060
+ const hasResourceId = await this.db.hasColumn(storage.TABLE_WORKFLOW_SNAPSHOT, "resourceId");
2933
3061
  if (hasResourceId) {
2934
3062
  conditions.push(`[resourceId] = @resourceId`);
2935
3063
  paramMap["resourceId"] = resourceId;
@@ -2947,11 +3075,11 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2947
3075
  }
2948
3076
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2949
3077
  let total = 0;
2950
- const tableName = getTableName({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
3078
+ const tableName = getTableName2({ indexName: storage.TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.schema) });
2951
3079
  const request = this.pool.request();
2952
3080
  Object.entries(paramMap).forEach(([key, value]) => {
2953
3081
  if (value instanceof Date) {
2954
- request.input(key, sql2__default.default.DateTime, value);
3082
+ request.input(key, sql__default.default.DateTime, value);
2955
3083
  } else {
2956
3084
  request.input(key, value);
2957
3085
  }
@@ -2976,7 +3104,7 @@ var WorkflowsMSSQL = class extends storage.WorkflowsStorage {
2976
3104
  } catch (error$1) {
2977
3105
  throw new error.MastraError(
2978
3106
  {
2979
- id: "MASTRA_STORAGE_MSSQL_STORE_LIST_WORKFLOW_RUNS_FAILED",
3107
+ id: storage.createStorageErrorId("MSSQL", "LIST_WORKFLOW_RUNS", "FAILED"),
2980
3108
  domain: error.ErrorDomain.STORAGE,
2981
3109
  category: error.ErrorCategory.THIRD_PARTY,
2982
3110
  details: {
@@ -2994,12 +3122,13 @@ var MSSQLStore = class extends storage.MastraStorage {
2994
3122
  pool;
2995
3123
  schema;
2996
3124
  isConnected = null;
3125
+ #db;
2997
3126
  stores;
2998
3127
  constructor(config) {
2999
3128
  if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
3000
3129
  throw new Error("MSSQLStore: id must be provided and cannot be empty.");
3001
3130
  }
3002
- super({ id: config.id, name: "MSSQLStore" });
3131
+ super({ id: config.id, name: "MSSQLStore", disableInit: config.disableInit });
3003
3132
  try {
3004
3133
  if ("connectionString" in config) {
3005
3134
  if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
@@ -3014,7 +3143,7 @@ var MSSQLStore = class extends storage.MastraStorage {
3014
3143
  }
3015
3144
  }
3016
3145
  this.schema = config.schemaName || "dbo";
3017
- this.pool = "connectionString" in config ? new sql2__default.default.ConnectionPool(config.connectionString) : new sql2__default.default.ConnectionPool({
3146
+ this.pool = "connectionString" in config ? new sql__default.default.ConnectionPool(config.connectionString) : new sql__default.default.ConnectionPool({
3018
3147
  server: config.server,
3019
3148
  database: config.database,
3020
3149
  user: config.user,
@@ -3022,13 +3151,13 @@ var MSSQLStore = class extends storage.MastraStorage {
3022
3151
  port: config.port,
3023
3152
  options: config.options || { encrypt: true, trustServerCertificate: true }
3024
3153
  });
3025
- const operations = new StoreOperationsMSSQL({ pool: this.pool, schemaName: this.schema });
3026
- const scores = new ScoresMSSQL({ pool: this.pool, operations, schema: this.schema });
3027
- const workflows = new WorkflowsMSSQL({ pool: this.pool, operations, schema: this.schema });
3028
- const memory = new MemoryMSSQL({ pool: this.pool, schema: this.schema, operations });
3029
- const observability = new ObservabilityMSSQL({ pool: this.pool, operations, schema: this.schema });
3154
+ this.#db = new MssqlDB({ pool: this.pool, schemaName: this.schema });
3155
+ const domainConfig = { pool: this.pool, db: this.#db, schema: this.schema };
3156
+ const scores = new ScoresMSSQL(domainConfig);
3157
+ const workflows = new WorkflowsMSSQL(domainConfig);
3158
+ const memory = new MemoryMSSQL(domainConfig);
3159
+ const observability = new ObservabilityMSSQL(domainConfig);
3030
3160
  this.stores = {
3031
- operations,
3032
3161
  scores,
3033
3162
  workflows,
3034
3163
  memory,
@@ -3037,7 +3166,7 @@ var MSSQLStore = class extends storage.MastraStorage {
3037
3166
  } catch (e) {
3038
3167
  throw new error.MastraError(
3039
3168
  {
3040
- id: "MASTRA_STORAGE_MSSQL_STORE_INITIALIZATION_FAILED",
3169
+ id: storage.createStorageErrorId("MSSQL", "INITIALIZATION", "FAILED"),
3041
3170
  domain: error.ErrorDomain.STORAGE,
3042
3171
  category: error.ErrorCategory.USER
3043
3172
  },
@@ -3053,7 +3182,7 @@ var MSSQLStore = class extends storage.MastraStorage {
3053
3182
  await this.isConnected;
3054
3183
  await super.init();
3055
3184
  try {
3056
- await this.stores.operations.createAutomaticIndexes();
3185
+ await this.#db.createAutomaticIndexes();
3057
3186
  } catch (indexError) {
3058
3187
  this.logger?.warn?.("Failed to create indexes:", indexError);
3059
3188
  }
@@ -3061,7 +3190,7 @@ var MSSQLStore = class extends storage.MastraStorage {
3061
3190
  this.isConnected = null;
3062
3191
  throw new error.MastraError(
3063
3192
  {
3064
- id: "MASTRA_STORAGE_MSSQL_STORE_INIT_FAILED",
3193
+ id: storage.createStorageErrorId("MSSQL", "INIT", "FAILED"),
3065
3194
  domain: error.ErrorDomain.STORAGE,
3066
3195
  category: error.ErrorCategory.THIRD_PARTY
3067
3196
  },
@@ -3089,34 +3218,6 @@ var MSSQLStore = class extends storage.MastraStorage {
3089
3218
  indexManagement: true
3090
3219
  };
3091
3220
  }
3092
- async createTable({
3093
- tableName,
3094
- schema
3095
- }) {
3096
- return this.stores.operations.createTable({ tableName, schema });
3097
- }
3098
- async alterTable({
3099
- tableName,
3100
- schema,
3101
- ifNotExists
3102
- }) {
3103
- return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
3104
- }
3105
- async clearTable({ tableName }) {
3106
- return this.stores.operations.clearTable({ tableName });
3107
- }
3108
- async dropTable({ tableName }) {
3109
- return this.stores.operations.dropTable({ tableName });
3110
- }
3111
- async insert({ tableName, record }) {
3112
- return this.stores.operations.insert({ tableName, record });
3113
- }
3114
- async batchInsert({ tableName, records }) {
3115
- return this.stores.operations.batchInsert({ tableName, records });
3116
- }
3117
- async load({ tableName, keys }) {
3118
- return this.stores.operations.load({ tableName, keys });
3119
- }
3120
3221
  /**
3121
3222
  * Memory
3122
3223
  */
@@ -3205,6 +3306,9 @@ var MSSQLStore = class extends storage.MastraStorage {
3205
3306
  }) {
3206
3307
  return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
3207
3308
  }
3309
+ async deleteWorkflowRunById({ runId, workflowName }) {
3310
+ return this.stores.workflows.deleteWorkflowRunById({ runId, workflowName });
3311
+ }
3208
3312
  async close() {
3209
3313
  await this.pool.close();
3210
3314
  }
@@ -3212,16 +3316,16 @@ var MSSQLStore = class extends storage.MastraStorage {
3212
3316
  * Index Management
3213
3317
  */
3214
3318
  async createIndex(options) {
3215
- return this.stores.operations.createIndex(options);
3319
+ return this.#db.createIndex(options);
3216
3320
  }
3217
3321
  async listIndexes(tableName) {
3218
- return this.stores.operations.listIndexes(tableName);
3322
+ return this.#db.listIndexes(tableName);
3219
3323
  }
3220
3324
  async describeIndex(indexName) {
3221
- return this.stores.operations.describeIndex(indexName);
3325
+ return this.#db.describeIndex(indexName);
3222
3326
  }
3223
3327
  async dropIndex(indexName) {
3224
- return this.stores.operations.dropIndex(indexName);
3328
+ return this.#db.dropIndex(indexName);
3225
3329
  }
3226
3330
  /**
3227
3331
  * Tracing / Observability
@@ -3229,7 +3333,7 @@ var MSSQLStore = class extends storage.MastraStorage {
3229
3333
  getObservabilityStore() {
3230
3334
  if (!this.stores.observability) {
3231
3335
  throw new error.MastraError({
3232
- id: "MSSQL_STORE_OBSERVABILITY_NOT_INITIALIZED",
3336
+ id: storage.createStorageErrorId("MSSQL", "OBSERVABILITY", "NOT_INITIALIZED"),
3233
3337
  domain: error.ErrorDomain.STORAGE,
3234
3338
  category: error.ErrorCategory.SYSTEM,
3235
3339
  text: "Observability storage is not initialized"
@@ -3283,8 +3387,8 @@ var MSSQLStore = class extends storage.MastraStorage {
3283
3387
  source: _source
3284
3388
  });
3285
3389
  }
3286
- async saveScore(_score) {
3287
- return this.stores.scores.saveScore(_score);
3390
+ async saveScore(score) {
3391
+ return this.stores.scores.saveScore(score);
3288
3392
  }
3289
3393
  async listScoresByRunId({
3290
3394
  runId: _runId,