@mastra/mssql 0.0.0-fix-prompt-enhance-route-20251210210827 → 0.0.0-fix-11329-windows-path-20251222155941

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