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

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