@mastra/mssql 1.0.0-beta.0 → 1.0.0-beta.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @mastra/mssql
2
2
 
3
+ ## 1.0.0-beta.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixes MSSQL store test configuration and pagination issues: ([#9905](https://github.com/mastra-ai/mastra/pull/9905))
8
+
9
+ Adds missing id parameter to test configuration and updates documentation
10
+ Implements page validation to handle negative page numbers
11
+ Fixes sorting and pagination bugs in message listing (improves ORDER BY with seq_id secondary sort and corrects hasMore calculation)
12
+
13
+ - Prevents double stringification for MSSQL jsonb columns by reusing incoming strings that already contain valid JSON while still stringifying other inputs as needed. ([#9901](https://github.com/mastra-ai/mastra/pull/9901))
14
+
15
+ - Updated dependencies [[`465ac05`](https://github.com/mastra-ai/mastra/commit/465ac0526a91d175542091c675181f1a96c98c46)]:
16
+ - @mastra/core@1.0.0-beta.2
17
+
3
18
  ## 1.0.0-beta.0
4
19
 
5
20
  ### Major Changes
package/README.md CHANGED
@@ -27,6 +27,7 @@ MSSQLStore supports multiple connection methods:
27
27
  import { MSSQLStore } from '@mastra/mssql';
28
28
 
29
29
  const store = new MSSQLStore({
30
+ id: 'mssql-storage',
30
31
  connectionString:
31
32
  'Server=localhost,1433;Database=mastra;User Id=sa;Password=yourPassword;Encrypt=true;TrustServerCertificate=true',
32
33
  });
@@ -36,6 +37,7 @@ const store = new MSSQLStore({
36
37
 
37
38
  ```typescript
38
39
  const store = new MSSQLStore({
40
+ id: 'mssql-storage',
39
41
  server: 'localhost',
40
42
  port: 1433,
41
43
  database: 'mastra',
@@ -49,6 +51,7 @@ const store = new MSSQLStore({
49
51
 
50
52
  ```typescript
51
53
  const store = new MSSQLStore({
54
+ id: 'mssql-storage',
52
55
  connectionString:
53
56
  'Server=localhost,1433;Database=mastra;User Id=sa;Password=yourPassword;Encrypt=true;TrustServerCertificate=true',
54
57
  schemaName: 'custom_schema', // Use custom schema (default: dbo)
@@ -103,6 +106,10 @@ const messages = await store.listMessages({ threadId: 'thread-123' });
103
106
 
104
107
  ## Configuration
105
108
 
109
+ ### Identifier
110
+
111
+ - `id`: Unique identifier for this store instance (required)
112
+
106
113
  ### Connection Methods
107
114
 
108
115
  MSSQLStore supports multiple connection methods:
@@ -111,6 +118,7 @@ MSSQLStore supports multiple connection methods:
111
118
 
112
119
  ```typescript
113
120
  {
121
+ id: 'mssql-storage',
114
122
  connectionString: 'Server=localhost,1433;Database=mastra;User Id=sa;Password=yourPassword;Encrypt=true;TrustServerCertificate=true';
115
123
  }
116
124
  ```
@@ -118,6 +126,7 @@ MSSQLStore supports multiple connection methods:
118
126
  2. **Server/Port/Database**
119
127
  ```typescript
120
128
  {
129
+ id: 'mssql-storage',
121
130
  server: 'localhost',
122
131
  port: 1433,
123
132
  database: 'mastra',
package/dist/index.cjs CHANGED
@@ -156,6 +156,18 @@ var MemoryMSSQL = class extends storage.MemoryStorage {
156
156
  }
157
157
  async listThreadsByResourceId(args) {
158
158
  const { resourceId, page = 0, perPage: perPageInput, orderBy } = args;
159
+ if (page < 0) {
160
+ throw new error.MastraError({
161
+ id: "MASTRA_STORAGE_MSSQL_STORE_INVALID_PAGE",
162
+ domain: error.ErrorDomain.STORAGE,
163
+ category: error.ErrorCategory.USER,
164
+ text: "Page number must be non-negative",
165
+ details: {
166
+ resourceId,
167
+ page
168
+ }
169
+ });
170
+ }
159
171
  const perPage = storage.normalizePerPage(perPageInput, 100);
160
172
  const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
161
173
  const { field, direction } = this.parseOrderBy(orderBy);
@@ -511,43 +523,59 @@ var MemoryMSSQL = class extends storage.MemoryStorage {
511
523
  new Error("threadId must be a non-empty string")
512
524
  );
513
525
  }
526
+ if (page < 0) {
527
+ throw new error.MastraError({
528
+ id: "MASTRA_STORAGE_MSSQL_STORE_INVALID_PAGE",
529
+ domain: error.ErrorDomain.STORAGE,
530
+ category: error.ErrorCategory.USER,
531
+ text: "Page number must be non-negative",
532
+ details: {
533
+ threadId,
534
+ page
535
+ }
536
+ });
537
+ }
514
538
  const perPage = storage.normalizePerPage(perPageInput, 40);
515
539
  const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
516
540
  try {
517
541
  const { field, direction } = this.parseOrderBy(orderBy, "ASC");
518
- const orderByStatement = `ORDER BY [${field}] ${direction}`;
519
- const selectStatement = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId`;
542
+ const orderByStatement = `ORDER BY [${field}] ${direction}, [seq_id] ${direction}`;
520
543
  const tableName = getTableName({ indexName: storage.TABLE_MESSAGES, schemaName: getSchemaName(this.schema) });
521
- const conditions = ["[thread_id] = @threadId"];
522
- const request = this.pool.request();
523
- request.input("threadId", threadId);
524
- if (resourceId) {
525
- conditions.push("[resourceId] = @resourceId");
526
- request.input("resourceId", resourceId);
527
- }
528
- if (filter?.dateRange?.start) {
529
- conditions.push("[createdAt] >= @fromDate");
530
- request.input("fromDate", filter.dateRange.start);
531
- }
532
- if (filter?.dateRange?.end) {
533
- conditions.push("[createdAt] <= @toDate");
534
- request.input("toDate", filter.dateRange.end);
535
- }
536
- const whereClause = `WHERE ${conditions.join(" AND ")}`;
537
- const countQuery = `SELECT COUNT(*) as total FROM ${tableName} ${whereClause}`;
538
- const countResult = await request.query(countQuery);
544
+ const baseQuery = `SELECT seq_id, id, content, role, type, [createdAt], thread_id AS threadId, resourceId FROM ${tableName}`;
545
+ const filters = {
546
+ thread_id: threadId,
547
+ ...resourceId ? { resourceId } : {},
548
+ ...buildDateRangeFilter(filter?.dateRange, "createdAt")
549
+ };
550
+ const { sql: actualWhereClause = "", params: whereParams } = prepareWhereClause(
551
+ filters);
552
+ const bindWhereParams = (req) => {
553
+ Object.entries(whereParams).forEach(([paramName, paramValue]) => req.input(paramName, paramValue));
554
+ };
555
+ const countRequest = this.pool.request();
556
+ bindWhereParams(countRequest);
557
+ const countResult = await countRequest.query(`SELECT COUNT(*) as total FROM ${tableName}${actualWhereClause}`);
539
558
  const total = parseInt(countResult.recordset[0]?.total, 10) || 0;
540
- const limitValue = perPageInput === false ? total : perPage;
541
- const dataQuery = `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`;
542
- request.input("offset", offset);
543
- if (limitValue > 2147483647) {
544
- request.input("limit", sql2__default.default.BigInt, limitValue);
545
- } else {
546
- request.input("limit", limitValue);
547
- }
548
- const rowsResult = await request.query(dataQuery);
549
- const rows = rowsResult.recordset || [];
550
- const messages = [...rows];
559
+ const fetchBaseMessages = async () => {
560
+ const request = this.pool.request();
561
+ bindWhereParams(request);
562
+ if (perPageInput === false) {
563
+ const result2 = await request.query(`${baseQuery}${actualWhereClause} ${orderByStatement}`);
564
+ return result2.recordset || [];
565
+ }
566
+ request.input("offset", offset);
567
+ request.input("limit", perPage > 2147483647 ? sql2__default.default.BigInt : sql2__default.default.Int, perPage);
568
+ const result = await request.query(
569
+ `${baseQuery}${actualWhereClause} ${orderByStatement} OFFSET @offset ROWS FETCH NEXT @limit ROWS ONLY`
570
+ );
571
+ return result.recordset || [];
572
+ };
573
+ const baseRows = perPage === 0 ? [] : await fetchBaseMessages();
574
+ const messages = [...baseRows];
575
+ const seqById = /* @__PURE__ */ new Map();
576
+ messages.forEach((msg) => {
577
+ if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
578
+ });
551
579
  if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
552
580
  return {
553
581
  messages: [],
@@ -557,28 +585,33 @@ var MemoryMSSQL = class extends storage.MemoryStorage {
557
585
  hasMore: false
558
586
  };
559
587
  }
560
- const messageIds = new Set(messages.map((m) => m.id));
561
- if (include && include.length > 0) {
588
+ if (include?.length) {
589
+ const messageIds = new Set(messages.map((m) => m.id));
562
590
  const includeMessages = await this._getIncludedMessages({ threadId, include });
563
- if (includeMessages) {
564
- for (const includeMsg of includeMessages) {
565
- if (!messageIds.has(includeMsg.id)) {
566
- messages.push(includeMsg);
567
- messageIds.add(includeMsg.id);
568
- }
591
+ includeMessages?.forEach((msg) => {
592
+ if (!messageIds.has(msg.id)) {
593
+ messages.push(msg);
594
+ messageIds.add(msg.id);
595
+ if (typeof msg.seq_id === "number") seqById.set(msg.id, msg.seq_id);
569
596
  }
570
- }
597
+ });
571
598
  }
572
599
  const parsed = this._parseAndFormatMessages(messages, "v2");
573
- let finalMessages = parsed;
574
- finalMessages = finalMessages.sort((a, b) => {
575
- const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
576
- const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
577
- return direction === "ASC" ? aValue - bValue : bValue - aValue;
600
+ const mult = direction === "ASC" ? 1 : -1;
601
+ const finalMessages = parsed.sort((a, b) => {
602
+ const aVal = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
603
+ const bVal = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
604
+ if (aVal == null || bVal == null) {
605
+ return aVal == null && bVal == null ? a.id.localeCompare(b.id) : aVal == null ? 1 : -1;
606
+ }
607
+ const diff = (typeof aVal === "number" && typeof bVal === "number" ? aVal - bVal : String(aVal).localeCompare(String(bVal))) * mult;
608
+ if (diff !== 0) return diff;
609
+ const seqA = seqById.get(a.id);
610
+ const seqB = seqById.get(b.id);
611
+ return seqA != null && seqB != null ? (seqA - seqB) * mult : a.id.localeCompare(b.id);
578
612
  });
579
- const returnedThreadMessageIds = new Set(finalMessages.filter((m) => m.threadId === threadId).map((m) => m.id));
580
- const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
581
- const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
613
+ const returnedThreadMessageCount = finalMessages.filter((m) => m.threadId === threadId).length;
614
+ const hasMore = perPageInput !== false && returnedThreadMessageCount < total && offset + perPage < total;
582
615
  return {
583
616
  messages: finalMessages,
584
617
  total,
@@ -1685,6 +1718,20 @@ ${columns}
1685
1718
  return value ? 1 : 0;
1686
1719
  }
1687
1720
  if (columnSchema?.type === "jsonb") {
1721
+ if (typeof value === "string") {
1722
+ const trimmed = value.trim();
1723
+ if (trimmed.length > 0) {
1724
+ try {
1725
+ JSON.parse(trimmed);
1726
+ return trimmed;
1727
+ } catch {
1728
+ }
1729
+ }
1730
+ return JSON.stringify(value);
1731
+ }
1732
+ if (typeof value === "bigint") {
1733
+ return value.toString();
1734
+ }
1688
1735
  return JSON.stringify(value);
1689
1736
  }
1690
1737
  if (typeof value === "object") {