@mastra/mssql 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
2
- import { MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, TABLE_SCHEMAS, createStorageErrorId, normalizePerPage, calculatePagination, ObservabilityStorage, TABLE_SPANS, SPAN_SCHEMA, listTracesArgsSchema, toTraceSpans, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraCompositeStore, TraceStatus, getDefaultValue, transformScoreRow as transformScoreRow$1 } from '@mastra/core/storage';
2
+ import { MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, TABLE_SCHEMAS, createStorageErrorId, normalizePerPage, calculatePagination, ObservabilityStorage, TABLE_SPANS, SPAN_SCHEMA, listTracesArgsSchema, toTraceSpans, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraCompositeStore, getDefaultValue, transformScoreRow as transformScoreRow$1, TraceStatus } from '@mastra/core/storage';
3
3
  import sql from 'mssql';
4
4
  import { MessageList } from '@mastra/core/agent';
5
5
  import { MastraBase } from '@mastra/core/base';
@@ -52,6 +52,8 @@ var MssqlDB = class extends MastraBase {
52
52
  skipDefaultIndexes;
53
53
  setupSchemaPromise = null;
54
54
  schemaSetupComplete = void 0;
55
+ /** Cache of actual table columns: tableName -> Set<columnName> */
56
+ tableColumnsCache = /* @__PURE__ */ new Map();
55
57
  /**
56
58
  * Columns that participate in composite indexes need smaller sizes (NVARCHAR(100)).
57
59
  * MSSQL has a 900-byte index key limit, so composite indexes with NVARCHAR(400) columns fail.
@@ -136,6 +138,41 @@ var MssqlDB = class extends MastraBase {
136
138
  this.schemaName = schemaName;
137
139
  this.skipDefaultIndexes = skipDefaultIndexes;
138
140
  }
141
+ /**
142
+ * Gets the set of column names that actually exist in the database table.
143
+ * Results are cached; the cache is invalidated when alterTable() adds new columns.
144
+ */
145
+ async getTableColumns(tableName) {
146
+ const cached = this.tableColumnsCache.get(tableName);
147
+ if (cached) return cached;
148
+ const schema = this.schemaName || "dbo";
149
+ const request = this.pool.request();
150
+ request.input("schema", schema);
151
+ request.input("tableName", tableName);
152
+ const result = await request.query(
153
+ `SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @tableName`
154
+ );
155
+ const columns = new Set((result.recordset || []).map((r) => r.COLUMN_NAME));
156
+ if (columns.size > 0) {
157
+ this.tableColumnsCache.set(tableName, columns);
158
+ }
159
+ return columns;
160
+ }
161
+ /**
162
+ * Filters a record to only include columns that exist in the actual database table.
163
+ * Unknown columns are silently dropped to ensure forward compatibility.
164
+ */
165
+ async filterRecordToKnownColumns(tableName, record) {
166
+ const knownColumns = await this.getTableColumns(tableName);
167
+ if (knownColumns.size === 0) return record;
168
+ const filtered = {};
169
+ for (const [key, value] of Object.entries(record)) {
170
+ if (knownColumns.has(key)) {
171
+ filtered[key] = value;
172
+ }
173
+ }
174
+ return filtered;
175
+ }
139
176
  async hasColumn(table, column) {
140
177
  const schema = this.schemaName || "dbo";
141
178
  const request = this.pool.request();
@@ -191,13 +228,15 @@ var MssqlDB = class extends MastraBase {
191
228
  transaction
192
229
  }) {
193
230
  try {
194
- const columns = Object.keys(record);
231
+ const filteredRecord = await this.filterRecordToKnownColumns(tableName, record);
232
+ const columns = Object.keys(filteredRecord);
233
+ if (columns.length === 0) return;
195
234
  const parsedColumns = columns.map((col) => parseSqlIdentifier(col, "column name"));
196
235
  const paramNames = columns.map((_, i) => `@param${i}`);
197
236
  const insertSql = `INSERT INTO ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} (${parsedColumns.map((c) => `[${c}]`).join(", ")}) VALUES (${paramNames.join(", ")})`;
198
237
  const request = transaction ? transaction.request() : this.pool.request();
199
238
  columns.forEach((col, i) => {
200
- const value = record[col];
239
+ const value = filteredRecord[col];
201
240
  const preparedValue = this.prepareValue(value, col, tableName);
202
241
  if (preparedValue instanceof Date) {
203
242
  request.input(`param${i}`, sql.DateTime2, preparedValue);
@@ -388,6 +427,8 @@ Note: This migration may take some time for large tables.
388
427
  },
389
428
  error
390
429
  );
430
+ } finally {
431
+ this.tableColumnsCache.delete(tableName);
391
432
  }
392
433
  }
393
434
  /**
@@ -615,6 +656,8 @@ Note: This migration may take some time for large tables.
615
656
  },
616
657
  error
617
658
  );
659
+ } finally {
660
+ this.tableColumnsCache.delete(tableName);
618
661
  }
619
662
  }
620
663
  async load({ tableName, keys }) {
@@ -698,6 +741,8 @@ Note: This migration may take some time for large tables.
698
741
  },
699
742
  error
700
743
  );
744
+ } finally {
745
+ this.tableColumnsCache.delete(tableName);
701
746
  }
702
747
  }
703
748
  /**
@@ -789,10 +834,12 @@ Note: This migration may take some time for large tables.
789
834
  text: "Cannot update without keys to identify records"
790
835
  });
791
836
  }
837
+ const filteredData = await this.filterRecordToKnownColumns(tableName, data);
838
+ if (Object.keys(filteredData).length === 0) return;
792
839
  const setClauses = [];
793
840
  const request = transaction ? transaction.request() : this.pool.request();
794
841
  let paramIndex = 0;
795
- Object.entries(data).forEach(([key, value]) => {
842
+ Object.entries(filteredData).forEach(([key, value]) => {
796
843
  const parsedKey = parseSqlIdentifier(key, "column name");
797
844
  const paramName = `set${paramIndex++}`;
798
845
  setClauses.push(`[${parsedKey}] = @${paramName}`);
@@ -1728,6 +1775,18 @@ var MemoryMSSQL = class _MemoryMSSQL extends MemoryStorage {
1728
1775
  );
1729
1776
  }
1730
1777
  }
1778
+ _sortMessages(messages, field, direction) {
1779
+ const mult = direction === "ASC" ? 1 : -1;
1780
+ return messages.sort((a, b) => {
1781
+ const aVal = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
1782
+ const bVal = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
1783
+ if (aVal == null || bVal == null) {
1784
+ return aVal == null && bVal == null ? a.id.localeCompare(b.id) : aVal == null ? 1 : -1;
1785
+ }
1786
+ const diff = (typeof aVal === "number" && typeof bVal === "number" ? aVal - bVal : String(aVal).localeCompare(String(bVal))) * mult;
1787
+ return diff !== 0 ? diff : a.id.localeCompare(b.id);
1788
+ });
1789
+ }
1731
1790
  async _getIncludedMessages({ include }) {
1732
1791
  if (!include || include.length === 0) return null;
1733
1792
  const unionQueries = [];
@@ -1889,6 +1948,20 @@ var MemoryMSSQL = class _MemoryMSSQL extends MemoryStorage {
1889
1948
  const bindWhereParams = (req) => {
1890
1949
  Object.entries(whereParams).forEach(([paramName, paramValue]) => req.input(paramName, paramValue));
1891
1950
  };
1951
+ if (perPage === 0 && (!include || include.length === 0)) {
1952
+ return { messages: [], total: 0, page, perPage: perPageForResponse, hasMore: false };
1953
+ }
1954
+ if (perPage === 0 && include && include.length > 0) {
1955
+ const includeMessages = await this._getIncludedMessages({ include });
1956
+ const messages2 = this._parseAndFormatMessages(includeMessages ?? [], "v2");
1957
+ return {
1958
+ messages: this._sortMessages(messages2, field, direction),
1959
+ total: 0,
1960
+ page,
1961
+ perPage: perPageForResponse,
1962
+ hasMore: false
1963
+ };
1964
+ }
1892
1965
  const countRequest = this.pool.request();
1893
1966
  bindWhereParams(countRequest);
1894
1967
  const countResult = await countRequest.query(`SELECT COUNT(*) as total FROM ${tableName}${actualWhereClause}`);
@@ -2354,6 +2427,11 @@ var ObservabilityMSSQL = class _ObservabilityMSSQL extends ObservabilityStorage
2354
2427
  this.needsConnect = false;
2355
2428
  }
2356
2429
  await this.db.createTable({ tableName: TABLE_SPANS, schema: SPAN_SCHEMA });
2430
+ await this.db.alterTable({
2431
+ tableName: TABLE_SPANS,
2432
+ schema: SPAN_SCHEMA,
2433
+ ifNotExists: ["requestContext"]
2434
+ });
2357
2435
  await this.createDefaultIndexes();
2358
2436
  await this.createCustomIndexes();
2359
2437
  }
@@ -2666,7 +2744,8 @@ var ObservabilityMSSQL = class _ObservabilityMSSQL extends ObservabilityStorage
2666
2744
  }
2667
2745
  async listTraces(args) {
2668
2746
  const { filters, pagination, orderBy } = listTracesArgsSchema.parse(args);
2669
- const { page, perPage } = pagination;
2747
+ const page = pagination?.page ?? 0;
2748
+ const perPage = pagination?.perPage ?? 10;
2670
2749
  const tableName = getTableName2({
2671
2750
  indexName: TABLE_SPANS,
2672
2751
  schemaName: getSchemaName2(this.schema)
@@ -2831,8 +2910,8 @@ var ObservabilityMSSQL = class _ObservabilityMSSQL extends ObservabilityStorage
2831
2910
  }
2832
2911
  }
2833
2912
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2834
- const sortField = orderBy.field;
2835
- const sortDirection = orderBy.direction;
2913
+ const sortField = orderBy?.field ?? "startedAt";
2914
+ const sortDirection = orderBy?.direction ?? "DESC";
2836
2915
  const countRequest = this.pool.request();
2837
2916
  Object.entries(params).forEach(([key, value]) => {
2838
2917
  countRequest.input(key, value);