@mastra/mssql 0.0.0-mssql-store-20250804200341 → 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.
Files changed (43) hide show
  1. package/CHANGELOG.md +938 -3
  2. package/README.md +324 -37
  3. package/dist/index.cjs +1741 -688
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1743 -690
  8. package/dist/index.js.map +1 -1
  9. package/dist/storage/domains/memory/index.d.ts +16 -37
  10. package/dist/storage/domains/memory/index.d.ts.map +1 -1
  11. package/dist/storage/domains/observability/index.d.ts +44 -0
  12. package/dist/storage/domains/observability/index.d.ts.map +1 -0
  13. package/dist/storage/domains/operations/index.d.ts +67 -4
  14. package/dist/storage/domains/operations/index.d.ts.map +1 -1
  15. package/dist/storage/domains/scores/index.d.ts +15 -6
  16. package/dist/storage/domains/scores/index.d.ts.map +1 -1
  17. package/dist/storage/domains/utils.d.ts +19 -0
  18. package/dist/storage/domains/utils.d.ts.map +1 -1
  19. package/dist/storage/domains/workflows/index.d.ts +25 -11
  20. package/dist/storage/domains/workflows/index.d.ts.map +1 -1
  21. package/dist/storage/index.d.ts +91 -73
  22. package/dist/storage/index.d.ts.map +1 -1
  23. package/package.json +31 -12
  24. package/dist/storage/domains/legacy-evals/index.d.ts +0 -20
  25. package/dist/storage/domains/legacy-evals/index.d.ts.map +0 -1
  26. package/dist/storage/domains/traces/index.d.ts +0 -37
  27. package/dist/storage/domains/traces/index.d.ts.map +0 -1
  28. package/docker-compose.yaml +0 -14
  29. package/eslint.config.js +0 -6
  30. package/src/index.ts +0 -2
  31. package/src/storage/domains/legacy-evals/index.ts +0 -175
  32. package/src/storage/domains/memory/index.ts +0 -1024
  33. package/src/storage/domains/operations/index.ts +0 -401
  34. package/src/storage/domains/scores/index.ts +0 -289
  35. package/src/storage/domains/traces/index.ts +0 -212
  36. package/src/storage/domains/utils.ts +0 -12
  37. package/src/storage/domains/workflows/index.ts +0 -259
  38. package/src/storage/index.test.ts +0 -2228
  39. package/src/storage/index.ts +0 -448
  40. package/tsconfig.build.json +0 -9
  41. package/tsconfig.json +0 -5
  42. package/tsup.config.ts +0 -22
  43. package/vitest.config.ts +0 -12
package/dist/index.js CHANGED
@@ -1,8 +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';
5
+ import { parseSqlIdentifier } from '@mastra/core/utils';
6
+ import { randomUUID } from 'crypto';
7
+ import { saveScorePayloadSchema } from '@mastra/core/evals';
6
8
 
7
9
  // src/storage/index.ts
8
10
  function getSchemaName(schema) {
@@ -14,154 +16,71 @@ function getTableName({ indexName, schemaName }) {
14
16
  const quotedSchemaName = schemaName;
15
17
  return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
16
18
  }
17
-
18
- // src/storage/domains/legacy-evals/index.ts
19
- function transformEvalRow(row) {
20
- let testInfoValue = null, resultValue = null;
21
- if (row.test_info) {
22
- try {
23
- testInfoValue = typeof row.test_info === "string" ? JSON.parse(row.test_info) : row.test_info;
24
- } catch {
25
- }
19
+ function buildDateRangeFilter(dateRange, fieldName) {
20
+ const filters = {};
21
+ if (dateRange?.start) {
22
+ filters[`${fieldName}_gte`] = dateRange.start;
26
23
  }
27
- if (row.test_info) {
28
- try {
29
- resultValue = typeof row.result === "string" ? JSON.parse(row.result) : row.result;
30
- } catch {
31
- }
24
+ if (dateRange?.end) {
25
+ filters[`${fieldName}_lte`] = dateRange.end;
32
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
+ });
33
51
  return {
34
- agentName: row.agent_name,
35
- input: row.input,
36
- output: row.output,
37
- result: resultValue,
38
- metricName: row.metric_name,
39
- instructions: row.instructions,
40
- testInfo: testInfoValue,
41
- globalRunId: row.global_run_id,
42
- runId: row.run_id,
43
- createdAt: row.created_at
52
+ sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
53
+ params
44
54
  };
45
55
  }
46
- var LegacyEvalsMSSQL = class extends LegacyEvalsStorage {
47
- pool;
48
- schema;
49
- constructor({ pool, schema }) {
50
- super();
51
- this.pool = pool;
52
- this.schema = schema;
53
- }
54
- /** @deprecated use getEvals instead */
55
- async getEvalsByAgentName(agentName, type) {
56
- try {
57
- let query = `SELECT * FROM ${getTableName({ indexName: TABLE_EVALS, schemaName: getSchemaName(this.schema) })} WHERE agent_name = @p1`;
58
- if (type === "test") {
59
- query += " AND test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL";
60
- } else if (type === "live") {
61
- query += " AND (test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)";
62
- }
63
- query += " ORDER BY created_at DESC";
64
- const request = this.pool.request();
65
- request.input("p1", agentName);
66
- const result = await request.query(query);
67
- const rows = result.recordset;
68
- return typeof transformEvalRow === "function" ? rows?.map((row) => transformEvalRow(row)) ?? [] : rows ?? [];
69
- } catch (error) {
70
- if (error && error.number === 208 && error.message && error.message.includes("Invalid object name")) {
71
- return [];
72
- }
73
- console.error("Failed to get evals for the specified agent: " + error?.message);
74
- throw error;
75
- }
76
- }
77
- async getEvals(options = {}) {
78
- const { agentName, type, page = 0, perPage = 100, dateRange } = options;
79
- const fromDate = dateRange?.start;
80
- const toDate = dateRange?.end;
81
- const where = [];
82
- const params = {};
83
- if (agentName) {
84
- where.push("agent_name = @agentName");
85
- params["agentName"] = agentName;
86
- }
87
- if (type === "test") {
88
- where.push("test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL");
89
- } else if (type === "live") {
90
- where.push("(test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)");
91
- }
92
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
93
- where.push(`[created_at] >= @fromDate`);
94
- params[`fromDate`] = fromDate.toISOString();
95
- }
96
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
97
- where.push(`[created_at] <= @toDate`);
98
- params[`toDate`] = toDate.toISOString();
99
- }
100
- const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
101
- const tableName = getTableName({ indexName: TABLE_EVALS, schemaName: getSchemaName(this.schema) });
102
- const offset = page * perPage;
103
- const countQuery = `SELECT COUNT(*) as total FROM ${tableName} ${whereClause}`;
104
- const dataQuery = `SELECT * FROM ${tableName} ${whereClause} ORDER BY seq_id DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
105
- try {
106
- const countReq = this.pool.request();
107
- Object.entries(params).forEach(([key, value]) => {
108
- if (value instanceof Date) {
109
- countReq.input(key, sql2.DateTime, value);
110
- } else {
111
- countReq.input(key, value);
112
- }
113
- });
114
- const countResult = await countReq.query(countQuery);
115
- const total = countResult.recordset[0]?.total || 0;
116
- if (total === 0) {
117
- return {
118
- evals: [],
119
- total: 0,
120
- page,
121
- perPage,
122
- hasMore: false
123
- };
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;
124
69
  }
125
- const req = this.pool.request();
126
- Object.entries(params).forEach(([key, value]) => {
127
- if (value instanceof Date) {
128
- req.input(key, sql2.DateTime, value);
129
- } else {
130
- req.input(key, value);
131
- }
132
- });
133
- req.input("offset", offset);
134
- req.input("perPage", perPage);
135
- const result = await req.query(dataQuery);
136
- const rows = result.recordset;
137
- return {
138
- evals: rows?.map((row) => transformEvalRow(row)) ?? [],
139
- total,
140
- page,
141
- perPage,
142
- hasMore: offset + (rows?.length ?? 0) < total
143
- };
144
- } catch (error) {
145
- const mastraError = new MastraError(
146
- {
147
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_EVALS_FAILED",
148
- domain: ErrorDomain.STORAGE,
149
- category: ErrorCategory.THIRD_PARTY,
150
- details: {
151
- agentName: agentName || "all",
152
- type: type || "all",
153
- page,
154
- perPage
155
- }
156
- },
157
- error
158
- );
159
- this.logger?.error?.(mastraError.toString());
160
- this.logger?.trackException(mastraError);
161
- 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;
162
78
  }
163
- }
164
- };
79
+ });
80
+ return result;
81
+ }
82
+
83
+ // src/storage/domains/memory/index.ts
165
84
  var MemoryMSSQL = class extends MemoryStorage {
166
85
  pool;
167
86
  schema;
@@ -179,7 +98,7 @@ var MemoryMSSQL = class extends MemoryStorage {
179
98
  });
180
99
  const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
181
100
  const list = new MessageList().add(cleanMessages, "memory");
182
- return format === "v2" ? list.get.all.v2() : list.get.all.v1();
101
+ return format === "v2" ? list.get.all.db() : list.get.all.v1();
183
102
  }
184
103
  constructor({
185
104
  pool,
@@ -193,7 +112,7 @@ var MemoryMSSQL = class extends MemoryStorage {
193
112
  }
194
113
  async getThreadById({ threadId }) {
195
114
  try {
196
- const sql7 = `SELECT
115
+ const sql5 = `SELECT
197
116
  id,
198
117
  [resourceId],
199
118
  title,
@@ -204,7 +123,7 @@ var MemoryMSSQL = class extends MemoryStorage {
204
123
  WHERE id = @threadId`;
205
124
  const request = this.pool.request();
206
125
  request.input("threadId", threadId);
207
- const resultSet = await request.query(sql7);
126
+ const resultSet = await request.query(sql5);
208
127
  const thread = resultSet.recordset[0] || null;
209
128
  if (!thread) {
210
129
  return null;
@@ -229,11 +148,24 @@ var MemoryMSSQL = class extends MemoryStorage {
229
148
  );
230
149
  }
231
150
  }
232
- async getThreadsByResourceIdPaginated(args) {
233
- 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);
234
168
  try {
235
- const perPage = perPageInput !== void 0 ? perPageInput : 100;
236
- const currentOffset = page * perPage;
237
169
  const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
238
170
  const countQuery = `SELECT COUNT(*) as count ${baseQuery}`;
239
171
  const countRequest = this.pool.request();
@@ -245,16 +177,22 @@ var MemoryMSSQL = class extends MemoryStorage {
245
177
  threads: [],
246
178
  total: 0,
247
179
  page,
248
- perPage,
180
+ perPage: perPageForResponse,
249
181
  hasMore: false
250
182
  };
251
183
  }
252
- const orderByField = orderBy === "createdAt" ? "[createdAt]" : "[updatedAt]";
253
- 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`;
254
188
  const dataRequest = this.pool.request();
255
189
  dataRequest.input("resourceId", resourceId);
256
- dataRequest.input("perPage", perPage);
257
- 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
+ }
258
196
  const rowsResult = await dataRequest.query(dataQuery);
259
197
  const rows = rowsResult.recordset || [];
260
198
  const threads = rows.map((thread) => ({
@@ -267,13 +205,13 @@ var MemoryMSSQL = class extends MemoryStorage {
267
205
  threads,
268
206
  total,
269
207
  page,
270
- perPage,
271
- hasMore: currentOffset + threads.length < total
208
+ perPage: perPageForResponse,
209
+ hasMore: perPageInput === false ? false : offset + perPage < total
272
210
  };
273
211
  } catch (error) {
274
212
  const mastraError = new MastraError(
275
213
  {
276
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED",
214
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_THREADS_BY_RESOURCE_ID_FAILED",
277
215
  domain: ErrorDomain.STORAGE,
278
216
  category: ErrorCategory.THIRD_PARTY,
279
217
  details: {
@@ -285,7 +223,13 @@ var MemoryMSSQL = class extends MemoryStorage {
285
223
  );
286
224
  this.logger?.error?.(mastraError.toString());
287
225
  this.logger?.trackException?.(mastraError);
288
- 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
+ };
289
233
  }
290
234
  }
291
235
  async saveThread({ thread }) {
@@ -307,7 +251,12 @@ var MemoryMSSQL = class extends MemoryStorage {
307
251
  req.input("id", thread.id);
308
252
  req.input("resourceId", thread.resourceId);
309
253
  req.input("title", thread.title);
310
- 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
+ }
311
260
  req.input("createdAt", sql2.DateTime2, thread.createdAt);
312
261
  req.input("updatedAt", sql2.DateTime2, thread.updatedAt);
313
262
  await req.query(mergeSql);
@@ -326,30 +275,6 @@ var MemoryMSSQL = class extends MemoryStorage {
326
275
  );
327
276
  }
328
277
  }
329
- /**
330
- * @deprecated use getThreadsByResourceIdPaginated instead
331
- */
332
- async getThreadsByResourceId(args) {
333
- const { resourceId, orderBy = "createdAt", sortDirection = "DESC" } = args;
334
- try {
335
- const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
336
- const orderByField = orderBy === "createdAt" ? "[createdAt]" : "[updatedAt]";
337
- const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${sortDirection}`;
338
- const request = this.pool.request();
339
- request.input("resourceId", resourceId);
340
- const resultSet = await request.query(dataQuery);
341
- const rows = resultSet.recordset || [];
342
- return rows.map((thread) => ({
343
- ...thread,
344
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
345
- createdAt: thread.createdAt,
346
- updatedAt: thread.updatedAt
347
- }));
348
- } catch (error) {
349
- this.logger?.error?.(`Error getting threads for resource ${resourceId}:`, error);
350
- return [];
351
- }
352
- }
353
278
  /**
354
279
  * Updates a thread's title and metadata, merging with existing metadata. Returns the updated thread.
355
280
  */
@@ -377,7 +302,7 @@ var MemoryMSSQL = class extends MemoryStorage {
377
302
  };
378
303
  try {
379
304
  const table = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
380
- const sql7 = `UPDATE ${table}
305
+ const sql5 = `UPDATE ${table}
381
306
  SET title = @title,
382
307
  metadata = @metadata,
383
308
  [updatedAt] = @updatedAt
@@ -388,7 +313,7 @@ var MemoryMSSQL = class extends MemoryStorage {
388
313
  req.input("title", title);
389
314
  req.input("metadata", JSON.stringify(mergedMetadata));
390
315
  req.input("updatedAt", /* @__PURE__ */ new Date());
391
- const result = await req.query(sql7);
316
+ const result = await req.query(sql5);
392
317
  let thread = result.recordset && result.recordset[0];
393
318
  if (thread && "seq_id" in thread) {
394
319
  const { seq_id, ...rest } = thread;
@@ -458,10 +383,9 @@ var MemoryMSSQL = class extends MemoryStorage {
458
383
  }
459
384
  async _getIncludedMessages({
460
385
  threadId,
461
- selectBy,
462
- orderByStatement
386
+ include
463
387
  }) {
464
- const include = selectBy?.include;
388
+ if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
465
389
  if (!include) return null;
466
390
  const unionQueries = [];
467
391
  const paramValues = [];
@@ -486,7 +410,7 @@ var MemoryMSSQL = class extends MemoryStorage {
486
410
  m.[resourceId],
487
411
  m.seq_id
488
412
  FROM (
489
- SELECT *, ROW_NUMBER() OVER (${orderByStatement}) as row_num
413
+ SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
490
414
  FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}
491
415
  WHERE [thread_id] = ${pThreadId}
492
416
  ) AS m
@@ -494,15 +418,17 @@ var MemoryMSSQL = class extends MemoryStorage {
494
418
  OR EXISTS (
495
419
  SELECT 1
496
420
  FROM (
497
- SELECT *, ROW_NUMBER() OVER (${orderByStatement}) as row_num
421
+ SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
498
422
  FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}
499
423
  WHERE [thread_id] = ${pThreadId}
500
424
  ) AS target
501
425
  WHERE target.id = ${pId}
502
426
  AND (
503
- (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})
504
429
  OR
505
- (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})
506
432
  )
507
433
  )
508
434
  `
@@ -531,33 +457,16 @@ var MemoryMSSQL = class extends MemoryStorage {
531
457
  });
532
458
  return dedupedRows;
533
459
  }
534
- async getMessages(args) {
535
- const { threadId, format, selectBy } = args;
460
+ async listMessagesById({ messageIds }) {
461
+ if (messageIds.length === 0) return { messages: [] };
536
462
  const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
537
463
  const orderByStatement = `ORDER BY [seq_id] DESC`;
538
- const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
539
464
  try {
540
465
  let rows = [];
541
- const include = selectBy?.include || [];
542
- if (include?.length) {
543
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
544
- if (includeMessages) {
545
- rows.push(...includeMessages);
546
- }
547
- }
548
- const excludeIds = rows.map((m) => m.id).filter(Boolean);
549
- 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(", ")})`;
550
467
  const request = this.pool.request();
551
- request.input("threadId", threadId);
552
- if (excludeIds.length > 0) {
553
- const excludeParams = excludeIds.map((_, idx) => `@id${idx}`);
554
- query += ` AND id NOT IN (${excludeParams.join(", ")})`;
555
- excludeIds.forEach((id, idx) => {
556
- request.input(`id${idx}`, id);
557
- });
558
- }
559
- query += ` ${orderByStatement} OFFSET 0 ROWS FETCH NEXT @limit ROWS ONLY`;
560
- request.input("limit", limit);
468
+ messageIds.forEach((id, i) => request.input(`id${i}`, id));
469
+ query += ` ${orderByStatement}`;
561
470
  const result = await request.query(query);
562
471
  const remainingRows = result.recordset || [];
563
472
  rows.push(...remainingRows);
@@ -565,116 +474,171 @@ var MemoryMSSQL = class extends MemoryStorage {
565
474
  const timeDiff = a.seq_id - b.seq_id;
566
475
  return timeDiff;
567
476
  });
568
- rows = rows.map(({ seq_id, ...rest }) => rest);
569
- 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() };
570
490
  } catch (error) {
571
491
  const mastraError = new MastraError(
572
492
  {
573
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_MESSAGES_FAILED",
493
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_MESSAGES_BY_ID_FAILED",
574
494
  domain: ErrorDomain.STORAGE,
575
495
  category: ErrorCategory.THIRD_PARTY,
576
496
  details: {
577
- threadId
497
+ messageIds: JSON.stringify(messageIds)
578
498
  }
579
499
  },
580
500
  error
581
501
  );
582
502
  this.logger?.error?.(mastraError.toString());
583
- this.logger?.trackException(mastraError);
584
- return [];
503
+ this.logger?.trackException?.(mastraError);
504
+ return { messages: [] };
585
505
  }
586
506
  }
587
- async getMessagesPaginated(args) {
588
- const { threadId, selectBy } = args;
589
- const { page = 0, perPage: perPageInput } = selectBy?.pagination || {};
590
- const orderByStatement = `ORDER BY [seq_id] DESC`;
591
- if (selectBy?.include?.length) {
592
- await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
507
+ async listMessages(args) {
508
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
509
+ if (!threadId.trim()) {
510
+ throw new MastraError(
511
+ {
512
+ id: "STORAGE_MSSQL_LIST_MESSAGES_INVALID_THREAD_ID",
513
+ domain: ErrorDomain.STORAGE,
514
+ category: ErrorCategory.THIRD_PARTY,
515
+ details: { threadId }
516
+ },
517
+ new Error("threadId must be a non-empty string")
518
+ );
519
+ }
520
+ if (page < 0) {
521
+ throw new MastraError({
522
+ id: "MASTRA_STORAGE_MSSQL_STORE_INVALID_PAGE",
523
+ domain: ErrorDomain.STORAGE,
524
+ category: ErrorCategory.USER,
525
+ text: "Page number must be non-negative",
526
+ details: {
527
+ threadId,
528
+ page
529
+ }
530
+ });
593
531
  }
532
+ const perPage = normalizePerPage(perPageInput, 40);
533
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
594
534
  try {
595
- const { threadId: threadId2, format, selectBy: selectBy2 } = args;
596
- const { page: page2 = 0, perPage: perPageInput2, dateRange } = selectBy2?.pagination || {};
597
- const fromDate = dateRange?.start;
598
- const toDate = dateRange?.end;
599
- const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
600
- const orderByStatement2 = `ORDER BY [seq_id] DESC`;
601
- let messages2 = [];
602
- if (selectBy2?.include?.length) {
603
- const includeMessages = await this._getIncludedMessages({ threadId: threadId2, selectBy: selectBy2, orderByStatement: orderByStatement2 });
604
- if (includeMessages) messages2.push(...includeMessages);
605
- }
606
- const perPage = perPageInput2 !== void 0 ? perPageInput2 : resolveMessageLimit({ last: selectBy2?.last, defaultLimit: 40 });
607
- const currentOffset = page2 * perPage;
608
- const conditions = ["[thread_id] = @threadId"];
609
- const request = this.pool.request();
610
- request.input("threadId", threadId2);
611
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
612
- conditions.push("[createdAt] >= @fromDate");
613
- request.input("fromDate", fromDate.toISOString());
614
- }
615
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
616
- conditions.push("[createdAt] <= @toDate");
617
- request.input("toDate", toDate.toISOString());
618
- }
619
- const whereClause = `WHERE ${conditions.join(" AND ")}`;
620
- const countQuery = `SELECT COUNT(*) as total FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} ${whereClause}`;
621
- 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}`);
622
552
  const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
623
- if (total === 0 && messages2.length > 0) {
624
- const parsedIncluded = this._parseAndFormatMessages(messages2, 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)) {
625
574
  return {
626
- messages: parsedIncluded,
627
- total: parsedIncluded.length,
628
- page: page2,
629
- perPage,
575
+ messages: [],
576
+ total: 0,
577
+ page,
578
+ perPage: perPageForResponse,
630
579
  hasMore: false
631
580
  };
632
581
  }
633
- const excludeIds = messages2.map((m) => m.id);
634
- if (excludeIds.length > 0) {
635
- const excludeParams = excludeIds.map((_, idx) => `@id${idx}`);
636
- conditions.push(`id NOT IN (${excludeParams.join(", ")})`);
637
- excludeIds.forEach((id, idx) => request.input(`id${idx}`, id));
638
- }
639
- const finalWhereClause = `WHERE ${conditions.join(" AND ")}`;
640
- const dataQuery = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} ${finalWhereClause} ${orderByStatement2} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
641
- request.input("offset", currentOffset);
642
- request.input("limit", perPage);
643
- const rowsResult = await request.query(dataQuery);
644
- const rows = rowsResult.recordset || [];
645
- rows.sort((a, b) => a.seq_id - b.seq_id);
646
- messages2.push(...rows);
647
- const parsed = this._parseAndFormatMessages(messages2, 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;
648
609
  return {
649
- messages: parsed,
650
- total: total + excludeIds.length,
651
- page: page2,
652
- perPage,
653
- hasMore: currentOffset + rows.length < total
610
+ messages: finalMessages,
611
+ total,
612
+ page,
613
+ perPage: perPageForResponse,
614
+ hasMore
654
615
  };
655
616
  } catch (error) {
656
617
  const mastraError = new MastraError(
657
618
  {
658
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_MESSAGES_PAGINATED_FAILED",
619
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_MESSAGES_FAILED",
659
620
  domain: ErrorDomain.STORAGE,
660
621
  category: ErrorCategory.THIRD_PARTY,
661
622
  details: {
662
623
  threadId,
663
- page
624
+ resourceId: resourceId ?? ""
664
625
  }
665
626
  },
666
627
  error
667
628
  );
668
629
  this.logger?.error?.(mastraError.toString());
669
- this.logger?.trackException(mastraError);
670
- 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
+ };
671
638
  }
672
639
  }
673
- async saveMessages({
674
- messages,
675
- format
676
- }) {
677
- if (messages.length === 0) return messages;
640
+ async saveMessages({ messages }) {
641
+ if (messages.length === 0) return { messages: [] };
678
642
  const threadId = messages[0]?.threadId;
679
643
  if (!threadId) {
680
644
  throw new MastraError({
@@ -756,8 +720,7 @@ var MemoryMSSQL = class extends MemoryStorage {
756
720
  return message;
757
721
  });
758
722
  const list = new MessageList().add(messagesWithParsedContent, "memory");
759
- if (format === "v2") return list.get.all.v2();
760
- return list.get.all.v1();
723
+ return { messages: list.get.all.db() };
761
724
  } catch (error) {
762
725
  throw new MastraError(
763
726
  {
@@ -933,8 +896,10 @@ var MemoryMSSQL = class extends MemoryStorage {
933
896
  return null;
934
897
  }
935
898
  return {
936
- ...result,
937
- 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,
938
903
  metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
939
904
  };
940
905
  } catch (error) {
@@ -948,7 +913,7 @@ var MemoryMSSQL = class extends MemoryStorage {
948
913
  error
949
914
  );
950
915
  this.logger?.error?.(mastraError.toString());
951
- this.logger?.trackException(mastraError);
916
+ this.logger?.trackException?.(mastraError);
952
917
  throw mastraError;
953
918
  }
954
919
  }
@@ -957,7 +922,7 @@ var MemoryMSSQL = class extends MemoryStorage {
957
922
  tableName: TABLE_RESOURCES,
958
923
  record: {
959
924
  ...resource,
960
- metadata: JSON.stringify(resource.metadata)
925
+ metadata: resource.metadata
961
926
  }
962
927
  });
963
928
  return resource;
@@ -1015,132 +980,457 @@ var MemoryMSSQL = class extends MemoryStorage {
1015
980
  error
1016
981
  );
1017
982
  this.logger?.error?.(mastraError.toString());
1018
- this.logger?.trackException(mastraError);
983
+ this.logger?.trackException?.(mastraError);
1019
984
  throw mastraError;
1020
985
  }
1021
986
  }
1022
987
  };
1023
- var StoreOperationsMSSQL = class extends StoreOperations {
988
+ var ObservabilityMSSQL = class extends ObservabilityStorage {
1024
989
  pool;
1025
- schemaName;
1026
- setupSchemaPromise = null;
1027
- schemaSetupComplete = void 0;
1028
- getSqlType(type, isPrimaryKey = false) {
1029
- switch (type) {
1030
- case "text":
1031
- return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(MAX)";
1032
- case "timestamp":
1033
- return "DATETIME2(7)";
1034
- case "uuid":
1035
- return "UNIQUEIDENTIFIER";
1036
- case "jsonb":
1037
- return "NVARCHAR(MAX)";
1038
- case "integer":
1039
- return "INT";
1040
- case "bigint":
1041
- return "BIGINT";
1042
- case "float":
1043
- return "FLOAT";
1044
- default:
1045
- throw new MastraError({
1046
- id: "MASTRA_STORAGE_MSSQL_STORE_TYPE_NOT_SUPPORTED",
1047
- domain: ErrorDomain.STORAGE,
1048
- category: ErrorCategory.THIRD_PARTY
1049
- });
1050
- }
1051
- }
1052
- constructor({ pool, schemaName }) {
990
+ operations;
991
+ schema;
992
+ constructor({
993
+ pool,
994
+ operations,
995
+ schema
996
+ }) {
1053
997
  super();
1054
998
  this.pool = pool;
1055
- this.schemaName = schemaName;
1056
- }
1057
- async hasColumn(table, column) {
1058
- const schema = this.schemaName || "dbo";
1059
- const request = this.pool.request();
1060
- request.input("schema", schema);
1061
- request.input("table", table);
1062
- request.input("column", column);
1063
- request.input("columnLower", column.toLowerCase());
1064
- const result = await request.query(
1065
- `SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
1066
- );
1067
- return result.recordset.length > 0;
999
+ this.operations = operations;
1000
+ this.schema = schema;
1068
1001
  }
1069
- async setupSchema() {
1070
- if (!this.schemaName || this.schemaSetupComplete) {
1071
- return;
1072
- }
1073
- if (!this.setupSchemaPromise) {
1074
- this.setupSchemaPromise = (async () => {
1075
- try {
1076
- const checkRequest = this.pool.request();
1077
- checkRequest.input("schemaName", this.schemaName);
1078
- const checkResult = await checkRequest.query(`
1079
- SELECT 1 AS found FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = @schemaName
1080
- `);
1081
- const schemaExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
1082
- if (!schemaExists) {
1083
- try {
1084
- await this.pool.request().query(`CREATE SCHEMA [${this.schemaName}]`);
1085
- this.logger?.info?.(`Schema "${this.schemaName}" created successfully`);
1086
- } catch (error) {
1087
- this.logger?.error?.(`Failed to create schema "${this.schemaName}"`, { error });
1088
- throw new Error(
1089
- `Unable to create schema "${this.schemaName}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
1090
- );
1091
- }
1092
- }
1093
- this.schemaSetupComplete = true;
1094
- this.logger?.debug?.(`Schema "${this.schemaName}" is ready for use`);
1095
- } catch (error) {
1096
- this.schemaSetupComplete = void 0;
1097
- this.setupSchemaPromise = null;
1098
- throw error;
1099
- } finally {
1100
- this.setupSchemaPromise = null;
1101
- }
1102
- })();
1103
- }
1104
- await this.setupSchemaPromise;
1002
+ get tracingStrategy() {
1003
+ return {
1004
+ preferred: "batch-with-updates",
1005
+ supported: ["batch-with-updates", "insert-only"]
1006
+ };
1105
1007
  }
1106
- async insert({ tableName, record }) {
1008
+ async createSpan(span) {
1107
1009
  try {
1108
- const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
1109
- const values = Object.values(record);
1110
- const paramNames = values.map((_, i) => `@param${i}`);
1111
- const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${columns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
1112
- const request = this.pool.request();
1113
- values.forEach((value, i) => {
1114
- if (value instanceof Date) {
1115
- request.input(`param${i}`, sql2.DateTime2, value);
1116
- } else if (typeof value === "object" && value !== null) {
1117
- request.input(`param${i}`, JSON.stringify(value));
1118
- } else {
1119
- request.input(`param${i}`, value);
1120
- }
1121
- });
1122
- await request.query(insertSql);
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 });
1123
1019
  } catch (error) {
1124
1020
  throw new MastraError(
1125
1021
  {
1126
- id: "MASTRA_STORAGE_MSSQL_STORE_INSERT_FAILED",
1022
+ id: "MSSQL_STORE_CREATE_SPAN_FAILED",
1127
1023
  domain: ErrorDomain.STORAGE,
1128
- category: ErrorCategory.THIRD_PARTY,
1024
+ category: ErrorCategory.USER,
1129
1025
  details: {
1130
- tableName
1026
+ spanId: span.spanId,
1027
+ traceId: span.traceId,
1028
+ spanType: span.spanType,
1029
+ spanName: span.name
1131
1030
  }
1132
1031
  },
1133
1032
  error
1134
1033
  );
1135
1034
  }
1136
1035
  }
1137
- async clearTable({ tableName }) {
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 {
1368
+ await this.pool.request().query(`CREATE SCHEMA [${this.schemaName}]`);
1369
+ this.logger?.info?.(`Schema "${this.schemaName}" created successfully`);
1370
+ } catch (error) {
1371
+ this.logger?.error?.(`Failed to create schema "${this.schemaName}"`, { error });
1372
+ throw new Error(
1373
+ `Unable to create schema "${this.schemaName}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
1374
+ );
1375
+ }
1376
+ }
1377
+ this.schemaSetupComplete = true;
1378
+ this.logger?.debug?.(`Schema "${this.schemaName}" is ready for use`);
1379
+ } catch (error) {
1380
+ this.schemaSetupComplete = void 0;
1381
+ this.setupSchemaPromise = null;
1382
+ throw error;
1383
+ } finally {
1384
+ this.setupSchemaPromise = null;
1385
+ }
1386
+ })();
1387
+ }
1388
+ await this.setupSchemaPromise;
1389
+ }
1390
+ async insert({
1391
+ tableName,
1392
+ record,
1393
+ transaction
1394
+ }) {
1395
+ try {
1396
+ const columns = Object.keys(record);
1397
+ const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
1398
+ const paramNames = columns.map((_, i) => `@param${i}`);
1399
+ const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${parsedColumns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
1400
+ const request = transaction ? transaction.request() : this.pool.request();
1401
+ columns.forEach((col, i) => {
1402
+ const value = record[col];
1403
+ const preparedValue = this.prepareValue(value, col, tableName);
1404
+ if (preparedValue instanceof Date) {
1405
+ request.input(`param${i}`, sql2.DateTime2, preparedValue);
1406
+ } else if (preparedValue === null || preparedValue === void 0) {
1407
+ request.input(`param${i}`, this.getMssqlType(tableName, col), null);
1408
+ } else {
1409
+ request.input(`param${i}`, preparedValue);
1410
+ }
1411
+ });
1412
+ await request.query(insertSql);
1413
+ } catch (error) {
1414
+ throw new MastraError(
1415
+ {
1416
+ id: "MASTRA_STORAGE_MSSQL_STORE_INSERT_FAILED",
1417
+ domain: ErrorDomain.STORAGE,
1418
+ category: ErrorCategory.THIRD_PARTY,
1419
+ details: {
1420
+ tableName
1421
+ }
1422
+ },
1423
+ error
1424
+ );
1425
+ }
1426
+ }
1427
+ async clearTable({ tableName }) {
1138
1428
  const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1139
1429
  try {
1140
1430
  try {
1141
1431
  await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
1142
1432
  } catch (truncateError) {
1143
- if (truncateError.message && truncateError.message.includes("foreign key")) {
1433
+ if (truncateError?.number === 4712) {
1144
1434
  await this.pool.request().query(`DELETE FROM ${fullTableName}`);
1145
1435
  } else {
1146
1436
  throw truncateError;
@@ -1163,9 +1453,11 @@ var StoreOperationsMSSQL = class extends StoreOperations {
1163
1453
  getDefaultValue(type) {
1164
1454
  switch (type) {
1165
1455
  case "timestamp":
1166
- return "DEFAULT SYSDATETIMEOFFSET()";
1456
+ return "DEFAULT SYSUTCDATETIME()";
1167
1457
  case "jsonb":
1168
1458
  return "DEFAULT N'{}'";
1459
+ case "boolean":
1460
+ return "DEFAULT 0";
1169
1461
  default:
1170
1462
  return super.getDefaultValue(type);
1171
1463
  }
@@ -1176,13 +1468,29 @@ var StoreOperationsMSSQL = class extends StoreOperations {
1176
1468
  }) {
1177
1469
  try {
1178
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
+ ];
1179
1486
  const columns = Object.entries(schema).map(([name, def]) => {
1180
1487
  const parsedName = parseSqlIdentifier(name, "column name");
1181
1488
  const constraints = [];
1182
1489
  if (def.primaryKey) constraints.push("PRIMARY KEY");
1183
1490
  if (!def.nullable) constraints.push("NOT NULL");
1184
1491
  const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
1185
- 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();
1186
1494
  }).join(",\n");
1187
1495
  if (this.schemaName) {
1188
1496
  await this.setupSchema();
@@ -1269,7 +1577,19 @@ ${columns}
1269
1577
  const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
1270
1578
  if (!columnExists) {
1271
1579
  const columnDef = schema[columnName];
1272
- 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);
1273
1593
  const nullable = columnDef.nullable === false ? "NOT NULL" : "";
1274
1594
  const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1275
1595
  const parsedColumnName = parseSqlIdentifier(columnName, "column name");
@@ -1297,13 +1617,17 @@ ${columns}
1297
1617
  try {
1298
1618
  const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
1299
1619
  const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
1300
- const values = keyEntries.map(([_, value]) => value);
1301
- const sql7 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
1620
+ const sql5 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
1302
1621
  const request = this.pool.request();
1303
- values.forEach((value, i) => {
1304
- request.input(`param${i}`, value);
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
+ }
1305
1629
  });
1306
- const resultSet = await request.query(sql7);
1630
+ const resultSet = await request.query(sql5);
1307
1631
  const result = resultSet.recordset[0] || null;
1308
1632
  if (!result) {
1309
1633
  return null;
@@ -1335,7 +1659,7 @@ ${columns}
1335
1659
  try {
1336
1660
  await transaction.begin();
1337
1661
  for (const record of records) {
1338
- await this.insert({ tableName, record });
1662
+ await this.insert({ tableName, record, transaction });
1339
1663
  }
1340
1664
  await transaction.commit();
1341
1665
  } catch (error) {
@@ -1354,19 +1678,576 @@ ${columns}
1354
1678
  );
1355
1679
  }
1356
1680
  }
1357
- async dropTable({ tableName }) {
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",
2011
+ domain: ErrorDomain.STORAGE,
2012
+ category: ErrorCategory.THIRD_PARTY,
2013
+ details: {
2014
+ indexName
2015
+ }
2016
+ },
2017
+ error
2018
+ );
2019
+ }
2020
+ }
2021
+ /**
2022
+ * List indexes for a specific table or all tables
2023
+ */
2024
+ async listIndexes(tableName) {
2025
+ try {
2026
+ const schemaName = this.schemaName || "dbo";
2027
+ let query;
2028
+ const request = this.pool.request();
2029
+ request.input("schemaName", schemaName);
2030
+ if (tableName) {
2031
+ query = `
2032
+ SELECT
2033
+ i.name as name,
2034
+ o.name as [table],
2035
+ i.is_unique as is_unique,
2036
+ CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size
2037
+ FROM sys.indexes i
2038
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
2039
+ INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
2040
+ LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
2041
+ WHERE sch.name = @schemaName
2042
+ AND o.name = @tableName
2043
+ AND i.name IS NOT NULL
2044
+ GROUP BY i.name, o.name, i.is_unique
2045
+ `;
2046
+ request.input("tableName", tableName);
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
+ `;
2062
+ }
2063
+ const result = await request.query(query);
2064
+ const indexes = [];
2065
+ for (const row of result.recordset) {
2066
+ const colRequest = this.pool.request();
2067
+ colRequest.input("indexName", row.name);
2068
+ colRequest.input("schemaName", schemaName);
2069
+ const colResult = await colRequest.query(`
2070
+ SELECT c.name as column_name
2071
+ FROM sys.indexes i
2072
+ INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
2073
+ INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
2074
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
2075
+ INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
2076
+ WHERE i.name = @indexName
2077
+ AND s.name = @schemaName
2078
+ ORDER BY ic.key_ordinal
2079
+ `);
2080
+ indexes.push({
2081
+ name: row.name,
2082
+ table: row.table,
2083
+ columns: colResult.recordset.map((c) => c.column_name),
2084
+ unique: row.is_unique || false,
2085
+ size: row.size || "0 MB",
2086
+ definition: ""
2087
+ // MSSQL doesn't store definition like PG
2088
+ });
2089
+ }
2090
+ return indexes;
2091
+ } catch (error) {
2092
+ throw new MastraError(
2093
+ {
2094
+ id: "MASTRA_STORAGE_MSSQL_INDEX_LIST_FAILED",
2095
+ domain: ErrorDomain.STORAGE,
2096
+ category: ErrorCategory.THIRD_PARTY,
2097
+ details: tableName ? {
2098
+ tableName
2099
+ } : {}
2100
+ },
2101
+ error
2102
+ );
2103
+ }
2104
+ }
2105
+ /**
2106
+ * Get detailed statistics for a specific index
2107
+ */
2108
+ async describeIndex(indexName) {
2109
+ try {
2110
+ const schemaName = this.schemaName || "dbo";
2111
+ const request = this.pool.request();
2112
+ request.input("indexName", indexName);
2113
+ request.input("schemaName", schemaName);
2114
+ const query = `
2115
+ SELECT
2116
+ i.name as name,
2117
+ o.name as [table],
2118
+ i.is_unique as is_unique,
2119
+ CAST(SUM(s.used_page_count) * 8 / 1024.0 AS VARCHAR(50)) + ' MB' as size,
2120
+ i.type_desc as method,
2121
+ ISNULL(us.user_scans, 0) as scans,
2122
+ ISNULL(us.user_seeks + us.user_scans, 0) as tuples_read,
2123
+ ISNULL(us.user_lookups, 0) as tuples_fetched
2124
+ FROM sys.indexes i
2125
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
2126
+ INNER JOIN sys.schemas sch ON o.schema_id = sch.schema_id
2127
+ LEFT JOIN sys.dm_db_partition_stats s ON i.object_id = s.object_id AND i.index_id = s.index_id
2128
+ LEFT JOIN sys.dm_db_index_usage_stats us ON i.object_id = us.object_id AND i.index_id = us.index_id
2129
+ WHERE i.name = @indexName
2130
+ AND sch.name = @schemaName
2131
+ GROUP BY i.name, o.name, i.is_unique, i.type_desc, us.user_seeks, us.user_scans, us.user_lookups
2132
+ `;
2133
+ const result = await request.query(query);
2134
+ if (!result.recordset || result.recordset.length === 0) {
2135
+ throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
2136
+ }
2137
+ const row = result.recordset[0];
2138
+ const colRequest = this.pool.request();
2139
+ colRequest.input("indexName", indexName);
2140
+ colRequest.input("schemaName", schemaName);
2141
+ const colResult = await colRequest.query(`
2142
+ SELECT c.name as column_name
2143
+ FROM sys.indexes i
2144
+ INNER JOIN sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
2145
+ INNER JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
2146
+ INNER JOIN sys.objects o ON i.object_id = o.object_id
2147
+ INNER JOIN sys.schemas s ON o.schema_id = s.schema_id
2148
+ WHERE i.name = @indexName
2149
+ AND s.name = @schemaName
2150
+ ORDER BY ic.key_ordinal
2151
+ `);
2152
+ return {
2153
+ name: row.name,
2154
+ table: row.table,
2155
+ columns: colResult.recordset.map((c) => c.column_name),
2156
+ unique: row.is_unique || false,
2157
+ size: row.size || "0 MB",
2158
+ definition: "",
2159
+ method: row.method?.toLowerCase() || "nonclustered",
2160
+ scans: Number(row.scans) || 0,
2161
+ tuples_read: Number(row.tuples_read) || 0,
2162
+ tuples_fetched: Number(row.tuples_fetched) || 0
2163
+ };
2164
+ } catch (error) {
2165
+ throw new MastraError(
2166
+ {
2167
+ id: "MASTRA_STORAGE_MSSQL_INDEX_DESCRIBE_FAILED",
2168
+ domain: ErrorDomain.STORAGE,
2169
+ category: ErrorCategory.THIRD_PARTY,
2170
+ details: {
2171
+ indexName
2172
+ }
2173
+ },
2174
+ error
2175
+ );
2176
+ }
2177
+ }
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() {
1358
2236
  try {
1359
- const tableNameWithSchema = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1360
- 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
+ }
1361
2245
  } catch (error) {
1362
2246
  throw new MastraError(
1363
2247
  {
1364
- id: "MASTRA_STORAGE_MSSQL_STORE_DROP_TABLE_FAILED",
2248
+ id: "MASTRA_STORAGE_MSSQL_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
1365
2249
  domain: ErrorDomain.STORAGE,
1366
- category: ErrorCategory.THIRD_PARTY,
1367
- details: {
1368
- tableName
1369
- }
2250
+ category: ErrorCategory.THIRD_PARTY
1370
2251
  },
1371
2252
  error
1372
2253
  );
@@ -1374,19 +2255,19 @@ ${columns}
1374
2255
  }
1375
2256
  };
1376
2257
  function transformScoreRow(row) {
1377
- let input = void 0;
1378
- if (row.input) {
1379
- try {
1380
- input = JSON.parse(row.input);
1381
- } catch {
1382
- input = row.input;
1383
- }
1384
- }
1385
2258
  return {
1386
2259
  ...row,
1387
- input,
1388
- createdAt: row.createdAt,
1389
- 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)
1390
2271
  };
1391
2272
  }
1392
2273
  var ScoresMSSQL = class extends ScoresStorage {
@@ -1427,15 +2308,47 @@ var ScoresMSSQL = class extends ScoresStorage {
1427
2308
  }
1428
2309
  }
1429
2310
  async saveScore(score) {
2311
+ let validatedScore;
2312
+ try {
2313
+ validatedScore = saveScorePayloadSchema.parse(score);
2314
+ } catch (error) {
2315
+ throw new MastraError(
2316
+ {
2317
+ id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_SCORE_VALIDATION_FAILED",
2318
+ domain: ErrorDomain.STORAGE,
2319
+ category: ErrorCategory.THIRD_PARTY
2320
+ },
2321
+ error
2322
+ );
2323
+ }
1430
2324
  try {
1431
- const scoreId = crypto.randomUUID();
1432
- const { input, ...rest } = score;
2325
+ const scoreId = randomUUID();
2326
+ const {
2327
+ scorer,
2328
+ preprocessStepResult,
2329
+ analyzeStepResult,
2330
+ metadata,
2331
+ input,
2332
+ output,
2333
+ additionalContext,
2334
+ requestContext,
2335
+ entity,
2336
+ ...rest
2337
+ } = validatedScore;
1433
2338
  await this.operations.insert({
1434
2339
  tableName: TABLE_SCORERS,
1435
2340
  record: {
1436
2341
  id: scoreId,
1437
2342
  ...rest,
1438
- input: JSON.stringify(input),
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,
1439
2352
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1440
2353
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1441
2354
  }
@@ -1453,41 +2366,70 @@ var ScoresMSSQL = class extends ScoresStorage {
1453
2366
  );
1454
2367
  }
1455
2368
  }
1456
- async getScoresByScorerId({
2369
+ async listScoresByScorerId({
1457
2370
  scorerId,
1458
- pagination
2371
+ pagination,
2372
+ entityId,
2373
+ entityType,
2374
+ source
1459
2375
  }) {
1460
2376
  try {
1461
- const request = this.pool.request();
1462
- request.input("p1", scorerId);
1463
- const totalResult = await request.query(
1464
- `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [scorerId] = @p1`
1465
- );
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}`);
1466
2402
  const total = totalResult.recordset[0]?.count || 0;
2403
+ const { page, perPage: perPageInput } = pagination;
1467
2404
  if (total === 0) {
1468
2405
  return {
1469
2406
  pagination: {
1470
2407
  total: 0,
1471
- page: pagination.page,
1472
- perPage: pagination.perPage,
2408
+ page,
2409
+ perPage: perPageInput,
1473
2410
  hasMore: false
1474
2411
  },
1475
2412
  scores: []
1476
2413
  };
1477
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;
1478
2419
  const dataRequest = this.pool.request();
1479
- dataRequest.input("p1", scorerId);
1480
- dataRequest.input("p2", pagination.perPage);
1481
- dataRequest.input("p3", pagination.page * pagination.perPage);
1482
- const result = await dataRequest.query(
1483
- `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`
1484
- );
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);
1485
2427
  return {
1486
2428
  pagination: {
1487
2429
  total: Number(total),
1488
- page: pagination.page,
1489
- perPage: pagination.perPage,
1490
- hasMore: Number(total) > (pagination.page + 1) * pagination.perPage
2430
+ page,
2431
+ perPage: perPageForResponse,
2432
+ hasMore: end < total
1491
2433
  },
1492
2434
  scores: result.recordset.map((row) => transformScoreRow(row))
1493
2435
  };
@@ -1503,7 +2445,7 @@ var ScoresMSSQL = class extends ScoresStorage {
1503
2445
  );
1504
2446
  }
1505
2447
  }
1506
- async getScoresByRunId({
2448
+ async listScoresByRunId({
1507
2449
  runId,
1508
2450
  pagination
1509
2451
  }) {
@@ -1514,30 +2456,35 @@ var ScoresMSSQL = class extends ScoresStorage {
1514
2456
  `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1`
1515
2457
  );
1516
2458
  const total = totalResult.recordset[0]?.count || 0;
2459
+ const { page, perPage: perPageInput } = pagination;
1517
2460
  if (total === 0) {
1518
2461
  return {
1519
2462
  pagination: {
1520
2463
  total: 0,
1521
- page: pagination.page,
1522
- perPage: pagination.perPage,
2464
+ page,
2465
+ perPage: perPageInput,
1523
2466
  hasMore: false
1524
2467
  },
1525
2468
  scores: []
1526
2469
  };
1527
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;
1528
2475
  const dataRequest = this.pool.request();
1529
2476
  dataRequest.input("p1", runId);
1530
- dataRequest.input("p2", pagination.perPage);
1531
- dataRequest.input("p3", pagination.page * pagination.perPage);
2477
+ dataRequest.input("p2", limitValue);
2478
+ dataRequest.input("p3", start);
1532
2479
  const result = await dataRequest.query(
1533
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`
1534
2481
  );
1535
2482
  return {
1536
2483
  pagination: {
1537
2484
  total: Number(total),
1538
- page: pagination.page,
1539
- perPage: pagination.perPage,
1540
- hasMore: Number(total) > (pagination.page + 1) * pagination.perPage
2485
+ page,
2486
+ perPage: perPageForResponse,
2487
+ hasMore: end < total
1541
2488
  },
1542
2489
  scores: result.recordset.map((row) => transformScoreRow(row))
1543
2490
  };
@@ -1553,7 +2500,7 @@ var ScoresMSSQL = class extends ScoresStorage {
1553
2500
  );
1554
2501
  }
1555
2502
  }
1556
- async getScoresByEntityId({
2503
+ async listScoresByEntityId({
1557
2504
  entityId,
1558
2505
  entityType,
1559
2506
  pagination
@@ -1566,31 +2513,36 @@ var ScoresMSSQL = class extends ScoresStorage {
1566
2513
  `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2`
1567
2514
  );
1568
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);
1569
2519
  if (total === 0) {
1570
2520
  return {
1571
2521
  pagination: {
1572
2522
  total: 0,
1573
- page: pagination.page,
1574
- perPage: pagination.perPage,
2523
+ page,
2524
+ perPage: perPageForResponse,
1575
2525
  hasMore: false
1576
2526
  },
1577
2527
  scores: []
1578
2528
  };
1579
2529
  }
2530
+ const limitValue = perPageInput === false ? total : perPage;
2531
+ const end = perPageInput === false ? total : start + perPage;
1580
2532
  const dataRequest = this.pool.request();
1581
2533
  dataRequest.input("p1", entityId);
1582
2534
  dataRequest.input("p2", entityType);
1583
- dataRequest.input("p3", pagination.perPage);
1584
- dataRequest.input("p4", pagination.page * pagination.perPage);
2535
+ dataRequest.input("p3", limitValue);
2536
+ dataRequest.input("p4", start);
1585
2537
  const result = await dataRequest.query(
1586
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`
1587
2539
  );
1588
2540
  return {
1589
2541
  pagination: {
1590
2542
  total: Number(total),
1591
- page: pagination.page,
1592
- perPage: pagination.perPage,
1593
- hasMore: Number(total) > (pagination.page + 1) * pagination.perPage
2543
+ page,
2544
+ perPage: perPageForResponse,
2545
+ hasMore: end < total
1594
2546
  },
1595
2547
  scores: result.recordset.map((row) => transformScoreRow(row))
1596
2548
  };
@@ -1606,8 +2558,66 @@ var ScoresMSSQL = class extends ScoresStorage {
1606
2558
  );
1607
2559
  }
1608
2560
  }
2561
+ async listScoresBySpan({
2562
+ traceId,
2563
+ spanId,
2564
+ pagination
2565
+ }) {
2566
+ try {
2567
+ const request = this.pool.request();
2568
+ request.input("p1", traceId);
2569
+ request.input("p2", spanId);
2570
+ const totalResult = await request.query(
2571
+ `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2`
2572
+ );
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);
2577
+ if (total === 0) {
2578
+ return {
2579
+ pagination: {
2580
+ total: 0,
2581
+ page,
2582
+ perPage: perPageForResponse,
2583
+ hasMore: false
2584
+ },
2585
+ scores: []
2586
+ };
2587
+ }
2588
+ const limitValue = perPageInput === false ? total : perPage;
2589
+ const end = perPageInput === false ? total : start + perPage;
2590
+ const dataRequest = this.pool.request();
2591
+ dataRequest.input("p1", traceId);
2592
+ dataRequest.input("p2", spanId);
2593
+ dataRequest.input("p3", limitValue);
2594
+ dataRequest.input("p4", start);
2595
+ const result = await dataRequest.query(
2596
+ `SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [traceId] = @p1 AND [spanId] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
2597
+ );
2598
+ return {
2599
+ pagination: {
2600
+ total: Number(total),
2601
+ page,
2602
+ perPage: perPageForResponse,
2603
+ hasMore: end < total
2604
+ },
2605
+ scores: result.recordset.map((row) => transformScoreRow(row))
2606
+ };
2607
+ } catch (error) {
2608
+ throw new MastraError(
2609
+ {
2610
+ id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORES_BY_SPAN_FAILED",
2611
+ domain: ErrorDomain.STORAGE,
2612
+ category: ErrorCategory.THIRD_PARTY,
2613
+ details: { traceId, spanId }
2614
+ },
2615
+ error
2616
+ );
2617
+ }
2618
+ }
1609
2619
  };
1610
- var TracesMSSQL = class extends TracesStorage {
2620
+ var WorkflowsMSSQL = class extends WorkflowsStorage {
1611
2621
  pool;
1612
2622
  operations;
1613
2623
  schema;
@@ -1621,194 +2631,169 @@ var TracesMSSQL = class extends TracesStorage {
1621
2631
  this.operations = operations;
1622
2632
  this.schema = schema;
1623
2633
  }
1624
- /** @deprecated use getTracesPaginated instead*/
1625
- async getTraces(args) {
1626
- if (args.fromDate || args.toDate) {
1627
- args.dateRange = {
1628
- start: args.fromDate,
1629
- end: args.toDate
1630
- };
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
+ }
1631
2642
  }
1632
- const result = await this.getTracesPaginated(args);
1633
- 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
+ };
1634
2651
  }
1635
- async getTracesPaginated(args) {
1636
- const { name, scope, page = 0, perPage: perPageInput, attributes, filters, dateRange } = args;
1637
- const fromDate = dateRange?.start;
1638
- const toDate = dateRange?.end;
1639
- const perPage = perPageInput !== void 0 ? perPageInput : 100;
1640
- const currentOffset = page * perPage;
1641
- const paramMap = {};
1642
- const conditions = [];
1643
- let paramIndex = 1;
1644
- if (name) {
1645
- const paramName = `p${paramIndex++}`;
1646
- conditions.push(`[name] LIKE @${paramName}`);
1647
- paramMap[paramName] = `${name}%`;
1648
- }
1649
- if (scope) {
1650
- const paramName = `p${paramIndex++}`;
1651
- conditions.push(`[scope] = @${paramName}`);
1652
- paramMap[paramName] = scope;
1653
- }
1654
- if (attributes) {
1655
- Object.entries(attributes).forEach(([key, value]) => {
1656
- const parsedKey = parseFieldKey(key);
1657
- const paramName = `p${paramIndex++}`;
1658
- conditions.push(`JSON_VALUE([attributes], '$.${parsedKey}') = @${paramName}`);
1659
- paramMap[paramName] = value;
1660
- });
1661
- }
1662
- if (filters) {
1663
- Object.entries(filters).forEach(([key, value]) => {
1664
- const parsedKey = parseFieldKey(key);
1665
- const paramName = `p${paramIndex++}`;
1666
- conditions.push(`[${parsedKey}] = @${paramName}`);
1667
- paramMap[paramName] = value;
1668
- });
1669
- }
1670
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
1671
- const paramName = `p${paramIndex++}`;
1672
- conditions.push(`[createdAt] >= @${paramName}`);
1673
- paramMap[paramName] = fromDate.toISOString();
1674
- }
1675
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
1676
- const paramName = `p${paramIndex++}`;
1677
- conditions.push(`[createdAt] <= @${paramName}`);
1678
- paramMap[paramName] = toDate.toISOString();
1679
- }
1680
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1681
- const countQuery = `SELECT COUNT(*) as total FROM ${getTableName({ indexName: TABLE_TRACES, schemaName: getSchemaName(this.schema) })} ${whereClause}`;
1682
- 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();
1683
2661
  try {
1684
- const countRequest = this.pool.request();
1685
- Object.entries(paramMap).forEach(([key, value]) => {
1686
- if (value instanceof Date) {
1687
- countRequest.input(key, sql2.DateTime, value);
1688
- } else {
1689
- countRequest.input(key, value);
1690
- }
1691
- });
1692
- const countResult = await countRequest.query(countQuery);
1693
- 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;
1694
2707
  } catch (error) {
2708
+ try {
2709
+ await transaction.rollback();
2710
+ } catch {
2711
+ }
1695
2712
  throw new MastraError(
1696
2713
  {
1697
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TOTAL_COUNT",
2714
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_RESULTS_FAILED",
1698
2715
  domain: ErrorDomain.STORAGE,
1699
2716
  category: ErrorCategory.THIRD_PARTY,
1700
2717
  details: {
1701
- name: args.name ?? "",
1702
- scope: args.scope ?? ""
2718
+ workflowName,
2719
+ runId,
2720
+ stepId
1703
2721
  }
1704
2722
  },
1705
2723
  error
1706
2724
  );
1707
2725
  }
1708
- if (total === 0) {
1709
- return {
1710
- traces: [],
1711
- total: 0,
1712
- page,
1713
- perPage,
1714
- hasMore: false
1715
- };
1716
- }
1717
- 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`;
1718
- const dataRequest = this.pool.request();
1719
- Object.entries(paramMap).forEach(([key, value]) => {
1720
- if (value instanceof Date) {
1721
- dataRequest.input(key, sql2.DateTime, value);
1722
- } else {
1723
- dataRequest.input(key, value);
1724
- }
1725
- });
1726
- dataRequest.input("offset", currentOffset);
1727
- 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();
1728
2734
  try {
1729
- const rowsResult = await dataRequest.query(dataQuery);
1730
- const rows = rowsResult.recordset;
1731
- const traces = rows.map((row) => ({
1732
- id: row.id,
1733
- parentSpanId: row.parentSpanId,
1734
- traceId: row.traceId,
1735
- name: row.name,
1736
- scope: row.scope,
1737
- kind: row.kind,
1738
- status: JSON.parse(row.status),
1739
- events: JSON.parse(row.events),
1740
- links: JSON.parse(row.links),
1741
- attributes: JSON.parse(row.attributes),
1742
- startTime: row.startTime,
1743
- endTime: row.endTime,
1744
- other: row.other,
1745
- createdAt: row.createdAt
1746
- }));
1747
- return {
1748
- traces,
1749
- total,
1750
- page,
1751
- perPage,
1752
- hasMore: currentOffset + traces.length < total
1753
- };
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;
1754
2774
  } catch (error) {
2775
+ try {
2776
+ await transaction.rollback();
2777
+ } catch {
2778
+ }
1755
2779
  throw new MastraError(
1756
2780
  {
1757
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TRACES",
2781
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_FAILED",
1758
2782
  domain: ErrorDomain.STORAGE,
1759
2783
  category: ErrorCategory.THIRD_PARTY,
1760
2784
  details: {
1761
- name: args.name ?? "",
1762
- scope: args.scope ?? ""
2785
+ workflowName,
2786
+ runId
1763
2787
  }
1764
2788
  },
1765
2789
  error
1766
2790
  );
1767
2791
  }
1768
2792
  }
1769
- async batchTraceInsert({ records }) {
1770
- this.logger.debug("Batch inserting traces", { count: records.length });
1771
- await this.operations.batchInsert({
1772
- tableName: TABLE_TRACES,
1773
- records
1774
- });
1775
- }
1776
- };
1777
- function parseWorkflowRun(row) {
1778
- let parsedSnapshot = row.snapshot;
1779
- if (typeof parsedSnapshot === "string") {
1780
- try {
1781
- parsedSnapshot = JSON.parse(row.snapshot);
1782
- } catch (e) {
1783
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1784
- }
1785
- }
1786
- return {
1787
- workflowName: row.workflow_name,
1788
- runId: row.run_id,
1789
- snapshot: parsedSnapshot,
1790
- createdAt: row.createdAt,
1791
- updatedAt: row.updatedAt,
1792
- resourceId: row.resourceId
1793
- };
1794
- }
1795
- var WorkflowsMSSQL = class extends WorkflowsStorage {
1796
- pool;
1797
- operations;
1798
- schema;
1799
- constructor({
1800
- pool,
1801
- operations,
1802
- schema
1803
- }) {
1804
- super();
1805
- this.pool = pool;
1806
- this.operations = operations;
1807
- this.schema = schema;
1808
- }
1809
2793
  async persistWorkflowSnapshot({
1810
2794
  workflowName,
1811
2795
  runId,
2796
+ resourceId,
1812
2797
  snapshot
1813
2798
  }) {
1814
2799
  const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
@@ -1817,6 +2802,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
1817
2802
  const request = this.pool.request();
1818
2803
  request.input("workflow_name", workflowName);
1819
2804
  request.input("run_id", runId);
2805
+ request.input("resourceId", resourceId);
1820
2806
  request.input("snapshot", JSON.stringify(snapshot));
1821
2807
  request.input("createdAt", sql2.DateTime2, new Date(now));
1822
2808
  request.input("updatedAt", sql2.DateTime2, new Date(now));
@@ -1824,10 +2810,11 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
1824
2810
  USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
1825
2811
  ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
1826
2812
  WHEN MATCHED THEN UPDATE SET
2813
+ resourceId = @resourceId,
1827
2814
  snapshot = @snapshot,
1828
2815
  [updatedAt] = @updatedAt
1829
- WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, snapshot, [createdAt], [updatedAt])
1830
- VALUES (@workflow_name, @run_id, @snapshot, @createdAt, @updatedAt);`;
2816
+ WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, resourceId, snapshot, [createdAt], [updatedAt])
2817
+ VALUES (@workflow_name, @run_id, @resourceId, @snapshot, @createdAt, @updatedAt);`;
1831
2818
  await request.query(mergeSql);
1832
2819
  } catch (error) {
1833
2820
  throw new MastraError(
@@ -1899,7 +2886,7 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
1899
2886
  if (!result.recordset || result.recordset.length === 0) {
1900
2887
  return null;
1901
2888
  }
1902
- return parseWorkflowRun(result.recordset[0]);
2889
+ return this.parseWorkflowRun(result.recordset[0]);
1903
2890
  } catch (error) {
1904
2891
  throw new MastraError(
1905
2892
  {
@@ -1915,13 +2902,14 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
1915
2902
  );
1916
2903
  }
1917
2904
  }
1918
- async getWorkflowRuns({
2905
+ async listWorkflowRuns({
1919
2906
  workflowName,
1920
2907
  fromDate,
1921
2908
  toDate,
1922
- limit,
1923
- offset,
1924
- resourceId
2909
+ page,
2910
+ perPage,
2911
+ resourceId,
2912
+ status
1925
2913
  } = {}) {
1926
2914
  try {
1927
2915
  const conditions = [];
@@ -1930,13 +2918,17 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
1930
2918
  conditions.push(`[workflow_name] = @workflowName`);
1931
2919
  paramMap["workflowName"] = workflowName;
1932
2920
  }
2921
+ if (status) {
2922
+ conditions.push(`JSON_VALUE([snapshot], '$.status') = @status`);
2923
+ paramMap["status"] = status;
2924
+ }
1933
2925
  if (resourceId) {
1934
2926
  const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
1935
2927
  if (hasResourceId) {
1936
2928
  conditions.push(`[resourceId] = @resourceId`);
1937
2929
  paramMap["resourceId"] = resourceId;
1938
2930
  } else {
1939
- 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.`);
1940
2932
  }
1941
2933
  }
1942
2934
  if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
@@ -1958,24 +2950,27 @@ var WorkflowsMSSQL = class extends WorkflowsStorage {
1958
2950
  request.input(key, value);
1959
2951
  }
1960
2952
  });
1961
- if (limit !== void 0 && offset !== void 0) {
2953
+ const usePagination = typeof perPage === "number" && typeof page === "number";
2954
+ if (usePagination) {
1962
2955
  const countQuery = `SELECT COUNT(*) as count FROM ${tableName} ${whereClause}`;
1963
2956
  const countResult = await request.query(countQuery);
1964
2957
  total = Number(countResult.recordset[0]?.count || 0);
1965
2958
  }
1966
2959
  let query = `SELECT * FROM ${tableName} ${whereClause} ORDER BY [seq_id] DESC`;
1967
- if (limit !== void 0 && offset !== void 0) {
1968
- query += ` OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
1969
- 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);
1970
2965
  request.input("offset", offset);
1971
2966
  }
1972
2967
  const result = await request.query(query);
1973
- const runs = (result.recordset || []).map((row) => parseWorkflowRun(row));
2968
+ const runs = (result.recordset || []).map((row) => this.parseWorkflowRun(row));
1974
2969
  return { runs, total: total || runs.length };
1975
2970
  } catch (error) {
1976
2971
  throw new MastraError(
1977
2972
  {
1978
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_WORKFLOW_RUNS_FAILED",
2973
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_WORKFLOW_RUNS_FAILED",
1979
2974
  domain: ErrorDomain.STORAGE,
1980
2975
  category: ErrorCategory.THIRD_PARTY,
1981
2976
  details: {
@@ -1995,7 +2990,10 @@ var MSSQLStore = class extends MastraStorage {
1995
2990
  isConnected = null;
1996
2991
  stores;
1997
2992
  constructor(config) {
1998
- 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" });
1999
2997
  try {
2000
2998
  if ("connectionString" in config) {
2001
2999
  if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
@@ -2018,19 +3016,17 @@ var MSSQLStore = class extends MastraStorage {
2018
3016
  port: config.port,
2019
3017
  options: config.options || { encrypt: true, trustServerCertificate: true }
2020
3018
  });
2021
- const legacyEvals = new LegacyEvalsMSSQL({ pool: this.pool, schema: this.schema });
2022
3019
  const operations = new StoreOperationsMSSQL({ pool: this.pool, schemaName: this.schema });
2023
3020
  const scores = new ScoresMSSQL({ pool: this.pool, operations, schema: this.schema });
2024
- const traces = new TracesMSSQL({ pool: this.pool, operations, schema: this.schema });
2025
3021
  const workflows = new WorkflowsMSSQL({ pool: this.pool, operations, schema: this.schema });
2026
3022
  const memory = new MemoryMSSQL({ pool: this.pool, schema: this.schema, operations });
3023
+ const observability = new ObservabilityMSSQL({ pool: this.pool, operations, schema: this.schema });
2027
3024
  this.stores = {
2028
3025
  operations,
2029
3026
  scores,
2030
- traces,
2031
3027
  workflows,
2032
- legacyEvals,
2033
- memory
3028
+ memory,
3029
+ observability
2034
3030
  };
2035
3031
  } catch (e) {
2036
3032
  throw new MastraError(
@@ -2050,6 +3046,11 @@ var MSSQLStore = class extends MastraStorage {
2050
3046
  try {
2051
3047
  await this.isConnected;
2052
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
+ }
2053
3054
  } catch (error) {
2054
3055
  this.isConnected = null;
2055
3056
  throw new MastraError(
@@ -2076,28 +3077,12 @@ var MSSQLStore = class extends MastraStorage {
2076
3077
  resourceWorkingMemory: true,
2077
3078
  hasColumn: true,
2078
3079
  createTable: true,
2079
- deleteMessages: true
3080
+ deleteMessages: true,
3081
+ listScoresBySpan: true,
3082
+ observabilityInstance: true,
3083
+ indexManagement: true
2080
3084
  };
2081
3085
  }
2082
- /** @deprecated use getEvals instead */
2083
- async getEvalsByAgentName(agentName, type) {
2084
- return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
2085
- }
2086
- async getEvals(options = {}) {
2087
- return this.stores.legacyEvals.getEvals(options);
2088
- }
2089
- /**
2090
- * @deprecated use getTracesPaginated instead
2091
- */
2092
- async getTraces(args) {
2093
- return this.stores.traces.getTraces(args);
2094
- }
2095
- async getTracesPaginated(args) {
2096
- return this.stores.traces.getTracesPaginated(args);
2097
- }
2098
- async batchTraceInsert({ records }) {
2099
- return this.stores.traces.batchTraceInsert({ records });
2100
- }
2101
3086
  async createTable({
2102
3087
  tableName,
2103
3088
  schema
@@ -2132,15 +3117,6 @@ var MSSQLStore = class extends MastraStorage {
2132
3117
  async getThreadById({ threadId }) {
2133
3118
  return this.stores.memory.getThreadById({ threadId });
2134
3119
  }
2135
- /**
2136
- * @deprecated use getThreadsByResourceIdPaginated instead
2137
- */
2138
- async getThreadsByResourceId(args) {
2139
- return this.stores.memory.getThreadsByResourceId(args);
2140
- }
2141
- async getThreadsByResourceIdPaginated(args) {
2142
- return this.stores.memory.getThreadsByResourceIdPaginated(args);
2143
- }
2144
3120
  async saveThread({ thread }) {
2145
3121
  return this.stores.memory.saveThread({ thread });
2146
3122
  }
@@ -2154,11 +3130,8 @@ var MSSQLStore = class extends MastraStorage {
2154
3130
  async deleteThread({ threadId }) {
2155
3131
  return this.stores.memory.deleteThread({ threadId });
2156
3132
  }
2157
- async getMessages(args) {
2158
- return this.stores.memory.getMessages(args);
2159
- }
2160
- async getMessagesPaginated(args) {
2161
- return this.stores.memory.getMessagesPaginated(args);
3133
+ async listMessagesById({ messageIds }) {
3134
+ return this.stores.memory.listMessagesById({ messageIds });
2162
3135
  }
2163
3136
  async saveMessages(args) {
2164
3137
  return this.stores.memory.saveMessages(args);
@@ -2187,12 +3160,29 @@ var MSSQLStore = class extends MastraStorage {
2187
3160
  /**
2188
3161
  * Workflows
2189
3162
  */
3163
+ async updateWorkflowResults({
3164
+ workflowName,
3165
+ runId,
3166
+ stepId,
3167
+ result,
3168
+ requestContext
3169
+ }) {
3170
+ return this.stores.workflows.updateWorkflowResults({ workflowName, runId, stepId, result, requestContext });
3171
+ }
3172
+ async updateWorkflowState({
3173
+ workflowName,
3174
+ runId,
3175
+ opts
3176
+ }) {
3177
+ return this.stores.workflows.updateWorkflowState({ workflowName, runId, opts });
3178
+ }
2190
3179
  async persistWorkflowSnapshot({
2191
3180
  workflowName,
2192
3181
  runId,
3182
+ resourceId,
2193
3183
  snapshot
2194
3184
  }) {
2195
- return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, snapshot });
3185
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
2196
3186
  }
2197
3187
  async loadWorkflowSnapshot({
2198
3188
  workflowName,
@@ -2200,15 +3190,8 @@ var MSSQLStore = class extends MastraStorage {
2200
3190
  }) {
2201
3191
  return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
2202
3192
  }
2203
- async getWorkflowRuns({
2204
- workflowName,
2205
- fromDate,
2206
- toDate,
2207
- limit,
2208
- offset,
2209
- resourceId
2210
- } = {}) {
2211
- return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
3193
+ async listWorkflowRuns(args = {}) {
3194
+ return this.stores.workflows.listWorkflowRuns(args);
2212
3195
  }
2213
3196
  async getWorkflowRunById({
2214
3197
  runId,
@@ -2219,38 +3202,108 @@ var MSSQLStore = class extends MastraStorage {
2219
3202
  async close() {
2220
3203
  await this.pool.close();
2221
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
+ }
2222
3259
  /**
2223
3260
  * Scorers
2224
3261
  */
2225
3262
  async getScoreById({ id: _id }) {
2226
3263
  return this.stores.scores.getScoreById({ id: _id });
2227
3264
  }
2228
- async getScoresByScorerId({
3265
+ async listScoresByScorerId({
2229
3266
  scorerId: _scorerId,
2230
- pagination: _pagination
3267
+ pagination: _pagination,
3268
+ entityId: _entityId,
3269
+ entityType: _entityType,
3270
+ source: _source
2231
3271
  }) {
2232
- 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
+ });
2233
3279
  }
2234
3280
  async saveScore(_score) {
2235
3281
  return this.stores.scores.saveScore(_score);
2236
3282
  }
2237
- async getScoresByRunId({
3283
+ async listScoresByRunId({
2238
3284
  runId: _runId,
2239
3285
  pagination: _pagination
2240
3286
  }) {
2241
- return this.stores.scores.getScoresByRunId({ runId: _runId, pagination: _pagination });
3287
+ return this.stores.scores.listScoresByRunId({ runId: _runId, pagination: _pagination });
2242
3288
  }
2243
- async getScoresByEntityId({
3289
+ async listScoresByEntityId({
2244
3290
  entityId: _entityId,
2245
3291
  entityType: _entityType,
2246
3292
  pagination: _pagination
2247
3293
  }) {
2248
- return this.stores.scores.getScoresByEntityId({
3294
+ return this.stores.scores.listScoresByEntityId({
2249
3295
  entityId: _entityId,
2250
3296
  entityType: _entityType,
2251
3297
  pagination: _pagination
2252
3298
  });
2253
3299
  }
3300
+ async listScoresBySpan({
3301
+ traceId,
3302
+ spanId,
3303
+ pagination: _pagination
3304
+ }) {
3305
+ return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination: _pagination });
3306
+ }
2254
3307
  };
2255
3308
 
2256
3309
  export { MSSQLStore };