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