@mastra/mssql 0.0.0-remove-unused-import-20250909212718 → 0.0.0-remove-ai-peer-dep-from-evals-20260105220639

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