@mastra/mssql 0.0.0-monorepo-binary-20251013210052 → 0.0.0-netlify-no-bundle-20251127120354

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,9 +1,10 @@
1
1
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
2
- import { MastraStorage, LegacyEvalsStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, ScoresStorage, TABLE_SCORERS, TracesStorage, TABLE_TRACES, WorkflowsStorage, MemoryStorage, resolveMessageLimit, TABLE_RESOURCES, TABLE_EVALS, TABLE_THREADS, TABLE_MESSAGES } from '@mastra/core/storage';
2
+ import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_TRACES, TABLE_SCORERS, TABLE_SPANS, ScoresStorage, normalizePerPage, calculatePagination, WorkflowsStorage, MemoryStorage, TABLE_RESOURCES, ObservabilityStorage, safelyParseJSON } from '@mastra/core/storage';
3
3
  import sql2 from 'mssql';
4
- import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
5
4
  import { MessageList } from '@mastra/core/agent';
6
- import { saveScorePayloadSchema } from '@mastra/core/scores';
5
+ import { parseSqlIdentifier } from '@mastra/core/utils';
6
+ import { randomUUID } from 'crypto';
7
+ import { saveScorePayloadSchema } from '@mastra/core/evals';
7
8
 
8
9
  // src/storage/index.ts
9
10
  function getSchemaName(schema) {
@@ -15,154 +16,71 @@ function getTableName({ indexName, schemaName }) {
15
16
  const quotedSchemaName = schemaName;
16
17
  return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
17
18
  }
18
-
19
- // src/storage/domains/legacy-evals/index.ts
20
- function transformEvalRow(row) {
21
- let testInfoValue = null, resultValue = null;
22
- if (row.test_info) {
23
- try {
24
- testInfoValue = typeof row.test_info === "string" ? JSON.parse(row.test_info) : row.test_info;
25
- } catch {
26
- }
19
+ function buildDateRangeFilter(dateRange, fieldName) {
20
+ const filters = {};
21
+ if (dateRange?.start) {
22
+ filters[`${fieldName}_gte`] = dateRange.start;
27
23
  }
28
- if (row.test_info) {
29
- try {
30
- resultValue = typeof row.result === "string" ? JSON.parse(row.result) : row.result;
31
- } catch {
32
- }
24
+ if (dateRange?.end) {
25
+ filters[`${fieldName}_lte`] = dateRange.end;
33
26
  }
27
+ return filters;
28
+ }
29
+ function prepareWhereClause(filters, _schema) {
30
+ const conditions = [];
31
+ const params = {};
32
+ let paramIndex = 1;
33
+ Object.entries(filters).forEach(([key, value]) => {
34
+ if (value === void 0) return;
35
+ const paramName = `p${paramIndex++}`;
36
+ if (key.endsWith("_gte")) {
37
+ const fieldName = key.slice(0, -4);
38
+ conditions.push(`[${parseSqlIdentifier(fieldName, "field name")}] >= @${paramName}`);
39
+ params[paramName] = value instanceof Date ? value.toISOString() : value;
40
+ } else if (key.endsWith("_lte")) {
41
+ const fieldName = key.slice(0, -4);
42
+ conditions.push(`[${parseSqlIdentifier(fieldName, "field name")}] <= @${paramName}`);
43
+ params[paramName] = value instanceof Date ? value.toISOString() : value;
44
+ } else if (value === null) {
45
+ conditions.push(`[${parseSqlIdentifier(key, "field name")}] IS NULL`);
46
+ } else {
47
+ conditions.push(`[${parseSqlIdentifier(key, "field name")}] = @${paramName}`);
48
+ params[paramName] = value instanceof Date ? value.toISOString() : value;
49
+ }
50
+ });
34
51
  return {
35
- agentName: row.agent_name,
36
- input: row.input,
37
- output: row.output,
38
- result: resultValue,
39
- metricName: row.metric_name,
40
- instructions: row.instructions,
41
- testInfo: testInfoValue,
42
- globalRunId: row.global_run_id,
43
- runId: row.run_id,
44
- createdAt: row.created_at
52
+ sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
53
+ params
45
54
  };
46
55
  }
47
- var LegacyEvalsMSSQL = class extends LegacyEvalsStorage {
48
- pool;
49
- schema;
50
- constructor({ pool, schema }) {
51
- super();
52
- this.pool = pool;
53
- this.schema = schema;
54
- }
55
- /** @deprecated use getEvals instead */
56
- async getEvalsByAgentName(agentName, type) {
57
- try {
58
- let query = `SELECT * FROM ${getTableName({ indexName: TABLE_EVALS, schemaName: getSchemaName(this.schema) })} WHERE agent_name = @p1`;
59
- if (type === "test") {
60
- query += " AND test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL";
61
- } else if (type === "live") {
62
- query += " AND (test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)";
63
- }
64
- query += " ORDER BY created_at DESC";
65
- const request = this.pool.request();
66
- request.input("p1", agentName);
67
- const result = await request.query(query);
68
- const rows = result.recordset;
69
- return typeof transformEvalRow === "function" ? rows?.map((row) => transformEvalRow(row)) ?? [] : rows ?? [];
70
- } catch (error) {
71
- if (error && error.number === 208 && error.message && error.message.includes("Invalid object name")) {
72
- return [];
73
- }
74
- console.error("Failed to get evals for the specified agent: " + error?.message);
75
- throw error;
76
- }
77
- }
78
- async getEvals(options = {}) {
79
- const { agentName, type, page = 0, perPage = 100, dateRange } = options;
80
- const fromDate = dateRange?.start;
81
- const toDate = dateRange?.end;
82
- const where = [];
83
- const params = {};
84
- if (agentName) {
85
- where.push("agent_name = @agentName");
86
- params["agentName"] = agentName;
87
- }
88
- if (type === "test") {
89
- where.push("test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL");
90
- } else if (type === "live") {
91
- where.push("(test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)");
92
- }
93
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
94
- where.push(`[created_at] >= @fromDate`);
95
- params[`fromDate`] = fromDate.toISOString();
96
- }
97
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
98
- where.push(`[created_at] <= @toDate`);
99
- params[`toDate`] = toDate.toISOString();
100
- }
101
- const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
102
- const tableName = getTableName({ indexName: TABLE_EVALS, schemaName: getSchemaName(this.schema) });
103
- const offset = page * perPage;
104
- const countQuery = `SELECT COUNT(*) as total FROM ${tableName} ${whereClause}`;
105
- const dataQuery = `SELECT * FROM ${tableName} ${whereClause} ORDER BY seq_id DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
106
- try {
107
- const countReq = this.pool.request();
108
- Object.entries(params).forEach(([key, value]) => {
109
- if (value instanceof Date) {
110
- countReq.input(key, sql2.DateTime, value);
111
- } else {
112
- countReq.input(key, value);
113
- }
114
- });
115
- const countResult = await countReq.query(countQuery);
116
- const total = countResult.recordset[0]?.total || 0;
117
- if (total === 0) {
118
- return {
119
- evals: [],
120
- total: 0,
121
- page,
122
- perPage,
123
- hasMore: false
124
- };
56
+ function transformFromSqlRow({
57
+ tableName,
58
+ sqlRow
59
+ }) {
60
+ const schema = TABLE_SCHEMAS[tableName];
61
+ const result = {};
62
+ Object.entries(sqlRow).forEach(([key, value]) => {
63
+ const columnSchema = schema?.[key];
64
+ if (columnSchema?.type === "jsonb" && typeof value === "string") {
65
+ try {
66
+ result[key] = JSON.parse(value);
67
+ } catch {
68
+ result[key] = value;
125
69
  }
126
- const req = this.pool.request();
127
- Object.entries(params).forEach(([key, value]) => {
128
- if (value instanceof Date) {
129
- req.input(key, sql2.DateTime, value);
130
- } else {
131
- req.input(key, value);
132
- }
133
- });
134
- req.input("offset", offset);
135
- req.input("perPage", perPage);
136
- const result = await req.query(dataQuery);
137
- const rows = result.recordset;
138
- return {
139
- evals: rows?.map((row) => transformEvalRow(row)) ?? [],
140
- total,
141
- page,
142
- perPage,
143
- hasMore: offset + (rows?.length ?? 0) < total
144
- };
145
- } catch (error) {
146
- const mastraError = new MastraError(
147
- {
148
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_EVALS_FAILED",
149
- domain: ErrorDomain.STORAGE,
150
- category: ErrorCategory.THIRD_PARTY,
151
- details: {
152
- agentName: agentName || "all",
153
- type: type || "all",
154
- page,
155
- perPage
156
- }
157
- },
158
- error
159
- );
160
- this.logger?.error?.(mastraError.toString());
161
- this.logger?.trackException(mastraError);
162
- throw mastraError;
70
+ } else if (columnSchema?.type === "timestamp" && value && typeof value === "string") {
71
+ result[key] = new Date(value);
72
+ } else if (columnSchema?.type === "timestamp" && value instanceof Date) {
73
+ result[key] = value;
74
+ } else if (columnSchema?.type === "boolean") {
75
+ result[key] = Boolean(value);
76
+ } else {
77
+ result[key] = value;
163
78
  }
164
- }
165
- };
79
+ });
80
+ return result;
81
+ }
82
+
83
+ // src/storage/domains/memory/index.ts
166
84
  var MemoryMSSQL = class extends MemoryStorage {
167
85
  pool;
168
86
  schema;
@@ -180,7 +98,7 @@ var MemoryMSSQL = class extends MemoryStorage {
180
98
  });
181
99
  const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
182
100
  const list = new MessageList().add(cleanMessages, "memory");
183
- return format === "v2" ? list.get.all.v2() : list.get.all.v1();
101
+ return format === "v2" ? list.get.all.db() : list.get.all.v1();
184
102
  }
185
103
  constructor({
186
104
  pool,
@@ -194,7 +112,7 @@ var MemoryMSSQL = class extends MemoryStorage {
194
112
  }
195
113
  async getThreadById({ threadId }) {
196
114
  try {
197
- const sql7 = `SELECT
115
+ const sql5 = `SELECT
198
116
  id,
199
117
  [resourceId],
200
118
  title,
@@ -205,7 +123,7 @@ var MemoryMSSQL = class extends MemoryStorage {
205
123
  WHERE id = @threadId`;
206
124
  const request = this.pool.request();
207
125
  request.input("threadId", threadId);
208
- const resultSet = await request.query(sql7);
126
+ const resultSet = await request.query(sql5);
209
127
  const thread = resultSet.recordset[0] || null;
210
128
  if (!thread) {
211
129
  return null;
@@ -230,11 +148,24 @@ var MemoryMSSQL = class extends MemoryStorage {
230
148
  );
231
149
  }
232
150
  }
233
- async getThreadsByResourceIdPaginated(args) {
234
- const { resourceId, page = 0, perPage: perPageInput, orderBy = "createdAt", sortDirection = "DESC" } = args;
151
+ async listThreadsByResourceId(args) {
152
+ const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
153
+ if (page < 0) {
154
+ throw new MastraError({
155
+ id: "MASTRA_STORAGE_MSSQL_STORE_INVALID_PAGE",
156
+ domain: ErrorDomain.STORAGE,
157
+ category: ErrorCategory.USER,
158
+ text: "Page number must be non-negative",
159
+ details: {
160
+ resourceId,
161
+ page
162
+ }
163
+ });
164
+ }
165
+ const perPage = normalizePerPage(perPageInput, 100);
166
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
167
+ const { field, direction } = this.parseOrderBy(orderBy);
235
168
  try {
236
- const perPage = perPageInput !== void 0 ? perPageInput : 100;
237
- const currentOffset = page * perPage;
238
169
  const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
239
170
  const countQuery = `SELECT COUNT(*) as count ${baseQuery}`;
240
171
  const countRequest = this.pool.request();
@@ -246,16 +177,22 @@ var MemoryMSSQL = class extends MemoryStorage {
246
177
  threads: [],
247
178
  total: 0,
248
179
  page,
249
- perPage,
180
+ perPage: perPageForResponse,
250
181
  hasMore: false
251
182
  };
252
183
  }
253
- const orderByField = orderBy === "createdAt" ? "[createdAt]" : "[updatedAt]";
254
- const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${sortDirection} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
184
+ const orderByField = field === "createdAt" ? "[createdAt]" : "[updatedAt]";
185
+ const dir = (direction || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
186
+ const limitValue = perPageInput === false ? total : perPage;
187
+ const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
255
188
  const dataRequest = this.pool.request();
256
189
  dataRequest.input("resourceId", resourceId);
257
- dataRequest.input("perPage", perPage);
258
- dataRequest.input("offset", currentOffset);
190
+ dataRequest.input("offset", offset);
191
+ if (limitValue > 2147483647) {
192
+ dataRequest.input("perPage", sql2.BigInt, limitValue);
193
+ } else {
194
+ dataRequest.input("perPage", limitValue);
195
+ }
259
196
  const rowsResult = await dataRequest.query(dataQuery);
260
197
  const rows = rowsResult.recordset || [];
261
198
  const threads = rows.map((thread) => ({
@@ -268,13 +205,13 @@ var MemoryMSSQL = class extends MemoryStorage {
268
205
  threads,
269
206
  total,
270
207
  page,
271
- perPage,
272
- hasMore: currentOffset + threads.length < total
208
+ perPage: perPageForResponse,
209
+ hasMore: perPageInput === false ? false : offset + perPage < total
273
210
  };
274
211
  } catch (error) {
275
212
  const mastraError = new MastraError(
276
213
  {
277
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED",
214
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_THREADS_BY_RESOURCE_ID_FAILED",
278
215
  domain: ErrorDomain.STORAGE,
279
216
  category: ErrorCategory.THIRD_PARTY,
280
217
  details: {
@@ -286,7 +223,13 @@ var MemoryMSSQL = class extends MemoryStorage {
286
223
  );
287
224
  this.logger?.error?.(mastraError.toString());
288
225
  this.logger?.trackException?.(mastraError);
289
- return { threads: [], total: 0, page, perPage: perPageInput || 100, hasMore: false };
226
+ return {
227
+ threads: [],
228
+ total: 0,
229
+ page,
230
+ perPage: perPageForResponse,
231
+ hasMore: false
232
+ };
290
233
  }
291
234
  }
292
235
  async saveThread({ thread }) {
@@ -308,7 +251,12 @@ var MemoryMSSQL = class extends MemoryStorage {
308
251
  req.input("id", thread.id);
309
252
  req.input("resourceId", thread.resourceId);
310
253
  req.input("title", thread.title);
311
- req.input("metadata", thread.metadata ? JSON.stringify(thread.metadata) : null);
254
+ const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
255
+ if (metadata === null) {
256
+ req.input("metadata", sql2.NVarChar, null);
257
+ } else {
258
+ req.input("metadata", metadata);
259
+ }
312
260
  req.input("createdAt", sql2.DateTime2, thread.createdAt);
313
261
  req.input("updatedAt", sql2.DateTime2, thread.updatedAt);
314
262
  await req.query(mergeSql);
@@ -327,30 +275,6 @@ var MemoryMSSQL = class extends MemoryStorage {
327
275
  );
328
276
  }
329
277
  }
330
- /**
331
- * @deprecated use getThreadsByResourceIdPaginated instead
332
- */
333
- async getThreadsByResourceId(args) {
334
- const { resourceId, orderBy = "createdAt", sortDirection = "DESC" } = args;
335
- try {
336
- const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
337
- const orderByField = orderBy === "createdAt" ? "[createdAt]" : "[updatedAt]";
338
- const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${sortDirection}`;
339
- const request = this.pool.request();
340
- request.input("resourceId", resourceId);
341
- const resultSet = await request.query(dataQuery);
342
- const rows = resultSet.recordset || [];
343
- return rows.map((thread) => ({
344
- ...thread,
345
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
346
- createdAt: thread.createdAt,
347
- updatedAt: thread.updatedAt
348
- }));
349
- } catch (error) {
350
- this.logger?.error?.(`Error getting threads for resource ${resourceId}:`, error);
351
- return [];
352
- }
353
- }
354
278
  /**
355
279
  * Updates a thread's title and metadata, merging with existing metadata. Returns the updated thread.
356
280
  */
@@ -378,7 +302,7 @@ var MemoryMSSQL = class extends MemoryStorage {
378
302
  };
379
303
  try {
380
304
  const table = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
381
- const sql7 = `UPDATE ${table}
305
+ const sql5 = `UPDATE ${table}
382
306
  SET title = @title,
383
307
  metadata = @metadata,
384
308
  [updatedAt] = @updatedAt
@@ -389,7 +313,7 @@ var MemoryMSSQL = class extends MemoryStorage {
389
313
  req.input("title", title);
390
314
  req.input("metadata", JSON.stringify(mergedMetadata));
391
315
  req.input("updatedAt", /* @__PURE__ */ new Date());
392
- const result = await req.query(sql7);
316
+ const result = await req.query(sql5);
393
317
  let thread = result.recordset && result.recordset[0];
394
318
  if (thread && "seq_id" in thread) {
395
319
  const { seq_id, ...rest } = thread;
@@ -459,11 +383,9 @@ var MemoryMSSQL = class extends MemoryStorage {
459
383
  }
460
384
  async _getIncludedMessages({
461
385
  threadId,
462
- selectBy,
463
- orderByStatement
386
+ include
464
387
  }) {
465
388
  if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
466
- const include = selectBy?.include;
467
389
  if (!include) return null;
468
390
  const unionQueries = [];
469
391
  const paramValues = [];
@@ -488,7 +410,7 @@ var MemoryMSSQL = class extends MemoryStorage {
488
410
  m.[resourceId],
489
411
  m.seq_id
490
412
  FROM (
491
- SELECT *, ROW_NUMBER() OVER (${orderByStatement}) as row_num
413
+ SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
492
414
  FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}
493
415
  WHERE [thread_id] = ${pThreadId}
494
416
  ) AS m
@@ -496,15 +418,17 @@ var MemoryMSSQL = class extends MemoryStorage {
496
418
  OR EXISTS (
497
419
  SELECT 1
498
420
  FROM (
499
- SELECT *, ROW_NUMBER() OVER (${orderByStatement}) as row_num
421
+ SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
500
422
  FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}
501
423
  WHERE [thread_id] = ${pThreadId}
502
424
  ) AS target
503
425
  WHERE target.id = ${pId}
504
426
  AND (
505
- (m.row_num <= target.row_num + ${pPrev} AND m.row_num > target.row_num)
427
+ -- Get previous messages (messages that come BEFORE the target)
428
+ (m.row_num < target.row_num AND m.row_num >= target.row_num - ${pPrev})
506
429
  OR
507
- (m.row_num >= target.row_num - ${pNext} AND m.row_num < target.row_num)
430
+ -- Get next messages (messages that come AFTER the target)
431
+ (m.row_num > target.row_num AND m.row_num <= target.row_num + ${pNext})
508
432
  )
509
433
  )
510
434
  `
@@ -533,34 +457,16 @@ var MemoryMSSQL = class extends MemoryStorage {
533
457
  });
534
458
  return dedupedRows;
535
459
  }
536
- async getMessages(args) {
537
- const { threadId, resourceId, format, selectBy } = args;
460
+ async listMessagesById({ messageIds }) {
461
+ if (messageIds.length === 0) return { messages: [] };
538
462
  const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
539
463
  const orderByStatement = `ORDER BY [seq_id] DESC`;
540
- const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
541
464
  try {
542
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
543
465
  let rows = [];
544
- const include = selectBy?.include || [];
545
- if (include?.length) {
546
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
547
- if (includeMessages) {
548
- rows.push(...includeMessages);
549
- }
550
- }
551
- const excludeIds = rows.map((m) => m.id).filter(Boolean);
552
- let query = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE [thread_id] = @threadId`;
466
+ let query = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE [id] IN (${messageIds.map((_, i) => `@id${i}`).join(", ")})`;
553
467
  const request = this.pool.request();
554
- request.input("threadId", threadId);
555
- if (excludeIds.length > 0) {
556
- const excludeParams = excludeIds.map((_, idx) => `@id${idx}`);
557
- query += ` AND id NOT IN (${excludeParams.join(", ")})`;
558
- excludeIds.forEach((id, idx) => {
559
- request.input(`id${idx}`, id);
560
- });
561
- }
562
- query += ` ${orderByStatement} OFFSET 0 ROWS FETCH NEXT @limit ROWS ONLY`;
563
- request.input("limit", limit);
468
+ messageIds.forEach((id, i) => request.input(`id${i}`, id));
469
+ query += ` ${orderByStatement}`;
564
470
  const result = await request.query(query);
565
471
  const remainingRows = result.recordset || [];
566
472
  rows.push(...remainingRows);
@@ -568,153 +474,171 @@ var MemoryMSSQL = class extends MemoryStorage {
568
474
  const timeDiff = a.seq_id - b.seq_id;
569
475
  return timeDiff;
570
476
  });
571
- rows = rows.map(({ seq_id, ...rest }) => rest);
572
- return this._parseAndFormatMessages(rows, format);
477
+ const messagesWithParsedContent = rows.map((row) => {
478
+ if (typeof row.content === "string") {
479
+ try {
480
+ return { ...row, content: JSON.parse(row.content) };
481
+ } catch {
482
+ return row;
483
+ }
484
+ }
485
+ return row;
486
+ });
487
+ const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
488
+ const list = new MessageList().add(cleanMessages, "memory");
489
+ return { messages: list.get.all.db() };
573
490
  } catch (error) {
574
491
  const mastraError = new MastraError(
575
492
  {
576
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_MESSAGES_FAILED",
493
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_MESSAGES_BY_ID_FAILED",
577
494
  domain: ErrorDomain.STORAGE,
578
495
  category: ErrorCategory.THIRD_PARTY,
579
496
  details: {
580
- threadId,
581
- resourceId: resourceId ?? ""
497
+ messageIds: JSON.stringify(messageIds)
582
498
  }
583
499
  },
584
500
  error
585
501
  );
586
502
  this.logger?.error?.(mastraError.toString());
587
- this.logger?.trackException(mastraError);
588
- return [];
503
+ this.logger?.trackException?.(mastraError);
504
+ return { messages: [] };
589
505
  }
590
506
  }
591
- async getMessagesById({
592
- messageIds,
593
- format
594
- }) {
595
- if (messageIds.length === 0) return [];
596
- const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
597
- const orderByStatement = `ORDER BY [seq_id] DESC`;
598
- try {
599
- let rows = [];
600
- let query = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE [id] IN (${messageIds.map((_, i) => `@id${i}`).join(", ")})`;
601
- const request = this.pool.request();
602
- messageIds.forEach((id, i) => request.input(`id${i}`, id));
603
- query += ` ${orderByStatement}`;
604
- const result = await request.query(query);
605
- const remainingRows = result.recordset || [];
606
- rows.push(...remainingRows);
607
- rows.sort((a, b) => {
608
- const timeDiff = a.seq_id - b.seq_id;
609
- return timeDiff;
610
- });
611
- rows = rows.map(({ seq_id, ...rest }) => rest);
612
- if (format === `v1`) return this._parseAndFormatMessages(rows, format);
613
- return this._parseAndFormatMessages(rows, `v2`);
614
- } catch (error) {
615
- const mastraError = new MastraError(
507
+ async listMessages(args) {
508
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
509
+ if (!threadId.trim()) {
510
+ throw new MastraError(
616
511
  {
617
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_MESSAGES_BY_ID_FAILED",
512
+ id: "STORAGE_MSSQL_LIST_MESSAGES_INVALID_THREAD_ID",
618
513
  domain: ErrorDomain.STORAGE,
619
514
  category: ErrorCategory.THIRD_PARTY,
620
- details: {
621
- messageIds: JSON.stringify(messageIds)
622
- }
515
+ details: { threadId }
623
516
  },
624
- error
517
+ new Error("threadId must be a non-empty string")
625
518
  );
626
- this.logger?.error?.(mastraError.toString());
627
- this.logger?.trackException(mastraError);
628
- return [];
629
519
  }
630
- }
631
- async getMessagesPaginated(args) {
632
- const { threadId, resourceId, format, selectBy } = args;
633
- const { page = 0, perPage: perPageInput, dateRange } = selectBy?.pagination || {};
520
+ if (page < 0) {
521
+ throw new MastraError({
522
+ id: "MASTRA_STORAGE_MSSQL_STORE_INVALID_PAGE",
523
+ domain: ErrorDomain.STORAGE,
524
+ category: ErrorCategory.USER,
525
+ text: "Page number must be non-negative",
526
+ details: {
527
+ threadId,
528
+ page
529
+ }
530
+ });
531
+ }
532
+ const perPage = normalizePerPage(perPageInput, 40);
533
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
634
534
  try {
635
- if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
636
- const fromDate = dateRange?.start;
637
- const toDate = dateRange?.end;
638
- const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
639
- const orderByStatement = `ORDER BY [seq_id] DESC`;
640
- let messages = [];
641
- if (selectBy?.include?.length) {
642
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
643
- if (includeMessages) messages.push(...includeMessages);
644
- }
645
- const perPage = perPageInput !== void 0 ? perPageInput : resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
646
- const currentOffset = page * perPage;
647
- const conditions = ["[thread_id] = @threadId"];
648
- const request = this.pool.request();
649
- request.input("threadId", threadId);
650
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
651
- conditions.push("[createdAt] >= @fromDate");
652
- request.input("fromDate", fromDate.toISOString());
653
- }
654
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
655
- conditions.push("[createdAt] <= @toDate");
656
- request.input("toDate", toDate.toISOString());
657
- }
658
- const whereClause = `WHERE ${conditions.join(" AND ")}`;
659
- const countQuery = `SELECT COUNT(*) as total FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} ${whereClause}`;
660
- const countResult = await request.query(countQuery);
535
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
536
+ const orderByStatement = `ORDER BY [${field}] ${direction}, [seq_id] ${direction}`;
537
+ const tableName = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
538
+ const baseQuery = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId FROM ${tableName}`;
539
+ const filters = {
540
+ thread_id: threadId,
541
+ ...resourceId ? { resourceId } : {},
542
+ ...buildDateRangeFilter(filter?.dateRange, "createdAt")
543
+ };
544
+ const { sql: actualWhereClause = "", params: whereParams } = prepareWhereClause(
545
+ filters);
546
+ const bindWhereParams = (req) => {
547
+ Object.entries(whereParams).forEach(([paramName, paramValue]) => req.input(paramName, paramValue));
548
+ };
549
+ const countRequest = this.pool.request();
550
+ bindWhereParams(countRequest);
551
+ const countResult = await countRequest.query(`SELECT COUNT(*) as total FROM ${tableName}${actualWhereClause}`);
661
552
  const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
662
- if (total === 0 && messages.length > 0) {
663
- const parsedIncluded = this._parseAndFormatMessages(messages, format);
553
+ const fetchBaseMessages = async () => {
554
+ const request = this.pool.request();
555
+ bindWhereParams(request);
556
+ if (perPageInput === false) {
557
+ const result2 = await request.query(`${baseQuery}${actualWhereClause} ${orderByStatement}`);
558
+ return result2.recordset || [];
559
+ }
560
+ request.input("offset", offset);
561
+ request.input("limit", perPage > 2147483647 ? sql2.BigInt : sql2.Int, perPage);
562
+ const result = await request.query(
563
+ `${baseQuery}${actualWhereClause} ${orderByStatement} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
564
+ );
565
+ return result.recordset || [];
566
+ };
567
+ const baseRows = perPage === 0 ? [] : await fetchBaseMessages();
568
+ const messages = [...baseRows];
569
+ const seqById = /* @__PURE__ */ new Map();
570
+ messages.forEach((msg) => {
571
+ if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
572
+ });
573
+ if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
664
574
  return {
665
- messages: parsedIncluded,
666
- total: parsedIncluded.length,
575
+ messages: [],
576
+ total: 0,
667
577
  page,
668
- perPage,
578
+ perPage: perPageForResponse,
669
579
  hasMore: false
670
580
  };
671
581
  }
672
- const excludeIds = messages.map((m) => m.id);
673
- if (excludeIds.length > 0) {
674
- const excludeParams = excludeIds.map((_, idx) => `@id${idx}`);
675
- conditions.push(`id NOT IN (${excludeParams.join(", ")})`);
676
- excludeIds.forEach((id, idx) => request.input(`id${idx}`, id));
677
- }
678
- const finalWhereClause = `WHERE ${conditions.join(" AND ")}`;
679
- const dataQuery = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} ${finalWhereClause} ${orderByStatement} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
680
- request.input("offset", currentOffset);
681
- request.input("limit", perPage);
682
- const rowsResult = await request.query(dataQuery);
683
- const rows = rowsResult.recordset || [];
684
- rows.sort((a, b) => a.seq_id - b.seq_id);
685
- messages.push(...rows);
686
- const parsed = this._parseAndFormatMessages(messages, format);
582
+ if (include?.length) {
583
+ const messageIds = new Set(messages.map((m) => m.id));
584
+ const includeMessages = await this._getIncludedMessages({ threadId, include });
585
+ includeMessages?.forEach((msg) => {
586
+ if (!messageIds.has(msg.id)) {
587
+ messages.push(msg);
588
+ messageIds.add(msg.id);
589
+ if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
590
+ }
591
+ });
592
+ }
593
+ const parsed = this._parseAndFormatMessages(messages, "v2");
594
+ const mult = direction === "ASC" ? 1 : -1;
595
+ const finalMessages = parsed.sort((a, b) => {
596
+ const aVal = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
597
+ const bVal = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
598
+ if (aVal == null || bVal == null) {
599
+ return aVal == null && bVal == null ? a.id.localeCompare(b.id) : aVal == null ? 1 : -1;
600
+ }
601
+ const diff = (typeof aVal === "number" && typeof bVal === "number" ? aVal - bVal : String(aVal).localeCompare(String(bVal))) * mult;
602
+ if (diff !== 0) return diff;
603
+ const seqA = seqById.get(a.id);
604
+ const seqB = seqById.get(b.id);
605
+ return seqA != null && seqB != null ? (seqA - seqB) * mult : a.id.localeCompare(b.id);
606
+ });
607
+ const returnedThreadMessageCount = finalMessages.filter((m) => m.threadId === threadId).length;
608
+ const hasMore = perPageInput !== false && returnedThreadMessageCount < total && offset + perPage < total;
687
609
  return {
688
- messages: parsed,
689
- total: total + excludeIds.length,
610
+ messages: finalMessages,
611
+ total,
690
612
  page,
691
- perPage,
692
- hasMore: currentOffset + rows.length < total
613
+ perPage: perPageForResponse,
614
+ hasMore
693
615
  };
694
616
  } catch (error) {
695
617
  const mastraError = new MastraError(
696
618
  {
697
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_MESSAGES_PAGINATED_FAILED",
619
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_MESSAGES_FAILED",
698
620
  domain: ErrorDomain.STORAGE,
699
621
  category: ErrorCategory.THIRD_PARTY,
700
622
  details: {
701
623
  threadId,
702
- resourceId: resourceId ?? "",
703
- page
624
+ resourceId: resourceId ?? ""
704
625
  }
705
626
  },
706
627
  error
707
628
  );
708
629
  this.logger?.error?.(mastraError.toString());
709
- this.logger?.trackException(mastraError);
710
- return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
630
+ this.logger?.trackException?.(mastraError);
631
+ return {
632
+ messages: [],
633
+ total: 0,
634
+ page,
635
+ perPage: perPageForResponse,
636
+ hasMore: false
637
+ };
711
638
  }
712
639
  }
713
- async saveMessages({
714
- messages,
715
- format
716
- }) {
717
- if (messages.length === 0) return messages;
640
+ async saveMessages({ messages }) {
641
+ if (messages.length === 0) return { messages: [] };
718
642
  const threadId = messages[0]?.threadId;
719
643
  if (!threadId) {
720
644
  throw new MastraError({
@@ -796,8 +720,7 @@ var MemoryMSSQL = class extends MemoryStorage {
796
720
  return message;
797
721
  });
798
722
  const list = new MessageList().add(messagesWithParsedContent, "memory");
799
- if (format === "v2") return list.get.all.v2();
800
- return list.get.all.v1();
723
+ return { messages: list.get.all.db() };
801
724
  } catch (error) {
802
725
  throw new MastraError(
803
726
  {
@@ -973,8 +896,10 @@ var MemoryMSSQL = class extends MemoryStorage {
973
896
  return null;
974
897
  }
975
898
  return {
976
- ...result,
977
- workingMemory: typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
899
+ id: result.id,
900
+ createdAt: result.createdAt,
901
+ updatedAt: result.updatedAt,
902
+ workingMemory: result.workingMemory,
978
903
  metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
979
904
  };
980
905
  } catch (error) {
@@ -988,7 +913,7 @@ var MemoryMSSQL = class extends MemoryStorage {
988
913
  error
989
914
  );
990
915
  this.logger?.error?.(mastraError.toString());
991
- this.logger?.trackException(mastraError);
916
+ this.logger?.trackException?.(mastraError);
992
917
  throw mastraError;
993
918
  }
994
919
  }
@@ -997,7 +922,7 @@ var MemoryMSSQL = class extends MemoryStorage {
997
922
  tableName: TABLE_RESOURCES,
998
923
  record: {
999
924
  ...resource,
1000
- metadata: JSON.stringify(resource.metadata)
925
+ metadata: resource.metadata
1001
926
  }
1002
927
  });
1003
928
  return resource;
@@ -1055,72 +980,391 @@ var MemoryMSSQL = class extends MemoryStorage {
1055
980
  error
1056
981
  );
1057
982
  this.logger?.error?.(mastraError.toString());
1058
- this.logger?.trackException(mastraError);
983
+ this.logger?.trackException?.(mastraError);
1059
984
  throw mastraError;
1060
985
  }
1061
986
  }
1062
987
  };
1063
- var StoreOperationsMSSQL = class extends StoreOperations {
988
+ var ObservabilityMSSQL = class extends ObservabilityStorage {
1064
989
  pool;
1065
- schemaName;
1066
- setupSchemaPromise = null;
1067
- schemaSetupComplete = void 0;
1068
- getSqlType(type, isPrimaryKey = false) {
1069
- switch (type) {
1070
- case "text":
1071
- return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(MAX)";
1072
- case "timestamp":
1073
- return "DATETIME2(7)";
1074
- case "uuid":
1075
- return "UNIQUEIDENTIFIER";
1076
- case "jsonb":
1077
- return "NVARCHAR(MAX)";
1078
- case "integer":
1079
- return "INT";
1080
- case "bigint":
1081
- return "BIGINT";
1082
- case "float":
1083
- return "FLOAT";
1084
- default:
1085
- throw new MastraError({
1086
- id: "MASTRA_STORAGE_MSSQL_STORE_TYPE_NOT_SUPPORTED",
1087
- domain: ErrorDomain.STORAGE,
1088
- category: ErrorCategory.THIRD_PARTY
1089
- });
1090
- }
1091
- }
1092
- constructor({ pool, schemaName }) {
990
+ operations;
991
+ schema;
992
+ constructor({
993
+ pool,
994
+ operations,
995
+ schema
996
+ }) {
1093
997
  super();
1094
998
  this.pool = pool;
1095
- this.schemaName = schemaName;
999
+ this.operations = operations;
1000
+ this.schema = schema;
1096
1001
  }
1097
- async hasColumn(table, column) {
1098
- const schema = this.schemaName || "dbo";
1099
- const request = this.pool.request();
1100
- request.input("schema", schema);
1101
- request.input("table", table);
1102
- request.input("column", column);
1103
- request.input("columnLower", column.toLowerCase());
1104
- const result = await request.query(
1105
- `SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
1106
- );
1107
- return result.recordset.length > 0;
1002
+ get tracingStrategy() {
1003
+ return {
1004
+ preferred: "batch-with-updates",
1005
+ supported: ["batch-with-updates", "insert-only"]
1006
+ };
1108
1007
  }
1109
- async setupSchema() {
1110
- if (!this.schemaName || this.schemaSetupComplete) {
1111
- return;
1008
+ async createSpan(span) {
1009
+ try {
1010
+ const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt;
1011
+ const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt;
1012
+ const record = {
1013
+ ...span,
1014
+ startedAt,
1015
+ endedAt
1016
+ // Note: createdAt/updatedAt will be set by default values
1017
+ };
1018
+ return this.operations.insert({ tableName: TABLE_SPANS, record });
1019
+ } catch (error) {
1020
+ throw new MastraError(
1021
+ {
1022
+ id: "MSSQL_STORE_CREATE_SPAN_FAILED",
1023
+ domain: ErrorDomain.STORAGE,
1024
+ category: ErrorCategory.USER,
1025
+ details: {
1026
+ spanId: span.spanId,
1027
+ traceId: span.traceId,
1028
+ spanType: span.spanType,
1029
+ spanName: span.name
1030
+ }
1031
+ },
1032
+ error
1033
+ );
1112
1034
  }
1113
- if (!this.setupSchemaPromise) {
1114
- this.setupSchemaPromise = (async () => {
1115
- try {
1116
- const checkRequest = this.pool.request();
1117
- checkRequest.input("schemaName", this.schemaName);
1118
- const checkResult = await checkRequest.query(`
1119
- SELECT 1 AS found FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = @schemaName
1120
- `);
1121
- const schemaExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
1122
- if (!schemaExists) {
1123
- try {
1035
+ }
1036
+ async getTrace(traceId) {
1037
+ try {
1038
+ const tableName = getTableName({
1039
+ indexName: TABLE_SPANS,
1040
+ schemaName: getSchemaName(this.schema)
1041
+ });
1042
+ const request = this.pool.request();
1043
+ request.input("traceId", traceId);
1044
+ const result = await request.query(
1045
+ `SELECT
1046
+ [traceId], [spanId], [parentSpanId], [name], [scope], [spanType],
1047
+ [attributes], [metadata], [links], [input], [output], [error], [isEvent],
1048
+ [startedAt], [endedAt], [createdAt], [updatedAt]
1049
+ FROM ${tableName}
1050
+ WHERE [traceId] = @traceId
1051
+ ORDER BY [startedAt] DESC`
1052
+ );
1053
+ if (!result.recordset || result.recordset.length === 0) {
1054
+ return null;
1055
+ }
1056
+ return {
1057
+ traceId,
1058
+ spans: result.recordset.map(
1059
+ (span) => transformFromSqlRow({
1060
+ tableName: TABLE_SPANS,
1061
+ sqlRow: span
1062
+ })
1063
+ )
1064
+ };
1065
+ } catch (error) {
1066
+ throw new MastraError(
1067
+ {
1068
+ id: "MSSQL_STORE_GET_TRACE_FAILED",
1069
+ domain: ErrorDomain.STORAGE,
1070
+ category: ErrorCategory.USER,
1071
+ details: {
1072
+ traceId
1073
+ }
1074
+ },
1075
+ error
1076
+ );
1077
+ }
1078
+ }
1079
+ async updateSpan({
1080
+ spanId,
1081
+ traceId,
1082
+ updates
1083
+ }) {
1084
+ try {
1085
+ const data = { ...updates };
1086
+ if (data.endedAt instanceof Date) {
1087
+ data.endedAt = data.endedAt.toISOString();
1088
+ }
1089
+ if (data.startedAt instanceof Date) {
1090
+ data.startedAt = data.startedAt.toISOString();
1091
+ }
1092
+ await this.operations.update({
1093
+ tableName: TABLE_SPANS,
1094
+ keys: { spanId, traceId },
1095
+ data
1096
+ });
1097
+ } catch (error) {
1098
+ throw new MastraError(
1099
+ {
1100
+ id: "MSSQL_STORE_UPDATE_SPAN_FAILED",
1101
+ domain: ErrorDomain.STORAGE,
1102
+ category: ErrorCategory.USER,
1103
+ details: {
1104
+ spanId,
1105
+ traceId
1106
+ }
1107
+ },
1108
+ error
1109
+ );
1110
+ }
1111
+ }
1112
+ async getTracesPaginated({
1113
+ filters,
1114
+ pagination
1115
+ }) {
1116
+ const page = pagination?.page ?? 0;
1117
+ const perPage = pagination?.perPage ?? 10;
1118
+ const { entityId, entityType, ...actualFilters } = filters || {};
1119
+ const filtersWithDateRange = {
1120
+ ...actualFilters,
1121
+ ...buildDateRangeFilter(pagination?.dateRange, "startedAt"),
1122
+ parentSpanId: null
1123
+ // Only get root spans for traces
1124
+ };
1125
+ const whereClause = prepareWhereClause(filtersWithDateRange);
1126
+ let actualWhereClause = whereClause.sql;
1127
+ const params = { ...whereClause.params };
1128
+ let currentParamIndex = Object.keys(params).length + 1;
1129
+ if (entityId && entityType) {
1130
+ let name = "";
1131
+ if (entityType === "workflow") {
1132
+ name = `workflow run: '${entityId}'`;
1133
+ } else if (entityType === "agent") {
1134
+ name = `agent run: '${entityId}'`;
1135
+ } else {
1136
+ const error = new MastraError({
1137
+ id: "MSSQL_STORE_GET_TRACES_PAGINATED_FAILED",
1138
+ domain: ErrorDomain.STORAGE,
1139
+ category: ErrorCategory.USER,
1140
+ details: {
1141
+ entityType
1142
+ },
1143
+ text: `Cannot filter by entity type: ${entityType}`
1144
+ });
1145
+ throw error;
1146
+ }
1147
+ const entityParam = `p${currentParamIndex++}`;
1148
+ if (actualWhereClause) {
1149
+ actualWhereClause += ` AND [name] = @${entityParam}`;
1150
+ } else {
1151
+ actualWhereClause = ` WHERE [name] = @${entityParam}`;
1152
+ }
1153
+ params[entityParam] = name;
1154
+ }
1155
+ const tableName = getTableName({
1156
+ indexName: TABLE_SPANS,
1157
+ schemaName: getSchemaName(this.schema)
1158
+ });
1159
+ try {
1160
+ const countRequest = this.pool.request();
1161
+ Object.entries(params).forEach(([key, value]) => {
1162
+ countRequest.input(key, value);
1163
+ });
1164
+ const countResult = await countRequest.query(
1165
+ `SELECT COUNT(*) as count FROM ${tableName}${actualWhereClause}`
1166
+ );
1167
+ const total = countResult.recordset[0]?.count ?? 0;
1168
+ if (total === 0) {
1169
+ return {
1170
+ pagination: {
1171
+ total: 0,
1172
+ page,
1173
+ perPage,
1174
+ hasMore: false
1175
+ },
1176
+ spans: []
1177
+ };
1178
+ }
1179
+ const dataRequest = this.pool.request();
1180
+ Object.entries(params).forEach(([key, value]) => {
1181
+ dataRequest.input(key, value);
1182
+ });
1183
+ dataRequest.input("offset", page * perPage);
1184
+ dataRequest.input("limit", perPage);
1185
+ const dataResult = await dataRequest.query(
1186
+ `SELECT * FROM ${tableName}${actualWhereClause} ORDER BY [startedAt] DESC OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
1187
+ );
1188
+ const spans = dataResult.recordset.map(
1189
+ (row) => transformFromSqlRow({
1190
+ tableName: TABLE_SPANS,
1191
+ sqlRow: row
1192
+ })
1193
+ );
1194
+ return {
1195
+ pagination: {
1196
+ total,
1197
+ page,
1198
+ perPage,
1199
+ hasMore: (page + 1) * perPage < total
1200
+ },
1201
+ spans
1202
+ };
1203
+ } catch (error) {
1204
+ throw new MastraError(
1205
+ {
1206
+ id: "MSSQL_STORE_GET_TRACES_PAGINATED_FAILED",
1207
+ domain: ErrorDomain.STORAGE,
1208
+ category: ErrorCategory.USER
1209
+ },
1210
+ error
1211
+ );
1212
+ }
1213
+ }
1214
+ async batchCreateSpans(args) {
1215
+ if (!args.records || args.records.length === 0) {
1216
+ return;
1217
+ }
1218
+ try {
1219
+ await this.operations.batchInsert({
1220
+ tableName: TABLE_SPANS,
1221
+ records: args.records.map((span) => ({
1222
+ ...span,
1223
+ startedAt: span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt,
1224
+ endedAt: span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt
1225
+ }))
1226
+ });
1227
+ } catch (error) {
1228
+ throw new MastraError(
1229
+ {
1230
+ id: "MSSQL_STORE_BATCH_CREATE_SPANS_FAILED",
1231
+ domain: ErrorDomain.STORAGE,
1232
+ category: ErrorCategory.USER,
1233
+ details: {
1234
+ count: args.records.length
1235
+ }
1236
+ },
1237
+ error
1238
+ );
1239
+ }
1240
+ }
1241
+ async batchUpdateSpans(args) {
1242
+ if (!args.records || args.records.length === 0) {
1243
+ return;
1244
+ }
1245
+ try {
1246
+ const updates = args.records.map(({ traceId, spanId, updates: data }) => {
1247
+ const processedData = { ...data };
1248
+ if (processedData.endedAt instanceof Date) {
1249
+ processedData.endedAt = processedData.endedAt.toISOString();
1250
+ }
1251
+ if (processedData.startedAt instanceof Date) {
1252
+ processedData.startedAt = processedData.startedAt.toISOString();
1253
+ }
1254
+ return {
1255
+ keys: { spanId, traceId },
1256
+ data: processedData
1257
+ };
1258
+ });
1259
+ await this.operations.batchUpdate({
1260
+ tableName: TABLE_SPANS,
1261
+ updates
1262
+ });
1263
+ } catch (error) {
1264
+ throw new MastraError(
1265
+ {
1266
+ id: "MSSQL_STORE_BATCH_UPDATE_SPANS_FAILED",
1267
+ domain: ErrorDomain.STORAGE,
1268
+ category: ErrorCategory.USER,
1269
+ details: {
1270
+ count: args.records.length
1271
+ }
1272
+ },
1273
+ error
1274
+ );
1275
+ }
1276
+ }
1277
+ async batchDeleteTraces(args) {
1278
+ if (!args.traceIds || args.traceIds.length === 0) {
1279
+ return;
1280
+ }
1281
+ try {
1282
+ const keys = args.traceIds.map((traceId) => ({ traceId }));
1283
+ await this.operations.batchDelete({
1284
+ tableName: TABLE_SPANS,
1285
+ keys
1286
+ });
1287
+ } catch (error) {
1288
+ throw new MastraError(
1289
+ {
1290
+ id: "MSSQL_STORE_BATCH_DELETE_TRACES_FAILED",
1291
+ domain: ErrorDomain.STORAGE,
1292
+ category: ErrorCategory.USER,
1293
+ details: {
1294
+ count: args.traceIds.length
1295
+ }
1296
+ },
1297
+ error
1298
+ );
1299
+ }
1300
+ }
1301
+ };
1302
+ var StoreOperationsMSSQL = class extends StoreOperations {
1303
+ pool;
1304
+ schemaName;
1305
+ setupSchemaPromise = null;
1306
+ schemaSetupComplete = void 0;
1307
+ getSqlType(type, isPrimaryKey = false, useLargeStorage = false) {
1308
+ switch (type) {
1309
+ case "text":
1310
+ if (useLargeStorage) {
1311
+ return "NVARCHAR(MAX)";
1312
+ }
1313
+ return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(400)";
1314
+ case "timestamp":
1315
+ return "DATETIME2(7)";
1316
+ case "uuid":
1317
+ return "UNIQUEIDENTIFIER";
1318
+ case "jsonb":
1319
+ return "NVARCHAR(MAX)";
1320
+ case "integer":
1321
+ return "INT";
1322
+ case "bigint":
1323
+ return "BIGINT";
1324
+ case "float":
1325
+ return "FLOAT";
1326
+ case "boolean":
1327
+ return "BIT";
1328
+ default:
1329
+ throw new MastraError({
1330
+ id: "MASTRA_STORAGE_MSSQL_STORE_TYPE_NOT_SUPPORTED",
1331
+ domain: ErrorDomain.STORAGE,
1332
+ category: ErrorCategory.THIRD_PARTY
1333
+ });
1334
+ }
1335
+ }
1336
+ constructor({ pool, schemaName }) {
1337
+ super();
1338
+ this.pool = pool;
1339
+ this.schemaName = schemaName;
1340
+ }
1341
+ async hasColumn(table, column) {
1342
+ const schema = this.schemaName || "dbo";
1343
+ const request = this.pool.request();
1344
+ request.input("schema", schema);
1345
+ request.input("table", table);
1346
+ request.input("column", column);
1347
+ request.input("columnLower", column.toLowerCase());
1348
+ const result = await request.query(
1349
+ `SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
1350
+ );
1351
+ return result.recordset.length > 0;
1352
+ }
1353
+ async setupSchema() {
1354
+ if (!this.schemaName || this.schemaSetupComplete) {
1355
+ return;
1356
+ }
1357
+ if (!this.setupSchemaPromise) {
1358
+ this.setupSchemaPromise = (async () => {
1359
+ try {
1360
+ const checkRequest = this.pool.request();
1361
+ checkRequest.input("schemaName", this.schemaName);
1362
+ const checkResult = await checkRequest.query(`
1363
+ SELECT 1 AS found FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = @schemaName
1364
+ `);
1365
+ const schemaExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
1366
+ if (!schemaExists) {
1367
+ try {
1124
1368
  await this.pool.request().query(`CREATE SCHEMA [${this.schemaName}]`);
1125
1369
  this.logger?.info?.(`Schema "${this.schemaName}" created successfully`);
1126
1370
  } catch (error) {
@@ -1143,20 +1387,26 @@ var StoreOperationsMSSQL = class extends StoreOperations {
1143
1387
  }
1144
1388
  await this.setupSchemaPromise;
1145
1389
  }
1146
- async insert({ tableName, record }) {
1390
+ async insert({
1391
+ tableName,
1392
+ record,
1393
+ transaction
1394
+ }) {
1147
1395
  try {
1148
- const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1149
- const values = Object.values(record);
1150
- const paramNames = values.map((_, i) => `@param${i}`);
1151
- const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${columns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
1152
- const request = this.pool.request();
1153
- values.forEach((value, i) => {
1154
- if (value instanceof Date) {
1155
- request.input(`param${i}`, sql2.DateTime2, value);
1156
- } else if (typeof value === "object" && value !== null) {
1157
- request.input(`param${i}`, JSON.stringify(value));
1396
+ const columns = Object.keys(record);
1397
+ const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
1398
+ const paramNames = columns.map((_, i) => `@param${i}`);
1399
+ const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${parsedColumns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
1400
+ const request = transaction ? transaction.request() : this.pool.request();
1401
+ columns.forEach((col, i) => {
1402
+ const value = record[col];
1403
+ const preparedValue = this.prepareValue(value, col, tableName);
1404
+ if (preparedValue instanceof Date) {
1405
+ request.input(`param${i}`, sql2.DateTime2, preparedValue);
1406
+ } else if (preparedValue === null || preparedValue === void 0) {
1407
+ request.input(`param${i}`, this.getMssqlType(tableName, col), null);
1158
1408
  } else {
1159
- request.input(`param${i}`, value);
1409
+ request.input(`param${i}`, preparedValue);
1160
1410
  }
1161
1411
  });
1162
1412
  await request.query(insertSql);
@@ -1180,7 +1430,7 @@ var StoreOperationsMSSQL = class extends StoreOperations {
1180
1430
  try {
1181
1431
  await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
1182
1432
  } catch (truncateError) {
1183
- if (truncateError.message && truncateError.message.includes("foreign key")) {
1433
+ if (truncateError?.number === 4712) {
1184
1434
  await this.pool.request().query(`DELETE FROM ${fullTableName}`);
1185
1435
  } else {
1186
1436
  throw truncateError;
@@ -1203,9 +1453,11 @@ var StoreOperationsMSSQL = class extends StoreOperations {
1203
1453
  getDefaultValue(type) {
1204
1454
  switch (type) {
1205
1455
  case "timestamp":
1206
- return "DEFAULT SYSDATETIMEOFFSET()";
1456
+ return "DEFAULT SYSUTCDATETIME()";
1207
1457
  case "jsonb":
1208
1458
  return "DEFAULT N'{}'";
1459
+ case "boolean":
1460
+ return "DEFAULT 0";
1209
1461
  default:
1210
1462
  return super.getDefaultValue(type);
1211
1463
  }
@@ -1216,13 +1468,29 @@ var StoreOperationsMSSQL = class extends StoreOperations {
1216
1468
  }) {
1217
1469
  try {
1218
1470
  const uniqueConstraintColumns = tableName === TABLE_WORKFLOW_SNAPSHOT ? ["workflow_name", "run_id"] : [];
1471
+ const largeDataColumns = [
1472
+ "workingMemory",
1473
+ "snapshot",
1474
+ "metadata",
1475
+ "content",
1476
+ // messages.content - can be very long conversation content
1477
+ "input",
1478
+ // evals.input - test input data
1479
+ "output",
1480
+ // evals.output - test output data
1481
+ "instructions",
1482
+ // evals.instructions - evaluation instructions
1483
+ "other"
1484
+ // traces.other - additional trace data
1485
+ ];
1219
1486
  const columns = Object.entries(schema).map(([name, def]) => {
1220
1487
  const parsedName = parseSqlIdentifier(name, "column name");
1221
1488
  const constraints = [];
1222
1489
  if (def.primaryKey) constraints.push("PRIMARY KEY");
1223
1490
  if (!def.nullable) constraints.push("NOT NULL");
1224
1491
  const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
1225
- return `[${parsedName}] ${this.getSqlType(def.type, isIndexed)} ${constraints.join(" ")}`.trim();
1492
+ const useLargeStorage = largeDataColumns.includes(name);
1493
+ return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
1226
1494
  }).join(",\n");
1227
1495
  if (this.schemaName) {
1228
1496
  await this.setupSchema();
@@ -1309,7 +1577,19 @@ ${columns}
1309
1577
  const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
1310
1578
  if (!columnExists) {
1311
1579
  const columnDef = schema[columnName];
1312
- const sqlType = this.getSqlType(columnDef.type);
1580
+ const largeDataColumns = [
1581
+ "workingMemory",
1582
+ "snapshot",
1583
+ "metadata",
1584
+ "content",
1585
+ "input",
1586
+ "output",
1587
+ "instructions",
1588
+ "other"
1589
+ ];
1590
+ const useLargeStorage = largeDataColumns.includes(columnName);
1591
+ const isIndexed = !!columnDef.primaryKey;
1592
+ const sqlType = this.getSqlType(columnDef.type, isIndexed, useLargeStorage);
1313
1593
  const nullable = columnDef.nullable === false ? "NOT NULL" : "";
1314
1594
  const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1315
1595
  const parsedColumnName = parseSqlIdentifier(columnName, "column name");
@@ -1322,118 +1602,672 @@ ${columns}
1322
1602
  } catch (error) {
1323
1603
  throw new MastraError(
1324
1604
  {
1325
- id: "MASTRA_STORAGE_MSSQL_STORE_ALTER_TABLE_FAILED",
1605
+ id: "MASTRA_STORAGE_MSSQL_STORE_ALTER_TABLE_FAILED",
1606
+ domain: ErrorDomain.STORAGE,
1607
+ category: ErrorCategory.THIRD_PARTY,
1608
+ details: {
1609
+ tableName
1610
+ }
1611
+ },
1612
+ error
1613
+ );
1614
+ }
1615
+ }
1616
+ async load({ tableName, keys }) {
1617
+ try {
1618
+ const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
1619
+ const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
1620
+ const sql5 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
1621
+ const request = this.pool.request();
1622
+ keyEntries.forEach(([key, value], i) => {
1623
+ const preparedValue = this.prepareValue(value, key, tableName);
1624
+ if (preparedValue === null || preparedValue === void 0) {
1625
+ request.input(`param${i}`, this.getMssqlType(tableName, key), null);
1626
+ } else {
1627
+ request.input(`param${i}`, preparedValue);
1628
+ }
1629
+ });
1630
+ const resultSet = await request.query(sql5);
1631
+ const result = resultSet.recordset[0] || null;
1632
+ if (!result) {
1633
+ return null;
1634
+ }
1635
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
1636
+ const snapshot = result;
1637
+ if (typeof snapshot.snapshot === "string") {
1638
+ snapshot.snapshot = JSON.parse(snapshot.snapshot);
1639
+ }
1640
+ return snapshot;
1641
+ }
1642
+ return result;
1643
+ } catch (error) {
1644
+ throw new MastraError(
1645
+ {
1646
+ id: "MASTRA_STORAGE_MSSQL_STORE_LOAD_FAILED",
1647
+ domain: ErrorDomain.STORAGE,
1648
+ category: ErrorCategory.THIRD_PARTY,
1649
+ details: {
1650
+ tableName
1651
+ }
1652
+ },
1653
+ error
1654
+ );
1655
+ }
1656
+ }
1657
+ async batchInsert({ tableName, records }) {
1658
+ const transaction = this.pool.transaction();
1659
+ try {
1660
+ await transaction.begin();
1661
+ for (const record of records) {
1662
+ await this.insert({ tableName, record, transaction });
1663
+ }
1664
+ await transaction.commit();
1665
+ } catch (error) {
1666
+ await transaction.rollback();
1667
+ throw new MastraError(
1668
+ {
1669
+ id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_INSERT_FAILED",
1670
+ domain: ErrorDomain.STORAGE,
1671
+ category: ErrorCategory.THIRD_PARTY,
1672
+ details: {
1673
+ tableName,
1674
+ numberOfRecords: records.length
1675
+ }
1676
+ },
1677
+ error
1678
+ );
1679
+ }
1680
+ }
1681
+ async dropTable({ tableName }) {
1682
+ try {
1683
+ const tableNameWithSchema = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1684
+ await this.pool.request().query(`DROP TABLE IF EXISTS ${tableNameWithSchema}`);
1685
+ } catch (error) {
1686
+ throw new MastraError(
1687
+ {
1688
+ id: "MASTRA_STORAGE_MSSQL_STORE_DROP_TABLE_FAILED",
1689
+ domain: ErrorDomain.STORAGE,
1690
+ category: ErrorCategory.THIRD_PARTY,
1691
+ details: {
1692
+ tableName
1693
+ }
1694
+ },
1695
+ error
1696
+ );
1697
+ }
1698
+ }
1699
+ /**
1700
+ * Prepares a value for database operations, handling Date objects and JSON serialization
1701
+ */
1702
+ prepareValue(value, columnName, tableName) {
1703
+ if (value === null || value === void 0) {
1704
+ return value;
1705
+ }
1706
+ if (value instanceof Date) {
1707
+ return value;
1708
+ }
1709
+ const schema = TABLE_SCHEMAS[tableName];
1710
+ const columnSchema = schema?.[columnName];
1711
+ if (columnSchema?.type === "boolean") {
1712
+ return value ? 1 : 0;
1713
+ }
1714
+ if (columnSchema?.type === "jsonb") {
1715
+ if (typeof value === "string") {
1716
+ const trimmed = value.trim();
1717
+ if (trimmed.length > 0) {
1718
+ try {
1719
+ JSON.parse(trimmed);
1720
+ return trimmed;
1721
+ } catch {
1722
+ }
1723
+ }
1724
+ return JSON.stringify(value);
1725
+ }
1726
+ if (typeof value === "bigint") {
1727
+ return value.toString();
1728
+ }
1729
+ return JSON.stringify(value);
1730
+ }
1731
+ if (typeof value === "object") {
1732
+ return JSON.stringify(value);
1733
+ }
1734
+ return value;
1735
+ }
1736
+ /**
1737
+ * Maps TABLE_SCHEMAS types to mssql param types (used when value is null)
1738
+ */
1739
+ getMssqlType(tableName, columnName) {
1740
+ const col = TABLE_SCHEMAS[tableName]?.[columnName];
1741
+ switch (col?.type) {
1742
+ case "text":
1743
+ return sql2.NVarChar;
1744
+ case "timestamp":
1745
+ return sql2.DateTime2;
1746
+ case "uuid":
1747
+ return sql2.UniqueIdentifier;
1748
+ case "jsonb":
1749
+ return sql2.NVarChar;
1750
+ case "integer":
1751
+ return sql2.Int;
1752
+ case "bigint":
1753
+ return sql2.BigInt;
1754
+ case "float":
1755
+ return sql2.Float;
1756
+ case "boolean":
1757
+ return sql2.Bit;
1758
+ default:
1759
+ return sql2.NVarChar;
1760
+ }
1761
+ }
1762
+ /**
1763
+ * Update a single record in the database
1764
+ */
1765
+ async update({
1766
+ tableName,
1767
+ keys,
1768
+ data,
1769
+ transaction
1770
+ }) {
1771
+ try {
1772
+ if (!data || Object.keys(data).length === 0) {
1773
+ throw new MastraError({
1774
+ id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_DATA",
1775
+ domain: ErrorDomain.STORAGE,
1776
+ category: ErrorCategory.USER,
1777
+ text: "Cannot update with empty data payload"
1778
+ });
1779
+ }
1780
+ if (!keys || Object.keys(keys).length === 0) {
1781
+ throw new MastraError({
1782
+ id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_KEYS",
1783
+ domain: ErrorDomain.STORAGE,
1784
+ category: ErrorCategory.USER,
1785
+ text: "Cannot update without keys to identify records"
1786
+ });
1787
+ }
1788
+ const setClauses = [];
1789
+ const request = transaction ? transaction.request() : this.pool.request();
1790
+ let paramIndex = 0;
1791
+ Object.entries(data).forEach(([key, value]) => {
1792
+ const parsedKey = parseSqlIdentifier(key, "column name");
1793
+ const paramName = `set${paramIndex++}`;
1794
+ setClauses.push(`[${parsedKey}] = @${paramName}`);
1795
+ const preparedValue = this.prepareValue(value, key, tableName);
1796
+ if (preparedValue === null || preparedValue === void 0) {
1797
+ request.input(paramName, this.getMssqlType(tableName, key), null);
1798
+ } else {
1799
+ request.input(paramName, preparedValue);
1800
+ }
1801
+ });
1802
+ const whereConditions = [];
1803
+ Object.entries(keys).forEach(([key, value]) => {
1804
+ const parsedKey = parseSqlIdentifier(key, "column name");
1805
+ const paramName = `where${paramIndex++}`;
1806
+ whereConditions.push(`[${parsedKey}] = @${paramName}`);
1807
+ const preparedValue = this.prepareValue(value, key, tableName);
1808
+ if (preparedValue === null || preparedValue === void 0) {
1809
+ request.input(paramName, this.getMssqlType(tableName, key), null);
1810
+ } else {
1811
+ request.input(paramName, preparedValue);
1812
+ }
1813
+ });
1814
+ const tableName_ = getTableName({
1815
+ indexName: tableName,
1816
+ schemaName: getSchemaName(this.schemaName)
1817
+ });
1818
+ const updateSql = `UPDATE ${tableName_} SET ${setClauses.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
1819
+ await request.query(updateSql);
1820
+ } catch (error) {
1821
+ throw new MastraError(
1822
+ {
1823
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_FAILED",
1824
+ domain: ErrorDomain.STORAGE,
1825
+ category: ErrorCategory.THIRD_PARTY,
1826
+ details: {
1827
+ tableName
1828
+ }
1829
+ },
1830
+ error
1831
+ );
1832
+ }
1833
+ }
1834
+ /**
1835
+ * Update multiple records in a single batch transaction
1836
+ */
1837
+ async batchUpdate({
1838
+ tableName,
1839
+ updates
1840
+ }) {
1841
+ const transaction = this.pool.transaction();
1842
+ try {
1843
+ await transaction.begin();
1844
+ for (const { keys, data } of updates) {
1845
+ await this.update({ tableName, keys, data, transaction });
1846
+ }
1847
+ await transaction.commit();
1848
+ } catch (error) {
1849
+ await transaction.rollback();
1850
+ throw new MastraError(
1851
+ {
1852
+ id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_UPDATE_FAILED",
1853
+ domain: ErrorDomain.STORAGE,
1854
+ category: ErrorCategory.THIRD_PARTY,
1855
+ details: {
1856
+ tableName,
1857
+ numberOfRecords: updates.length
1858
+ }
1859
+ },
1860
+ error
1861
+ );
1862
+ }
1863
+ }
1864
+ /**
1865
+ * Delete multiple records by keys
1866
+ */
1867
+ async batchDelete({ tableName, keys }) {
1868
+ if (keys.length === 0) {
1869
+ return;
1870
+ }
1871
+ const tableName_ = getTableName({
1872
+ indexName: tableName,
1873
+ schemaName: getSchemaName(this.schemaName)
1874
+ });
1875
+ const transaction = this.pool.transaction();
1876
+ try {
1877
+ await transaction.begin();
1878
+ for (const keySet of keys) {
1879
+ const conditions = [];
1880
+ const request = transaction.request();
1881
+ let paramIndex = 0;
1882
+ Object.entries(keySet).forEach(([key, value]) => {
1883
+ const parsedKey = parseSqlIdentifier(key, "column name");
1884
+ const paramName = `p${paramIndex++}`;
1885
+ conditions.push(`[${parsedKey}] = @${paramName}`);
1886
+ const preparedValue = this.prepareValue(value, key, tableName);
1887
+ if (preparedValue === null || preparedValue === void 0) {
1888
+ request.input(paramName, this.getMssqlType(tableName, key), null);
1889
+ } else {
1890
+ request.input(paramName, preparedValue);
1891
+ }
1892
+ });
1893
+ const deleteSql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
1894
+ await request.query(deleteSql);
1895
+ }
1896
+ await transaction.commit();
1897
+ } catch (error) {
1898
+ await transaction.rollback();
1899
+ throw new MastraError(
1900
+ {
1901
+ id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_DELETE_FAILED",
1902
+ domain: ErrorDomain.STORAGE,
1903
+ category: ErrorCategory.THIRD_PARTY,
1904
+ details: {
1905
+ tableName,
1906
+ numberOfRecords: keys.length
1907
+ }
1908
+ },
1909
+ error
1910
+ );
1911
+ }
1912
+ }
1913
+ /**
1914
+ * Create a new index on a table
1915
+ */
1916
+ async createIndex(options) {
1917
+ try {
1918
+ const { name, table, columns, unique = false, where } = options;
1919
+ const schemaName = this.schemaName || "dbo";
1920
+ const fullTableName = getTableName({
1921
+ indexName: table,
1922
+ schemaName: getSchemaName(this.schemaName)
1923
+ });
1924
+ const indexNameSafe = parseSqlIdentifier(name, "index name");
1925
+ const checkRequest = this.pool.request();
1926
+ checkRequest.input("indexName", indexNameSafe);
1927
+ checkRequest.input("schemaName", schemaName);
1928
+ checkRequest.input("tableName", table);
1929
+ const indexExists = await checkRequest.query(`
1930
+ SELECT 1 as found
1931
+ FROM sys.indexes i
1932
+ INNER JOIN sys.tables t ON i.object_id = t.object_id
1933
+ INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
1934
+ WHERE i.name = @indexName
1935
+ AND s.name = @schemaName
1936
+ AND t.name = @tableName
1937
+ `);
1938
+ if (indexExists.recordset && indexExists.recordset.length > 0) {
1939
+ return;
1940
+ }
1941
+ const uniqueStr = unique ? "UNIQUE " : "";
1942
+ const columnsStr = columns.map((col) => {
1943
+ if (col.includes(" DESC") || col.includes(" ASC")) {
1944
+ const [colName, ...modifiers] = col.split(" ");
1945
+ if (!colName) {
1946
+ throw new Error(`Invalid column specification: ${col}`);
1947
+ }
1948
+ return `[${parseSqlIdentifier(colName, "column name")}] ${modifiers.join(" ")}`;
1949
+ }
1950
+ return `[${parseSqlIdentifier(col, "column name")}]`;
1951
+ }).join(", ");
1952
+ const whereStr = where ? ` WHERE ${where}` : "";
1953
+ const createIndexSql = `CREATE ${uniqueStr}INDEX [${indexNameSafe}] ON ${fullTableName} (${columnsStr})${whereStr}`;
1954
+ await this.pool.request().query(createIndexSql);
1955
+ } catch (error) {
1956
+ throw new MastraError(
1957
+ {
1958
+ id: "MASTRA_STORAGE_MSSQL_INDEX_CREATE_FAILED",
1959
+ domain: ErrorDomain.STORAGE,
1960
+ category: ErrorCategory.THIRD_PARTY,
1961
+ details: {
1962
+ indexName: options.name,
1963
+ tableName: options.table
1964
+ }
1965
+ },
1966
+ error
1967
+ );
1968
+ }
1969
+ }
1970
+ /**
1971
+ * Drop an existing index
1972
+ */
1973
+ async dropIndex(indexName) {
1974
+ try {
1975
+ const schemaName = this.schemaName || "dbo";
1976
+ const indexNameSafe = parseSqlIdentifier(indexName, "index name");
1977
+ const checkRequest = this.pool.request();
1978
+ checkRequest.input("indexName", indexNameSafe);
1979
+ checkRequest.input("schemaName", schemaName);
1980
+ const result = await checkRequest.query(`
1981
+ SELECT t.name as table_name
1982
+ FROM sys.indexes i
1983
+ INNER JOIN sys.tables t ON i.object_id = t.object_id
1984
+ INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
1985
+ WHERE i.name = @indexName
1986
+ AND s.name = @schemaName
1987
+ `);
1988
+ if (!result.recordset || result.recordset.length === 0) {
1989
+ return;
1990
+ }
1991
+ if (result.recordset.length > 1) {
1992
+ const tables = result.recordset.map((r) => r.table_name).join(", ");
1993
+ throw new MastraError({
1994
+ id: "MASTRA_STORAGE_MSSQL_INDEX_AMBIGUOUS",
1995
+ domain: ErrorDomain.STORAGE,
1996
+ category: ErrorCategory.USER,
1997
+ text: `Index "${indexNameSafe}" exists on multiple tables (${tables}) in schema "${schemaName}". Please drop indexes manually or ensure unique index names.`
1998
+ });
1999
+ }
2000
+ const tableName = result.recordset[0].table_name;
2001
+ const fullTableName = getTableName({
2002
+ indexName: tableName,
2003
+ schemaName: getSchemaName(this.schemaName)
2004
+ });
2005
+ const dropSql = `DROP INDEX [${indexNameSafe}] ON ${fullTableName}`;
2006
+ await this.pool.request().query(dropSql);
2007
+ } catch (error) {
2008
+ throw new MastraError(
2009
+ {
2010
+ id: "MASTRA_STORAGE_MSSQL_INDEX_DROP_FAILED",
1326
2011
  domain: ErrorDomain.STORAGE,
1327
2012
  category: ErrorCategory.THIRD_PARTY,
1328
2013
  details: {
1329
- tableName
2014
+ indexName
1330
2015
  }
1331
2016
  },
1332
2017
  error
1333
2018
  );
1334
2019
  }
1335
2020
  }
1336
- async load({ tableName, keys }) {
2021
+ /**
2022
+ * List indexes for a specific table or all tables
2023
+ */
2024
+ async listIndexes(tableName) {
1337
2025
  try {
1338
- const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
1339
- const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
1340
- const values = keyEntries.map(([_, value]) => value);
1341
- const sql7 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
2026
+ const schemaName = this.schemaName || "dbo";
2027
+ let query;
1342
2028
  const request = this.pool.request();
1343
- values.forEach((value, i) => {
1344
- request.input(`param${i}`, value);
1345
- });
1346
- const resultSet = await request.query(sql7);
1347
- const result = resultSet.recordset[0] || null;
1348
- if (!result) {
1349
- return null;
2029
+ request.input("schemaName", schemaName);
2030
+ if (tableName) {
2031
+ query = `
2032
+ SELECT
2033
+ i.name as name,
2034
+ o.name as [table],
2035
+ i.is_unique as is_unique,
2036
+ CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
2037
+ FROM sys.indexes i
2038
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
2039
+ INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
2040
+ LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
2041
+ WHERE sch.name = @schemaName
2042
+ AND o.name = @tableName
2043
+ AND i.name IS NOT NULL
2044
+ GROUP BY i.name, o.name, i.is_unique
2045
+ `;
2046
+ request.input("tableName", tableName);
2047
+ } else {
2048
+ query = `
2049
+ SELECT
2050
+ i.name as name,
2051
+ o.name as [table],
2052
+ i.is_unique as is_unique,
2053
+ CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
2054
+ FROM sys.indexes i
2055
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
2056
+ INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
2057
+ LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
2058
+ WHERE sch.name = @schemaName
2059
+ AND i.name IS NOT NULL
2060
+ GROUP BY i.name, o.name, i.is_unique
2061
+ `;
1350
2062
  }
1351
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
1352
- const snapshot = result;
1353
- if (typeof snapshot.snapshot === "string") {
1354
- snapshot.snapshot = JSON.parse(snapshot.snapshot);
1355
- }
1356
- return snapshot;
2063
+ const result = await request.query(query);
2064
+ const indexes = [];
2065
+ for (const row of result.recordset) {
2066
+ const colRequest = this.pool.request();
2067
+ colRequest.input("indexName", row.name);
2068
+ colRequest.input("schemaName", schemaName);
2069
+ const colResult = await colRequest.query(`
2070
+ SELECT c.name as column_name
2071
+ FROM sys.indexes i
2072
+ INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
2073
+ INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
2074
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
2075
+ INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
2076
+ WHERE i.name = @indexName
2077
+ AND s.name = @schemaName
2078
+ ORDER BY ic.key_ordinal
2079
+ `);
2080
+ indexes.push({
2081
+ name: row.name,
2082
+ table: row.table,
2083
+ columns: colResult.recordset.map((c) => c.column_name),
2084
+ unique: row.is_unique || false,
2085
+ size: row.size || "0 MB",
2086
+ definition: ""
2087
+ // MSSQL doesn't store definition like PG
2088
+ });
1357
2089
  }
1358
- return result;
2090
+ return indexes;
1359
2091
  } catch (error) {
1360
2092
  throw new MastraError(
1361
2093
  {
1362
- id: "MASTRA_STORAGE_MSSQL_STORE_LOAD_FAILED",
2094
+ id: "MASTRA_STORAGE_MSSQL_INDEX_LIST_FAILED",
1363
2095
  domain: ErrorDomain.STORAGE,
1364
2096
  category: ErrorCategory.THIRD_PARTY,
1365
- details: {
2097
+ details: tableName ? {
1366
2098
  tableName
1367
- }
2099
+ } : {}
1368
2100
  },
1369
2101
  error
1370
2102
  );
1371
2103
  }
1372
2104
  }
1373
- async batchInsert({ tableName, records }) {
1374
- const transaction = this.pool.transaction();
2105
+ /**
2106
+ * Get detailed statistics for a specific index
2107
+ */
2108
+ async describeIndex(indexName) {
1375
2109
  try {
1376
- await transaction.begin();
1377
- for (const record of records) {
1378
- await this.insert({ tableName, record });
2110
+ const schemaName = this.schemaName || "dbo";
2111
+ const request = this.pool.request();
2112
+ request.input("indexName", indexName);
2113
+ request.input("schemaName", schemaName);
2114
+ const query = `
2115
+ SELECT
2116
+ i.name as name,
2117
+ o.name as [table],
2118
+ i.is_unique as is_unique,
2119
+ CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size,
2120
+ i.type_desc as method,
2121
+ ISNULL(us.user_scans, 0) as scans,
2122
+ ISNULL(us.user_seeks + us.user_scans, 0) as tuples_read,
2123
+ ISNULL(us.user_lookups, 0) as tuples_fetched
2124
+ FROM sys.indexes i
2125
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
2126
+ INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
2127
+ LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
2128
+ LEFT JOIN sys.dm_db_index_usage_stats us ON i.object_id = us.object_id AND i.index_id = us.index_id
2129
+ WHERE i.name = @indexName
2130
+ AND sch.name = @schemaName
2131
+ GROUP BY i.name, o.name, i.is_unique, i.type_desc, us.user_seeks, us.user_scans, us.user_lookups
2132
+ `;
2133
+ const result = await request.query(query);
2134
+ if (!result.recordset || result.recordset.length === 0) {
2135
+ throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
1379
2136
  }
1380
- await transaction.commit();
2137
+ const row = result.recordset[0];
2138
+ const colRequest = this.pool.request();
2139
+ colRequest.input("indexName", indexName);
2140
+ colRequest.input("schemaName", schemaName);
2141
+ const colResult = await colRequest.query(`
2142
+ SELECT c.name as column_name
2143
+ FROM sys.indexes i
2144
+ INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
2145
+ INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
2146
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
2147
+ INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
2148
+ WHERE i.name = @indexName
2149
+ AND s.name = @schemaName
2150
+ ORDER BY ic.key_ordinal
2151
+ `);
2152
+ return {
2153
+ name: row.name,
2154
+ table: row.table,
2155
+ columns: colResult.recordset.map((c) => c.column_name),
2156
+ unique: row.is_unique || false,
2157
+ size: row.size || "0 MB",
2158
+ definition: "",
2159
+ method: row.method?.toLowerCase() || "nonclustered",
2160
+ scans: Number(row.scans) || 0,
2161
+ tuples_read: Number(row.tuples_read) || 0,
2162
+ tuples_fetched: Number(row.tuples_fetched) || 0
2163
+ };
1381
2164
  } catch (error) {
1382
- await transaction.rollback();
1383
2165
  throw new MastraError(
1384
2166
  {
1385
- id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_INSERT_FAILED",
2167
+ id: "MASTRA_STORAGE_MSSQL_INDEX_DESCRIBE_FAILED",
1386
2168
  domain: ErrorDomain.STORAGE,
1387
2169
  category: ErrorCategory.THIRD_PARTY,
1388
2170
  details: {
1389
- tableName,
1390
- numberOfRecords: records.length
2171
+ indexName
1391
2172
  }
1392
2173
  },
1393
2174
  error
1394
2175
  );
1395
2176
  }
1396
2177
  }
1397
- async dropTable({ tableName }) {
2178
+ /**
2179
+ * Returns definitions for automatic performance indexes
2180
+ * IMPORTANT: Uses seq_id DESC instead of createdAt DESC for MSSQL due to millisecond accuracy limitations
2181
+ * NOTE: Using NVARCHAR(400) for text columns (800 bytes) leaves room for composite indexes
2182
+ */
2183
+ getAutomaticIndexDefinitions() {
2184
+ const schemaPrefix = this.schemaName ? `${this.schemaName}_` : "";
2185
+ return [
2186
+ // Composite indexes for optimal filtering + sorting performance
2187
+ // NVARCHAR(400) = 800 bytes, plus BIGINT (8 bytes) = 808 bytes total (under 900-byte limit)
2188
+ {
2189
+ name: `${schemaPrefix}mastra_threads_resourceid_seqid_idx`,
2190
+ table: TABLE_THREADS,
2191
+ columns: ["resourceId", "seq_id DESC"]
2192
+ },
2193
+ {
2194
+ name: `${schemaPrefix}mastra_messages_thread_id_seqid_idx`,
2195
+ table: TABLE_MESSAGES,
2196
+ columns: ["thread_id", "seq_id DESC"]
2197
+ },
2198
+ {
2199
+ name: `${schemaPrefix}mastra_traces_name_seqid_idx`,
2200
+ table: TABLE_TRACES,
2201
+ columns: ["name", "seq_id DESC"]
2202
+ },
2203
+ {
2204
+ name: `${schemaPrefix}mastra_scores_trace_id_span_id_seqid_idx`,
2205
+ table: TABLE_SCORERS,
2206
+ columns: ["traceId", "spanId", "seq_id DESC"]
2207
+ },
2208
+ // Spans indexes for optimal trace querying
2209
+ {
2210
+ name: `${schemaPrefix}mastra_ai_spans_traceid_startedat_idx`,
2211
+ table: TABLE_SPANS,
2212
+ columns: ["traceId", "startedAt DESC"]
2213
+ },
2214
+ {
2215
+ name: `${schemaPrefix}mastra_ai_spans_parentspanid_startedat_idx`,
2216
+ table: TABLE_SPANS,
2217
+ columns: ["parentSpanId", "startedAt DESC"]
2218
+ },
2219
+ {
2220
+ name: `${schemaPrefix}mastra_ai_spans_name_idx`,
2221
+ table: TABLE_SPANS,
2222
+ columns: ["name"]
2223
+ },
2224
+ {
2225
+ name: `${schemaPrefix}mastra_ai_spans_spantype_startedat_idx`,
2226
+ table: TABLE_SPANS,
2227
+ columns: ["spanType", "startedAt DESC"]
2228
+ }
2229
+ ];
2230
+ }
2231
+ /**
2232
+ * Creates automatic indexes for optimal query performance
2233
+ * Uses getAutomaticIndexDefinitions() to determine which indexes to create
2234
+ */
2235
+ async createAutomaticIndexes() {
1398
2236
  try {
1399
- const tableNameWithSchema = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1400
- await this.pool.request().query(`DROP TABLE IF EXISTS ${tableNameWithSchema}`);
2237
+ const indexes = this.getAutomaticIndexDefinitions();
2238
+ for (const indexOptions of indexes) {
2239
+ try {
2240
+ await this.createIndex(indexOptions);
2241
+ } catch (error) {
2242
+ this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
2243
+ }
2244
+ }
1401
2245
  } catch (error) {
1402
2246
  throw new MastraError(
1403
2247
  {
1404
- id: "MASTRA_STORAGE_MSSQL_STORE_DROP_TABLE_FAILED",
2248
+ id: "MASTRA_STORAGE_MSSQL_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
1405
2249
  domain: ErrorDomain.STORAGE,
1406
- category: ErrorCategory.THIRD_PARTY,
1407
- details: {
1408
- tableName
1409
- }
2250
+ category: ErrorCategory.THIRD_PARTY
1410
2251
  },
1411
2252
  error
1412
2253
  );
1413
2254
  }
1414
2255
  }
1415
2256
  };
1416
- function parseJSON(jsonString) {
1417
- try {
1418
- return JSON.parse(jsonString);
1419
- } catch {
1420
- return jsonString;
1421
- }
1422
- }
1423
2257
  function transformScoreRow(row) {
1424
2258
  return {
1425
2259
  ...row,
1426
- input: parseJSON(row.input),
1427
- scorer: parseJSON(row.scorer),
1428
- preprocessStepResult: parseJSON(row.preprocessStepResult),
1429
- analyzeStepResult: parseJSON(row.analyzeStepResult),
1430
- metadata: parseJSON(row.metadata),
1431
- output: parseJSON(row.output),
1432
- additionalContext: parseJSON(row.additionalContext),
1433
- runtimeContext: parseJSON(row.runtimeContext),
1434
- entity: parseJSON(row.entity),
1435
- createdAt: row.createdAt,
1436
- updatedAt: row.updatedAt
2260
+ input: safelyParseJSON(row.input),
2261
+ scorer: safelyParseJSON(row.scorer),
2262
+ preprocessStepResult: safelyParseJSON(row.preprocessStepResult),
2263
+ analyzeStepResult: safelyParseJSON(row.analyzeStepResult),
2264
+ metadata: safelyParseJSON(row.metadata),
2265
+ output: safelyParseJSON(row.output),
2266
+ additionalContext: safelyParseJSON(row.additionalContext),
2267
+ requestContext: safelyParseJSON(row.requestContext),
2268
+ entity: safelyParseJSON(row.entity),
2269
+ createdAt: new Date(row.createdAt),
2270
+ updatedAt: new Date(row.updatedAt)
1437
2271
  };
1438
2272
  }
1439
2273
  var ScoresMSSQL = class extends ScoresStorage {
@@ -1488,7 +2322,7 @@ var ScoresMSSQL = class extends ScoresStorage {
1488
2322
  );
1489
2323
  }
1490
2324
  try {
1491
- const scoreId = crypto.randomUUID();
2325
+ const scoreId = randomUUID();
1492
2326
  const {
1493
2327
  scorer,
1494
2328
  preprocessStepResult,
@@ -1497,7 +2331,7 @@ var ScoresMSSQL = class extends ScoresStorage {
1497
2331
  input,
1498
2332
  output,
1499
2333
  additionalContext,
1500
- runtimeContext,
2334
+ requestContext,
1501
2335
  entity,
1502
2336
  ...rest
1503
2337
  } = validatedScore;
@@ -1506,15 +2340,15 @@ var ScoresMSSQL = class extends ScoresStorage {
1506
2340
  record: {
1507
2341
  id: scoreId,
1508
2342
  ...rest,
1509
- input: JSON.stringify(input) || "",
1510
- output: JSON.stringify(output) || "",
1511
- preprocessStepResult: preprocessStepResult ? JSON.stringify(preprocessStepResult) : null,
1512
- analyzeStepResult: analyzeStepResult ? JSON.stringify(analyzeStepResult) : null,
1513
- metadata: metadata ? JSON.stringify(metadata) : null,
1514
- additionalContext: additionalContext ? JSON.stringify(additionalContext) : null,
1515
- runtimeContext: runtimeContext ? JSON.stringify(runtimeContext) : null,
1516
- entity: entity ? JSON.stringify(entity) : null,
1517
- scorer: scorer ? JSON.stringify(scorer) : null,
2343
+ input: input || "",
2344
+ output: output || "",
2345
+ preprocessStepResult: preprocessStepResult || null,
2346
+ analyzeStepResult: analyzeStepResult || null,
2347
+ metadata: metadata || null,
2348
+ additionalContext: additionalContext || null,
2349
+ requestContext: requestContext || null,
2350
+ entity: entity || null,
2351
+ scorer: scorer || null,
1518
2352
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1519
2353
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1520
2354
  }
@@ -1532,41 +2366,70 @@ var ScoresMSSQL = class extends ScoresStorage {
1532
2366
  );
1533
2367
  }
1534
2368
  }
1535
- async getScoresByScorerId({
2369
+ async listScoresByScorerId({
1536
2370
  scorerId,
1537
- pagination
2371
+ pagination,
2372
+ entityId,
2373
+ entityType,
2374
+ source
1538
2375
  }) {
1539
2376
  try {
1540
- const request = this.pool.request();
1541
- request.input("p1", scorerId);
1542
- const totalResult = await request.query(
1543
- `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [scorerId] = @p1`
1544
- );
2377
+ const conditions = ["[scorerId] = @p1"];
2378
+ const params = { p1: scorerId };
2379
+ let paramIndex = 2;
2380
+ if (entityId) {
2381
+ conditions.push(`[entityId] = @p${paramIndex}`);
2382
+ params[`p${paramIndex}`] = entityId;
2383
+ paramIndex++;
2384
+ }
2385
+ if (entityType) {
2386
+ conditions.push(`[entityType] = @p${paramIndex}`);
2387
+ params[`p${paramIndex}`] = entityType;
2388
+ paramIndex++;
2389
+ }
2390
+ if (source) {
2391
+ conditions.push(`[source] = @p${paramIndex}`);
2392
+ params[`p${paramIndex}`] = source;
2393
+ paramIndex++;
2394
+ }
2395
+ const whereClause = conditions.join(" AND ");
2396
+ const tableName = getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) });
2397
+ const countRequest = this.pool.request();
2398
+ Object.entries(params).forEach(([key, value]) => {
2399
+ countRequest.input(key, value);
2400
+ });
2401
+ const totalResult = await countRequest.query(`SELECT COUNT(*) as count FROM ${tableName} WHERE ${whereClause}`);
1545
2402
  const total = totalResult.recordset[0]?.count || 0;
2403
+ const { page, perPage: perPageInput } = pagination;
1546
2404
  if (total === 0) {
1547
2405
  return {
1548
2406
  pagination: {
1549
2407
  total: 0,
1550
- page: pagination.page,
1551
- perPage: pagination.perPage,
2408
+ page,
2409
+ perPage: perPageInput,
1552
2410
  hasMore: false
1553
2411
  },
1554
2412
  scores: []
1555
2413
  };
1556
2414
  }
2415
+ const perPage = normalizePerPage(perPageInput, 100);
2416
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2417
+ const limitValue = perPageInput === false ? total : perPage;
2418
+ const end = perPageInput === false ? total : start + perPage;
1557
2419
  const dataRequest = this.pool.request();
1558
- dataRequest.input("p1", scorerId);
1559
- dataRequest.input("p2", pagination.perPage);
1560
- dataRequest.input("p3", pagination.page * pagination.perPage);
1561
- const result = await dataRequest.query(
1562
- `SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [scorerId] = @p1 ORDER BY [createdAt] DESC OFFSET @p3 ROWS FETCH NEXT @p2 ROWS ONLY`
1563
- );
2420
+ Object.entries(params).forEach(([key, value]) => {
2421
+ dataRequest.input(key, value);
2422
+ });
2423
+ dataRequest.input("perPage", limitValue);
2424
+ dataRequest.input("offset", start);
2425
+ const dataQuery = `SELECT * FROM ${tableName} WHERE ${whereClause} ORDER BY [createdAt] DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
2426
+ const result = await dataRequest.query(dataQuery);
1564
2427
  return {
1565
2428
  pagination: {
1566
2429
  total: Number(total),
1567
- page: pagination.page,
1568
- perPage: pagination.perPage,
1569
- hasMore: Number(total) > (pagination.page + 1) * pagination.perPage
2430
+ page,
2431
+ perPage: perPageForResponse,
2432
+ hasMore: end < total
1570
2433
  },
1571
2434
  scores: result.recordset.map((row) => transformScoreRow(row))
1572
2435
  };
@@ -1582,7 +2445,7 @@ var ScoresMSSQL = class extends ScoresStorage {
1582
2445
  );
1583
2446
  }
1584
2447
  }
1585
- async getScoresByRunId({
2448
+ async listScoresByRunId({
1586
2449
  runId,
1587
2450
  pagination
1588
2451
  }) {
@@ -1593,30 +2456,35 @@ var ScoresMSSQL = class extends ScoresStorage {
1593
2456
  `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1`
1594
2457
  );
1595
2458
  const total = totalResult.recordset[0]?.count || 0;
2459
+ const { page, perPage: perPageInput } = pagination;
1596
2460
  if (total === 0) {
1597
2461
  return {
1598
2462
  pagination: {
1599
2463
  total: 0,
1600
- page: pagination.page,
1601
- perPage: pagination.perPage,
2464
+ page,
2465
+ perPage: perPageInput,
1602
2466
  hasMore: false
1603
2467
  },
1604
2468
  scores: []
1605
2469
  };
1606
2470
  }
2471
+ const perPage = normalizePerPage(perPageInput, 100);
2472
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2473
+ const limitValue = perPageInput === false ? total : perPage;
2474
+ const end = perPageInput === false ? total : start + perPage;
1607
2475
  const dataRequest = this.pool.request();
1608
2476
  dataRequest.input("p1", runId);
1609
- dataRequest.input("p2", pagination.perPage);
1610
- dataRequest.input("p3", pagination.page * pagination.perPage);
2477
+ dataRequest.input("p2", limitValue);
2478
+ dataRequest.input("p3", start);
1611
2479
  const result = await dataRequest.query(
1612
2480
  `SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1 ORDER BY [createdAt] DESC OFFSET @p3 ROWS FETCH NEXT @p2 ROWS ONLY`
1613
2481
  );
1614
2482
  return {
1615
2483
  pagination: {
1616
2484
  total: Number(total),
1617
- page: pagination.page,
1618
- perPage: pagination.perPage,
1619
- hasMore: Number(total) > (pagination.page + 1) * pagination.perPage
2485
+ page,
2486
+ perPage: perPageForResponse,
2487
+ hasMore: end < total
1620
2488
  },
1621
2489
  scores: result.recordset.map((row) => transformScoreRow(row))
1622
2490
  };
@@ -1632,7 +2500,7 @@ var ScoresMSSQL = class extends ScoresStorage {
1632
2500
  );
1633
2501
  }
1634
2502
  }
1635
- async getScoresByEntityId({
2503
+ async listScoresByEntityId({
1636
2504
  entityId,
1637
2505
  entityType,
1638
2506
  pagination
@@ -1645,31 +2513,36 @@ var ScoresMSSQL = class extends ScoresStorage {
1645
2513
  `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2`
1646
2514
  );
1647
2515
  const total = totalResult.recordset[0]?.count || 0;
2516
+ const { page, perPage: perPageInput } = pagination;
2517
+ const perPage = normalizePerPage(perPageInput, 100);
2518
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1648
2519
  if (total === 0) {
1649
2520
  return {
1650
2521
  pagination: {
1651
2522
  total: 0,
1652
- page: pagination.page,
1653
- perPage: pagination.perPage,
2523
+ page,
2524
+ perPage: perPageForResponse,
1654
2525
  hasMore: false
1655
2526
  },
1656
2527
  scores: []
1657
2528
  };
1658
2529
  }
2530
+ const limitValue = perPageInput === false ? total : perPage;
2531
+ const end = perPageInput === false ? total : start + perPage;
1659
2532
  const dataRequest = this.pool.request();
1660
2533
  dataRequest.input("p1", entityId);
1661
2534
  dataRequest.input("p2", entityType);
1662
- dataRequest.input("p3", pagination.perPage);
1663
- dataRequest.input("p4", pagination.page * pagination.perPage);
2535
+ dataRequest.input("p3", limitValue);
2536
+ dataRequest.input("p4", start);
1664
2537
  const result = await dataRequest.query(
1665
2538
  `SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
1666
2539
  );
1667
2540
  return {
1668
2541
  pagination: {
1669
2542
  total: Number(total),
1670
- page: pagination.page,
1671
- perPage: pagination.perPage,
1672
- hasMore: Number(total) > (pagination.page + 1) * pagination.perPage
2543
+ page,
2544
+ perPage: perPageForResponse,
2545
+ hasMore: end < total
1673
2546
  },
1674
2547
  scores: result.recordset.map((row) => transformScoreRow(row))
1675
2548
  };
@@ -1685,7 +2558,7 @@ var ScoresMSSQL = class extends ScoresStorage {
1685
2558
  );
1686
2559
  }
1687
2560
  }
1688
- async getScoresBySpan({
2561
+ async listScoresBySpan({
1689
2562
  traceId,
1690
2563
  spanId,
1691
2564
  pagination
@@ -1698,34 +2571,38 @@ var ScoresMSSQL = class extends ScoresStorage {
1698
2571
  `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2`
1699
2572
  );
1700
2573
  const total = totalResult.recordset[0]?.count || 0;
2574
+ const { page, perPage: perPageInput } = pagination;
2575
+ const perPage = normalizePerPage(perPageInput, 100);
2576
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1701
2577
  if (total === 0) {
1702
2578
  return {
1703
2579
  pagination: {
1704
2580
  total: 0,
1705
- page: pagination.page,
1706
- perPage: pagination.perPage,
2581
+ page,
2582
+ perPage: perPageForResponse,
1707
2583
  hasMore: false
1708
2584
  },
1709
2585
  scores: []
1710
2586
  };
1711
2587
  }
1712
- const limit = pagination.perPage + 1;
2588
+ const limitValue = perPageInput === false ? total : perPage;
2589
+ const end = perPageInput === false ? total : start + perPage;
1713
2590
  const dataRequest = this.pool.request();
1714
2591
  dataRequest.input("p1", traceId);
1715
2592
  dataRequest.input("p2", spanId);
1716
- dataRequest.input("p3", limit);
1717
- dataRequest.input("p4", pagination.page * pagination.perPage);
2593
+ dataRequest.input("p3", limitValue);
2594
+ dataRequest.input("p4", start);
1718
2595
  const result = await dataRequest.query(
1719
2596
  `SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
1720
2597
  );
1721
2598
  return {
1722
2599
  pagination: {
1723
2600
  total: Number(total),
1724
- page: pagination.page,
1725
- perPage: pagination.perPage,
1726
- hasMore: result.recordset.length > pagination.perPage
2601
+ page,
2602
+ perPage: perPageForResponse,
2603
+ hasMore: end < total
1727
2604
  },
1728
- scores: result.recordset.slice(0, pagination.perPage).map((row) => transformScoreRow(row))
2605
+ scores: result.recordset.map((row) => transformScoreRow(row))
1729
2606
  };
1730
2607
  } catch (error) {
1731
2608
  throw new MastraError(
@@ -1740,7 +2617,7 @@ var ScoresMSSQL = class extends ScoresStorage {
1740
2617
  }
1741
2618
  }
1742
2619
  };
1743
- var TracesMSSQL = class extends TracesStorage {
2620
+ var WorkflowsMSSQL = class extends WorkflowsStorage {
1744
2621
  pool;
1745
2622
  operations;
1746
2623
  schema;
@@ -1754,207 +2631,165 @@ var TracesMSSQL = class extends TracesStorage {
1754
2631
  this.operations = operations;
1755
2632
  this.schema = schema;
1756
2633
  }
1757
- /** @deprecated use getTracesPaginated instead*/
1758
- async getTraces(args) {
1759
- if (args.fromDate || args.toDate) {
1760
- args.dateRange = {
1761
- start: args.fromDate,
1762
- end: args.toDate
1763
- };
2634
+ parseWorkflowRun(row) {
2635
+ let parsedSnapshot = row.snapshot;
2636
+ if (typeof parsedSnapshot === "string") {
2637
+ try {
2638
+ parsedSnapshot = JSON.parse(row.snapshot);
2639
+ } catch (e) {
2640
+ this.logger?.warn?.(`Failed to parse snapshot for workflow ${row.workflow_name}:`, e);
2641
+ }
1764
2642
  }
1765
- const result = await this.getTracesPaginated(args);
1766
- return result.traces;
2643
+ return {
2644
+ workflowName: row.workflow_name,
2645
+ runId: row.run_id,
2646
+ snapshot: parsedSnapshot,
2647
+ createdAt: row.createdAt,
2648
+ updatedAt: row.updatedAt,
2649
+ resourceId: row.resourceId
2650
+ };
1767
2651
  }
1768
- async getTracesPaginated(args) {
1769
- const { name, scope, page = 0, perPage: perPageInput, attributes, filters, dateRange } = args;
1770
- const fromDate = dateRange?.start;
1771
- const toDate = dateRange?.end;
1772
- const perPage = perPageInput !== void 0 ? perPageInput : 100;
1773
- const currentOffset = page * perPage;
1774
- const paramMap = {};
1775
- const conditions = [];
1776
- let paramIndex = 1;
1777
- if (name) {
1778
- const paramName = `p${paramIndex++}`;
1779
- conditions.push(`[name] LIKE @${paramName}`);
1780
- paramMap[paramName] = `${name}%`;
1781
- }
1782
- if (scope) {
1783
- const paramName = `p${paramIndex++}`;
1784
- conditions.push(`[scope] = @${paramName}`);
1785
- paramMap[paramName] = scope;
1786
- }
1787
- if (attributes) {
1788
- Object.entries(attributes).forEach(([key, value]) => {
1789
- const parsedKey = parseFieldKey(key);
1790
- const paramName = `p${paramIndex++}`;
1791
- conditions.push(`JSON_VALUE([attributes], '$.${parsedKey}') = @${paramName}`);
1792
- paramMap[paramName] = value;
1793
- });
1794
- }
1795
- if (filters) {
1796
- Object.entries(filters).forEach(([key, value]) => {
1797
- const parsedKey = parseFieldKey(key);
1798
- const paramName = `p${paramIndex++}`;
1799
- conditions.push(`[${parsedKey}] = @${paramName}`);
1800
- paramMap[paramName] = value;
1801
- });
1802
- }
1803
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
1804
- const paramName = `p${paramIndex++}`;
1805
- conditions.push(`[createdAt] >= @${paramName}`);
1806
- paramMap[paramName] = fromDate.toISOString();
1807
- }
1808
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
1809
- const paramName = `p${paramIndex++}`;
1810
- conditions.push(`[createdAt] <= @${paramName}`);
1811
- paramMap[paramName] = toDate.toISOString();
1812
- }
1813
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1814
- const countQuery = `SELECT COUNT(*) as total FROM ${getTableName({ indexName: TABLE_TRACES, schemaName: getSchemaName(this.schema) })} ${whereClause}`;
1815
- let total = 0;
2652
+ async updateWorkflowResults({
2653
+ workflowName,
2654
+ runId,
2655
+ stepId,
2656
+ result,
2657
+ requestContext
2658
+ }) {
2659
+ const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
2660
+ const transaction = this.pool.transaction();
1816
2661
  try {
1817
- const countRequest = this.pool.request();
1818
- Object.entries(paramMap).forEach(([key, value]) => {
1819
- if (value instanceof Date) {
1820
- countRequest.input(key, sql2.DateTime, value);
1821
- } else {
1822
- countRequest.input(key, value);
1823
- }
1824
- });
1825
- const countResult = await countRequest.query(countQuery);
1826
- total = parseInt(countResult.recordset[0].total, 10);
2662
+ await transaction.begin();
2663
+ const selectRequest = new sql2.Request(transaction);
2664
+ selectRequest.input("workflow_name", workflowName);
2665
+ selectRequest.input("run_id", runId);
2666
+ const existingSnapshotResult = await selectRequest.query(
2667
+ `SELECT snapshot FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE workflow_name = @workflow_name AND run_id = @run_id`
2668
+ );
2669
+ let snapshot;
2670
+ if (!existingSnapshotResult.recordset || existingSnapshotResult.recordset.length === 0) {
2671
+ snapshot = {
2672
+ context: {},
2673
+ activePaths: [],
2674
+ activeStepsPath: {},
2675
+ timestamp: Date.now(),
2676
+ suspendedPaths: {},
2677
+ resumeLabels: {},
2678
+ serializedStepGraph: [],
2679
+ status: "pending",
2680
+ value: {},
2681
+ waitingPaths: {},
2682
+ runId,
2683
+ requestContext: {}
2684
+ };
2685
+ } else {
2686
+ const existingSnapshot = existingSnapshotResult.recordset[0].snapshot;
2687
+ snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
2688
+ }
2689
+ snapshot.context[stepId] = result;
2690
+ snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
2691
+ const upsertReq = new sql2.Request(transaction);
2692
+ upsertReq.input("workflow_name", workflowName);
2693
+ upsertReq.input("run_id", runId);
2694
+ upsertReq.input("snapshot", JSON.stringify(snapshot));
2695
+ upsertReq.input("createdAt", sql2.DateTime2, /* @__PURE__ */ new Date());
2696
+ upsertReq.input("updatedAt", sql2.DateTime2, /* @__PURE__ */ new Date());
2697
+ await upsertReq.query(
2698
+ `MERGE ${table} AS target
2699
+ USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
2700
+ ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
2701
+ WHEN MATCHED THEN UPDATE SET snapshot = @snapshot, [updatedAt] = @updatedAt
2702
+ WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, snapshot, [createdAt], [updatedAt])
2703
+ VALUES (@workflow_name, @run_id, @snapshot, @createdAt, @updatedAt);`
2704
+ );
2705
+ await transaction.commit();
2706
+ return snapshot.context;
1827
2707
  } catch (error) {
2708
+ try {
2709
+ await transaction.rollback();
2710
+ } catch {
2711
+ }
1828
2712
  throw new MastraError(
1829
2713
  {
1830
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TOTAL_COUNT",
2714
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_RESULTS_FAILED",
1831
2715
  domain: ErrorDomain.STORAGE,
1832
2716
  category: ErrorCategory.THIRD_PARTY,
1833
2717
  details: {
1834
- name: args.name ?? "",
1835
- scope: args.scope ?? ""
2718
+ workflowName,
2719
+ runId,
2720
+ stepId
1836
2721
  }
1837
2722
  },
1838
2723
  error
1839
2724
  );
1840
2725
  }
1841
- if (total === 0) {
1842
- return {
1843
- traces: [],
1844
- total: 0,
1845
- page,
1846
- perPage,
1847
- hasMore: false
1848
- };
1849
- }
1850
- const dataQuery = `SELECT * FROM ${getTableName({ indexName: TABLE_TRACES, schemaName: getSchemaName(this.schema) })} ${whereClause} ORDER BY [seq_id] DESC OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
1851
- const dataRequest = this.pool.request();
1852
- Object.entries(paramMap).forEach(([key, value]) => {
1853
- if (value instanceof Date) {
1854
- dataRequest.input(key, sql2.DateTime, value);
1855
- } else {
1856
- dataRequest.input(key, value);
1857
- }
1858
- });
1859
- dataRequest.input("offset", currentOffset);
1860
- dataRequest.input("limit", perPage);
2726
+ }
2727
+ async updateWorkflowState({
2728
+ workflowName,
2729
+ runId,
2730
+ opts
2731
+ }) {
2732
+ const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
2733
+ const transaction = this.pool.transaction();
1861
2734
  try {
1862
- const rowsResult = await dataRequest.query(dataQuery);
1863
- const rows = rowsResult.recordset;
1864
- const traces = rows.map((row) => ({
1865
- id: row.id,
1866
- parentSpanId: row.parentSpanId,
1867
- traceId: row.traceId,
1868
- name: row.name,
1869
- scope: row.scope,
1870
- kind: row.kind,
1871
- status: JSON.parse(row.status),
1872
- events: JSON.parse(row.events),
1873
- links: JSON.parse(row.links),
1874
- attributes: JSON.parse(row.attributes),
1875
- startTime: row.startTime,
1876
- endTime: row.endTime,
1877
- other: row.other,
1878
- createdAt: row.createdAt
1879
- }));
1880
- return {
1881
- traces,
1882
- total,
1883
- page,
1884
- perPage,
1885
- hasMore: currentOffset + traces.length < total
1886
- };
2735
+ await transaction.begin();
2736
+ const selectRequest = new sql2.Request(transaction);
2737
+ selectRequest.input("workflow_name", workflowName);
2738
+ selectRequest.input("run_id", runId);
2739
+ const existingSnapshotResult = await selectRequest.query(
2740
+ `SELECT snapshot FROM ${table} WITH (UPDLOCK, HOLDLOCK) WHERE workflow_name = @workflow_name AND run_id = @run_id`
2741
+ );
2742
+ if (!existingSnapshotResult.recordset || existingSnapshotResult.recordset.length === 0) {
2743
+ await transaction.rollback();
2744
+ return void 0;
2745
+ }
2746
+ const existingSnapshot = existingSnapshotResult.recordset[0].snapshot;
2747
+ const snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
2748
+ if (!snapshot || !snapshot?.context) {
2749
+ await transaction.rollback();
2750
+ throw new MastraError(
2751
+ {
2752
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_SNAPSHOT_NOT_FOUND",
2753
+ domain: ErrorDomain.STORAGE,
2754
+ category: ErrorCategory.SYSTEM,
2755
+ details: {
2756
+ workflowName,
2757
+ runId
2758
+ }
2759
+ },
2760
+ new Error(`Snapshot not found for runId ${runId}`)
2761
+ );
2762
+ }
2763
+ const updatedSnapshot = { ...snapshot, ...opts };
2764
+ const updateRequest = new sql2.Request(transaction);
2765
+ updateRequest.input("snapshot", JSON.stringify(updatedSnapshot));
2766
+ updateRequest.input("workflow_name", workflowName);
2767
+ updateRequest.input("run_id", runId);
2768
+ updateRequest.input("updatedAt", sql2.DateTime2, /* @__PURE__ */ new Date());
2769
+ await updateRequest.query(
2770
+ `UPDATE ${table} SET snapshot = @snapshot, [updatedAt] = @updatedAt WHERE workflow_name = @workflow_name AND run_id = @run_id`
2771
+ );
2772
+ await transaction.commit();
2773
+ return updatedSnapshot;
1887
2774
  } catch (error) {
2775
+ try {
2776
+ await transaction.rollback();
2777
+ } catch {
2778
+ }
1888
2779
  throw new MastraError(
1889
2780
  {
1890
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TRACES",
2781
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_FAILED",
1891
2782
  domain: ErrorDomain.STORAGE,
1892
2783
  category: ErrorCategory.THIRD_PARTY,
1893
2784
  details: {
1894
- name: args.name ?? "",
1895
- scope: args.scope ?? ""
2785
+ workflowName,
2786
+ runId
1896
2787
  }
1897
2788
  },
1898
2789
  error
1899
2790
  );
1900
2791
  }
1901
2792
  }
1902
- async batchTraceInsert({ records }) {
1903
- this.logger.debug("Batch inserting traces", { count: records.length });
1904
- await this.operations.batchInsert({
1905
- tableName: TABLE_TRACES,
1906
- records
1907
- });
1908
- }
1909
- };
1910
- function parseWorkflowRun(row) {
1911
- let parsedSnapshot = row.snapshot;
1912
- if (typeof parsedSnapshot === "string") {
1913
- try {
1914
- parsedSnapshot = JSON.parse(row.snapshot);
1915
- } catch (e) {
1916
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1917
- }
1918
- }
1919
- return {
1920
- workflowName: row.workflow_name,
1921
- runId: row.run_id,
1922
- snapshot: parsedSnapshot,
1923
- createdAt: row.createdAt,
1924
- updatedAt: row.updatedAt,
1925
- resourceId: row.resourceId
1926
- };
1927
- }
1928
- var WorkflowsMSSQL = class extends WorkflowsStorage {
1929
- pool;
1930
- operations;
1931
- schema;
1932
- constructor({
1933
- pool,
1934
- operations,
1935
- schema
1936
- }) {
1937
- super();
1938
- this.pool = pool;
1939
- this.operations = operations;
1940
- this.schema = schema;
1941
- }
1942
- updateWorkflowResults({
1943
- // workflowName,
1944
- // runId,
1945
- // stepId,
1946
- // result,
1947
- // runtimeContext,
1948
- }) {
1949
- throw new Error("Method not implemented.");
1950
- }
1951
- updateWorkflowState({
1952
- // workflowName,
1953
- // runId,
1954
- // opts,
1955
- }) {
1956
- throw new Error("Method not implemented.");
1957
- }
1958
2793
  async persistWorkflowSnapshot({
1959
2794
  workflowName,
1960
2795
  runId,
@@ -2051,7 +2886,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
2051
2886
  if (!result.recordset || result.recordset.length === 0) {
2052
2887
  return null;
2053
2888
  }
2054
- return parseWorkflowRun(result.recordset[0]);
2889
+ return this.parseWorkflowRun(result.recordset[0]);
2055
2890
  } catch (error) {
2056
2891
  throw new MastraError(
2057
2892
  {
@@ -2067,13 +2902,14 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
2067
2902
  );
2068
2903
  }
2069
2904
  }
2070
- async getWorkflowRuns({
2905
+ async listWorkflowRuns({
2071
2906
  workflowName,
2072
2907
  fromDate,
2073
2908
  toDate,
2074
- limit,
2075
- offset,
2076
- resourceId
2909
+ page,
2910
+ perPage,
2911
+ resourceId,
2912
+ status
2077
2913
  } = {}) {
2078
2914
  try {
2079
2915
  const conditions = [];
@@ -2082,13 +2918,17 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
2082
2918
  conditions.push(`[workflow_name] = @workflowName`);
2083
2919
  paramMap["workflowName"] = workflowName;
2084
2920
  }
2921
+ if (status) {
2922
+ conditions.push(`JSON_VALUE([snapshot], '$.status') = @status`);
2923
+ paramMap["status"] = status;
2924
+ }
2085
2925
  if (resourceId) {
2086
2926
  const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
2087
2927
  if (hasResourceId) {
2088
2928
  conditions.push(`[resourceId] = @resourceId`);
2089
2929
  paramMap["resourceId"] = resourceId;
2090
2930
  } else {
2091
- console.warn(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
2931
+ this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
2092
2932
  }
2093
2933
  }
2094
2934
  if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
@@ -2110,24 +2950,27 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
2110
2950
  request.input(key, value);
2111
2951
  }
2112
2952
  });
2113
- if (limit !== void 0 && offset !== void 0) {
2953
+ const usePagination = typeof perPage === "number" && typeof page === "number";
2954
+ if (usePagination) {
2114
2955
  const countQuery = `SELECT COUNT(*) as count FROM ${tableName} ${whereClause}`;
2115
2956
  const countResult = await request.query(countQuery);
2116
2957
  total = Number(countResult.recordset[0]?.count || 0);
2117
2958
  }
2118
2959
  let query = `SELECT * FROM ${tableName} ${whereClause} ORDER BY [seq_id] DESC`;
2119
- if (limit !== void 0 && offset !== void 0) {
2120
- query += ` OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
2121
- request.input("limit", limit);
2960
+ if (usePagination) {
2961
+ const normalizedPerPage = normalizePerPage(perPage, Number.MAX_SAFE_INTEGER);
2962
+ const offset = page * normalizedPerPage;
2963
+ query += ` OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
2964
+ request.input("perPage", normalizedPerPage);
2122
2965
  request.input("offset", offset);
2123
2966
  }
2124
2967
  const result = await request.query(query);
2125
- const runs = (result.recordset || []).map((row) => parseWorkflowRun(row));
2968
+ const runs = (result.recordset || []).map((row) => this.parseWorkflowRun(row));
2126
2969
  return { runs, total: total || runs.length };
2127
2970
  } catch (error) {
2128
2971
  throw new MastraError(
2129
2972
  {
2130
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_WORKFLOW_RUNS_FAILED",
2973
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_WORKFLOW_RUNS_FAILED",
2131
2974
  domain: ErrorDomain.STORAGE,
2132
2975
  category: ErrorCategory.THIRD_PARTY,
2133
2976
  details: {
@@ -2147,7 +2990,10 @@ var MSSQLStore = class extends MastraStorage {
2147
2990
  isConnected = null;
2148
2991
  stores;
2149
2992
  constructor(config) {
2150
- super({ name: "MSSQLStore" });
2993
+ if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
2994
+ throw new Error("MSSQLStore: id must be provided and cannot be empty.");
2995
+ }
2996
+ super({ id: config.id, name: "MSSQLStore" });
2151
2997
  try {
2152
2998
  if ("connectionString" in config) {
2153
2999
  if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
@@ -2170,19 +3016,17 @@ var MSSQLStore = class extends MastraStorage {
2170
3016
  port: config.port,
2171
3017
  options: config.options || { encrypt: true, trustServerCertificate: true }
2172
3018
  });
2173
- const legacyEvals = new LegacyEvalsMSSQL({ pool: this.pool, schema: this.schema });
2174
3019
  const operations = new StoreOperationsMSSQL({ pool: this.pool, schemaName: this.schema });
2175
3020
  const scores = new ScoresMSSQL({ pool: this.pool, operations, schema: this.schema });
2176
- const traces = new TracesMSSQL({ pool: this.pool, operations, schema: this.schema });
2177
3021
  const workflows = new WorkflowsMSSQL({ pool: this.pool, operations, schema: this.schema });
2178
3022
  const memory = new MemoryMSSQL({ pool: this.pool, schema: this.schema, operations });
3023
+ const observability = new ObservabilityMSSQL({ pool: this.pool, operations, schema: this.schema });
2179
3024
  this.stores = {
2180
3025
  operations,
2181
3026
  scores,
2182
- traces,
2183
3027
  workflows,
2184
- legacyEvals,
2185
- memory
3028
+ memory,
3029
+ observability
2186
3030
  };
2187
3031
  } catch (e) {
2188
3032
  throw new MastraError(
@@ -2202,6 +3046,11 @@ var MSSQLStore = class extends MastraStorage {
2202
3046
  try {
2203
3047
  await this.isConnected;
2204
3048
  await super.init();
3049
+ try {
3050
+ await this.stores.operations.createAutomaticIndexes();
3051
+ } catch (indexError) {
3052
+ this.logger?.warn?.("Failed to create indexes:", indexError);
3053
+ }
2205
3054
  } catch (error) {
2206
3055
  this.isConnected = null;
2207
3056
  throw new MastraError(
@@ -2229,28 +3078,11 @@ var MSSQLStore = class extends MastraStorage {
2229
3078
  hasColumn: true,
2230
3079
  createTable: true,
2231
3080
  deleteMessages: true,
2232
- getScoresBySpan: true
3081
+ listScoresBySpan: true,
3082
+ observabilityInstance: true,
3083
+ indexManagement: true
2233
3084
  };
2234
3085
  }
2235
- /** @deprecated use getEvals instead */
2236
- async getEvalsByAgentName(agentName, type) {
2237
- return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
2238
- }
2239
- async getEvals(options = {}) {
2240
- return this.stores.legacyEvals.getEvals(options);
2241
- }
2242
- /**
2243
- * @deprecated use getTracesPaginated instead
2244
- */
2245
- async getTraces(args) {
2246
- return this.stores.traces.getTraces(args);
2247
- }
2248
- async getTracesPaginated(args) {
2249
- return this.stores.traces.getTracesPaginated(args);
2250
- }
2251
- async batchTraceInsert({ records }) {
2252
- return this.stores.traces.batchTraceInsert({ records });
2253
- }
2254
3086
  async createTable({
2255
3087
  tableName,
2256
3088
  schema
@@ -2285,15 +3117,6 @@ var MSSQLStore = class extends MastraStorage {
2285
3117
  async getThreadById({ threadId }) {
2286
3118
  return this.stores.memory.getThreadById({ threadId });
2287
3119
  }
2288
- /**
2289
- * @deprecated use getThreadsByResourceIdPaginated instead
2290
- */
2291
- async getThreadsByResourceId(args) {
2292
- return this.stores.memory.getThreadsByResourceId(args);
2293
- }
2294
- async getThreadsByResourceIdPaginated(args) {
2295
- return this.stores.memory.getThreadsByResourceIdPaginated(args);
2296
- }
2297
3120
  async saveThread({ thread }) {
2298
3121
  return this.stores.memory.saveThread({ thread });
2299
3122
  }
@@ -2307,17 +3130,8 @@ var MSSQLStore = class extends MastraStorage {
2307
3130
  async deleteThread({ threadId }) {
2308
3131
  return this.stores.memory.deleteThread({ threadId });
2309
3132
  }
2310
- async getMessages(args) {
2311
- return this.stores.memory.getMessages(args);
2312
- }
2313
- async getMessagesById({
2314
- messageIds,
2315
- format
2316
- }) {
2317
- return this.stores.memory.getMessagesById({ messageIds, format });
2318
- }
2319
- async getMessagesPaginated(args) {
2320
- return this.stores.memory.getMessagesPaginated(args);
3133
+ async listMessagesById({ messageIds }) {
3134
+ return this.stores.memory.listMessagesById({ messageIds });
2321
3135
  }
2322
3136
  async saveMessages(args) {
2323
3137
  return this.stores.memory.saveMessages(args);
@@ -2351,9 +3165,9 @@ var MSSQLStore = class extends MastraStorage {
2351
3165
  runId,
2352
3166
  stepId,
2353
3167
  result,
2354
- runtimeContext
3168
+ requestContext
2355
3169
  }) {
2356
- return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, runtimeContext });
3170
+ return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
2357
3171
  }
2358
3172
  async updateWorkflowState({
2359
3173
  workflowName,
@@ -2376,15 +3190,8 @@ var MSSQLStore = class extends MastraStorage {
2376
3190
  }) {
2377
3191
  return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
2378
3192
  }
2379
- async getWorkflowRuns({
2380
- workflowName,
2381
- fromDate,
2382
- toDate,
2383
- limit,
2384
- offset,
2385
- resourceId
2386
- } = {}) {
2387
- return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
3193
+ async listWorkflowRuns(args = {}) {
3194
+ return this.stores.workflows.listWorkflowRuns(args);
2388
3195
  }
2389
3196
  async getWorkflowRunById({
2390
3197
  runId,
@@ -2395,44 +3202,107 @@ var MSSQLStore = class extends MastraStorage {
2395
3202
  async close() {
2396
3203
  await this.pool.close();
2397
3204
  }
3205
+ /**
3206
+ * Index Management
3207
+ */
3208
+ async createIndex(options) {
3209
+ return this.stores.operations.createIndex(options);
3210
+ }
3211
+ async listIndexes(tableName) {
3212
+ return this.stores.operations.listIndexes(tableName);
3213
+ }
3214
+ async describeIndex(indexName) {
3215
+ return this.stores.operations.describeIndex(indexName);
3216
+ }
3217
+ async dropIndex(indexName) {
3218
+ return this.stores.operations.dropIndex(indexName);
3219
+ }
3220
+ /**
3221
+ * Tracing / Observability
3222
+ */
3223
+ getObservabilityStore() {
3224
+ if (!this.stores.observability) {
3225
+ throw new MastraError({
3226
+ id: "MSSQL_STORE_OBSERVABILITY_NOT_INITIALIZED",
3227
+ domain: ErrorDomain.STORAGE,
3228
+ category: ErrorCategory.SYSTEM,
3229
+ text: "Observability storage is not initialized"
3230
+ });
3231
+ }
3232
+ return this.stores.observability;
3233
+ }
3234
+ async createSpan(span) {
3235
+ return this.getObservabilityStore().createSpan(span);
3236
+ }
3237
+ async updateSpan({
3238
+ spanId,
3239
+ traceId,
3240
+ updates
3241
+ }) {
3242
+ return this.getObservabilityStore().updateSpan({ spanId, traceId, updates });
3243
+ }
3244
+ async getTrace(traceId) {
3245
+ return this.getObservabilityStore().getTrace(traceId);
3246
+ }
3247
+ async getTracesPaginated(args) {
3248
+ return this.getObservabilityStore().getTracesPaginated(args);
3249
+ }
3250
+ async batchCreateSpans(args) {
3251
+ return this.getObservabilityStore().batchCreateSpans(args);
3252
+ }
3253
+ async batchUpdateSpans(args) {
3254
+ return this.getObservabilityStore().batchUpdateSpans(args);
3255
+ }
3256
+ async batchDeleteTraces(args) {
3257
+ return this.getObservabilityStore().batchDeleteTraces(args);
3258
+ }
2398
3259
  /**
2399
3260
  * Scorers
2400
3261
  */
2401
3262
  async getScoreById({ id: _id }) {
2402
3263
  return this.stores.scores.getScoreById({ id: _id });
2403
3264
  }
2404
- async getScoresByScorerId({
3265
+ async listScoresByScorerId({
2405
3266
  scorerId: _scorerId,
2406
- pagination: _pagination
3267
+ pagination: _pagination,
3268
+ entityId: _entityId,
3269
+ entityType: _entityType,
3270
+ source: _source
2407
3271
  }) {
2408
- return this.stores.scores.getScoresByScorerId({ scorerId: _scorerId, pagination: _pagination });
3272
+ return this.stores.scores.listScoresByScorerId({
3273
+ scorerId: _scorerId,
3274
+ pagination: _pagination,
3275
+ entityId: _entityId,
3276
+ entityType: _entityType,
3277
+ source: _source
3278
+ });
2409
3279
  }
2410
3280
  async saveScore(_score) {
2411
3281
  return this.stores.scores.saveScore(_score);
2412
3282
  }
2413
- async getScoresByRunId({
3283
+ async listScoresByRunId({
2414
3284
  runId: _runId,
2415
3285
  pagination: _pagination
2416
3286
  }) {
2417
- return this.stores.scores.getScoresByRunId({ runId: _runId, pagination: _pagination });
3287
+ return this.stores.scores.listScoresByRunId({ runId: _runId, pagination: _pagination });
2418
3288
  }
2419
- async getScoresByEntityId({
3289
+ async listScoresByEntityId({
2420
3290
  entityId: _entityId,
2421
3291
  entityType: _entityType,
2422
3292
  pagination: _pagination
2423
3293
  }) {
2424
- return this.stores.scores.getScoresByEntityId({
3294
+ return this.stores.scores.listScoresByEntityId({
2425
3295
  entityId: _entityId,
2426
3296
  entityType: _entityType,
2427
3297
  pagination: _pagination
2428
3298
  });
2429
3299
  }
2430
- async getScoresBySpan({
3300
+ async listScoresBySpan({
2431
3301
  traceId,
2432
3302
  spanId,
2433
3303
  pagination: _pagination
2434
3304
  }) {
2435
- return this.stores.scores.getScoresBySpan({ traceId, spanId, pagination: _pagination });
3305
+ return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination: _pagination });
2436
3306
  }
2437
3307
  };
2438
3308