@mastra/mssql 0.0.0-scorers-api-v2-20250801171841 → 0.0.0-scorers-logs-20251208093427

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