@mastra/mssql 0.0.0-share-agent-metadata-with-cloud-20250718123411 → 0.0.0-span-scorring-test-20251124132129

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,1746 +1,3311 @@
1
- import { MessageList } from '@mastra/core/agent';
2
1
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
- import { MastraStorage, TABLE_EVALS, TABLE_TRACES, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES } from '@mastra/core/storage';
4
- import { parseSqlIdentifier, parseFieldKey } from '@mastra/core/utils';
5
- import sql from 'mssql';
2
+ import { MastraStorage, StoreOperations, TABLE_WORKFLOW_SNAPSHOT, TABLE_SCHEMAS, TABLE_THREADS, TABLE_MESSAGES, TABLE_TRACES, TABLE_SCORERS, TABLE_SPANS, ScoresStorage, normalizePerPage, calculatePagination, WorkflowsStorage, MemoryStorage, TABLE_RESOURCES, ObservabilityStorage, safelyParseJSON } from '@mastra/core/storage';
3
+ import sql2 from 'mssql';
4
+ import { MessageList } from '@mastra/core/agent';
5
+ import { parseSqlIdentifier } from '@mastra/core/utils';
6
+ import { randomUUID } from 'crypto';
7
+ import { saveScorePayloadSchema } from '@mastra/core/evals';
6
8
 
7
9
  // src/storage/index.ts
8
- var MSSQLStore = class extends MastraStorage {
9
- pool;
10
- schema;
11
- setupSchemaPromise = null;
12
- schemaSetupComplete = void 0;
13
- isConnected = null;
14
- constructor(config) {
15
- super({ name: "MSSQLStore" });
16
- try {
17
- if ("connectionString" in config) {
18
- if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
19
- throw new Error("MSSQLStore: connectionString must be provided and cannot be empty.");
20
- }
21
- } else {
22
- const required = ["server", "database", "user", "password"];
23
- for (const key of required) {
24
- if (!(key in config) || typeof config[key] !== "string" || config[key].trim() === "") {
25
- throw new Error(`MSSQLStore: ${key} must be provided and cannot be empty.`);
26
- }
27
- }
28
- }
29
- this.schema = config.schemaName;
30
- this.pool = "connectionString" in config ? new sql.ConnectionPool(config.connectionString) : new sql.ConnectionPool({
31
- server: config.server,
32
- database: config.database,
33
- user: config.user,
34
- password: config.password,
35
- port: config.port,
36
- options: config.options || { encrypt: true, trustServerCertificate: true }
37
- });
38
- } catch (e) {
39
- throw new MastraError(
40
- {
41
- id: "MASTRA_STORAGE_MSSQL_STORE_INITIALIZATION_FAILED",
42
- domain: ErrorDomain.STORAGE,
43
- category: ErrorCategory.USER
44
- },
45
- e
46
- );
47
- }
10
+ function getSchemaName(schema) {
11
+ return schema ? `[${parseSqlIdentifier(schema, "schema name")}]` : void 0;
12
+ }
13
+ function getTableName({ indexName, schemaName }) {
14
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
15
+ const quotedIndexName = `[${parsedIndexName}]`;
16
+ const quotedSchemaName = schemaName;
17
+ return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
18
+ }
19
+ function buildDateRangeFilter(dateRange, fieldName) {
20
+ const filters = {};
21
+ if (dateRange?.start) {
22
+ filters[`${fieldName}_gte`] = dateRange.start;
48
23
  }
49
- async init() {
50
- if (this.isConnected === null) {
51
- this.isConnected = this._performInitializationAndStore();
52
- }
53
- try {
54
- await this.isConnected;
55
- await super.init();
56
- } catch (error) {
57
- this.isConnected = null;
58
- throw new MastraError(
59
- {
60
- id: "MASTRA_STORAGE_MSSQL_STORE_INIT_FAILED",
61
- domain: ErrorDomain.STORAGE,
62
- category: ErrorCategory.THIRD_PARTY
63
- },
64
- error
65
- );
66
- }
24
+ if (dateRange?.end) {
25
+ filters[`${fieldName}_lte`] = dateRange.end;
67
26
  }
68
- async _performInitializationAndStore() {
69
- try {
70
- await this.pool.connect();
71
- return true;
72
- } catch (err) {
73
- throw err;
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;
74
49
  }
75
- }
76
- get supports() {
77
- return {
78
- selectByIncludeResourceScope: true,
79
- resourceWorkingMemory: true
80
- };
81
- }
82
- getTableName(indexName) {
83
- const parsedIndexName = parseSqlIdentifier(indexName, "index name");
84
- const quotedIndexName = `[${parsedIndexName}]`;
85
- const quotedSchemaName = this.getSchemaName();
86
- return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
87
- }
88
- getSchemaName() {
89
- return this.schema ? `[${parseSqlIdentifier(this.schema, "schema name")}]` : void 0;
90
- }
91
- transformEvalRow(row) {
92
- let testInfoValue = null, resultValue = null;
93
- if (row.test_info) {
50
+ });
51
+ return {
52
+ sql: conditions.length > 0 ? ` WHERE ${conditions.join(" AND ")}` : "",
53
+ params
54
+ };
55
+ }
56
+ function transformFromSqlRow({
57
+ tableName,
58
+ sqlRow
59
+ }) {
60
+ const schema = TABLE_SCHEMAS[tableName];
61
+ const result = {};
62
+ Object.entries(sqlRow).forEach(([key, value]) => {
63
+ const columnSchema = schema?.[key];
64
+ if (columnSchema?.type === "jsonb" && typeof value === "string") {
94
65
  try {
95
- testInfoValue = typeof row.test_info === "string" ? JSON.parse(row.test_info) : row.test_info;
66
+ result[key] = JSON.parse(value);
96
67
  } catch {
68
+ result[key] = value;
97
69
  }
70
+ } else if (columnSchema?.type === "timestamp" && value && typeof value === "string") {
71
+ result[key] = new Date(value);
72
+ } else if (columnSchema?.type === "timestamp" && value instanceof Date) {
73
+ result[key] = value;
74
+ } else if (columnSchema?.type === "boolean") {
75
+ result[key] = Boolean(value);
76
+ } else {
77
+ result[key] = value;
98
78
  }
99
- if (row.test_info) {
100
- try {
101
- resultValue = typeof row.result === "string" ? JSON.parse(row.result) : row.result;
102
- } catch {
79
+ });
80
+ return result;
81
+ }
82
+
83
+ // src/storage/domains/memory/index.ts
84
+ var MemoryMSSQL = class extends MemoryStorage {
85
+ pool;
86
+ schema;
87
+ operations;
88
+ _parseAndFormatMessages(messages, format) {
89
+ const messagesWithParsedContent = messages.map((message) => {
90
+ if (typeof message.content === "string") {
91
+ try {
92
+ return { ...message, content: JSON.parse(message.content) };
93
+ } catch {
94
+ return message;
95
+ }
103
96
  }
104
- }
105
- return {
106
- agentName: row.agent_name,
107
- input: row.input,
108
- output: row.output,
109
- result: resultValue,
110
- metricName: row.metric_name,
111
- instructions: row.instructions,
112
- testInfo: testInfoValue,
113
- globalRunId: row.global_run_id,
114
- runId: row.run_id,
115
- createdAt: row.created_at
116
- };
97
+ return message;
98
+ });
99
+ const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
100
+ const list = new MessageList().add(cleanMessages, "memory");
101
+ return format === "v2" ? list.get.all.db() : list.get.all.v1();
117
102
  }
118
- /** @deprecated use getEvals instead */
119
- async getEvalsByAgentName(agentName, type) {
120
- try {
121
- let query = `SELECT * FROM ${this.getTableName(TABLE_EVALS)} WHERE agent_name = @p1`;
122
- if (type === "test") {
123
- query += " AND test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL";
124
- } else if (type === "live") {
125
- query += " AND (test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)";
126
- }
127
- query += " ORDER BY created_at DESC";
128
- const request = this.pool.request();
129
- request.input("p1", agentName);
130
- const result = await request.query(query);
131
- const rows = result.recordset;
132
- return typeof this.transformEvalRow === "function" ? rows?.map((row) => this.transformEvalRow(row)) ?? [] : rows ?? [];
133
- } catch (error) {
134
- if (error && error.number === 208 && error.message && error.message.includes("Invalid object name")) {
135
- return [];
136
- }
137
- console.error("Failed to get evals for the specified agent: " + error?.message);
138
- throw error;
139
- }
103
+ constructor({
104
+ pool,
105
+ schema,
106
+ operations
107
+ }) {
108
+ super();
109
+ this.pool = pool;
110
+ this.schema = schema;
111
+ this.operations = operations;
140
112
  }
141
- async batchInsert({ tableName, records }) {
142
- const transaction = this.pool.transaction();
113
+ async getThreadById({ threadId }) {
143
114
  try {
144
- await transaction.begin();
145
- for (const record of records) {
146
- await this.insert({ tableName, record });
115
+ const sql5 = `SELECT
116
+ id,
117
+ [resourceId],
118
+ title,
119
+ metadata,
120
+ [createdAt],
121
+ [updatedAt]
122
+ FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })}
123
+ WHERE id = @threadId`;
124
+ const request = this.pool.request();
125
+ request.input("threadId", threadId);
126
+ const resultSet = await request.query(sql5);
127
+ const thread = resultSet.recordset[0] || null;
128
+ if (!thread) {
129
+ return null;
147
130
  }
148
- await transaction.commit();
131
+ return {
132
+ ...thread,
133
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
134
+ createdAt: thread.createdAt,
135
+ updatedAt: thread.updatedAt
136
+ };
149
137
  } catch (error) {
150
- await transaction.rollback();
151
138
  throw new MastraError(
152
139
  {
153
- id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_INSERT_FAILED",
140
+ id: "MASTRA_STORAGE_MSSQL_STORE_GET_THREAD_BY_ID_FAILED",
154
141
  domain: ErrorDomain.STORAGE,
155
142
  category: ErrorCategory.THIRD_PARTY,
156
143
  details: {
157
- tableName,
158
- numberOfRecords: records.length
144
+ threadId
159
145
  }
160
146
  },
161
147
  error
162
148
  );
163
149
  }
164
150
  }
165
- /** @deprecated use getTracesPaginated instead*/
166
- async getTraces(args) {
167
- if (args.fromDate || args.toDate) {
168
- args.dateRange = {
169
- start: args.fromDate,
170
- end: args.toDate
171
- };
172
- }
173
- const result = await this.getTracesPaginated(args);
174
- return result.traces;
175
- }
176
- async getTracesPaginated(args) {
177
- const { name, scope, page = 0, perPage: perPageInput, attributes, filters, dateRange } = args;
178
- const fromDate = dateRange?.start;
179
- const toDate = dateRange?.end;
180
- const perPage = perPageInput !== void 0 ? perPageInput : 100;
181
- const currentOffset = page * perPage;
182
- const paramMap = {};
183
- const conditions = [];
184
- let paramIndex = 1;
185
- if (name) {
186
- const paramName = `p${paramIndex++}`;
187
- conditions.push(`[name] LIKE @${paramName}`);
188
- paramMap[paramName] = `${name}%`;
189
- }
190
- if (scope) {
191
- const paramName = `p${paramIndex++}`;
192
- conditions.push(`[scope] = @${paramName}`);
193
- paramMap[paramName] = scope;
194
- }
195
- if (attributes) {
196
- Object.entries(attributes).forEach(([key, value]) => {
197
- const parsedKey = parseFieldKey(key);
198
- const paramName = `p${paramIndex++}`;
199
- conditions.push(`JSON_VALUE([attributes], '$.${parsedKey}') = @${paramName}`);
200
- paramMap[paramName] = value;
201
- });
202
- }
203
- if (filters) {
204
- Object.entries(filters).forEach(([key, value]) => {
205
- const parsedKey = parseFieldKey(key);
206
- const paramName = `p${paramIndex++}`;
207
- conditions.push(`[${parsedKey}] = @${paramName}`);
208
- paramMap[paramName] = value;
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
+ }
209
163
  });
210
164
  }
211
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
212
- const paramName = `p${paramIndex++}`;
213
- conditions.push(`[createdAt] >= @${paramName}`);
214
- paramMap[paramName] = fromDate.toISOString();
215
- }
216
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
217
- const paramName = `p${paramIndex++}`;
218
- conditions.push(`[createdAt] <= @${paramName}`);
219
- paramMap[paramName] = toDate.toISOString();
220
- }
221
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
222
- const countQuery = `SELECT COUNT(*) as total FROM ${this.getTableName(TABLE_TRACES)} ${whereClause}`;
223
- let total = 0;
165
+ const perPage = normalizePerPage(perPageInput, 100);
166
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
167
+ const { field, direction } = this.parseOrderBy(orderBy);
224
168
  try {
169
+ const baseQuery = `FROM ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} WHERE [resourceId] = @resourceId`;
170
+ const countQuery = `SELECT COUNT(*) as count ${baseQuery}`;
225
171
  const countRequest = this.pool.request();
226
- Object.entries(paramMap).forEach(([key, value]) => {
227
- if (value instanceof Date) {
228
- countRequest.input(key, sql.DateTime, value);
229
- } else {
230
- countRequest.input(key, value);
231
- }
232
- });
172
+ countRequest.input("resourceId", resourceId);
233
173
  const countResult = await countRequest.query(countQuery);
234
- total = parseInt(countResult.recordset[0].total, 10);
174
+ const total = parseInt(countResult.recordset[0]?.count ?? "0", 10);
175
+ if (total === 0) {
176
+ return {
177
+ threads: [],
178
+ total: 0,
179
+ page,
180
+ perPage: perPageForResponse,
181
+ hasMore: false
182
+ };
183
+ }
184
+ const orderByField = field === "createdAt" ? "[createdAt]" : "[updatedAt]";
185
+ const dir = (direction || "DESC").toUpperCase() === "ASC" ? "ASC" : "DESC";
186
+ const limitValue = perPageInput === false ? total : perPage;
187
+ const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
188
+ const dataRequest = this.pool.request();
189
+ dataRequest.input("resourceId", resourceId);
190
+ dataRequest.input("offset", offset);
191
+ if (limitValue > 2147483647) {
192
+ dataRequest.input("perPage", sql2.BigInt, limitValue);
193
+ } else {
194
+ dataRequest.input("perPage", limitValue);
195
+ }
196
+ const rowsResult = await dataRequest.query(dataQuery);
197
+ const rows = rowsResult.recordset || [];
198
+ const threads = rows.map((thread) => ({
199
+ ...thread,
200
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
201
+ createdAt: thread.createdAt,
202
+ updatedAt: thread.updatedAt
203
+ }));
204
+ return {
205
+ threads,
206
+ total,
207
+ page,
208
+ perPage: perPageForResponse,
209
+ hasMore: perPageInput === false ? false : offset + perPage < total
210
+ };
235
211
  } catch (error) {
236
- throw new MastraError(
212
+ const mastraError = new MastraError(
237
213
  {
238
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TOTAL_COUNT",
214
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_THREADS_BY_RESOURCE_ID_FAILED",
239
215
  domain: ErrorDomain.STORAGE,
240
216
  category: ErrorCategory.THIRD_PARTY,
241
217
  details: {
242
- name: args.name ?? "",
243
- scope: args.scope ?? ""
218
+ resourceId,
219
+ page
244
220
  }
245
221
  },
246
222
  error
247
223
  );
248
- }
249
- if (total === 0) {
224
+ this.logger?.error?.(mastraError.toString());
225
+ this.logger?.trackException?.(mastraError);
250
226
  return {
251
- traces: [],
227
+ threads: [],
252
228
  total: 0,
253
229
  page,
254
- perPage,
230
+ perPage: perPageForResponse,
255
231
  hasMore: false
256
232
  };
257
233
  }
258
- const dataQuery = `SELECT * FROM ${this.getTableName(TABLE_TRACES)} ${whereClause} ORDER BY [seq_id] DESC OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
259
- const dataRequest = this.pool.request();
260
- Object.entries(paramMap).forEach(([key, value]) => {
261
- if (value instanceof Date) {
262
- dataRequest.input(key, sql.DateTime, value);
234
+ }
235
+ async saveThread({ thread }) {
236
+ try {
237
+ const table = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
238
+ const mergeSql = `MERGE INTO ${table} WITH (HOLDLOCK) AS target
239
+ USING (SELECT @id AS id) AS source
240
+ ON (target.id = source.id)
241
+ WHEN MATCHED THEN
242
+ UPDATE SET
243
+ [resourceId] = @resourceId,
244
+ title = @title,
245
+ metadata = @metadata,
246
+ [updatedAt] = @updatedAt
247
+ WHEN NOT MATCHED THEN
248
+ INSERT (id, [resourceId], title, metadata, [createdAt], [updatedAt])
249
+ VALUES (@id, @resourceId, @title, @metadata, @createdAt, @updatedAt);`;
250
+ const req = this.pool.request();
251
+ req.input("id", thread.id);
252
+ req.input("resourceId", thread.resourceId);
253
+ req.input("title", thread.title);
254
+ const metadata = thread.metadata ? JSON.stringify(thread.metadata) : null;
255
+ if (metadata === null) {
256
+ req.input("metadata", sql2.NVarChar, null);
263
257
  } else {
264
- dataRequest.input(key, value);
258
+ req.input("metadata", metadata);
265
259
  }
266
- });
267
- dataRequest.input("offset", currentOffset);
268
- dataRequest.input("limit", perPage);
269
- try {
270
- const rowsResult = await dataRequest.query(dataQuery);
271
- const rows = rowsResult.recordset;
272
- const traces = rows.map((row) => ({
273
- id: row.id,
274
- parentSpanId: row.parentSpanId,
275
- traceId: row.traceId,
276
- name: row.name,
277
- scope: row.scope,
278
- kind: row.kind,
279
- status: JSON.parse(row.status),
280
- events: JSON.parse(row.events),
281
- links: JSON.parse(row.links),
282
- attributes: JSON.parse(row.attributes),
283
- startTime: row.startTime,
284
- endTime: row.endTime,
285
- other: row.other,
286
- createdAt: row.createdAt
287
- }));
288
- return {
289
- traces,
290
- total,
291
- page,
292
- perPage,
293
- hasMore: currentOffset + traces.length < total
294
- };
260
+ req.input("createdAt", sql2.DateTime2, thread.createdAt);
261
+ req.input("updatedAt", sql2.DateTime2, thread.updatedAt);
262
+ await req.query(mergeSql);
263
+ return thread;
295
264
  } catch (error) {
296
265
  throw new MastraError(
297
266
  {
298
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_TRACES_PAGINATED_FAILED_TO_RETRIEVE_TRACES",
267
+ id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_THREAD_FAILED",
299
268
  domain: ErrorDomain.STORAGE,
300
269
  category: ErrorCategory.THIRD_PARTY,
301
270
  details: {
302
- name: args.name ?? "",
303
- scope: args.scope ?? ""
271
+ threadId: thread.id
304
272
  }
305
273
  },
306
274
  error
307
275
  );
308
276
  }
309
277
  }
310
- async setupSchema() {
311
- if (!this.schema || this.schemaSetupComplete) {
312
- return;
278
+ /**
279
+ * Updates a thread's title and metadata, merging with existing metadata. Returns the updated thread.
280
+ */
281
+ async updateThread({
282
+ id,
283
+ title,
284
+ metadata
285
+ }) {
286
+ const existingThread = await this.getThreadById({ threadId: id });
287
+ if (!existingThread) {
288
+ throw new MastraError({
289
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_THREAD_FAILED",
290
+ domain: ErrorDomain.STORAGE,
291
+ category: ErrorCategory.USER,
292
+ text: `Thread ${id} not found`,
293
+ details: {
294
+ threadId: id,
295
+ title
296
+ }
297
+ });
313
298
  }
314
- if (!this.setupSchemaPromise) {
315
- this.setupSchemaPromise = (async () => {
316
- try {
317
- const checkRequest = this.pool.request();
318
- checkRequest.input("schemaName", this.schema);
319
- const checkResult = await checkRequest.query(`
320
- SELECT 1 AS found FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = @schemaName
321
- `);
322
- const schemaExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
323
- if (!schemaExists) {
324
- try {
325
- await this.pool.request().query(`CREATE SCHEMA [${this.schema}]`);
326
- this.logger?.info?.(`Schema "${this.schema}" created successfully`);
327
- } catch (error) {
328
- this.logger?.error?.(`Failed to create schema "${this.schema}"`, { error });
329
- throw new Error(
330
- `Unable to create schema "${this.schema}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
331
- );
332
- }
333
- }
334
- this.schemaSetupComplete = true;
335
- this.logger?.debug?.(`Schema "${this.schema}" is ready for use`);
336
- } catch (error) {
337
- this.schemaSetupComplete = void 0;
338
- this.setupSchemaPromise = null;
339
- throw error;
340
- } finally {
341
- this.setupSchemaPromise = null;
342
- }
343
- })();
344
- }
345
- await this.setupSchemaPromise;
346
- }
347
- getSqlType(type, isPrimaryKey = false) {
348
- switch (type) {
349
- case "text":
350
- return isPrimaryKey ? "NVARCHAR(255)" : "NVARCHAR(MAX)";
351
- case "timestamp":
352
- return "DATETIME2(7)";
353
- case "uuid":
354
- return "UNIQUEIDENTIFIER";
355
- case "jsonb":
356
- return "NVARCHAR(MAX)";
357
- case "integer":
358
- return "INT";
359
- case "bigint":
360
- return "BIGINT";
361
- default:
299
+ const mergedMetadata = {
300
+ ...existingThread.metadata,
301
+ ...metadata
302
+ };
303
+ try {
304
+ const table = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
305
+ const sql5 = `UPDATE ${table}
306
+ SET title = @title,
307
+ metadata = @metadata,
308
+ [updatedAt] = @updatedAt
309
+ OUTPUT INSERTED.*
310
+ WHERE id = @id`;
311
+ const req = this.pool.request();
312
+ req.input("id", id);
313
+ req.input("title", title);
314
+ req.input("metadata", JSON.stringify(mergedMetadata));
315
+ req.input("updatedAt", /* @__PURE__ */ new Date());
316
+ const result = await req.query(sql5);
317
+ let thread = result.recordset && result.recordset[0];
318
+ if (thread && "seq_id" in thread) {
319
+ const { seq_id, ...rest } = thread;
320
+ thread = rest;
321
+ }
322
+ if (!thread) {
362
323
  throw new MastraError({
363
- id: "MASTRA_STORAGE_MSSQL_STORE_TYPE_NOT_SUPPORTED",
324
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_THREAD_FAILED",
364
325
  domain: ErrorDomain.STORAGE,
365
- category: ErrorCategory.THIRD_PARTY
326
+ category: ErrorCategory.USER,
327
+ text: `Thread ${id} not found after update`,
328
+ details: {
329
+ threadId: id,
330
+ title
331
+ }
366
332
  });
367
- }
368
- }
369
- async createTable({
370
- tableName,
371
- schema
372
- }) {
373
- try {
374
- const uniqueConstraintColumns = tableName === TABLE_WORKFLOW_SNAPSHOT ? ["workflow_name", "run_id"] : [];
375
- const columns = Object.entries(schema).map(([name, def]) => {
376
- const parsedName = parseSqlIdentifier(name, "column name");
377
- const constraints = [];
378
- if (def.primaryKey) constraints.push("PRIMARY KEY");
379
- if (!def.nullable) constraints.push("NOT NULL");
380
- const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
381
- return `[${parsedName}] ${this.getSqlType(def.type, isIndexed)} ${constraints.join(" ")}`.trim();
382
- }).join(",\n");
383
- if (this.schema) {
384
- await this.setupSchema();
385
- }
386
- const checkTableRequest = this.pool.request();
387
- checkTableRequest.input("tableName", this.getTableName(tableName).replace(/[[\]]/g, "").split(".").pop());
388
- const checkTableSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName`;
389
- checkTableRequest.input("schema", this.schema || "dbo");
390
- const checkTableResult = await checkTableRequest.query(checkTableSql);
391
- const tableExists = Array.isArray(checkTableResult.recordset) && checkTableResult.recordset.length > 0;
392
- if (!tableExists) {
393
- const createSql = `CREATE TABLE ${this.getTableName(tableName)} (
394
- ${columns}
395
- )`;
396
- await this.pool.request().query(createSql);
397
- }
398
- const columnCheckSql = `
399
- SELECT 1 AS found
400
- FROM INFORMATION_SCHEMA.COLUMNS
401
- WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = 'seq_id'
402
- `;
403
- const checkColumnRequest = this.pool.request();
404
- checkColumnRequest.input("schema", this.schema || "dbo");
405
- checkColumnRequest.input("tableName", this.getTableName(tableName).replace(/[[\]]/g, "").split(".").pop());
406
- const columnResult = await checkColumnRequest.query(columnCheckSql);
407
- const columnExists = Array.isArray(columnResult.recordset) && columnResult.recordset.length > 0;
408
- if (!columnExists) {
409
- const alterSql = `ALTER TABLE ${this.getTableName(tableName)} ADD seq_id BIGINT IDENTITY(1,1)`;
410
- await this.pool.request().query(alterSql);
411
- }
412
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
413
- const constraintName = "mastra_workflow_snapshot_workflow_name_run_id_key";
414
- const checkConstraintSql = `SELECT 1 AS found FROM sys.key_constraints WHERE name = @constraintName`;
415
- const checkConstraintRequest = this.pool.request();
416
- checkConstraintRequest.input("constraintName", constraintName);
417
- const constraintResult = await checkConstraintRequest.query(checkConstraintSql);
418
- const constraintExists = Array.isArray(constraintResult.recordset) && constraintResult.recordset.length > 0;
419
- if (!constraintExists) {
420
- const addConstraintSql = `ALTER TABLE ${this.getTableName(tableName)} ADD CONSTRAINT ${constraintName} UNIQUE ([workflow_name], [run_id])`;
421
- await this.pool.request().query(addConstraintSql);
422
- }
423
333
  }
334
+ return {
335
+ ...thread,
336
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
337
+ createdAt: thread.createdAt,
338
+ updatedAt: thread.updatedAt
339
+ };
424
340
  } catch (error) {
425
341
  throw new MastraError(
426
342
  {
427
- id: "MASTRA_STORAGE_MSSQL_STORE_CREATE_TABLE_FAILED",
343
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_THREAD_FAILED",
428
344
  domain: ErrorDomain.STORAGE,
429
345
  category: ErrorCategory.THIRD_PARTY,
430
346
  details: {
431
- tableName
347
+ threadId: id,
348
+ title
432
349
  }
433
350
  },
434
351
  error
435
352
  );
436
353
  }
437
354
  }
438
- getDefaultValue(type) {
439
- switch (type) {
440
- case "timestamp":
441
- return "DEFAULT SYSDATETIMEOFFSET()";
442
- case "jsonb":
443
- return "DEFAULT N'{}'";
444
- default:
445
- return super.getDefaultValue(type);
446
- }
447
- }
448
- async alterTable({
449
- tableName,
450
- schema,
451
- ifNotExists
452
- }) {
453
- const fullTableName = this.getTableName(tableName);
355
+ async deleteThread({ threadId }) {
356
+ const messagesTable = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
357
+ const threadsTable = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
358
+ const deleteMessagesSql = `DELETE FROM ${messagesTable} WHERE [thread_id] = @threadId`;
359
+ const deleteThreadSql = `DELETE FROM ${threadsTable} WHERE id = @threadId`;
360
+ const tx = this.pool.transaction();
454
361
  try {
455
- for (const columnName of ifNotExists) {
456
- if (schema[columnName]) {
457
- const columnCheckRequest = this.pool.request();
458
- columnCheckRequest.input("tableName", fullTableName.replace(/[[\]]/g, "").split(".").pop());
459
- columnCheckRequest.input("columnName", columnName);
460
- columnCheckRequest.input("schema", this.schema || "dbo");
461
- const checkSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = @columnName`;
462
- const checkResult = await columnCheckRequest.query(checkSql);
463
- const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
464
- if (!columnExists) {
465
- const columnDef = schema[columnName];
466
- const sqlType = this.getSqlType(columnDef.type);
467
- const nullable = columnDef.nullable === false ? "NOT NULL" : "";
468
- const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
469
- const parsedColumnName = parseSqlIdentifier(columnName, "column name");
470
- const alterSql = `ALTER TABLE ${fullTableName} ADD [${parsedColumnName}] ${sqlType} ${nullable} ${defaultValue}`.trim();
471
- await this.pool.request().query(alterSql);
472
- this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
473
- }
474
- }
475
- }
362
+ await tx.begin();
363
+ const req = tx.request();
364
+ req.input("threadId", threadId);
365
+ await req.query(deleteMessagesSql);
366
+ await req.query(deleteThreadSql);
367
+ await tx.commit();
476
368
  } catch (error) {
369
+ await tx.rollback().catch(() => {
370
+ });
477
371
  throw new MastraError(
478
372
  {
479
- id: "MASTRA_STORAGE_MSSQL_STORE_ALTER_TABLE_FAILED",
373
+ id: "MASTRA_STORAGE_MSSQL_STORE_DELETE_THREAD_FAILED",
480
374
  domain: ErrorDomain.STORAGE,
481
375
  category: ErrorCategory.THIRD_PARTY,
482
376
  details: {
483
- tableName
377
+ threadId
484
378
  }
485
379
  },
486
380
  error
487
381
  );
488
382
  }
489
383
  }
490
- async clearTable({ tableName }) {
491
- const fullTableName = this.getTableName(tableName);
492
- try {
493
- const fkQuery = `
494
- SELECT
495
- OBJECT_SCHEMA_NAME(fk.parent_object_id) AS schema_name,
496
- OBJECT_NAME(fk.parent_object_id) AS table_name
497
- FROM sys.foreign_keys fk
498
- WHERE fk.referenced_object_id = OBJECT_ID(@fullTableName)
499
- `;
500
- const fkResult = await this.pool.request().input("fullTableName", fullTableName).query(fkQuery);
501
- const childTables = fkResult.recordset || [];
502
- for (const child of childTables) {
503
- const childTableName = this.schema ? `[${child.schema_name}].[${child.table_name}]` : `[${child.table_name}]`;
504
- await this.clearTable({ tableName: childTableName });
505
- }
506
- await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
507
- } catch (error) {
508
- throw new MastraError(
509
- {
510
- id: "MASTRA_STORAGE_MSSQL_STORE_CLEAR_TABLE_FAILED",
511
- domain: ErrorDomain.STORAGE,
512
- category: ErrorCategory.THIRD_PARTY,
513
- details: {
514
- tableName
515
- }
516
- },
517
- error
384
+ async _getIncludedMessages({
385
+ threadId,
386
+ include
387
+ }) {
388
+ if (!threadId.trim()) throw new Error("threadId must be a non-empty string");
389
+ if (!include) return null;
390
+ const unionQueries = [];
391
+ const paramValues = [];
392
+ let paramIdx = 1;
393
+ const paramNames = [];
394
+ for (const inc of include) {
395
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
396
+ const searchId = inc.threadId || threadId;
397
+ const pThreadId = `@p${paramIdx}`;
398
+ const pId = `@p${paramIdx + 1}`;
399
+ const pPrev = `@p${paramIdx + 2}`;
400
+ const pNext = `@p${paramIdx + 3}`;
401
+ unionQueries.push(
402
+ `
403
+ SELECT
404
+ m.id,
405
+ m.content,
406
+ m.role,
407
+ m.type,
408
+ m.[createdAt],
409
+ m.thread_id AS threadId,
410
+ m.[resourceId],
411
+ m.seq_id
412
+ FROM (
413
+ SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
414
+ FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}
415
+ WHERE [thread_id] = ${pThreadId}
416
+ ) AS m
417
+ WHERE m.id = ${pId}
418
+ OR EXISTS (
419
+ SELECT 1
420
+ FROM (
421
+ SELECT *, ROW_NUMBER() OVER (ORDER BY [createdAt] ASC) as row_num
422
+ FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}
423
+ WHERE [thread_id] = ${pThreadId}
424
+ ) AS target
425
+ WHERE target.id = ${pId}
426
+ AND (
427
+ -- Get previous messages (messages that come BEFORE the target)
428
+ (m.row_num < target.row_num AND m.row_num >= target.row_num - ${pPrev})
429
+ OR
430
+ -- Get next messages (messages that come AFTER the target)
431
+ (m.row_num > target.row_num AND m.row_num <= target.row_num + ${pNext})
432
+ )
433
+ )
434
+ `
518
435
  );
436
+ paramValues.push(searchId, id, withPreviousMessages, withNextMessages);
437
+ paramNames.push(`p${paramIdx}`, `p${paramIdx + 1}`, `p${paramIdx + 2}`, `p${paramIdx + 3}`);
438
+ paramIdx += 4;
439
+ }
440
+ const finalQuery = `
441
+ SELECT * FROM (
442
+ ${unionQueries.join(" UNION ALL ")}
443
+ ) AS union_result
444
+ ORDER BY [seq_id] ASC
445
+ `;
446
+ const req = this.pool.request();
447
+ for (let i = 0; i < paramValues.length; ++i) {
448
+ req.input(paramNames[i], paramValues[i]);
519
449
  }
450
+ const result = await req.query(finalQuery);
451
+ const includedRows = result.recordset || [];
452
+ const seen = /* @__PURE__ */ new Set();
453
+ const dedupedRows = includedRows.filter((row) => {
454
+ if (seen.has(row.id)) return false;
455
+ seen.add(row.id);
456
+ return true;
457
+ });
458
+ return dedupedRows;
520
459
  }
521
- async insert({ tableName, record }) {
460
+ async listMessagesById({ messageIds }) {
461
+ if (messageIds.length === 0) return { messages: [] };
462
+ const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
463
+ const orderByStatement = `ORDER BY [seq_id] DESC`;
522
464
  try {
523
- const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
524
- const values = Object.values(record);
525
- const paramNames = values.map((_, i) => `@param${i}`);
526
- const insertSql = `INSERT INTO ${this.getTableName(tableName)} (${columns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
465
+ let rows = [];
466
+ let query = `${selectStatement} FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} WHERE [id] IN (${messageIds.map((_, i) => `@id${i}`).join(", ")})`;
527
467
  const request = this.pool.request();
528
- values.forEach((value, i) => {
529
- if (value instanceof Date) {
530
- request.input(`param${i}`, sql.DateTime2, value);
531
- } else if (typeof value === "object" && value !== null) {
532
- request.input(`param${i}`, JSON.stringify(value));
533
- } else {
534
- request.input(`param${i}`, value);
468
+ messageIds.forEach((id, i) => request.input(`id${i}`, id));
469
+ query += ` ${orderByStatement}`;
470
+ const result = await request.query(query);
471
+ const remainingRows = result.recordset || [];
472
+ rows.push(...remainingRows);
473
+ rows.sort((a, b) => {
474
+ const timeDiff = a.seq_id - b.seq_id;
475
+ return timeDiff;
476
+ });
477
+ const messagesWithParsedContent = rows.map((row) => {
478
+ if (typeof row.content === "string") {
479
+ try {
480
+ return { ...row, content: JSON.parse(row.content) };
481
+ } catch {
482
+ return row;
483
+ }
535
484
  }
485
+ return row;
536
486
  });
537
- await request.query(insertSql);
487
+ const cleanMessages = messagesWithParsedContent.map(({ seq_id, ...rest }) => rest);
488
+ const list = new MessageList().add(cleanMessages, "memory");
489
+ return { messages: list.get.all.db() };
538
490
  } catch (error) {
539
- throw new MastraError(
491
+ const mastraError = new MastraError(
540
492
  {
541
- id: "MASTRA_STORAGE_MSSQL_STORE_INSERT_FAILED",
493
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_MESSAGES_BY_ID_FAILED",
542
494
  domain: ErrorDomain.STORAGE,
543
495
  category: ErrorCategory.THIRD_PARTY,
544
496
  details: {
545
- tableName
497
+ messageIds: JSON.stringify(messageIds)
546
498
  }
547
499
  },
548
500
  error
549
501
  );
502
+ this.logger?.error?.(mastraError.toString());
503
+ this.logger?.trackException?.(mastraError);
504
+ return { messages: [] };
550
505
  }
551
506
  }
552
- async load({ tableName, keys }) {
553
- try {
554
- const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
555
- const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
556
- const values = keyEntries.map(([_, value]) => value);
557
- const sql2 = `SELECT * FROM ${this.getTableName(tableName)} WHERE ${conditions}`;
558
- const request = this.pool.request();
559
- values.forEach((value, i) => {
560
- request.input(`param${i}`, value);
561
- });
562
- const resultSet = await request.query(sql2);
563
- const result = resultSet.recordset[0] || null;
564
- if (!result) {
565
- return null;
566
- }
567
- if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
568
- const snapshot = result;
569
- if (typeof snapshot.snapshot === "string") {
570
- snapshot.snapshot = JSON.parse(snapshot.snapshot);
571
- }
572
- return snapshot;
573
- }
574
- return result;
575
- } catch (error) {
507
+ async listMessages(args) {
508
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
509
+ if (!threadId.trim()) {
576
510
  throw new MastraError(
577
511
  {
578
- id: "MASTRA_STORAGE_MSSQL_STORE_LOAD_FAILED",
512
+ id: "STORAGE_MSSQL_LIST_MESSAGES_INVALID_THREAD_ID",
579
513
  domain: ErrorDomain.STORAGE,
580
514
  category: ErrorCategory.THIRD_PARTY,
581
- details: {
582
- tableName
583
- }
515
+ details: { threadId }
584
516
  },
585
- error
517
+ new Error("threadId must be a non-empty string")
586
518
  );
587
519
  }
588
- }
589
- async getThreadById({ threadId }) {
590
- try {
591
- const sql2 = `SELECT
592
- id,
593
- [resourceId],
594
- title,
595
- metadata,
596
- [createdAt],
597
- [updatedAt]
598
- FROM ${this.getTableName(TABLE_THREADS)}
599
- WHERE id = @threadId`;
600
- const request = this.pool.request();
601
- request.input("threadId", threadId);
602
- const resultSet = await request.query(sql2);
603
- const thread = resultSet.recordset[0] || null;
604
- if (!thread) {
605
- return null;
606
- }
607
- return {
608
- ...thread,
609
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
610
- createdAt: thread.createdAt,
611
- updatedAt: thread.updatedAt
612
- };
613
- } catch (error) {
614
- throw new MastraError(
615
- {
616
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_THREAD_BY_ID_FAILED",
617
- domain: ErrorDomain.STORAGE,
618
- category: ErrorCategory.THIRD_PARTY,
619
- details: {
620
- threadId
621
- }
622
- },
623
- error
624
- );
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
+ });
625
531
  }
626
- }
627
- async getThreadsByResourceIdPaginated(args) {
628
- const { resourceId, page = 0, perPage: perPageInput } = args;
532
+ const perPage = normalizePerPage(perPageInput, 40);
533
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
629
534
  try {
630
- const perPage = perPageInput !== void 0 ? perPageInput : 100;
631
- const currentOffset = page * perPage;
632
- const baseQuery = `FROM ${this.getTableName(TABLE_THREADS)} WHERE [resourceId] = @resourceId`;
633
- const countQuery = `SELECT COUNT(*) as count ${baseQuery}`;
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
+ };
634
549
  const countRequest = this.pool.request();
635
- countRequest.input("resourceId", resourceId);
636
- const countResult = await countRequest.query(countQuery);
637
- const total = parseInt(countResult.recordset[0]?.count ?? "0", 10);
638
- if (total === 0) {
550
+ bindWhereParams(countRequest);
551
+ const countResult = await countRequest.query(`SELECT COUNT(*) as total FROM ${tableName}${actualWhereClause}`);
552
+ const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
553
+ const fetchBaseMessages = async () => {
554
+ const request = this.pool.request();
555
+ bindWhereParams(request);
556
+ if (perPageInput === false) {
557
+ const result2 = await request.query(`${baseQuery}${actualWhereClause} ${orderByStatement}`);
558
+ return result2.recordset || [];
559
+ }
560
+ request.input("offset", offset);
561
+ request.input("limit", perPage > 2147483647 ? sql2.BigInt : sql2.Int, perPage);
562
+ const result = await request.query(
563
+ `${baseQuery}${actualWhereClause} ${orderByStatement} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
564
+ );
565
+ return result.recordset || [];
566
+ };
567
+ const baseRows = perPage === 0 ? [] : await fetchBaseMessages();
568
+ const messages = [...baseRows];
569
+ const seqById = /* @__PURE__ */ new Map();
570
+ messages.forEach((msg) => {
571
+ if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
572
+ });
573
+ if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
639
574
  return {
640
- threads: [],
575
+ messages: [],
641
576
  total: 0,
642
577
  page,
643
- perPage,
578
+ perPage: perPageForResponse,
644
579
  hasMore: false
645
580
  };
646
581
  }
647
- const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY [seq_id] DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
648
- const dataRequest = this.pool.request();
649
- dataRequest.input("resourceId", resourceId);
650
- dataRequest.input("perPage", perPage);
651
- dataRequest.input("offset", currentOffset);
652
- const rowsResult = await dataRequest.query(dataQuery);
653
- const rows = rowsResult.recordset || [];
654
- const threads = rows.map((thread) => ({
655
- ...thread,
656
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
657
- createdAt: thread.createdAt,
658
- updatedAt: thread.updatedAt
659
- }));
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;
660
609
  return {
661
- threads,
610
+ messages: finalMessages,
662
611
  total,
663
612
  page,
664
- perPage,
665
- hasMore: currentOffset + threads.length < total
613
+ perPage: perPageForResponse,
614
+ hasMore
666
615
  };
667
616
  } catch (error) {
668
617
  const mastraError = new MastraError(
669
618
  {
670
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED",
619
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_MESSAGES_FAILED",
671
620
  domain: ErrorDomain.STORAGE,
672
621
  category: ErrorCategory.THIRD_PARTY,
673
622
  details: {
674
- resourceId,
675
- page
623
+ threadId,
624
+ resourceId: resourceId ?? ""
676
625
  }
677
626
  },
678
627
  error
679
628
  );
680
629
  this.logger?.error?.(mastraError.toString());
681
630
  this.logger?.trackException?.(mastraError);
682
- return { threads: [], total: 0, page, perPage: perPageInput || 100, hasMore: false };
631
+ return {
632
+ messages: [],
633
+ total: 0,
634
+ page,
635
+ perPage: perPageForResponse,
636
+ hasMore: false
637
+ };
683
638
  }
684
639
  }
685
- async saveThread({ thread }) {
640
+ async saveMessages({ messages }) {
641
+ if (messages.length === 0) return { messages: [] };
642
+ const threadId = messages[0]?.threadId;
643
+ if (!threadId) {
644
+ throw new MastraError({
645
+ id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
646
+ domain: ErrorDomain.STORAGE,
647
+ category: ErrorCategory.THIRD_PARTY,
648
+ text: `Thread ID is required`
649
+ });
650
+ }
651
+ const thread = await this.getThreadById({ threadId });
652
+ if (!thread) {
653
+ throw new MastraError({
654
+ id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
655
+ domain: ErrorDomain.STORAGE,
656
+ category: ErrorCategory.THIRD_PARTY,
657
+ text: `Thread ${threadId} not found`,
658
+ details: { threadId }
659
+ });
660
+ }
661
+ const tableMessages = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
662
+ const tableThreads = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
686
663
  try {
687
- const table = this.getTableName(TABLE_THREADS);
688
- const mergeSql = `MERGE INTO ${table} WITH (HOLDLOCK) AS target
689
- USING (SELECT @id AS id) AS source
690
- ON (target.id = source.id)
691
- WHEN MATCHED THEN
692
- UPDATE SET
693
- [resourceId] = @resourceId,
694
- title = @title,
695
- metadata = @metadata,
696
- [createdAt] = @createdAt,
697
- [updatedAt] = @updatedAt
698
- WHEN NOT MATCHED THEN
699
- INSERT (id, [resourceId], title, metadata, [createdAt], [updatedAt])
700
- VALUES (@id, @resourceId, @title, @metadata, @createdAt, @updatedAt);`;
701
- const req = this.pool.request();
702
- req.input("id", thread.id);
703
- req.input("resourceId", thread.resourceId);
704
- req.input("title", thread.title);
705
- req.input("metadata", thread.metadata ? JSON.stringify(thread.metadata) : null);
706
- req.input("createdAt", thread.createdAt);
707
- req.input("updatedAt", thread.updatedAt);
708
- await req.query(mergeSql);
709
- return thread;
664
+ const transaction = this.pool.transaction();
665
+ await transaction.begin();
666
+ try {
667
+ for (const message of messages) {
668
+ if (!message.threadId) {
669
+ throw new Error(
670
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
671
+ );
672
+ }
673
+ if (!message.resourceId) {
674
+ throw new Error(
675
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
676
+ );
677
+ }
678
+ const request = transaction.request();
679
+ request.input("id", message.id);
680
+ request.input("thread_id", message.threadId);
681
+ request.input(
682
+ "content",
683
+ typeof message.content === "string" ? message.content : JSON.stringify(message.content)
684
+ );
685
+ request.input("createdAt", sql2.DateTime2, message.createdAt);
686
+ request.input("role", message.role);
687
+ request.input("type", message.type || "v2");
688
+ request.input("resourceId", message.resourceId);
689
+ const mergeSql = `MERGE INTO ${tableMessages} AS target
690
+ USING (SELECT @id AS id) AS src
691
+ ON target.id = src.id
692
+ WHEN MATCHED THEN UPDATE SET
693
+ thread_id = @thread_id,
694
+ content = @content,
695
+ [createdAt] = @createdAt,
696
+ role = @role,
697
+ type = @type,
698
+ resourceId = @resourceId
699
+ WHEN NOT MATCHED THEN INSERT (id, thread_id, content, [createdAt], role, type, resourceId)
700
+ VALUES (@id, @thread_id, @content, @createdAt, @role, @type, @resourceId);`;
701
+ await request.query(mergeSql);
702
+ }
703
+ const threadReq = transaction.request();
704
+ threadReq.input("updatedAt", sql2.DateTime2, /* @__PURE__ */ new Date());
705
+ threadReq.input("id", threadId);
706
+ await threadReq.query(`UPDATE ${tableThreads} SET [updatedAt] = @updatedAt WHERE id = @id`);
707
+ await transaction.commit();
708
+ } catch (error) {
709
+ await transaction.rollback();
710
+ throw error;
711
+ }
712
+ const messagesWithParsedContent = messages.map((message) => {
713
+ if (typeof message.content === "string") {
714
+ try {
715
+ return { ...message, content: JSON.parse(message.content) };
716
+ } catch {
717
+ return message;
718
+ }
719
+ }
720
+ return message;
721
+ });
722
+ const list = new MessageList().add(messagesWithParsedContent, "memory");
723
+ return { messages: list.get.all.db() };
710
724
  } catch (error) {
711
725
  throw new MastraError(
712
726
  {
713
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_THREAD_FAILED",
727
+ id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
714
728
  domain: ErrorDomain.STORAGE,
715
729
  category: ErrorCategory.THIRD_PARTY,
716
- details: {
717
- threadId: thread.id
718
- }
730
+ details: { threadId }
719
731
  },
720
732
  error
721
733
  );
722
734
  }
723
735
  }
724
- /**
725
- * @deprecated use getThreadsByResourceIdPaginated instead
726
- */
727
- async getThreadsByResourceId(args) {
728
- const { resourceId } = args;
729
- try {
730
- const baseQuery = `FROM ${this.getTableName(TABLE_THREADS)} WHERE [resourceId] = @resourceId`;
731
- const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY [seq_id] DESC`;
732
- const request = this.pool.request();
733
- request.input("resourceId", resourceId);
734
- const resultSet = await request.query(dataQuery);
735
- const rows = resultSet.recordset || [];
736
- return rows.map((thread) => ({
737
- ...thread,
738
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
739
- createdAt: thread.createdAt,
740
- updatedAt: thread.updatedAt
741
- }));
742
- } catch (error) {
743
- this.logger?.error?.(`Error getting threads for resource ${resourceId}:`, error);
736
+ async updateMessages({
737
+ messages
738
+ }) {
739
+ if (!messages || messages.length === 0) {
744
740
  return [];
745
741
  }
746
- }
747
- /**
748
- * Updates a thread's title and metadata, merging with existing metadata. Returns the updated thread.
749
- */
750
- async updateThread({
751
- id,
752
- title,
753
- metadata
754
- }) {
755
- const existingThread = await this.getThreadById({ threadId: id });
756
- if (!existingThread) {
757
- throw new MastraError({
758
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_THREAD_FAILED",
759
- domain: ErrorDomain.STORAGE,
760
- category: ErrorCategory.USER,
761
- text: `Thread ${id} not found`,
762
- details: {
763
- threadId: id,
764
- title
765
- }
766
- });
742
+ const messageIds = messages.map((m) => m.id);
743
+ const idParams = messageIds.map((_, i) => `@id${i}`).join(", ");
744
+ let selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })}`;
745
+ if (idParams.length > 0) {
746
+ selectQuery += ` WHERE id IN (${idParams})`;
747
+ } else {
748
+ return [];
767
749
  }
768
- const mergedMetadata = {
769
- ...existingThread.metadata,
770
- ...metadata
771
- };
772
- try {
773
- const table = this.getTableName(TABLE_THREADS);
774
- const sql2 = `UPDATE ${table}
775
- SET title = @title,
776
- metadata = @metadata,
777
- [updatedAt] = @updatedAt
778
- OUTPUT INSERTED.*
779
- WHERE id = @id`;
780
- const req = this.pool.request();
781
- req.input("id", id);
782
- req.input("title", title);
783
- req.input("metadata", JSON.stringify(mergedMetadata));
784
- req.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
785
- const result = await req.query(sql2);
786
- let thread = result.recordset && result.recordset[0];
787
- if (thread && "seq_id" in thread) {
788
- const { seq_id, ...rest } = thread;
789
- thread = rest;
750
+ const selectReq = this.pool.request();
751
+ messageIds.forEach((id, i) => selectReq.input(`id${i}`, id));
752
+ const existingMessagesDb = (await selectReq.query(selectQuery)).recordset;
753
+ if (!existingMessagesDb || existingMessagesDb.length === 0) {
754
+ return [];
755
+ }
756
+ const existingMessages = existingMessagesDb.map((msg) => {
757
+ if (typeof msg.content === "string") {
758
+ try {
759
+ msg.content = JSON.parse(msg.content);
760
+ } catch {
761
+ }
790
762
  }
791
- if (!thread) {
792
- throw new MastraError({
793
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_THREAD_FAILED",
794
- domain: ErrorDomain.STORAGE,
795
- category: ErrorCategory.USER,
796
- text: `Thread ${id} not found after update`,
797
- details: {
798
- threadId: id,
799
- title
763
+ return msg;
764
+ });
765
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
766
+ const transaction = this.pool.transaction();
767
+ try {
768
+ await transaction.begin();
769
+ for (const existingMessage of existingMessages) {
770
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
771
+ if (!updatePayload) continue;
772
+ const { id, ...fieldsToUpdate } = updatePayload;
773
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
774
+ threadIdsToUpdate.add(existingMessage.threadId);
775
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
776
+ threadIdsToUpdate.add(updatePayload.threadId);
777
+ }
778
+ const setClauses = [];
779
+ const req = transaction.request();
780
+ req.input("id", id);
781
+ const columnMapping = { threadId: "thread_id" };
782
+ const updatableFields = { ...fieldsToUpdate };
783
+ if (updatableFields.content) {
784
+ const newContent = {
785
+ ...existingMessage.content,
786
+ ...updatableFields.content,
787
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: { ...existingMessage.content.metadata, ...updatableFields.content.metadata } } : {}
788
+ };
789
+ setClauses.push(`content = @content`);
790
+ req.input("content", JSON.stringify(newContent));
791
+ delete updatableFields.content;
792
+ }
793
+ for (const key in updatableFields) {
794
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
795
+ const dbColumn = columnMapping[key] || key;
796
+ setClauses.push(`[${dbColumn}] = @${dbColumn}`);
797
+ req.input(dbColumn, updatableFields[key]);
800
798
  }
801
- });
799
+ }
800
+ if (setClauses.length > 0) {
801
+ const updateSql = `UPDATE ${getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) })} SET ${setClauses.join(", ")} WHERE id = @id`;
802
+ await req.query(updateSql);
803
+ }
802
804
  }
803
- return {
804
- ...thread,
805
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
806
- createdAt: thread.createdAt,
807
- updatedAt: thread.updatedAt
808
- };
805
+ if (threadIdsToUpdate.size > 0) {
806
+ const threadIdParams = Array.from(threadIdsToUpdate).map((_, i) => `@tid${i}`).join(", ");
807
+ const threadReq = transaction.request();
808
+ Array.from(threadIdsToUpdate).forEach((tid, i) => threadReq.input(`tid${i}`, tid));
809
+ threadReq.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
810
+ const threadSql = `UPDATE ${getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) })} SET updatedAt = @updatedAt WHERE id IN (${threadIdParams})`;
811
+ await threadReq.query(threadSql);
812
+ }
813
+ await transaction.commit();
809
814
  } catch (error) {
815
+ await transaction.rollback();
810
816
  throw new MastraError(
811
817
  {
812
- id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_THREAD_FAILED",
818
+ id: "MASTRA_STORAGE_MSSQL_UPDATE_MESSAGES_FAILED",
813
819
  domain: ErrorDomain.STORAGE,
814
- category: ErrorCategory.THIRD_PARTY,
815
- details: {
816
- threadId: id,
817
- title
818
- }
820
+ category: ErrorCategory.THIRD_PARTY
819
821
  },
820
822
  error
821
823
  );
822
824
  }
825
+ const refetchReq = this.pool.request();
826
+ messageIds.forEach((id, i) => refetchReq.input(`id${i}`, id));
827
+ const updatedMessages = (await refetchReq.query(selectQuery)).recordset;
828
+ return (updatedMessages || []).map((message) => {
829
+ if (typeof message.content === "string") {
830
+ try {
831
+ message.content = JSON.parse(message.content);
832
+ } catch {
833
+ }
834
+ }
835
+ return message;
836
+ });
823
837
  }
824
- async deleteThread({ threadId }) {
825
- const messagesTable = this.getTableName(TABLE_MESSAGES);
826
- const threadsTable = this.getTableName(TABLE_THREADS);
827
- const deleteMessagesSql = `DELETE FROM ${messagesTable} WHERE [thread_id] = @threadId`;
828
- const deleteThreadSql = `DELETE FROM ${threadsTable} WHERE id = @threadId`;
829
- const tx = this.pool.transaction();
838
+ async deleteMessages(messageIds) {
839
+ if (!messageIds || messageIds.length === 0) {
840
+ return;
841
+ }
830
842
  try {
831
- await tx.begin();
832
- const req = tx.request();
833
- req.input("threadId", threadId);
834
- await req.query(deleteMessagesSql);
835
- await req.query(deleteThreadSql);
836
- await tx.commit();
837
- } catch (error) {
838
- await tx.rollback().catch(() => {
843
+ const messageTableName = getTableName({ indexName: TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
844
+ const threadTableName = getTableName({ indexName: TABLE_THREADS, schemaName: getSchemaName(this.schema) });
845
+ const placeholders = messageIds.map((_, idx) => `@p${idx + 1}`).join(",");
846
+ const request = this.pool.request();
847
+ messageIds.forEach((id, idx) => {
848
+ request.input(`p${idx + 1}`, id);
839
849
  });
850
+ const messages = await request.query(
851
+ `SELECT DISTINCT [thread_id] FROM ${messageTableName} WHERE [id] IN (${placeholders})`
852
+ );
853
+ const threadIds = messages.recordset?.map((msg) => msg.thread_id).filter(Boolean) || [];
854
+ const transaction = this.pool.transaction();
855
+ await transaction.begin();
856
+ try {
857
+ const deleteRequest = transaction.request();
858
+ messageIds.forEach((id, idx) => {
859
+ deleteRequest.input(`p${idx + 1}`, id);
860
+ });
861
+ await deleteRequest.query(`DELETE FROM ${messageTableName} WHERE [id] IN (${placeholders})`);
862
+ if (threadIds.length > 0) {
863
+ for (const threadId of threadIds) {
864
+ const updateRequest = transaction.request();
865
+ updateRequest.input("p1", threadId);
866
+ await updateRequest.query(`UPDATE ${threadTableName} SET [updatedAt] = GETDATE() WHERE [id] = @p1`);
867
+ }
868
+ }
869
+ await transaction.commit();
870
+ } catch (error) {
871
+ try {
872
+ await transaction.rollback();
873
+ } catch {
874
+ }
875
+ throw error;
876
+ }
877
+ } catch (error) {
840
878
  throw new MastraError(
841
879
  {
842
- id: "MASTRA_STORAGE_MSSQL_STORE_DELETE_THREAD_FAILED",
880
+ id: "MASTRA_STORAGE_MSSQL_STORE_DELETE_MESSAGES_FAILED",
843
881
  domain: ErrorDomain.STORAGE,
844
882
  category: ErrorCategory.THIRD_PARTY,
845
- details: {
846
- threadId
847
- }
883
+ details: { messageIds: messageIds.join(", ") }
848
884
  },
849
885
  error
850
886
  );
851
887
  }
852
888
  }
853
- async _getIncludedMessages({
854
- threadId,
855
- selectBy,
856
- orderByStatement
857
- }) {
858
- const include = selectBy?.include;
859
- if (!include) return null;
860
- const unionQueries = [];
861
- const paramValues = [];
862
- let paramIdx = 1;
863
- const paramNames = [];
864
- for (const inc of include) {
865
- const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
866
- const searchId = inc.threadId || threadId;
867
- const pThreadId = `@p${paramIdx}`;
868
- const pId = `@p${paramIdx + 1}`;
869
- const pPrev = `@p${paramIdx + 2}`;
870
- const pNext = `@p${paramIdx + 3}`;
871
- unionQueries.push(
872
- `
873
- SELECT
874
- m.id,
875
- m.content,
876
- m.role,
877
- m.type,
878
- m.[createdAt],
879
- m.thread_id AS threadId,
880
- m.[resourceId],
881
- m.seq_id
882
- FROM (
883
- SELECT *, ROW_NUMBER() OVER (${orderByStatement}) as row_num
884
- FROM ${this.getTableName(TABLE_MESSAGES)}
885
- WHERE [thread_id] = ${pThreadId}
886
- ) AS m
887
- WHERE m.id = ${pId}
888
- OR EXISTS (
889
- SELECT 1
890
- FROM (
891
- SELECT *, ROW_NUMBER() OVER (${orderByStatement}) as row_num
892
- FROM ${this.getTableName(TABLE_MESSAGES)}
893
- WHERE [thread_id] = ${pThreadId}
894
- ) AS target
895
- WHERE target.id = ${pId}
896
- AND (
897
- (m.row_num <= target.row_num + ${pPrev} AND m.row_num > target.row_num)
898
- OR
899
- (m.row_num >= target.row_num - ${pNext} AND m.row_num < target.row_num)
900
- )
901
- )
902
- `
903
- );
904
- paramValues.push(searchId, id, withPreviousMessages, withNextMessages);
905
- paramNames.push(`p${paramIdx}`, `p${paramIdx + 1}`, `p${paramIdx + 2}`, `p${paramIdx + 3}`);
906
- paramIdx += 4;
907
- }
908
- const finalQuery = `
909
- SELECT * FROM (
910
- ${unionQueries.join(" UNION ALL ")}
911
- ) AS union_result
912
- ORDER BY [seq_id] ASC
913
- `;
914
- const req = this.pool.request();
915
- for (let i = 0; i < paramValues.length; ++i) {
916
- req.input(paramNames[i], paramValues[i]);
917
- }
918
- const result = await req.query(finalQuery);
919
- const includedRows = result.recordset || [];
920
- const seen = /* @__PURE__ */ new Set();
921
- const dedupedRows = includedRows.filter((row) => {
922
- if (seen.has(row.id)) return false;
923
- seen.add(row.id);
924
- return true;
925
- });
926
- return dedupedRows;
927
- }
928
- async getMessages(args) {
929
- const { threadId, format, selectBy } = args;
930
- const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId`;
931
- const orderByStatement = `ORDER BY [seq_id] DESC`;
932
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: 40 });
889
+ async getResourceById({ resourceId }) {
890
+ const tableName = getTableName({ indexName: TABLE_RESOURCES, schemaName: getSchemaName(this.schema) });
933
891
  try {
934
- let rows = [];
935
- const include = selectBy?.include || [];
936
- if (include?.length) {
937
- const includeMessages = await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
938
- if (includeMessages) {
939
- rows.push(...includeMessages);
940
- }
941
- }
942
- const excludeIds = rows.map((m) => m.id).filter(Boolean);
943
- let query = `${selectStatement} FROM ${this.getTableName(TABLE_MESSAGES)} WHERE [thread_id] = @threadId`;
944
- const request = this.pool.request();
945
- request.input("threadId", threadId);
946
- if (excludeIds.length > 0) {
947
- const excludeParams = excludeIds.map((_, idx) => `@id${idx}`);
948
- query += ` AND id NOT IN (${excludeParams.join(", ")})`;
949
- excludeIds.forEach((id, idx) => {
950
- request.input(`id${idx}`, id);
951
- });
892
+ const req = this.pool.request();
893
+ req.input("resourceId", resourceId);
894
+ const result = (await req.query(`SELECT * FROM ${tableName} WHERE id = @resourceId`)).recordset[0];
895
+ if (!result) {
896
+ return null;
952
897
  }
953
- query += ` ${orderByStatement} OFFSET 0 ROWS FETCH NEXT @limit ROWS ONLY`;
954
- request.input("limit", limit);
955
- const result = await request.query(query);
956
- const remainingRows = result.recordset || [];
957
- rows.push(...remainingRows);
958
- rows.sort((a, b) => {
959
- const timeDiff = a.seq_id - b.seq_id;
960
- return timeDiff;
961
- });
962
- rows = rows.map(({ seq_id, ...rest }) => rest);
963
- const fetchedMessages = (rows || []).map((message) => {
964
- if (typeof message.content === "string") {
965
- try {
966
- message.content = JSON.parse(message.content);
967
- } catch {
968
- }
969
- }
970
- if (format === "v1") {
971
- if (Array.isArray(message.content)) ; else if (typeof message.content === "object" && message.content && Array.isArray(message.content.parts)) {
972
- message.content = message.content.parts;
973
- } else {
974
- message.content = [{ type: "text", text: "" }];
975
- }
976
- } else {
977
- if (typeof message.content !== "object" || !message.content || !("parts" in message.content)) {
978
- message.content = { format: 2, parts: [{ type: "text", text: "" }] };
979
- }
980
- }
981
- if (message.type === "v2") delete message.type;
982
- return message;
983
- });
984
- return format === "v2" ? fetchedMessages.map(
985
- (m) => ({ ...m, content: m.content || { format: 2, parts: [{ type: "text", text: "" }] } })
986
- ) : fetchedMessages;
898
+ return {
899
+ id: result.id,
900
+ createdAt: result.createdAt,
901
+ updatedAt: result.updatedAt,
902
+ workingMemory: result.workingMemory,
903
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
904
+ };
987
905
  } catch (error) {
988
906
  const mastraError = new MastraError(
989
907
  {
990
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_MESSAGES_FAILED",
908
+ id: "MASTRA_STORAGE_MSSQL_GET_RESOURCE_BY_ID_FAILED",
991
909
  domain: ErrorDomain.STORAGE,
992
910
  category: ErrorCategory.THIRD_PARTY,
993
- details: {
994
- threadId
995
- }
911
+ details: { resourceId }
996
912
  },
997
913
  error
998
914
  );
999
915
  this.logger?.error?.(mastraError.toString());
1000
- this.logger?.trackException(mastraError);
1001
- return [];
916
+ this.logger?.trackException?.(mastraError);
917
+ throw mastraError;
1002
918
  }
1003
919
  }
1004
- async getMessagesPaginated(args) {
1005
- const { threadId, selectBy } = args;
1006
- const { page = 0, perPage: perPageInput } = selectBy?.pagination || {};
1007
- const orderByStatement = `ORDER BY [seq_id] DESC`;
1008
- if (selectBy?.include?.length) {
1009
- await this._getIncludedMessages({ threadId, selectBy, orderByStatement });
1010
- }
1011
- try {
1012
- const { threadId: threadId2, format, selectBy: selectBy2 } = args;
1013
- const { page: page2 = 0, perPage: perPageInput2, dateRange } = selectBy2?.pagination || {};
1014
- const fromDate = dateRange?.start;
1015
- const toDate = dateRange?.end;
1016
- const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId`;
1017
- const orderByStatement2 = `ORDER BY [seq_id] DESC`;
1018
- let messages2 = [];
1019
- if (selectBy2?.include?.length) {
1020
- const includeMessages = await this._getIncludedMessages({ threadId: threadId2, selectBy: selectBy2, orderByStatement: orderByStatement2 });
1021
- if (includeMessages) messages2.push(...includeMessages);
1022
- }
1023
- const perPage = perPageInput2 !== void 0 ? perPageInput2 : this.resolveMessageLimit({ last: selectBy2?.last, defaultLimit: 40 });
1024
- const currentOffset = page2 * perPage;
1025
- const conditions = ["[thread_id] = @threadId"];
1026
- const request = this.pool.request();
1027
- request.input("threadId", threadId2);
1028
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
1029
- conditions.push("[createdAt] >= @fromDate");
1030
- request.input("fromDate", fromDate.toISOString());
920
+ async saveResource({ resource }) {
921
+ await this.operations.insert({
922
+ tableName: TABLE_RESOURCES,
923
+ record: {
924
+ ...resource,
925
+ metadata: resource.metadata
1031
926
  }
1032
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
1033
- conditions.push("[createdAt] <= @toDate");
1034
- request.input("toDate", toDate.toISOString());
1035
- }
1036
- const whereClause = `WHERE ${conditions.join(" AND ")}`;
1037
- const countQuery = `SELECT COUNT(*) as total FROM ${this.getTableName(TABLE_MESSAGES)} ${whereClause}`;
1038
- const countResult = await request.query(countQuery);
1039
- const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
1040
- if (total === 0 && messages2.length > 0) {
1041
- const parsedIncluded = this._parseAndFormatMessages(messages2, format);
1042
- return {
1043
- messages: parsedIncluded,
1044
- total: parsedIncluded.length,
1045
- page: page2,
1046
- perPage,
1047
- hasMore: false
927
+ });
928
+ return resource;
929
+ }
930
+ async updateResource({
931
+ resourceId,
932
+ workingMemory,
933
+ metadata
934
+ }) {
935
+ try {
936
+ const existingResource = await this.getResourceById({ resourceId });
937
+ if (!existingResource) {
938
+ const newResource = {
939
+ id: resourceId,
940
+ workingMemory,
941
+ metadata: metadata || {},
942
+ createdAt: /* @__PURE__ */ new Date(),
943
+ updatedAt: /* @__PURE__ */ new Date()
1048
944
  };
945
+ return this.saveResource({ resource: newResource });
1049
946
  }
1050
- const excludeIds = messages2.map((m) => m.id);
1051
- if (excludeIds.length > 0) {
1052
- const excludeParams = excludeIds.map((_, idx) => `@id${idx}`);
1053
- conditions.push(`id NOT IN (${excludeParams.join(", ")})`);
1054
- excludeIds.forEach((id, idx) => request.input(`id${idx}`, id));
1055
- }
1056
- const finalWhereClause = `WHERE ${conditions.join(" AND ")}`;
1057
- const dataQuery = `${selectStatement} FROM ${this.getTableName(TABLE_MESSAGES)} ${finalWhereClause} ${orderByStatement2} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
1058
- request.input("offset", currentOffset);
1059
- request.input("limit", perPage);
1060
- const rowsResult = await request.query(dataQuery);
1061
- const rows = rowsResult.recordset || [];
1062
- rows.sort((a, b) => a.seq_id - b.seq_id);
1063
- messages2.push(...rows);
1064
- const parsed = this._parseAndFormatMessages(messages2, format);
1065
- return {
1066
- messages: parsed,
1067
- total: total + excludeIds.length,
1068
- page: page2,
1069
- perPage,
1070
- hasMore: currentOffset + rows.length < total
947
+ const updatedResource = {
948
+ ...existingResource,
949
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
950
+ metadata: {
951
+ ...existingResource.metadata,
952
+ ...metadata
953
+ },
954
+ updatedAt: /* @__PURE__ */ new Date()
1071
955
  };
956
+ const tableName = getTableName({ indexName: TABLE_RESOURCES, schemaName: getSchemaName(this.schema) });
957
+ const updates = [];
958
+ const req = this.pool.request();
959
+ if (workingMemory !== void 0) {
960
+ updates.push("workingMemory = @workingMemory");
961
+ req.input("workingMemory", workingMemory);
962
+ }
963
+ if (metadata) {
964
+ updates.push("metadata = @metadata");
965
+ req.input("metadata", JSON.stringify(updatedResource.metadata));
966
+ }
967
+ updates.push("updatedAt = @updatedAt");
968
+ req.input("updatedAt", updatedResource.updatedAt.toISOString());
969
+ req.input("id", resourceId);
970
+ await req.query(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = @id`);
971
+ return updatedResource;
1072
972
  } catch (error) {
1073
973
  const mastraError = new MastraError(
1074
974
  {
1075
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_MESSAGES_PAGINATED_FAILED",
975
+ id: "MASTRA_STORAGE_MSSQL_UPDATE_RESOURCE_FAILED",
1076
976
  domain: ErrorDomain.STORAGE,
1077
977
  category: ErrorCategory.THIRD_PARTY,
1078
- details: {
1079
- threadId,
1080
- page
1081
- }
978
+ details: { resourceId }
1082
979
  },
1083
980
  error
1084
981
  );
1085
982
  this.logger?.error?.(mastraError.toString());
1086
- this.logger?.trackException(mastraError);
1087
- return { messages: [], total: 0, page, perPage: perPageInput || 40, hasMore: false };
983
+ this.logger?.trackException?.(mastraError);
984
+ throw mastraError;
1088
985
  }
1089
986
  }
1090
- _parseAndFormatMessages(messages, format) {
1091
- const parsedMessages = messages.map((message) => {
1092
- let parsed = message;
1093
- if (typeof parsed.content === "string") {
1094
- try {
1095
- parsed = { ...parsed, content: JSON.parse(parsed.content) };
1096
- } catch {
1097
- }
1098
- }
1099
- if (format === "v1") {
1100
- if (Array.isArray(parsed.content)) ; else if (parsed.content?.parts) {
1101
- parsed.content = parsed.content.parts;
1102
- } else {
1103
- parsed.content = [{ type: "text", text: "" }];
1104
- }
1105
- } else {
1106
- if (!parsed.content?.parts) {
1107
- parsed = { ...parsed, content: { format: 2, parts: [{ type: "text", text: "" }] } };
1108
- }
1109
- }
1110
- return parsed;
1111
- });
1112
- const list = new MessageList().add(parsedMessages, "memory");
1113
- return format === "v2" ? list.get.all.v2() : list.get.all.v1();
1114
- }
1115
- async saveMessages({
1116
- messages,
1117
- format
987
+ };
988
+ var ObservabilityMSSQL = class extends ObservabilityStorage {
989
+ pool;
990
+ operations;
991
+ schema;
992
+ constructor({
993
+ pool,
994
+ operations,
995
+ schema
1118
996
  }) {
1119
- if (messages.length === 0) return messages;
1120
- const threadId = messages[0]?.threadId;
1121
- if (!threadId) {
1122
- throw new MastraError({
1123
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
1124
- domain: ErrorDomain.STORAGE,
1125
- category: ErrorCategory.THIRD_PARTY,
1126
- text: `Thread ID is required`
1127
- });
1128
- }
1129
- const thread = await this.getThreadById({ threadId });
1130
- if (!thread) {
1131
- throw new MastraError({
1132
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
1133
- domain: ErrorDomain.STORAGE,
1134
- category: ErrorCategory.THIRD_PARTY,
1135
- text: `Thread ${threadId} not found`,
1136
- details: { threadId }
1137
- });
1138
- }
1139
- const tableMessages = this.getTableName(TABLE_MESSAGES);
1140
- const tableThreads = this.getTableName(TABLE_THREADS);
997
+ super();
998
+ this.pool = pool;
999
+ this.operations = operations;
1000
+ this.schema = schema;
1001
+ }
1002
+ get tracingStrategy() {
1003
+ return {
1004
+ preferred: "batch-with-updates",
1005
+ supported: ["batch-with-updates", "insert-only"]
1006
+ };
1007
+ }
1008
+ async createSpan(span) {
1141
1009
  try {
1142
- const transaction = this.pool.transaction();
1143
- await transaction.begin();
1144
- try {
1145
- for (const message of messages) {
1146
- if (!message.threadId) {
1147
- throw new Error(
1148
- `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
1149
- );
1150
- }
1151
- if (!message.resourceId) {
1152
- throw new Error(
1153
- `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
1154
- );
1155
- }
1156
- const request = transaction.request();
1157
- request.input("id", message.id);
1158
- request.input("thread_id", message.threadId);
1159
- request.input(
1160
- "content",
1161
- typeof message.content === "string" ? message.content : JSON.stringify(message.content)
1162
- );
1163
- request.input("createdAt", message.createdAt.toISOString() || (/* @__PURE__ */ new Date()).toISOString());
1164
- request.input("role", message.role);
1165
- request.input("type", message.type || "v2");
1166
- request.input("resourceId", message.resourceId);
1167
- const mergeSql = `MERGE INTO ${tableMessages} AS target
1168
- USING (SELECT @id AS id) AS src
1169
- ON target.id = src.id
1170
- WHEN MATCHED THEN UPDATE SET
1171
- thread_id = @thread_id,
1172
- content = @content,
1173
- [createdAt] = @createdAt,
1174
- role = @role,
1175
- type = @type,
1176
- resourceId = @resourceId
1177
- WHEN NOT MATCHED THEN INSERT (id, thread_id, content, [createdAt], role, type, resourceId)
1178
- VALUES (@id, @thread_id, @content, @createdAt, @role, @type, @resourceId);`;
1179
- await request.query(mergeSql);
1180
- }
1181
- const threadReq = transaction.request();
1182
- threadReq.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
1183
- threadReq.input("id", threadId);
1184
- await threadReq.query(`UPDATE ${tableThreads} SET [updatedAt] = @updatedAt WHERE id = @id`);
1185
- await transaction.commit();
1186
- } catch (error) {
1187
- await transaction.rollback();
1188
- throw error;
1189
- }
1190
- const messagesWithParsedContent = messages.map((message) => {
1191
- if (typeof message.content === "string") {
1192
- try {
1193
- return { ...message, content: JSON.parse(message.content) };
1194
- } catch {
1195
- return message;
1196
- }
1197
- }
1198
- return message;
1199
- });
1200
- const list = new MessageList().add(messagesWithParsedContent, "memory");
1201
- if (format === "v2") return list.get.all.v2();
1202
- return list.get.all.v1();
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 });
1203
1019
  } catch (error) {
1204
1020
  throw new MastraError(
1205
1021
  {
1206
- id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_MESSAGES_FAILED",
1022
+ id: "MSSQL_STORE_CREATE_SPAN_FAILED",
1207
1023
  domain: ErrorDomain.STORAGE,
1208
- category: ErrorCategory.THIRD_PARTY,
1209
- details: { threadId }
1024
+ category: ErrorCategory.USER,
1025
+ details: {
1026
+ spanId: span.spanId,
1027
+ traceId: span.traceId,
1028
+ spanType: span.spanType,
1029
+ spanName: span.name
1030
+ }
1210
1031
  },
1211
1032
  error
1212
1033
  );
1213
1034
  }
1214
1035
  }
1215
- async persistWorkflowSnapshot({
1216
- workflowName,
1217
- runId,
1218
- snapshot
1219
- }) {
1220
- const table = this.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1221
- const now = (/* @__PURE__ */ new Date()).toISOString();
1036
+ async getTrace(traceId) {
1222
1037
  try {
1038
+ const tableName = getTableName({
1039
+ indexName: TABLE_SPANS,
1040
+ schemaName: getSchemaName(this.schema)
1041
+ });
1223
1042
  const request = this.pool.request();
1224
- request.input("workflow_name", workflowName);
1225
- request.input("run_id", runId);
1226
- request.input("snapshot", JSON.stringify(snapshot));
1227
- request.input("createdAt", now);
1228
- request.input("updatedAt", now);
1229
- const mergeSql = `MERGE INTO ${table} AS target
1230
- USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
1231
- ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
1232
- WHEN MATCHED THEN UPDATE SET
1233
- snapshot = @snapshot,
1234
- [updatedAt] = @updatedAt
1235
- WHEN NOT MATCHED THEN INSERT (workflow_name, run_id, snapshot, [createdAt], [updatedAt])
1236
- VALUES (@workflow_name, @run_id, @snapshot, @createdAt, @updatedAt);`;
1237
- await request.query(mergeSql);
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
+ };
1238
1065
  } catch (error) {
1239
1066
  throw new MastraError(
1240
1067
  {
1241
- id: "MASTRA_STORAGE_MSSQL_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
1068
+ id: "MSSQL_STORE_GET_TRACE_FAILED",
1242
1069
  domain: ErrorDomain.STORAGE,
1243
- category: ErrorCategory.THIRD_PARTY,
1070
+ category: ErrorCategory.USER,
1244
1071
  details: {
1245
- workflowName,
1246
- runId
1072
+ traceId
1247
1073
  }
1248
1074
  },
1249
1075
  error
1250
1076
  );
1251
1077
  }
1252
1078
  }
1253
- async loadWorkflowSnapshot({
1254
- workflowName,
1255
- runId
1079
+ async updateSpan({
1080
+ spanId,
1081
+ traceId,
1082
+ updates
1256
1083
  }) {
1257
1084
  try {
1258
- const result = await this.load({
1259
- tableName: TABLE_WORKFLOW_SNAPSHOT,
1260
- keys: {
1261
- workflow_name: workflowName,
1262
- run_id: runId
1263
- }
1264
- });
1265
- if (!result) {
1266
- return null;
1085
+ const data = { ...updates };
1086
+ if (data.endedAt instanceof Date) {
1087
+ data.endedAt = data.endedAt.toISOString();
1267
1088
  }
1268
- return result.snapshot;
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
+ });
1269
1097
  } catch (error) {
1270
1098
  throw new MastraError(
1271
1099
  {
1272
- id: "MASTRA_STORAGE_MSSQL_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
1100
+ id: "MSSQL_STORE_UPDATE_SPAN_FAILED",
1273
1101
  domain: ErrorDomain.STORAGE,
1274
- category: ErrorCategory.THIRD_PARTY,
1102
+ category: ErrorCategory.USER,
1275
1103
  details: {
1276
- workflowName,
1277
- runId
1104
+ spanId,
1105
+ traceId
1278
1106
  }
1279
1107
  },
1280
1108
  error
1281
1109
  );
1282
1110
  }
1283
1111
  }
1284
- async hasColumn(table, column) {
1285
- const schema = this.schema || "dbo";
1286
- const request = this.pool.request();
1287
- request.input("schema", schema);
1288
- request.input("table", table);
1289
- request.input("column", column);
1290
- request.input("columnLower", column.toLowerCase());
1291
- const result = await request.query(
1292
- `SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND (COLUMN_NAME = @column OR COLUMN_NAME = @columnLower)`
1293
- );
1294
- return result.recordset.length > 0;
1295
- }
1296
- parseWorkflowRun(row) {
1297
- let parsedSnapshot = row.snapshot;
1298
- if (typeof parsedSnapshot === "string") {
1299
- try {
1300
- parsedSnapshot = JSON.parse(row.snapshot);
1301
- } catch (e) {
1302
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
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;
1303
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;
1304
1154
  }
1305
- return {
1306
- workflowName: row.workflow_name,
1307
- runId: row.run_id,
1308
- snapshot: parsedSnapshot,
1309
- createdAt: row.createdAt,
1310
- updatedAt: row.updatedAt,
1311
- resourceId: row.resourceId
1312
- };
1313
- }
1314
- async getWorkflowRuns({
1315
- workflowName,
1316
- fromDate,
1317
- toDate,
1318
- limit,
1319
- offset,
1320
- resourceId
1321
- } = {}) {
1155
+ const tableName = getTableName({
1156
+ indexName: TABLE_SPANS,
1157
+ schemaName: getSchemaName(this.schema)
1158
+ });
1322
1159
  try {
1323
- const conditions = [];
1324
- const paramMap = {};
1325
- if (workflowName) {
1326
- conditions.push(`[workflow_name] = @workflowName`);
1327
- paramMap["workflowName"] = workflowName;
1328
- }
1329
- if (resourceId) {
1330
- const hasResourceId = await this.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
1331
- if (hasResourceId) {
1332
- conditions.push(`[resourceId] = @resourceId`);
1333
- paramMap["resourceId"] = resourceId;
1334
- } else {
1335
- console.warn(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
1336
- }
1337
- }
1338
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
1339
- conditions.push(`[createdAt] >= @fromDate`);
1340
- paramMap[`fromDate`] = fromDate.toISOString();
1341
- }
1342
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
1343
- conditions.push(`[createdAt] <= @toDate`);
1344
- paramMap[`toDate`] = toDate.toISOString();
1345
- }
1346
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1347
- let total = 0;
1348
- const tableName = this.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1349
- const request = this.pool.request();
1350
- Object.entries(paramMap).forEach(([key, value]) => {
1351
- if (value instanceof Date) {
1352
- request.input(key, sql.DateTime, value);
1353
- } else {
1354
- request.input(key, value);
1355
- }
1160
+ const countRequest = this.pool.request();
1161
+ Object.entries(params).forEach(([key, value]) => {
1162
+ countRequest.input(key, value);
1356
1163
  });
1357
- if (limit !== void 0 && offset !== void 0) {
1358
- const countQuery = `SELECT COUNT(*) as count FROM ${tableName} ${whereClause}`;
1359
- const countResult = await request.query(countQuery);
1360
- total = Number(countResult.recordset[0]?.count || 0);
1361
- }
1362
- let query = `SELECT * FROM ${tableName} ${whereClause} ORDER BY [seq_id] DESC`;
1363
- if (limit !== void 0 && offset !== void 0) {
1364
- query += ` OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
1365
- request.input("limit", limit);
1366
- request.input("offset", offset);
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
+ };
1367
1178
  }
1368
- const result = await request.query(query);
1369
- const runs = (result.recordset || []).map((row) => this.parseWorkflowRun(row));
1370
- return { runs, total: total || runs.length };
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
+ };
1371
1203
  } catch (error) {
1372
1204
  throw new MastraError(
1373
1205
  {
1374
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_WORKFLOW_RUNS_FAILED",
1206
+ id: "MSSQL_STORE_GET_TRACES_PAGINATED_FAILED",
1375
1207
  domain: ErrorDomain.STORAGE,
1376
- category: ErrorCategory.THIRD_PARTY,
1377
- details: {
1378
- workflowName: workflowName || "all"
1379
- }
1208
+ category: ErrorCategory.USER
1380
1209
  },
1381
1210
  error
1382
1211
  );
1383
1212
  }
1384
1213
  }
1385
- async getWorkflowRunById({
1386
- runId,
1387
- workflowName
1388
- }) {
1214
+ async batchCreateSpans(args) {
1215
+ if (!args.records || args.records.length === 0) {
1216
+ return;
1217
+ }
1389
1218
  try {
1390
- const conditions = [];
1391
- const paramMap = {};
1392
- if (runId) {
1393
- conditions.push(`[run_id] = @runId`);
1394
- paramMap["runId"] = runId;
1395
- }
1396
- if (workflowName) {
1397
- conditions.push(`[workflow_name] = @workflowName`);
1398
- paramMap["workflowName"] = workflowName;
1399
- }
1400
- const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1401
- const tableName = this.getTableName(TABLE_WORKFLOW_SNAPSHOT);
1402
- const query = `SELECT * FROM ${tableName} ${whereClause}`;
1403
- const request = this.pool.request();
1404
- Object.entries(paramMap).forEach(([key, value]) => request.input(key, value));
1405
- const result = await request.query(query);
1406
- if (!result.recordset || result.recordset.length === 0) {
1407
- return null;
1408
- }
1409
- return this.parseWorkflowRun(result.recordset[0]);
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
+ });
1410
1227
  } catch (error) {
1411
1228
  throw new MastraError(
1412
1229
  {
1413
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
1230
+ id: "MSSQL_STORE_BATCH_CREATE_SPANS_FAILED",
1414
1231
  domain: ErrorDomain.STORAGE,
1415
- category: ErrorCategory.THIRD_PARTY,
1232
+ category: ErrorCategory.USER,
1416
1233
  details: {
1417
- runId,
1418
- workflowName: workflowName || ""
1234
+ count: args.records.length
1419
1235
  }
1420
1236
  },
1421
1237
  error
1422
1238
  );
1423
1239
  }
1424
1240
  }
1425
- async updateMessages({
1426
- messages
1427
- }) {
1428
- if (!messages || messages.length === 0) {
1429
- return [];
1430
- }
1431
- const messageIds = messages.map((m) => m.id);
1432
- const idParams = messageIds.map((_, i) => `@id${i}`).join(", ");
1433
- let selectQuery = `SELECT id, content, role, type, createdAt, thread_id AS threadId, resourceId FROM ${this.getTableName(TABLE_MESSAGES)}`;
1434
- if (idParams.length > 0) {
1435
- selectQuery += ` WHERE id IN (${idParams})`;
1436
- } else {
1437
- return [];
1438
- }
1439
- const selectReq = this.pool.request();
1440
- messageIds.forEach((id, i) => selectReq.input(`id${i}`, id));
1441
- const existingMessagesDb = (await selectReq.query(selectQuery)).recordset;
1442
- if (!existingMessagesDb || existingMessagesDb.length === 0) {
1443
- return [];
1241
+ async batchUpdateSpans(args) {
1242
+ if (!args.records || args.records.length === 0) {
1243
+ return;
1444
1244
  }
1445
- const existingMessages = existingMessagesDb.map((msg) => {
1446
- if (typeof msg.content === "string") {
1447
- try {
1448
- msg.content = JSON.parse(msg.content);
1449
- } catch {
1450
- }
1451
- }
1452
- return msg;
1453
- });
1454
- const threadIdsToUpdate = /* @__PURE__ */ new Set();
1455
- const transaction = this.pool.transaction();
1456
1245
  try {
1457
- await transaction.begin();
1458
- for (const existingMessage of existingMessages) {
1459
- const updatePayload = messages.find((m) => m.id === existingMessage.id);
1460
- if (!updatePayload) continue;
1461
- const { id, ...fieldsToUpdate } = updatePayload;
1462
- if (Object.keys(fieldsToUpdate).length === 0) continue;
1463
- threadIdsToUpdate.add(existingMessage.threadId);
1464
- if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
1465
- threadIdsToUpdate.add(updatePayload.threadId);
1466
- }
1467
- const setClauses = [];
1468
- const req = transaction.request();
1469
- req.input("id", id);
1470
- const columnMapping = { threadId: "thread_id" };
1471
- const updatableFields = { ...fieldsToUpdate };
1472
- if (updatableFields.content) {
1473
- const newContent = {
1474
- ...existingMessage.content,
1475
- ...updatableFields.content,
1476
- ...existingMessage.content?.metadata && updatableFields.content.metadata ? { metadata: { ...existingMessage.content.metadata, ...updatableFields.content.metadata } } : {}
1477
- };
1478
- setClauses.push(`content = @content`);
1479
- req.input("content", JSON.stringify(newContent));
1480
- delete updatableFields.content;
1481
- }
1482
- for (const key in updatableFields) {
1483
- if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
1484
- const dbColumn = columnMapping[key] || key;
1485
- setClauses.push(`[${dbColumn}] = @${dbColumn}`);
1486
- req.input(dbColumn, updatableFields[key]);
1487
- }
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();
1488
1250
  }
1489
- if (setClauses.length > 0) {
1490
- const updateSql = `UPDATE ${this.getTableName(TABLE_MESSAGES)} SET ${setClauses.join(", ")} WHERE id = @id`;
1491
- await req.query(updateSql);
1251
+ if (processedData.startedAt instanceof Date) {
1252
+ processedData.startedAt = processedData.startedAt.toISOString();
1492
1253
  }
1493
- }
1494
- if (threadIdsToUpdate.size > 0) {
1495
- const threadIdParams = Array.from(threadIdsToUpdate).map((_, i) => `@tid${i}`).join(", ");
1496
- const threadReq = transaction.request();
1497
- Array.from(threadIdsToUpdate).forEach((tid, i) => threadReq.input(`tid${i}`, tid));
1498
- threadReq.input("updatedAt", (/* @__PURE__ */ new Date()).toISOString());
1499
- const threadSql = `UPDATE ${this.getTableName(TABLE_THREADS)} SET updatedAt = @updatedAt WHERE id IN (${threadIdParams})`;
1500
- await threadReq.query(threadSql);
1501
- }
1502
- await transaction.commit();
1254
+ return {
1255
+ keys: { spanId, traceId },
1256
+ data: processedData
1257
+ };
1258
+ });
1259
+ await this.operations.batchUpdate({
1260
+ tableName: TABLE_SPANS,
1261
+ updates
1262
+ });
1503
1263
  } catch (error) {
1504
- await transaction.rollback();
1505
1264
  throw new MastraError(
1506
1265
  {
1507
- id: "MASTRA_STORAGE_MSSQL_UPDATE_MESSAGES_FAILED",
1266
+ id: "MSSQL_STORE_BATCH_UPDATE_SPANS_FAILED",
1508
1267
  domain: ErrorDomain.STORAGE,
1509
- category: ErrorCategory.THIRD_PARTY
1268
+ category: ErrorCategory.USER,
1269
+ details: {
1270
+ count: args.records.length
1271
+ }
1510
1272
  },
1511
1273
  error
1512
1274
  );
1513
1275
  }
1514
- const refetchReq = this.pool.request();
1515
- messageIds.forEach((id, i) => refetchReq.input(`id${i}`, id));
1516
- const updatedMessages = (await refetchReq.query(selectQuery)).recordset;
1517
- return (updatedMessages || []).map((message) => {
1518
- if (typeof message.content === "string") {
1519
- try {
1520
- message.content = JSON.parse(message.content);
1521
- } catch {
1522
- }
1523
- }
1524
- return message;
1525
- });
1526
- }
1527
- async close() {
1528
- if (this.pool) {
1529
- try {
1530
- if (this.pool.connected) {
1531
- await this.pool.close();
1532
- } else if (this.pool.connecting) {
1533
- await this.pool.connect();
1534
- await this.pool.close();
1535
- }
1536
- } catch (err) {
1537
- if (err.message && err.message.includes("Cannot close a pool while it is connecting")) ; else {
1538
- throw err;
1539
- }
1540
- }
1541
- }
1542
1276
  }
1543
- async getEvals(options = {}) {
1544
- const { agentName, type, page = 0, perPage = 100, dateRange } = options;
1545
- const fromDate = dateRange?.start;
1546
- const toDate = dateRange?.end;
1547
- const where = [];
1548
- const params = {};
1549
- if (agentName) {
1550
- where.push("agent_name = @agentName");
1551
- params["agentName"] = agentName;
1552
- }
1553
- if (type === "test") {
1554
- where.push("test_info IS NOT NULL AND JSON_VALUE(test_info, '$.testPath') IS NOT NULL");
1555
- } else if (type === "live") {
1556
- where.push("(test_info IS NULL OR JSON_VALUE(test_info, '$.testPath') IS NULL)");
1557
- }
1558
- if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
1559
- where.push(`[created_at] >= @fromDate`);
1560
- params[`fromDate`] = fromDate.toISOString();
1561
- }
1562
- if (toDate instanceof Date && !isNaN(toDate.getTime())) {
1563
- where.push(`[created_at] <= @toDate`);
1564
- params[`toDate`] = toDate.toISOString();
1277
+ async batchDeleteTraces(args) {
1278
+ if (!args.traceIds || args.traceIds.length === 0) {
1279
+ return;
1565
1280
  }
1566
- const whereClause = where.length > 0 ? `WHERE ${where.join(" AND ")}` : "";
1567
- const tableName = this.getTableName(TABLE_EVALS);
1568
- const offset = page * perPage;
1569
- const countQuery = `SELECT COUNT(*) as total FROM ${tableName} ${whereClause}`;
1570
- const dataQuery = `SELECT * FROM ${tableName} ${whereClause} ORDER BY seq_id DESC OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
1571
1281
  try {
1572
- const countReq = this.pool.request();
1573
- Object.entries(params).forEach(([key, value]) => {
1574
- if (value instanceof Date) {
1575
- countReq.input(key, sql.DateTime, value);
1576
- } else {
1577
- countReq.input(key, value);
1578
- }
1579
- });
1580
- const countResult = await countReq.query(countQuery);
1581
- const total = countResult.recordset[0]?.total || 0;
1582
- if (total === 0) {
1583
- return {
1584
- evals: [],
1585
- total: 0,
1586
- page,
1587
- perPage,
1588
- hasMore: false
1589
- };
1590
- }
1591
- const req = this.pool.request();
1592
- Object.entries(params).forEach(([key, value]) => {
1593
- if (value instanceof Date) {
1594
- req.input(key, sql.DateTime, value);
1595
- } else {
1596
- req.input(key, value);
1597
- }
1282
+ const keys = args.traceIds.map((traceId) => ({ traceId }));
1283
+ await this.operations.batchDelete({
1284
+ tableName: TABLE_SPANS,
1285
+ keys
1598
1286
  });
1599
- req.input("offset", offset);
1600
- req.input("perPage", perPage);
1601
- const result = await req.query(dataQuery);
1602
- const rows = result.recordset;
1603
- return {
1604
- evals: rows?.map((row) => this.transformEvalRow(row)) ?? [],
1605
- total,
1606
- page,
1607
- perPage,
1608
- hasMore: offset + (rows?.length ?? 0) < total
1609
- };
1610
1287
  } catch (error) {
1611
- const mastraError = new MastraError(
1288
+ throw new MastraError(
1612
1289
  {
1613
- id: "MASTRA_STORAGE_MSSQL_STORE_GET_EVALS_FAILED",
1290
+ id: "MSSQL_STORE_BATCH_DELETE_TRACES_FAILED",
1614
1291
  domain: ErrorDomain.STORAGE,
1615
- category: ErrorCategory.THIRD_PARTY,
1292
+ category: ErrorCategory.USER,
1616
1293
  details: {
1617
- agentName: agentName || "all",
1618
- type: type || "all",
1619
- page,
1620
- perPage
1294
+ count: args.traceIds.length
1621
1295
  }
1622
1296
  },
1623
1297
  error
1624
1298
  );
1625
- this.logger?.error?.(mastraError.toString());
1626
- this.logger?.trackException(mastraError);
1627
- throw mastraError;
1628
1299
  }
1629
1300
  }
1630
- async saveResource({ resource }) {
1631
- const tableName = this.getTableName(TABLE_RESOURCES);
1632
- try {
1633
- const req = this.pool.request();
1634
- req.input("id", resource.id);
1635
- req.input("workingMemory", resource.workingMemory);
1636
- req.input("metadata", JSON.stringify(resource.metadata));
1637
- req.input("createdAt", resource.createdAt.toISOString());
1638
- req.input("updatedAt", resource.updatedAt.toISOString());
1639
- await req.query(
1640
- `INSERT INTO ${tableName} (id, workingMemory, metadata, createdAt, updatedAt) VALUES (@id, @workingMemory, @metadata, @createdAt, @updatedAt)`
1641
- );
1642
- return resource;
1643
- } catch (error) {
1644
- const mastraError = new MastraError(
1645
- {
1646
- id: "MASTRA_STORAGE_MSSQL_SAVE_RESOURCE_FAILED",
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",
1647
1331
  domain: ErrorDomain.STORAGE,
1648
- category: ErrorCategory.THIRD_PARTY,
1649
- details: { resourceId: resource.id }
1650
- },
1651
- error
1652
- );
1653
- this.logger?.error?.(mastraError.toString());
1654
- this.logger?.trackException(mastraError);
1655
- throw mastraError;
1332
+ category: ErrorCategory.THIRD_PARTY
1333
+ });
1656
1334
  }
1657
1335
  }
1658
- async updateResource({
1659
- resourceId,
1660
- workingMemory,
1661
- metadata
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
1662
1394
  }) {
1663
1395
  try {
1664
- const existingResource = await this.getResourceById({ resourceId });
1665
- if (!existingResource) {
1666
- const newResource = {
1667
- id: resourceId,
1668
- workingMemory,
1669
- metadata: metadata || {},
1670
- createdAt: /* @__PURE__ */ new Date(),
1671
- updatedAt: /* @__PURE__ */ new Date()
1672
- };
1673
- return this.saveResource({ resource: newResource });
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 }) {
1428
+ const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1429
+ try {
1430
+ try {
1431
+ await this.pool.request().query(`TRUNCATE TABLE ${fullTableName}`);
1432
+ } catch (truncateError) {
1433
+ if (truncateError?.number === 4712) {
1434
+ await this.pool.request().query(`DELETE FROM ${fullTableName}`);
1435
+ } else {
1436
+ throw truncateError;
1437
+ }
1674
1438
  }
1675
- const updatedResource = {
1676
- ...existingResource,
1677
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
1678
- metadata: {
1679
- ...existingResource.metadata,
1680
- ...metadata
1439
+ } catch (error) {
1440
+ throw new MastraError(
1441
+ {
1442
+ id: "MASTRA_STORAGE_MSSQL_STORE_CLEAR_TABLE_FAILED",
1443
+ domain: ErrorDomain.STORAGE,
1444
+ category: ErrorCategory.THIRD_PARTY,
1445
+ details: {
1446
+ tableName
1447
+ }
1681
1448
  },
1682
- updatedAt: /* @__PURE__ */ new Date()
1683
- };
1684
- const tableName = this.getTableName(TABLE_RESOURCES);
1685
- const updates = [];
1686
- const req = this.pool.request();
1687
- if (workingMemory !== void 0) {
1688
- updates.push("workingMemory = @workingMemory");
1689
- req.input("workingMemory", workingMemory);
1449
+ error
1450
+ );
1451
+ }
1452
+ }
1453
+ getDefaultValue(type) {
1454
+ switch (type) {
1455
+ case "timestamp":
1456
+ return "DEFAULT SYSUTCDATETIME()";
1457
+ case "jsonb":
1458
+ return "DEFAULT N'{}'";
1459
+ case "boolean":
1460
+ return "DEFAULT 0";
1461
+ default:
1462
+ return super.getDefaultValue(type);
1463
+ }
1464
+ }
1465
+ async createTable({
1466
+ tableName,
1467
+ schema
1468
+ }) {
1469
+ try {
1470
+ const uniqueConstraintColumns = tableName === TABLE_WORKFLOW_SNAPSHOT ? ["workflow_name", "run_id"] : [];
1471
+ const largeDataColumns = [
1472
+ "workingMemory",
1473
+ "snapshot",
1474
+ "metadata",
1475
+ "content",
1476
+ // messages.content - can be very long conversation content
1477
+ "input",
1478
+ // evals.input - test input data
1479
+ "output",
1480
+ // evals.output - test output data
1481
+ "instructions",
1482
+ // evals.instructions - evaluation instructions
1483
+ "other"
1484
+ // traces.other - additional trace data
1485
+ ];
1486
+ const columns = Object.entries(schema).map(([name, def]) => {
1487
+ const parsedName = parseSqlIdentifier(name, "column name");
1488
+ const constraints = [];
1489
+ if (def.primaryKey) constraints.push("PRIMARY KEY");
1490
+ if (!def.nullable) constraints.push("NOT NULL");
1491
+ const isIndexed = !!def.primaryKey || uniqueConstraintColumns.includes(name);
1492
+ const useLargeStorage = largeDataColumns.includes(name);
1493
+ return `[${parsedName}] ${this.getSqlType(def.type, isIndexed, useLargeStorage)} ${constraints.join(" ")}`.trim();
1494
+ }).join(",\n");
1495
+ if (this.schemaName) {
1496
+ await this.setupSchema();
1690
1497
  }
1691
- if (metadata) {
1692
- updates.push("metadata = @metadata");
1693
- req.input("metadata", JSON.stringify(updatedResource.metadata));
1498
+ const checkTableRequest = this.pool.request();
1499
+ checkTableRequest.input(
1500
+ "tableName",
1501
+ getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
1502
+ );
1503
+ const checkTableSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName`;
1504
+ checkTableRequest.input("schema", this.schemaName || "dbo");
1505
+ const checkTableResult = await checkTableRequest.query(checkTableSql);
1506
+ const tableExists = Array.isArray(checkTableResult.recordset) && checkTableResult.recordset.length > 0;
1507
+ if (!tableExists) {
1508
+ const createSql = `CREATE TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (
1509
+ ${columns}
1510
+ )`;
1511
+ await this.pool.request().query(createSql);
1512
+ }
1513
+ const columnCheckSql = `
1514
+ SELECT 1 AS found
1515
+ FROM INFORMATION_SCHEMA.COLUMNS
1516
+ WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = 'seq_id'
1517
+ `;
1518
+ const checkColumnRequest = this.pool.request();
1519
+ checkColumnRequest.input("schema", this.schemaName || "dbo");
1520
+ checkColumnRequest.input(
1521
+ "tableName",
1522
+ getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) }).replace(/[[\]]/g, "").split(".").pop()
1523
+ );
1524
+ const columnResult = await checkColumnRequest.query(columnCheckSql);
1525
+ const columnExists = Array.isArray(columnResult.recordset) && columnResult.recordset.length > 0;
1526
+ if (!columnExists) {
1527
+ const alterSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD seq_id BIGINT IDENTITY(1,1)`;
1528
+ await this.pool.request().query(alterSql);
1529
+ }
1530
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
1531
+ const constraintName = "mastra_workflow_snapshot_workflow_name_run_id_key";
1532
+ const checkConstraintSql = `SELECT 1 AS found FROM sys.key_constraints WHERE name = @constraintName`;
1533
+ const checkConstraintRequest = this.pool.request();
1534
+ checkConstraintRequest.input("constraintName", constraintName);
1535
+ const constraintResult = await checkConstraintRequest.query(checkConstraintSql);
1536
+ const constraintExists = Array.isArray(constraintResult.recordset) && constraintResult.recordset.length > 0;
1537
+ if (!constraintExists) {
1538
+ const addConstraintSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD CONSTRAINT ${constraintName} UNIQUE ([workflow_name], [run_id])`;
1539
+ await this.pool.request().query(addConstraintSql);
1540
+ }
1694
1541
  }
1695
- updates.push("updatedAt = @updatedAt");
1696
- req.input("updatedAt", updatedResource.updatedAt.toISOString());
1697
- req.input("id", resourceId);
1698
- await req.query(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = @id`);
1699
- return updatedResource;
1700
1542
  } catch (error) {
1701
- const mastraError = new MastraError(
1543
+ throw new MastraError(
1702
1544
  {
1703
- id: "MASTRA_STORAGE_MSSQL_UPDATE_RESOURCE_FAILED",
1545
+ id: "MASTRA_STORAGE_MSSQL_STORE_CREATE_TABLE_FAILED",
1704
1546
  domain: ErrorDomain.STORAGE,
1705
1547
  category: ErrorCategory.THIRD_PARTY,
1706
- details: { resourceId }
1548
+ details: {
1549
+ tableName
1550
+ }
1707
1551
  },
1708
1552
  error
1709
1553
  );
1710
- this.logger?.error?.(mastraError.toString());
1711
- this.logger?.trackException(mastraError);
1712
- throw mastraError;
1713
1554
  }
1714
1555
  }
1715
- async getResourceById({ resourceId }) {
1716
- const tableName = this.getTableName(TABLE_RESOURCES);
1556
+ /**
1557
+ * Alters table schema to add columns if they don't exist
1558
+ * @param tableName Name of the table
1559
+ * @param schema Schema of the table
1560
+ * @param ifNotExists Array of column names to add if they don't exist
1561
+ */
1562
+ async alterTable({
1563
+ tableName,
1564
+ schema,
1565
+ ifNotExists
1566
+ }) {
1567
+ const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1717
1568
  try {
1718
- const req = this.pool.request();
1719
- req.input("resourceId", resourceId);
1720
- const result = (await req.query(`SELECT * FROM ${tableName} WHERE id = @resourceId`)).recordset[0];
1569
+ for (const columnName of ifNotExists) {
1570
+ if (schema[columnName]) {
1571
+ const columnCheckRequest = this.pool.request();
1572
+ columnCheckRequest.input("tableName", fullTableName.replace(/[[\]]/g, "").split(".").pop());
1573
+ columnCheckRequest.input("columnName", columnName);
1574
+ columnCheckRequest.input("schema", this.schemaName || "dbo");
1575
+ const checkSql = `SELECT 1 AS found FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName AND COLUMN_NAME = @columnName`;
1576
+ const checkResult = await columnCheckRequest.query(checkSql);
1577
+ const columnExists = Array.isArray(checkResult.recordset) && checkResult.recordset.length > 0;
1578
+ if (!columnExists) {
1579
+ const columnDef = schema[columnName];
1580
+ const largeDataColumns = [
1581
+ "workingMemory",
1582
+ "snapshot",
1583
+ "metadata",
1584
+ "content",
1585
+ "input",
1586
+ "output",
1587
+ "instructions",
1588
+ "other"
1589
+ ];
1590
+ const useLargeStorage = largeDataColumns.includes(columnName);
1591
+ const isIndexed = !!columnDef.primaryKey;
1592
+ const sqlType = this.getSqlType(columnDef.type, isIndexed, useLargeStorage);
1593
+ const nullable = columnDef.nullable === false ? "NOT NULL" : "";
1594
+ const defaultValue = columnDef.nullable === false ? this.getDefaultValue(columnDef.type) : "";
1595
+ const parsedColumnName = parseSqlIdentifier(columnName, "column name");
1596
+ const alterSql = `ALTER TABLE ${fullTableName} ADD [${parsedColumnName}] ${sqlType} ${nullable} ${defaultValue}`.trim();
1597
+ await this.pool.request().query(alterSql);
1598
+ this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
1599
+ }
1600
+ }
1601
+ }
1602
+ } catch (error) {
1603
+ throw new MastraError(
1604
+ {
1605
+ id: "MASTRA_STORAGE_MSSQL_STORE_ALTER_TABLE_FAILED",
1606
+ domain: ErrorDomain.STORAGE,
1607
+ category: ErrorCategory.THIRD_PARTY,
1608
+ details: {
1609
+ tableName
1610
+ }
1611
+ },
1612
+ error
1613
+ );
1614
+ }
1615
+ }
1616
+ async load({ tableName, keys }) {
1617
+ try {
1618
+ const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
1619
+ const conditions = keyEntries.map(([key], i) => `[${key}] = @param${i}`).join(" AND ");
1620
+ const sql5 = `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions}`;
1621
+ const request = this.pool.request();
1622
+ keyEntries.forEach(([key, value], i) => {
1623
+ const preparedValue = this.prepareValue(value, key, tableName);
1624
+ if (preparedValue === null || preparedValue === void 0) {
1625
+ request.input(`param${i}`, this.getMssqlType(tableName, key), null);
1626
+ } else {
1627
+ request.input(`param${i}`, preparedValue);
1628
+ }
1629
+ });
1630
+ const resultSet = await request.query(sql5);
1631
+ const result = resultSet.recordset[0] || null;
1721
1632
  if (!result) {
1722
1633
  return null;
1723
1634
  }
1724
- return {
1725
- ...result,
1726
- workingMemory: typeof result.workingMemory === "object" ? JSON.stringify(result.workingMemory) : result.workingMemory,
1727
- metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
1728
- };
1635
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
1636
+ const snapshot = result;
1637
+ if (typeof snapshot.snapshot === "string") {
1638
+ snapshot.snapshot = JSON.parse(snapshot.snapshot);
1639
+ }
1640
+ return snapshot;
1641
+ }
1642
+ return result;
1729
1643
  } catch (error) {
1730
- const mastraError = new MastraError(
1644
+ throw new MastraError(
1731
1645
  {
1732
- id: "MASTRA_STORAGE_MSSQL_GET_RESOURCE_BY_ID_FAILED",
1646
+ id: "MASTRA_STORAGE_MSSQL_STORE_LOAD_FAILED",
1733
1647
  domain: ErrorDomain.STORAGE,
1734
1648
  category: ErrorCategory.THIRD_PARTY,
1735
- details: { resourceId }
1649
+ details: {
1650
+ tableName
1651
+ }
1736
1652
  },
1737
1653
  error
1738
1654
  );
1739
- this.logger?.error?.(mastraError.toString());
1740
- this.logger?.trackException(mastraError);
1741
- throw mastraError;
1742
1655
  }
1743
1656
  }
1657
+ async batchInsert({ tableName, records }) {
1658
+ const transaction = this.pool.transaction();
1659
+ try {
1660
+ await transaction.begin();
1661
+ for (const record of records) {
1662
+ await this.insert({ tableName, record, transaction });
1663
+ }
1664
+ await transaction.commit();
1665
+ } catch (error) {
1666
+ await transaction.rollback();
1667
+ throw new MastraError(
1668
+ {
1669
+ id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_INSERT_FAILED",
1670
+ domain: ErrorDomain.STORAGE,
1671
+ category: ErrorCategory.THIRD_PARTY,
1672
+ details: {
1673
+ tableName,
1674
+ numberOfRecords: records.length
1675
+ }
1676
+ },
1677
+ error
1678
+ );
1679
+ }
1680
+ }
1681
+ async dropTable({ tableName }) {
1682
+ try {
1683
+ const tableNameWithSchema = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
1684
+ await this.pool.request().query(`DROP TABLE IF EXISTS ${tableNameWithSchema}`);
1685
+ } catch (error) {
1686
+ throw new MastraError(
1687
+ {
1688
+ id: "MASTRA_STORAGE_MSSQL_STORE_DROP_TABLE_FAILED",
1689
+ domain: ErrorDomain.STORAGE,
1690
+ category: ErrorCategory.THIRD_PARTY,
1691
+ details: {
1692
+ tableName
1693
+ }
1694
+ },
1695
+ error
1696
+ );
1697
+ }
1698
+ }
1699
+ /**
1700
+ * Prepares a value for database operations, handling Date objects and JSON serialization
1701
+ */
1702
+ prepareValue(value, columnName, tableName) {
1703
+ if (value === null || value === void 0) {
1704
+ return value;
1705
+ }
1706
+ if (value instanceof Date) {
1707
+ return value;
1708
+ }
1709
+ const schema = TABLE_SCHEMAS[tableName];
1710
+ const columnSchema = schema?.[columnName];
1711
+ if (columnSchema?.type === "boolean") {
1712
+ return value ? 1 : 0;
1713
+ }
1714
+ if (columnSchema?.type === "jsonb") {
1715
+ if (typeof value === "string") {
1716
+ const trimmed = value.trim();
1717
+ if (trimmed.length > 0) {
1718
+ try {
1719
+ JSON.parse(trimmed);
1720
+ return trimmed;
1721
+ } catch {
1722
+ }
1723
+ }
1724
+ return JSON.stringify(value);
1725
+ }
1726
+ if (typeof value === "bigint") {
1727
+ return value.toString();
1728
+ }
1729
+ return JSON.stringify(value);
1730
+ }
1731
+ if (typeof value === "object") {
1732
+ return JSON.stringify(value);
1733
+ }
1734
+ return value;
1735
+ }
1736
+ /**
1737
+ * Maps TABLE_SCHEMAS types to mssql param types (used when value is null)
1738
+ */
1739
+ getMssqlType(tableName, columnName) {
1740
+ const col = TABLE_SCHEMAS[tableName]?.[columnName];
1741
+ switch (col?.type) {
1742
+ case "text":
1743
+ return sql2.NVarChar;
1744
+ case "timestamp":
1745
+ return sql2.DateTime2;
1746
+ case "uuid":
1747
+ return sql2.UniqueIdentifier;
1748
+ case "jsonb":
1749
+ return sql2.NVarChar;
1750
+ case "integer":
1751
+ return sql2.Int;
1752
+ case "bigint":
1753
+ return sql2.BigInt;
1754
+ case "float":
1755
+ return sql2.Float;
1756
+ case "boolean":
1757
+ return sql2.Bit;
1758
+ default:
1759
+ return sql2.NVarChar;
1760
+ }
1761
+ }
1762
+ /**
1763
+ * Update a single record in the database
1764
+ */
1765
+ async update({
1766
+ tableName,
1767
+ keys,
1768
+ data,
1769
+ transaction
1770
+ }) {
1771
+ try {
1772
+ if (!data || Object.keys(data).length === 0) {
1773
+ throw new MastraError({
1774
+ id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_DATA",
1775
+ domain: ErrorDomain.STORAGE,
1776
+ category: ErrorCategory.USER,
1777
+ text: "Cannot update with empty data payload"
1778
+ });
1779
+ }
1780
+ if (!keys || Object.keys(keys).length === 0) {
1781
+ throw new MastraError({
1782
+ id: "MASTRA_STORAGE_MSSQL_UPDATE_EMPTY_KEYS",
1783
+ domain: ErrorDomain.STORAGE,
1784
+ category: ErrorCategory.USER,
1785
+ text: "Cannot update without keys to identify records"
1786
+ });
1787
+ }
1788
+ const setClauses = [];
1789
+ const request = transaction ? transaction.request() : this.pool.request();
1790
+ let paramIndex = 0;
1791
+ Object.entries(data).forEach(([key, value]) => {
1792
+ const parsedKey = parseSqlIdentifier(key, "column name");
1793
+ const paramName = `set${paramIndex++}`;
1794
+ setClauses.push(`[${parsedKey}] = @${paramName}`);
1795
+ const preparedValue = this.prepareValue(value, key, tableName);
1796
+ if (preparedValue === null || preparedValue === void 0) {
1797
+ request.input(paramName, this.getMssqlType(tableName, key), null);
1798
+ } else {
1799
+ request.input(paramName, preparedValue);
1800
+ }
1801
+ });
1802
+ const whereConditions = [];
1803
+ Object.entries(keys).forEach(([key, value]) => {
1804
+ const parsedKey = parseSqlIdentifier(key, "column name");
1805
+ const paramName = `where${paramIndex++}`;
1806
+ whereConditions.push(`[${parsedKey}] = @${paramName}`);
1807
+ const preparedValue = this.prepareValue(value, key, tableName);
1808
+ if (preparedValue === null || preparedValue === void 0) {
1809
+ request.input(paramName, this.getMssqlType(tableName, key), null);
1810
+ } else {
1811
+ request.input(paramName, preparedValue);
1812
+ }
1813
+ });
1814
+ const tableName_ = getTableName({
1815
+ indexName: tableName,
1816
+ schemaName: getSchemaName(this.schemaName)
1817
+ });
1818
+ const updateSql = `UPDATE ${tableName_} SET ${setClauses.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
1819
+ await request.query(updateSql);
1820
+ } catch (error) {
1821
+ throw new MastraError(
1822
+ {
1823
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_FAILED",
1824
+ domain: ErrorDomain.STORAGE,
1825
+ category: ErrorCategory.THIRD_PARTY,
1826
+ details: {
1827
+ tableName
1828
+ }
1829
+ },
1830
+ error
1831
+ );
1832
+ }
1833
+ }
1834
+ /**
1835
+ * Update multiple records in a single batch transaction
1836
+ */
1837
+ async batchUpdate({
1838
+ tableName,
1839
+ updates
1840
+ }) {
1841
+ const transaction = this.pool.transaction();
1842
+ try {
1843
+ await transaction.begin();
1844
+ for (const { keys, data } of updates) {
1845
+ await this.update({ tableName, keys, data, transaction });
1846
+ }
1847
+ await transaction.commit();
1848
+ } catch (error) {
1849
+ await transaction.rollback();
1850
+ throw new MastraError(
1851
+ {
1852
+ id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_UPDATE_FAILED",
1853
+ domain: ErrorDomain.STORAGE,
1854
+ category: ErrorCategory.THIRD_PARTY,
1855
+ details: {
1856
+ tableName,
1857
+ numberOfRecords: updates.length
1858
+ }
1859
+ },
1860
+ error
1861
+ );
1862
+ }
1863
+ }
1864
+ /**
1865
+ * Delete multiple records by keys
1866
+ */
1867
+ async batchDelete({ tableName, keys }) {
1868
+ if (keys.length === 0) {
1869
+ return;
1870
+ }
1871
+ const tableName_ = getTableName({
1872
+ indexName: tableName,
1873
+ schemaName: getSchemaName(this.schemaName)
1874
+ });
1875
+ const transaction = this.pool.transaction();
1876
+ try {
1877
+ await transaction.begin();
1878
+ for (const keySet of keys) {
1879
+ const conditions = [];
1880
+ const request = transaction.request();
1881
+ let paramIndex = 0;
1882
+ Object.entries(keySet).forEach(([key, value]) => {
1883
+ const parsedKey = parseSqlIdentifier(key, "column name");
1884
+ const paramName = `p${paramIndex++}`;
1885
+ conditions.push(`[${parsedKey}] = @${paramName}`);
1886
+ const preparedValue = this.prepareValue(value, key, tableName);
1887
+ if (preparedValue === null || preparedValue === void 0) {
1888
+ request.input(paramName, this.getMssqlType(tableName, key), null);
1889
+ } else {
1890
+ request.input(paramName, preparedValue);
1891
+ }
1892
+ });
1893
+ const deleteSql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
1894
+ await request.query(deleteSql);
1895
+ }
1896
+ await transaction.commit();
1897
+ } catch (error) {
1898
+ await transaction.rollback();
1899
+ throw new MastraError(
1900
+ {
1901
+ id: "MASTRA_STORAGE_MSSQL_STORE_BATCH_DELETE_FAILED",
1902
+ domain: ErrorDomain.STORAGE,
1903
+ category: ErrorCategory.THIRD_PARTY,
1904
+ details: {
1905
+ tableName,
1906
+ numberOfRecords: keys.length
1907
+ }
1908
+ },
1909
+ error
1910
+ );
1911
+ }
1912
+ }
1913
+ /**
1914
+ * Create a new index on a table
1915
+ */
1916
+ async createIndex(options) {
1917
+ try {
1918
+ const { name, table, columns, unique = false, where } = options;
1919
+ const schemaName = this.schemaName || "dbo";
1920
+ const fullTableName = getTableName({
1921
+ indexName: table,
1922
+ schemaName: getSchemaName(this.schemaName)
1923
+ });
1924
+ const indexNameSafe = parseSqlIdentifier(name, "index name");
1925
+ const checkRequest = this.pool.request();
1926
+ checkRequest.input("indexName", indexNameSafe);
1927
+ checkRequest.input("schemaName", schemaName);
1928
+ checkRequest.input("tableName", table);
1929
+ const indexExists = await checkRequest.query(`
1930
+ SELECT 1 as found
1931
+ FROM sys.indexes i
1932
+ INNER JOIN sys.tables t ON i.object_id = t.object_id
1933
+ INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
1934
+ WHERE i.name = @indexName
1935
+ AND s.name = @schemaName
1936
+ AND t.name = @tableName
1937
+ `);
1938
+ if (indexExists.recordset && indexExists.recordset.length > 0) {
1939
+ return;
1940
+ }
1941
+ const uniqueStr = unique ? "UNIQUE " : "";
1942
+ const columnsStr = columns.map((col) => {
1943
+ if (col.includes(" DESC") || col.includes(" ASC")) {
1944
+ const [colName, ...modifiers] = col.split(" ");
1945
+ if (!colName) {
1946
+ throw new Error(`Invalid column specification: ${col}`);
1947
+ }
1948
+ return `[${parseSqlIdentifier(colName, "column name")}] ${modifiers.join(" ")}`;
1949
+ }
1950
+ return `[${parseSqlIdentifier(col, "column name")}]`;
1951
+ }).join(", ");
1952
+ const whereStr = where ? ` WHERE ${where}` : "";
1953
+ const createIndexSql = `CREATE ${uniqueStr}INDEX [${indexNameSafe}] ON ${fullTableName} (${columnsStr})${whereStr}`;
1954
+ await this.pool.request().query(createIndexSql);
1955
+ } catch (error) {
1956
+ throw new MastraError(
1957
+ {
1958
+ id: "MASTRA_STORAGE_MSSQL_INDEX_CREATE_FAILED",
1959
+ domain: ErrorDomain.STORAGE,
1960
+ category: ErrorCategory.THIRD_PARTY,
1961
+ details: {
1962
+ indexName: options.name,
1963
+ tableName: options.table
1964
+ }
1965
+ },
1966
+ error
1967
+ );
1968
+ }
1969
+ }
1970
+ /**
1971
+ * Drop an existing index
1972
+ */
1973
+ async dropIndex(indexName) {
1974
+ try {
1975
+ const schemaName = this.schemaName || "dbo";
1976
+ const indexNameSafe = parseSqlIdentifier(indexName, "index name");
1977
+ const checkRequest = this.pool.request();
1978
+ checkRequest.input("indexName", indexNameSafe);
1979
+ checkRequest.input("schemaName", schemaName);
1980
+ const result = await checkRequest.query(`
1981
+ SELECT t.name as table_name
1982
+ FROM sys.indexes i
1983
+ INNER JOIN sys.tables t ON i.object_id = t.object_id
1984
+ INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
1985
+ WHERE i.name = @indexName
1986
+ AND s.name = @schemaName
1987
+ `);
1988
+ if (!result.recordset || result.recordset.length === 0) {
1989
+ return;
1990
+ }
1991
+ if (result.recordset.length > 1) {
1992
+ const tables = result.recordset.map((r) => r.table_name).join(", ");
1993
+ throw new MastraError({
1994
+ id: "MASTRA_STORAGE_MSSQL_INDEX_AMBIGUOUS",
1995
+ domain: ErrorDomain.STORAGE,
1996
+ category: ErrorCategory.USER,
1997
+ text: `Index "${indexNameSafe}" exists on multiple tables (${tables}) in schema "${schemaName}". Please drop indexes manually or ensure unique index names.`
1998
+ });
1999
+ }
2000
+ const tableName = result.recordset[0].table_name;
2001
+ const fullTableName = getTableName({
2002
+ indexName: tableName,
2003
+ schemaName: getSchemaName(this.schemaName)
2004
+ });
2005
+ const dropSql = `DROP INDEX [${indexNameSafe}] ON ${fullTableName}`;
2006
+ await this.pool.request().query(dropSql);
2007
+ } catch (error) {
2008
+ throw new MastraError(
2009
+ {
2010
+ id: "MASTRA_STORAGE_MSSQL_INDEX_DROP_FAILED",
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() {
2236
+ try {
2237
+ const indexes = this.getAutomaticIndexDefinitions();
2238
+ for (const indexOptions of indexes) {
2239
+ try {
2240
+ await this.createIndex(indexOptions);
2241
+ } catch (error) {
2242
+ this.logger?.warn?.(`Failed to create index ${indexOptions.name}:`, error);
2243
+ }
2244
+ }
2245
+ } catch (error) {
2246
+ throw new MastraError(
2247
+ {
2248
+ id: "MASTRA_STORAGE_MSSQL_STORE_CREATE_PERFORMANCE_INDEXES_FAILED",
2249
+ domain: ErrorDomain.STORAGE,
2250
+ category: ErrorCategory.THIRD_PARTY
2251
+ },
2252
+ error
2253
+ );
2254
+ }
2255
+ }
2256
+ };
2257
+ function transformScoreRow(row) {
2258
+ return {
2259
+ ...row,
2260
+ input: safelyParseJSON(row.input),
2261
+ scorer: safelyParseJSON(row.scorer),
2262
+ preprocessStepResult: safelyParseJSON(row.preprocessStepResult),
2263
+ analyzeStepResult: safelyParseJSON(row.analyzeStepResult),
2264
+ metadata: safelyParseJSON(row.metadata),
2265
+ output: safelyParseJSON(row.output),
2266
+ additionalContext: safelyParseJSON(row.additionalContext),
2267
+ requestContext: safelyParseJSON(row.requestContext),
2268
+ entity: safelyParseJSON(row.entity),
2269
+ createdAt: row.createdAt,
2270
+ updatedAt: row.updatedAt
2271
+ };
2272
+ }
2273
+ var ScoresMSSQL = class extends ScoresStorage {
2274
+ pool;
2275
+ operations;
2276
+ schema;
2277
+ constructor({
2278
+ pool,
2279
+ operations,
2280
+ schema
2281
+ }) {
2282
+ super();
2283
+ this.pool = pool;
2284
+ this.operations = operations;
2285
+ this.schema = schema;
2286
+ }
2287
+ async getScoreById({ id }) {
2288
+ try {
2289
+ const request = this.pool.request();
2290
+ request.input("p1", id);
2291
+ const result = await request.query(
2292
+ `SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE id = @p1`
2293
+ );
2294
+ if (result.recordset.length === 0) {
2295
+ return null;
2296
+ }
2297
+ return transformScoreRow(result.recordset[0]);
2298
+ } catch (error) {
2299
+ throw new MastraError(
2300
+ {
2301
+ id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORE_BY_ID_FAILED",
2302
+ domain: ErrorDomain.STORAGE,
2303
+ category: ErrorCategory.THIRD_PARTY,
2304
+ details: { id }
2305
+ },
2306
+ error
2307
+ );
2308
+ }
2309
+ }
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
+ }
2324
+ try {
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;
2338
+ await this.operations.insert({
2339
+ tableName: TABLE_SCORERS,
2340
+ record: {
2341
+ id: scoreId,
2342
+ ...rest,
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,
2352
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2353
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2354
+ }
2355
+ });
2356
+ const scoreFromDb = await this.getScoreById({ id: scoreId });
2357
+ return { score: scoreFromDb };
2358
+ } catch (error) {
2359
+ throw new MastraError(
2360
+ {
2361
+ id: "MASTRA_STORAGE_MSSQL_STORE_SAVE_SCORE_FAILED",
2362
+ domain: ErrorDomain.STORAGE,
2363
+ category: ErrorCategory.THIRD_PARTY
2364
+ },
2365
+ error
2366
+ );
2367
+ }
2368
+ }
2369
+ async listScoresByScorerId({
2370
+ scorerId,
2371
+ pagination,
2372
+ entityId,
2373
+ entityType,
2374
+ source
2375
+ }) {
2376
+ try {
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}`);
2402
+ const total = totalResult.recordset[0]?.count || 0;
2403
+ const { page, perPage: perPageInput } = pagination;
2404
+ if (total === 0) {
2405
+ return {
2406
+ pagination: {
2407
+ total: 0,
2408
+ page,
2409
+ perPage: perPageInput,
2410
+ hasMore: false
2411
+ },
2412
+ scores: []
2413
+ };
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;
2419
+ const dataRequest = this.pool.request();
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);
2427
+ return {
2428
+ pagination: {
2429
+ total: Number(total),
2430
+ page,
2431
+ perPage: perPageForResponse,
2432
+ hasMore: end < total
2433
+ },
2434
+ scores: result.recordset.map((row) => transformScoreRow(row))
2435
+ };
2436
+ } catch (error) {
2437
+ throw new MastraError(
2438
+ {
2439
+ id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORES_BY_SCORER_ID_FAILED",
2440
+ domain: ErrorDomain.STORAGE,
2441
+ category: ErrorCategory.THIRD_PARTY,
2442
+ details: { scorerId }
2443
+ },
2444
+ error
2445
+ );
2446
+ }
2447
+ }
2448
+ async listScoresByRunId({
2449
+ runId,
2450
+ pagination
2451
+ }) {
2452
+ try {
2453
+ const request = this.pool.request();
2454
+ request.input("p1", runId);
2455
+ const totalResult = await request.query(
2456
+ `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1`
2457
+ );
2458
+ const total = totalResult.recordset[0]?.count || 0;
2459
+ const { page, perPage: perPageInput } = pagination;
2460
+ if (total === 0) {
2461
+ return {
2462
+ pagination: {
2463
+ total: 0,
2464
+ page,
2465
+ perPage: perPageInput,
2466
+ hasMore: false
2467
+ },
2468
+ scores: []
2469
+ };
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;
2475
+ const dataRequest = this.pool.request();
2476
+ dataRequest.input("p1", runId);
2477
+ dataRequest.input("p2", limitValue);
2478
+ dataRequest.input("p3", start);
2479
+ const result = await dataRequest.query(
2480
+ `SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [runId] = @p1 ORDER BY [createdAt] DESC OFFSET @p3 ROWS FETCH NEXT @p2 ROWS ONLY`
2481
+ );
2482
+ return {
2483
+ pagination: {
2484
+ total: Number(total),
2485
+ page,
2486
+ perPage: perPageForResponse,
2487
+ hasMore: end < total
2488
+ },
2489
+ scores: result.recordset.map((row) => transformScoreRow(row))
2490
+ };
2491
+ } catch (error) {
2492
+ throw new MastraError(
2493
+ {
2494
+ id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORES_BY_RUN_ID_FAILED",
2495
+ domain: ErrorDomain.STORAGE,
2496
+ category: ErrorCategory.THIRD_PARTY,
2497
+ details: { runId }
2498
+ },
2499
+ error
2500
+ );
2501
+ }
2502
+ }
2503
+ async listScoresByEntityId({
2504
+ entityId,
2505
+ entityType,
2506
+ pagination
2507
+ }) {
2508
+ try {
2509
+ const request = this.pool.request();
2510
+ request.input("p1", entityId);
2511
+ request.input("p2", entityType);
2512
+ const totalResult = await request.query(
2513
+ `SELECT COUNT(*) as count FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2`
2514
+ );
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);
2519
+ if (total === 0) {
2520
+ return {
2521
+ pagination: {
2522
+ total: 0,
2523
+ page,
2524
+ perPage: perPageForResponse,
2525
+ hasMore: false
2526
+ },
2527
+ scores: []
2528
+ };
2529
+ }
2530
+ const limitValue = perPageInput === false ? total : perPage;
2531
+ const end = perPageInput === false ? total : start + perPage;
2532
+ const dataRequest = this.pool.request();
2533
+ dataRequest.input("p1", entityId);
2534
+ dataRequest.input("p2", entityType);
2535
+ dataRequest.input("p3", limitValue);
2536
+ dataRequest.input("p4", start);
2537
+ const result = await dataRequest.query(
2538
+ `SELECT * FROM ${getTableName({ indexName: TABLE_SCORERS, schemaName: getSchemaName(this.schema) })} WHERE [entityId] = @p1 AND [entityType] = @p2 ORDER BY [createdAt] DESC OFFSET @p4 ROWS FETCH NEXT @p3 ROWS ONLY`
2539
+ );
2540
+ return {
2541
+ pagination: {
2542
+ total: Number(total),
2543
+ page,
2544
+ perPage: perPageForResponse,
2545
+ hasMore: end < total
2546
+ },
2547
+ scores: result.recordset.map((row) => transformScoreRow(row))
2548
+ };
2549
+ } catch (error) {
2550
+ throw new MastraError(
2551
+ {
2552
+ id: "MASTRA_STORAGE_MSSQL_STORE_GET_SCORES_BY_ENTITY_ID_FAILED",
2553
+ domain: ErrorDomain.STORAGE,
2554
+ category: ErrorCategory.THIRD_PARTY,
2555
+ details: { entityId, entityType }
2556
+ },
2557
+ error
2558
+ );
2559
+ }
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
+ }
2619
+ };
2620
+ var WorkflowsMSSQL = class extends WorkflowsStorage {
2621
+ pool;
2622
+ operations;
2623
+ schema;
2624
+ constructor({
2625
+ pool,
2626
+ operations,
2627
+ schema
2628
+ }) {
2629
+ super();
2630
+ this.pool = pool;
2631
+ this.operations = operations;
2632
+ this.schema = schema;
2633
+ }
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
+ }
2642
+ }
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
+ };
2651
+ }
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();
2661
+ try {
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;
2707
+ } catch (error) {
2708
+ try {
2709
+ await transaction.rollback();
2710
+ } catch {
2711
+ }
2712
+ throw new MastraError(
2713
+ {
2714
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_RESULTS_FAILED",
2715
+ domain: ErrorDomain.STORAGE,
2716
+ category: ErrorCategory.THIRD_PARTY,
2717
+ details: {
2718
+ workflowName,
2719
+ runId,
2720
+ stepId
2721
+ }
2722
+ },
2723
+ error
2724
+ );
2725
+ }
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();
2734
+ try {
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;
2774
+ } catch (error) {
2775
+ try {
2776
+ await transaction.rollback();
2777
+ } catch {
2778
+ }
2779
+ throw new MastraError(
2780
+ {
2781
+ id: "MASTRA_STORAGE_MSSQL_STORE_UPDATE_WORKFLOW_STATE_FAILED",
2782
+ domain: ErrorDomain.STORAGE,
2783
+ category: ErrorCategory.THIRD_PARTY,
2784
+ details: {
2785
+ workflowName,
2786
+ runId
2787
+ }
2788
+ },
2789
+ error
2790
+ );
2791
+ }
2792
+ }
2793
+ async persistWorkflowSnapshot({
2794
+ workflowName,
2795
+ runId,
2796
+ resourceId,
2797
+ snapshot
2798
+ }) {
2799
+ const table = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
2800
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2801
+ try {
2802
+ const request = this.pool.request();
2803
+ request.input("workflow_name", workflowName);
2804
+ request.input("run_id", runId);
2805
+ request.input("resourceId", resourceId);
2806
+ request.input("snapshot", JSON.stringify(snapshot));
2807
+ request.input("createdAt", sql2.DateTime2, new Date(now));
2808
+ request.input("updatedAt", sql2.DateTime2, new Date(now));
2809
+ const mergeSql = `MERGE INTO ${table} AS target
2810
+ USING (SELECT @workflow_name AS workflow_name, @run_id AS run_id) AS src
2811
+ ON target.workflow_name = src.workflow_name AND target.run_id = src.run_id
2812
+ WHEN MATCHED THEN UPDATE SET
2813
+ resourceId = @resourceId,
2814
+ snapshot = @snapshot,
2815
+ [updatedAt] = @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);`;
2818
+ await request.query(mergeSql);
2819
+ } catch (error) {
2820
+ throw new MastraError(
2821
+ {
2822
+ id: "MASTRA_STORAGE_MSSQL_STORE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
2823
+ domain: ErrorDomain.STORAGE,
2824
+ category: ErrorCategory.THIRD_PARTY,
2825
+ details: {
2826
+ workflowName,
2827
+ runId
2828
+ }
2829
+ },
2830
+ error
2831
+ );
2832
+ }
2833
+ }
2834
+ async loadWorkflowSnapshot({
2835
+ workflowName,
2836
+ runId
2837
+ }) {
2838
+ try {
2839
+ const result = await this.operations.load({
2840
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
2841
+ keys: {
2842
+ workflow_name: workflowName,
2843
+ run_id: runId
2844
+ }
2845
+ });
2846
+ if (!result) {
2847
+ return null;
2848
+ }
2849
+ return result.snapshot;
2850
+ } catch (error) {
2851
+ throw new MastraError(
2852
+ {
2853
+ id: "MASTRA_STORAGE_MSSQL_STORE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
2854
+ domain: ErrorDomain.STORAGE,
2855
+ category: ErrorCategory.THIRD_PARTY,
2856
+ details: {
2857
+ workflowName,
2858
+ runId
2859
+ }
2860
+ },
2861
+ error
2862
+ );
2863
+ }
2864
+ }
2865
+ async getWorkflowRunById({
2866
+ runId,
2867
+ workflowName
2868
+ }) {
2869
+ try {
2870
+ const conditions = [];
2871
+ const paramMap = {};
2872
+ if (runId) {
2873
+ conditions.push(`[run_id] = @runId`);
2874
+ paramMap["runId"] = runId;
2875
+ }
2876
+ if (workflowName) {
2877
+ conditions.push(`[workflow_name] = @workflowName`);
2878
+ paramMap["workflowName"] = workflowName;
2879
+ }
2880
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2881
+ const tableName = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
2882
+ const query = `SELECT * FROM ${tableName} ${whereClause}`;
2883
+ const request = this.pool.request();
2884
+ Object.entries(paramMap).forEach(([key, value]) => request.input(key, value));
2885
+ const result = await request.query(query);
2886
+ if (!result.recordset || result.recordset.length === 0) {
2887
+ return null;
2888
+ }
2889
+ return this.parseWorkflowRun(result.recordset[0]);
2890
+ } catch (error) {
2891
+ throw new MastraError(
2892
+ {
2893
+ id: "MASTRA_STORAGE_MSSQL_STORE_GET_WORKFLOW_RUN_BY_ID_FAILED",
2894
+ domain: ErrorDomain.STORAGE,
2895
+ category: ErrorCategory.THIRD_PARTY,
2896
+ details: {
2897
+ runId,
2898
+ workflowName: workflowName || ""
2899
+ }
2900
+ },
2901
+ error
2902
+ );
2903
+ }
2904
+ }
2905
+ async listWorkflowRuns({
2906
+ workflowName,
2907
+ fromDate,
2908
+ toDate,
2909
+ page,
2910
+ perPage,
2911
+ resourceId,
2912
+ status
2913
+ } = {}) {
2914
+ try {
2915
+ const conditions = [];
2916
+ const paramMap = {};
2917
+ if (workflowName) {
2918
+ conditions.push(`[workflow_name] = @workflowName`);
2919
+ paramMap["workflowName"] = workflowName;
2920
+ }
2921
+ if (status) {
2922
+ conditions.push(`JSON_VALUE([snapshot], '$.status') = @status`);
2923
+ paramMap["status"] = status;
2924
+ }
2925
+ if (resourceId) {
2926
+ const hasResourceId = await this.operations.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
2927
+ if (hasResourceId) {
2928
+ conditions.push(`[resourceId] = @resourceId`);
2929
+ paramMap["resourceId"] = resourceId;
2930
+ } else {
2931
+ this.logger?.warn?.(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
2932
+ }
2933
+ }
2934
+ if (fromDate instanceof Date && !isNaN(fromDate.getTime())) {
2935
+ conditions.push(`[createdAt] >= @fromDate`);
2936
+ paramMap[`fromDate`] = fromDate.toISOString();
2937
+ }
2938
+ if (toDate instanceof Date && !isNaN(toDate.getTime())) {
2939
+ conditions.push(`[createdAt] <= @toDate`);
2940
+ paramMap[`toDate`] = toDate.toISOString();
2941
+ }
2942
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2943
+ let total = 0;
2944
+ const tableName = getTableName({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName(this.schema) });
2945
+ const request = this.pool.request();
2946
+ Object.entries(paramMap).forEach(([key, value]) => {
2947
+ if (value instanceof Date) {
2948
+ request.input(key, sql2.DateTime, value);
2949
+ } else {
2950
+ request.input(key, value);
2951
+ }
2952
+ });
2953
+ const usePagination = typeof perPage === "number" && typeof page === "number";
2954
+ if (usePagination) {
2955
+ const countQuery = `SELECT COUNT(*) as count FROM ${tableName} ${whereClause}`;
2956
+ const countResult = await request.query(countQuery);
2957
+ total = Number(countResult.recordset[0]?.count || 0);
2958
+ }
2959
+ let query = `SELECT * FROM ${tableName} ${whereClause} ORDER BY [seq_id] DESC`;
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);
2965
+ request.input("offset", offset);
2966
+ }
2967
+ const result = await request.query(query);
2968
+ const runs = (result.recordset || []).map((row) => this.parseWorkflowRun(row));
2969
+ return { runs, total: total || runs.length };
2970
+ } catch (error) {
2971
+ throw new MastraError(
2972
+ {
2973
+ id: "MASTRA_STORAGE_MSSQL_STORE_LIST_WORKFLOW_RUNS_FAILED",
2974
+ domain: ErrorDomain.STORAGE,
2975
+ category: ErrorCategory.THIRD_PARTY,
2976
+ details: {
2977
+ workflowName: workflowName || "all"
2978
+ }
2979
+ },
2980
+ error
2981
+ );
2982
+ }
2983
+ }
2984
+ };
2985
+
2986
+ // src/storage/index.ts
2987
+ var MSSQLStore = class extends MastraStorage {
2988
+ pool;
2989
+ schema;
2990
+ isConnected = null;
2991
+ stores;
2992
+ constructor(config) {
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" });
2997
+ try {
2998
+ if ("connectionString" in config) {
2999
+ if (!config.connectionString || typeof config.connectionString !== "string" || config.connectionString.trim() === "") {
3000
+ throw new Error("MSSQLStore: connectionString must be provided and cannot be empty.");
3001
+ }
3002
+ } else {
3003
+ const required = ["server", "database", "user", "password"];
3004
+ for (const key of required) {
3005
+ if (!(key in config) || typeof config[key] !== "string" || config[key].trim() === "") {
3006
+ throw new Error(`MSSQLStore: ${key} must be provided and cannot be empty.`);
3007
+ }
3008
+ }
3009
+ }
3010
+ this.schema = config.schemaName || "dbo";
3011
+ this.pool = "connectionString" in config ? new sql2.ConnectionPool(config.connectionString) : new sql2.ConnectionPool({
3012
+ server: config.server,
3013
+ database: config.database,
3014
+ user: config.user,
3015
+ password: config.password,
3016
+ port: config.port,
3017
+ options: config.options || { encrypt: true, trustServerCertificate: true }
3018
+ });
3019
+ const operations = new StoreOperationsMSSQL({ pool: this.pool, schemaName: this.schema });
3020
+ const scores = new ScoresMSSQL({ pool: this.pool, operations, schema: this.schema });
3021
+ const workflows = new WorkflowsMSSQL({ pool: this.pool, operations, schema: this.schema });
3022
+ const memory = new MemoryMSSQL({ pool: this.pool, schema: this.schema, operations });
3023
+ const observability = new ObservabilityMSSQL({ pool: this.pool, operations, schema: this.schema });
3024
+ this.stores = {
3025
+ operations,
3026
+ scores,
3027
+ workflows,
3028
+ memory,
3029
+ observability
3030
+ };
3031
+ } catch (e) {
3032
+ throw new MastraError(
3033
+ {
3034
+ id: "MASTRA_STORAGE_MSSQL_STORE_INITIALIZATION_FAILED",
3035
+ domain: ErrorDomain.STORAGE,
3036
+ category: ErrorCategory.USER
3037
+ },
3038
+ e
3039
+ );
3040
+ }
3041
+ }
3042
+ async init() {
3043
+ if (this.isConnected === null) {
3044
+ this.isConnected = this._performInitializationAndStore();
3045
+ }
3046
+ try {
3047
+ await this.isConnected;
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
+ }
3054
+ } catch (error) {
3055
+ this.isConnected = null;
3056
+ throw new MastraError(
3057
+ {
3058
+ id: "MASTRA_STORAGE_MSSQL_STORE_INIT_FAILED",
3059
+ domain: ErrorDomain.STORAGE,
3060
+ category: ErrorCategory.THIRD_PARTY
3061
+ },
3062
+ error
3063
+ );
3064
+ }
3065
+ }
3066
+ async _performInitializationAndStore() {
3067
+ try {
3068
+ await this.pool.connect();
3069
+ return true;
3070
+ } catch (err) {
3071
+ throw err;
3072
+ }
3073
+ }
3074
+ get supports() {
3075
+ return {
3076
+ selectByIncludeResourceScope: true,
3077
+ resourceWorkingMemory: true,
3078
+ hasColumn: true,
3079
+ createTable: true,
3080
+ deleteMessages: true,
3081
+ listScoresBySpan: true,
3082
+ observabilityInstance: true,
3083
+ indexManagement: true
3084
+ };
3085
+ }
3086
+ async createTable({
3087
+ tableName,
3088
+ schema
3089
+ }) {
3090
+ return this.stores.operations.createTable({ tableName, schema });
3091
+ }
3092
+ async alterTable({
3093
+ tableName,
3094
+ schema,
3095
+ ifNotExists
3096
+ }) {
3097
+ return this.stores.operations.alterTable({ tableName, schema, ifNotExists });
3098
+ }
3099
+ async clearTable({ tableName }) {
3100
+ return this.stores.operations.clearTable({ tableName });
3101
+ }
3102
+ async dropTable({ tableName }) {
3103
+ return this.stores.operations.dropTable({ tableName });
3104
+ }
3105
+ async insert({ tableName, record }) {
3106
+ return this.stores.operations.insert({ tableName, record });
3107
+ }
3108
+ async batchInsert({ tableName, records }) {
3109
+ return this.stores.operations.batchInsert({ tableName, records });
3110
+ }
3111
+ async load({ tableName, keys }) {
3112
+ return this.stores.operations.load({ tableName, keys });
3113
+ }
3114
+ /**
3115
+ * Memory
3116
+ */
3117
+ async getThreadById({ threadId }) {
3118
+ return this.stores.memory.getThreadById({ threadId });
3119
+ }
3120
+ async saveThread({ thread }) {
3121
+ return this.stores.memory.saveThread({ thread });
3122
+ }
3123
+ async updateThread({
3124
+ id,
3125
+ title,
3126
+ metadata
3127
+ }) {
3128
+ return this.stores.memory.updateThread({ id, title, metadata });
3129
+ }
3130
+ async deleteThread({ threadId }) {
3131
+ return this.stores.memory.deleteThread({ threadId });
3132
+ }
3133
+ async listMessagesById({ messageIds }) {
3134
+ return this.stores.memory.listMessagesById({ messageIds });
3135
+ }
3136
+ async saveMessages(args) {
3137
+ return this.stores.memory.saveMessages(args);
3138
+ }
3139
+ async updateMessages({
3140
+ messages
3141
+ }) {
3142
+ return this.stores.memory.updateMessages({ messages });
3143
+ }
3144
+ async deleteMessages(messageIds) {
3145
+ return this.stores.memory.deleteMessages(messageIds);
3146
+ }
3147
+ async getResourceById({ resourceId }) {
3148
+ return this.stores.memory.getResourceById({ resourceId });
3149
+ }
3150
+ async saveResource({ resource }) {
3151
+ return this.stores.memory.saveResource({ resource });
3152
+ }
3153
+ async updateResource({
3154
+ resourceId,
3155
+ workingMemory,
3156
+ metadata
3157
+ }) {
3158
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
3159
+ }
3160
+ /**
3161
+ * Workflows
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
+ }
3179
+ async persistWorkflowSnapshot({
3180
+ workflowName,
3181
+ runId,
3182
+ resourceId,
3183
+ snapshot
3184
+ }) {
3185
+ return this.stores.workflows.persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot });
3186
+ }
3187
+ async loadWorkflowSnapshot({
3188
+ workflowName,
3189
+ runId
3190
+ }) {
3191
+ return this.stores.workflows.loadWorkflowSnapshot({ workflowName, runId });
3192
+ }
3193
+ async listWorkflowRuns(args = {}) {
3194
+ return this.stores.workflows.listWorkflowRuns(args);
3195
+ }
3196
+ async getWorkflowRunById({
3197
+ runId,
3198
+ workflowName
3199
+ }) {
3200
+ return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
3201
+ }
3202
+ async close() {
3203
+ await this.pool.close();
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
+ }
3259
+ /**
3260
+ * Scorers
3261
+ */
3262
+ async getScoreById({ id: _id }) {
3263
+ return this.stores.scores.getScoreById({ id: _id });
3264
+ }
3265
+ async listScoresByScorerId({
3266
+ scorerId: _scorerId,
3267
+ pagination: _pagination,
3268
+ entityId: _entityId,
3269
+ entityType: _entityType,
3270
+ source: _source
3271
+ }) {
3272
+ return this.stores.scores.listScoresByScorerId({
3273
+ scorerId: _scorerId,
3274
+ pagination: _pagination,
3275
+ entityId: _entityId,
3276
+ entityType: _entityType,
3277
+ source: _source
3278
+ });
3279
+ }
3280
+ async saveScore(_score) {
3281
+ return this.stores.scores.saveScore(_score);
3282
+ }
3283
+ async listScoresByRunId({
3284
+ runId: _runId,
3285
+ pagination: _pagination
3286
+ }) {
3287
+ return this.stores.scores.listScoresByRunId({ runId: _runId, pagination: _pagination });
3288
+ }
3289
+ async listScoresByEntityId({
3290
+ entityId: _entityId,
3291
+ entityType: _entityType,
3292
+ pagination: _pagination
3293
+ }) {
3294
+ return this.stores.scores.listScoresByEntityId({
3295
+ entityId: _entityId,
3296
+ entityType: _entityType,
3297
+ pagination: _pagination
3298
+ });
3299
+ }
3300
+ async listScoresBySpan({
3301
+ traceId,
3302
+ spanId,
3303
+ pagination: _pagination
3304
+ }) {
3305
+ return this.stores.scores.listScoresBySpan({ traceId, spanId, pagination: _pagination });
3306
+ }
1744
3307
  };
1745
3308
 
1746
3309
  export { MSSQLStore };
3310
+ //# sourceMappingURL=index.js.map
3311
+ //# sourceMappingURL=index.js.map