@mastra/mssql 1.0.0-beta.11 → 1.0.0-beta.13
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 +104 -0
- package/dist/docs/README.md +1 -1
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +275 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +276 -21
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +45 -0
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +2 -2
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts +23 -0
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/dist/storage/index.d.ts +2 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,109 @@
|
|
|
1
1
|
# @mastra/mssql
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.13
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Fixed duplicate spans migration issue across all storage backends. When upgrading from older versions, existing duplicate (traceId, spanId) combinations in the spans table could prevent the unique constraint from being created. The migration deduplicates spans before adding the constraint. ([#12073](https://github.com/mastra-ai/mastra/pull/12073))
|
|
8
|
+
|
|
9
|
+
**Deduplication rules (in priority order):**
|
|
10
|
+
1. Keep completed spans (those with `endedAt` set) over incomplete spans
|
|
11
|
+
2. Among spans with the same completion status, keep the one with the newest `updatedAt`
|
|
12
|
+
3. Use `createdAt` as the final tiebreaker
|
|
13
|
+
|
|
14
|
+
**What changed:**
|
|
15
|
+
- Added `migrateSpans()` method to observability stores for manual migration
|
|
16
|
+
- Added `checkSpansMigrationStatus()` method to check if migration is needed
|
|
17
|
+
- All stores use optimized single-query deduplication to avoid memory issues on large tables
|
|
18
|
+
|
|
19
|
+
**Usage example:**
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const observability = await storage.getStore('observability');
|
|
23
|
+
const status = await observability.checkSpansMigrationStatus();
|
|
24
|
+
if (status.needsMigration) {
|
|
25
|
+
const result = await observability.migrateSpans();
|
|
26
|
+
console.log(`Migration complete: ${result.duplicatesRemoved} duplicates removed`);
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Fixes #11840
|
|
31
|
+
|
|
32
|
+
- Renamed MastraStorage to MastraCompositeStore for better clarity. The old MastraStorage name remains available as a deprecated alias for backward compatibility, but will be removed in a future version. ([#12093](https://github.com/mastra-ai/mastra/pull/12093))
|
|
33
|
+
|
|
34
|
+
**Migration:**
|
|
35
|
+
|
|
36
|
+
Update your imports and usage:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Before
|
|
40
|
+
import { MastraStorage } from '@mastra/core/storage';
|
|
41
|
+
|
|
42
|
+
const storage = new MastraStorage({
|
|
43
|
+
id: 'composite',
|
|
44
|
+
domains: { ... }
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// After
|
|
48
|
+
import { MastraCompositeStore } from '@mastra/core/storage';
|
|
49
|
+
|
|
50
|
+
const storage = new MastraCompositeStore({
|
|
51
|
+
id: 'composite',
|
|
52
|
+
domains: { ... }
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The new name better reflects that this is a composite storage implementation that routes different domains (workflows, traces, messages) to different underlying stores, avoiding confusion with the general "Mastra Storage" concept.
|
|
57
|
+
|
|
58
|
+
- Updated dependencies [[`026b848`](https://github.com/mastra-ai/mastra/commit/026b8483fbf5b6d977be8f7e6aac8d15c75558ac), [`ffa553a`](https://github.com/mastra-ai/mastra/commit/ffa553a3edc1bd17d73669fba66d6b6f4ac10897)]:
|
|
59
|
+
- @mastra/core@1.0.0-beta.26
|
|
60
|
+
|
|
61
|
+
## 1.0.0-beta.12
|
|
62
|
+
|
|
63
|
+
### Patch Changes
|
|
64
|
+
|
|
65
|
+
- Added new `listThreads` method for flexible thread filtering across all storage adapters. ([#11832](https://github.com/mastra-ai/mastra/pull/11832))
|
|
66
|
+
|
|
67
|
+
**New Features**
|
|
68
|
+
- Filter threads by `resourceId`, `metadata`, or both (with AND logic for metadata key-value pairs)
|
|
69
|
+
- All filter parameters are optional, allowing you to list all threads or filter as needed
|
|
70
|
+
- Full pagination and sorting support
|
|
71
|
+
|
|
72
|
+
**Example Usage**
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// List all threads
|
|
76
|
+
const allThreads = await memory.listThreads({});
|
|
77
|
+
|
|
78
|
+
// Filter by resourceId only
|
|
79
|
+
const userThreads = await memory.listThreads({
|
|
80
|
+
filter: { resourceId: 'user-123' },
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// Filter by metadata only
|
|
84
|
+
const supportThreads = await memory.listThreads({
|
|
85
|
+
filter: { metadata: { category: 'support' } },
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Filter by both with pagination
|
|
89
|
+
const filteredThreads = await memory.listThreads({
|
|
90
|
+
filter: {
|
|
91
|
+
resourceId: 'user-123',
|
|
92
|
+
metadata: { priority: 'high', status: 'open' },
|
|
93
|
+
},
|
|
94
|
+
orderBy: { field: 'updatedAt', direction: 'DESC' },
|
|
95
|
+
page: 0,
|
|
96
|
+
perPage: 20,
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Security Improvements**
|
|
101
|
+
- Added validation to prevent SQL injection via malicious metadata keys
|
|
102
|
+
- Added pagination parameter validation to prevent integer overflow attacks
|
|
103
|
+
|
|
104
|
+
- Updated dependencies [[`ed3e3dd`](https://github.com/mastra-ai/mastra/commit/ed3e3ddec69d564fe2b125e083437f76331f1283), [`6833c69`](https://github.com/mastra-ai/mastra/commit/6833c69607418d257750bbcdd84638993d343539), [`47b1c16`](https://github.com/mastra-ai/mastra/commit/47b1c16a01c7ffb6765fe1e499b49092f8b7eba3), [`3a76a80`](https://github.com/mastra-ai/mastra/commit/3a76a80284cb71a0faa975abb3d4b2a9631e60cd), [`8538a0d`](https://github.com/mastra-ai/mastra/commit/8538a0d232619bf55dad7ddc2a8b0ca77c679a87), [`9312dcd`](https://github.com/mastra-ai/mastra/commit/9312dcd1c6f5b321929e7d382e763d95fdc030f5)]:
|
|
105
|
+
- @mastra/core@1.0.0-beta.25
|
|
106
|
+
|
|
3
107
|
## 1.0.0-beta.11
|
|
4
108
|
|
|
5
109
|
### Patch Changes
|
package/dist/docs/README.md
CHANGED
package/dist/docs/SKILL.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -340,15 +340,49 @@ ${columns}
|
|
|
340
340
|
);
|
|
341
341
|
const pkExists = Array.isArray(pkResult.recordset) && pkResult.recordset.length > 0;
|
|
342
342
|
if (!pkExists) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
343
|
+
const duplicateInfo = await this.checkForDuplicateSpans();
|
|
344
|
+
if (duplicateInfo.hasDuplicates) {
|
|
345
|
+
const errorMessage = `
|
|
346
|
+
===========================================================================
|
|
347
|
+
MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}
|
|
348
|
+
===========================================================================
|
|
349
|
+
|
|
350
|
+
Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations.
|
|
351
|
+
|
|
352
|
+
The spans table requires a unique constraint on (traceId, spanId), but your
|
|
353
|
+
database contains duplicate entries that must be resolved first.
|
|
354
|
+
|
|
355
|
+
To fix this, run the manual migration command:
|
|
356
|
+
|
|
357
|
+
npx mastra migrate
|
|
358
|
+
|
|
359
|
+
This command will:
|
|
360
|
+
1. Remove duplicate spans (keeping the most complete/recent version)
|
|
361
|
+
2. Add the required unique constraint
|
|
362
|
+
|
|
363
|
+
Note: This migration may take some time for large tables.
|
|
364
|
+
===========================================================================
|
|
365
|
+
`;
|
|
366
|
+
throw new error.MastraError({
|
|
367
|
+
id: storage.createStorageErrorId("MSSQL", "MIGRATION_REQUIRED", "DUPLICATE_SPANS"),
|
|
368
|
+
domain: error.ErrorDomain.STORAGE,
|
|
369
|
+
category: error.ErrorCategory.USER,
|
|
370
|
+
text: errorMessage
|
|
371
|
+
});
|
|
372
|
+
} else {
|
|
373
|
+
try {
|
|
374
|
+
const addPkSql = `ALTER TABLE ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} ADD CONSTRAINT [${pkConstraintName}] PRIMARY KEY ([traceId], [spanId])`;
|
|
375
|
+
await this.pool.request().query(addPkSql);
|
|
376
|
+
} catch (pkError) {
|
|
377
|
+
this.logger?.warn?.(`Failed to add composite primary key to spans table:`, pkError);
|
|
378
|
+
}
|
|
348
379
|
}
|
|
349
380
|
}
|
|
350
381
|
}
|
|
351
382
|
} catch (error$1) {
|
|
383
|
+
if (error$1 instanceof error.MastraError) {
|
|
384
|
+
throw error$1;
|
|
385
|
+
}
|
|
352
386
|
throw new error.MastraError(
|
|
353
387
|
{
|
|
354
388
|
id: storage.createStorageErrorId("MSSQL", "CREATE_TABLE", "FAILED"),
|
|
@@ -390,6 +424,154 @@ ${columns}
|
|
|
390
424
|
this.logger?.warn?.(`Failed to migrate spans table ${fullTableName}:`, error);
|
|
391
425
|
}
|
|
392
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* Deduplicates spans with the same (traceId, spanId) combination.
|
|
429
|
+
* This is needed for databases that existed before the unique constraint was added.
|
|
430
|
+
*
|
|
431
|
+
* Priority for keeping spans:
|
|
432
|
+
* 1. Completed spans (endedAt IS NOT NULL) over incomplete spans
|
|
433
|
+
* 2. Most recent updatedAt
|
|
434
|
+
* 3. Most recent createdAt (as tiebreaker)
|
|
435
|
+
*
|
|
436
|
+
* Note: This prioritizes migration completion over perfect data preservation.
|
|
437
|
+
* Old trace data may be lost, which is acceptable for this use case.
|
|
438
|
+
*/
|
|
439
|
+
async deduplicateSpans() {
|
|
440
|
+
const fullTableName = getTableName({ indexName: storage.TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
|
|
441
|
+
try {
|
|
442
|
+
const duplicateCheck = await this.pool.request().query(`
|
|
443
|
+
SELECT TOP 1 1 as has_duplicates
|
|
444
|
+
FROM ${fullTableName}
|
|
445
|
+
GROUP BY [traceId], [spanId]
|
|
446
|
+
HAVING COUNT(*) > 1
|
|
447
|
+
`);
|
|
448
|
+
if (!duplicateCheck.recordset || duplicateCheck.recordset.length === 0) {
|
|
449
|
+
this.logger?.debug?.(`No duplicate spans found in ${fullTableName}`);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
this.logger?.info?.(`Duplicate spans detected in ${fullTableName}, starting deduplication...`);
|
|
453
|
+
const result = await this.pool.request().query(`
|
|
454
|
+
WITH RankedSpans AS (
|
|
455
|
+
SELECT *, ROW_NUMBER() OVER (
|
|
456
|
+
PARTITION BY [traceId], [spanId]
|
|
457
|
+
ORDER BY
|
|
458
|
+
CASE WHEN [endedAt] IS NOT NULL THEN 0 ELSE 1 END,
|
|
459
|
+
[updatedAt] DESC,
|
|
460
|
+
[createdAt] DESC
|
|
461
|
+
) as rn
|
|
462
|
+
FROM ${fullTableName}
|
|
463
|
+
)
|
|
464
|
+
DELETE FROM RankedSpans WHERE rn > 1
|
|
465
|
+
`);
|
|
466
|
+
this.logger?.info?.(
|
|
467
|
+
`Deduplication complete: removed ${result.rowsAffected?.[0] ?? 0} duplicate spans from ${fullTableName}`
|
|
468
|
+
);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
this.logger?.warn?.("Failed to deduplicate spans:", error);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Checks for duplicate (traceId, spanId) combinations in the spans table.
|
|
475
|
+
* Returns information about duplicates for logging/CLI purposes.
|
|
476
|
+
*/
|
|
477
|
+
async checkForDuplicateSpans() {
|
|
478
|
+
const fullTableName = getTableName({ indexName: storage.TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
|
|
479
|
+
try {
|
|
480
|
+
const result = await this.pool.request().query(`
|
|
481
|
+
SELECT COUNT(*) as duplicate_count
|
|
482
|
+
FROM (
|
|
483
|
+
SELECT [traceId], [spanId]
|
|
484
|
+
FROM ${fullTableName}
|
|
485
|
+
GROUP BY [traceId], [spanId]
|
|
486
|
+
HAVING COUNT(*) > 1
|
|
487
|
+
) duplicates
|
|
488
|
+
`);
|
|
489
|
+
const duplicateCount = result.recordset?.[0]?.duplicate_count ?? 0;
|
|
490
|
+
return {
|
|
491
|
+
hasDuplicates: duplicateCount > 0,
|
|
492
|
+
duplicateCount,
|
|
493
|
+
tableName: fullTableName
|
|
494
|
+
};
|
|
495
|
+
} catch (error) {
|
|
496
|
+
this.logger?.debug?.(`Could not check for duplicates: ${error}`);
|
|
497
|
+
return { hasDuplicates: false, duplicateCount: 0, tableName: fullTableName };
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Checks if the PRIMARY KEY constraint on (traceId, spanId) already exists on the spans table.
|
|
502
|
+
*/
|
|
503
|
+
async spansPrimaryKeyExists() {
|
|
504
|
+
const schemaPrefix = this.schemaName ? `${utils.parseSqlIdentifier(this.schemaName, "schema name")}_` : "";
|
|
505
|
+
const pkConstraintName = `${schemaPrefix}mastra_ai_spans_traceid_spanid_pk`;
|
|
506
|
+
const checkPkRequest = this.pool.request();
|
|
507
|
+
checkPkRequest.input("constraintName", pkConstraintName);
|
|
508
|
+
const pkResult = await checkPkRequest.query(
|
|
509
|
+
`SELECT 1 AS found FROM sys.key_constraints WHERE name = @constraintName`
|
|
510
|
+
);
|
|
511
|
+
return Array.isArray(pkResult.recordset) && pkResult.recordset.length > 0;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Manually run the spans migration to deduplicate and add the unique constraint.
|
|
515
|
+
* This is intended to be called from the CLI when duplicates are detected.
|
|
516
|
+
*
|
|
517
|
+
* @returns Migration result with status and details
|
|
518
|
+
*/
|
|
519
|
+
async migrateSpans() {
|
|
520
|
+
const fullTableName = getTableName({ indexName: storage.TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
|
|
521
|
+
const pkExists = await this.spansPrimaryKeyExists();
|
|
522
|
+
if (pkExists) {
|
|
523
|
+
return {
|
|
524
|
+
success: true,
|
|
525
|
+
alreadyMigrated: true,
|
|
526
|
+
duplicatesRemoved: 0,
|
|
527
|
+
message: `Migration already complete. PRIMARY KEY constraint exists on ${fullTableName}.`
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
const duplicateInfo = await this.checkForDuplicateSpans();
|
|
531
|
+
if (duplicateInfo.hasDuplicates) {
|
|
532
|
+
this.logger?.info?.(
|
|
533
|
+
`Found ${duplicateInfo.duplicateCount} duplicate (traceId, spanId) combinations. Starting deduplication...`
|
|
534
|
+
);
|
|
535
|
+
await this.deduplicateSpans();
|
|
536
|
+
} else {
|
|
537
|
+
this.logger?.info?.(`No duplicate spans found.`);
|
|
538
|
+
}
|
|
539
|
+
const schemaPrefix = this.schemaName ? `${utils.parseSqlIdentifier(this.schemaName, "schema name")}_` : "";
|
|
540
|
+
const pkConstraintName = `${schemaPrefix}mastra_ai_spans_traceid_spanid_pk`;
|
|
541
|
+
const addPkSql = `ALTER TABLE ${fullTableName} ADD CONSTRAINT [${pkConstraintName}] PRIMARY KEY ([traceId], [spanId])`;
|
|
542
|
+
await this.pool.request().query(addPkSql);
|
|
543
|
+
return {
|
|
544
|
+
success: true,
|
|
545
|
+
alreadyMigrated: false,
|
|
546
|
+
duplicatesRemoved: duplicateInfo.duplicateCount,
|
|
547
|
+
message: duplicateInfo.hasDuplicates ? `Migration complete. Removed duplicates and added PRIMARY KEY constraint to ${fullTableName}.` : `Migration complete. Added PRIMARY KEY constraint to ${fullTableName}.`
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Check migration status for the spans table.
|
|
552
|
+
* Returns information about whether migration is needed.
|
|
553
|
+
*/
|
|
554
|
+
async checkSpansMigrationStatus() {
|
|
555
|
+
const fullTableName = getTableName({ indexName: storage.TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
|
|
556
|
+
const pkExists = await this.spansPrimaryKeyExists();
|
|
557
|
+
if (pkExists) {
|
|
558
|
+
return {
|
|
559
|
+
needsMigration: false,
|
|
560
|
+
hasDuplicates: false,
|
|
561
|
+
duplicateCount: 0,
|
|
562
|
+
constraintExists: true,
|
|
563
|
+
tableName: fullTableName
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const duplicateInfo = await this.checkForDuplicateSpans();
|
|
567
|
+
return {
|
|
568
|
+
needsMigration: true,
|
|
569
|
+
hasDuplicates: duplicateInfo.hasDuplicates,
|
|
570
|
+
duplicateCount: duplicateInfo.duplicateCount,
|
|
571
|
+
constraintExists: false,
|
|
572
|
+
tableName: fullTableName
|
|
573
|
+
};
|
|
574
|
+
}
|
|
393
575
|
/**
|
|
394
576
|
* Alters table schema to add columns if they don't exist
|
|
395
577
|
* @param tableName Name of the table
|
|
@@ -1265,28 +1447,76 @@ var MemoryMSSQL = class _MemoryMSSQL extends storage.MemoryStorage {
|
|
|
1265
1447
|
);
|
|
1266
1448
|
}
|
|
1267
1449
|
}
|
|
1268
|
-
async
|
|
1269
|
-
const {
|
|
1270
|
-
|
|
1450
|
+
async listThreads(args) {
|
|
1451
|
+
const { page = 0, perPage: perPageInput, orderBy, filter } = args;
|
|
1452
|
+
try {
|
|
1453
|
+
this.validatePaginationInput(page, perPageInput ?? 100);
|
|
1454
|
+
} catch (error$1) {
|
|
1271
1455
|
throw new error.MastraError({
|
|
1272
|
-
id: storage.createStorageErrorId("MSSQL", "
|
|
1456
|
+
id: storage.createStorageErrorId("MSSQL", "LIST_THREADS", "INVALID_PAGE"),
|
|
1273
1457
|
domain: error.ErrorDomain.STORAGE,
|
|
1274
1458
|
category: error.ErrorCategory.USER,
|
|
1275
|
-
text:
|
|
1276
|
-
details: {
|
|
1277
|
-
resourceId,
|
|
1278
|
-
page
|
|
1279
|
-
}
|
|
1459
|
+
text: error$1 instanceof Error ? error$1.message : "Invalid pagination parameters",
|
|
1460
|
+
details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } }
|
|
1280
1461
|
});
|
|
1281
1462
|
}
|
|
1282
1463
|
const perPage = storage.normalizePerPage(perPageInput, 100);
|
|
1464
|
+
try {
|
|
1465
|
+
this.validateMetadataKeys(filter?.metadata);
|
|
1466
|
+
} catch (error$1) {
|
|
1467
|
+
throw new error.MastraError({
|
|
1468
|
+
id: storage.createStorageErrorId("MSSQL", "LIST_THREADS", "INVALID_METADATA_KEY"),
|
|
1469
|
+
domain: error.ErrorDomain.STORAGE,
|
|
1470
|
+
category: error.ErrorCategory.USER,
|
|
1471
|
+
text: error$1 instanceof Error ? error$1.message : "Invalid metadata key",
|
|
1472
|
+
details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" }
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1283
1475
|
const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
|
|
1284
1476
|
const { field, direction } = this.parseOrderBy(orderBy);
|
|
1285
1477
|
try {
|
|
1286
|
-
const
|
|
1478
|
+
const tableName = getTableName2({ indexName: storage.TABLE_THREADS, schemaName: getSchemaName2(this.schema) });
|
|
1479
|
+
const whereClauses = [];
|
|
1480
|
+
const params = {};
|
|
1481
|
+
if (filter?.resourceId) {
|
|
1482
|
+
whereClauses.push("[resourceId] = @resourceId");
|
|
1483
|
+
params.resourceId = filter.resourceId;
|
|
1484
|
+
}
|
|
1485
|
+
if (filter?.metadata && Object.keys(filter.metadata).length > 0) {
|
|
1486
|
+
let metadataIndex = 0;
|
|
1487
|
+
for (const [key, value] of Object.entries(filter.metadata)) {
|
|
1488
|
+
if (value !== null && typeof value === "object") {
|
|
1489
|
+
throw new error.MastraError({
|
|
1490
|
+
id: storage.createStorageErrorId("MSSQL", "LIST_THREADS", "INVALID_METADATA_VALUE"),
|
|
1491
|
+
domain: error.ErrorDomain.STORAGE,
|
|
1492
|
+
category: error.ErrorCategory.USER,
|
|
1493
|
+
text: `Metadata filter value for key "${key}" must be a scalar type (string, number, boolean, or null), got ${Array.isArray(value) ? "array" : "object"}`,
|
|
1494
|
+
details: { key, valueType: Array.isArray(value) ? "array" : "object" }
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
if (value === null) {
|
|
1498
|
+
whereClauses.push(`JSON_VALUE(metadata, '$.${key}') IS NULL`);
|
|
1499
|
+
} else {
|
|
1500
|
+
const paramName = `metadata${metadataIndex}`;
|
|
1501
|
+
whereClauses.push(`JSON_VALUE(metadata, '$.${key}') = @${paramName}`);
|
|
1502
|
+
if (typeof value === "string") {
|
|
1503
|
+
params[paramName] = value;
|
|
1504
|
+
} else if (typeof value === "boolean") {
|
|
1505
|
+
params[paramName] = value ? "true" : "false";
|
|
1506
|
+
} else {
|
|
1507
|
+
params[paramName] = String(value);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
metadataIndex++;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
1514
|
+
const baseQuery = `FROM ${tableName} ${whereClause}`;
|
|
1287
1515
|
const countQuery = `SELECT COUNT(*) as count ${baseQuery}`;
|
|
1288
1516
|
const countRequest = this.pool.request();
|
|
1289
|
-
|
|
1517
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1518
|
+
countRequest.input(key, value);
|
|
1519
|
+
}
|
|
1290
1520
|
const countResult = await countRequest.query(countQuery);
|
|
1291
1521
|
const total = parseInt(countResult.recordset[0]?.count ?? "0", 10);
|
|
1292
1522
|
if (total === 0) {
|
|
@@ -1303,7 +1533,9 @@ var MemoryMSSQL = class _MemoryMSSQL extends storage.MemoryStorage {
|
|
|
1303
1533
|
const limitValue = perPageInput === false ? total : perPage;
|
|
1304
1534
|
const dataQuery = `SELECT id, [resourceId], title, metadata, [createdAt], [updatedAt] ${baseQuery} ORDER BY ${orderByField} ${dir} OFFSET @offset ROWS FETCH NEXT @perPage ROWS ONLY`;
|
|
1305
1535
|
const dataRequest = this.pool.request();
|
|
1306
|
-
|
|
1536
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1537
|
+
dataRequest.input(key, value);
|
|
1538
|
+
}
|
|
1307
1539
|
dataRequest.input("offset", offset);
|
|
1308
1540
|
if (limitValue > 2147483647) {
|
|
1309
1541
|
dataRequest.input("perPage", sql__default.default.BigInt, limitValue);
|
|
@@ -1326,13 +1558,17 @@ var MemoryMSSQL = class _MemoryMSSQL extends storage.MemoryStorage {
|
|
|
1326
1558
|
hasMore: perPageInput === false ? false : offset + perPage < total
|
|
1327
1559
|
};
|
|
1328
1560
|
} catch (error$1) {
|
|
1561
|
+
if (error$1 instanceof error.MastraError && error$1.category === error.ErrorCategory.USER) {
|
|
1562
|
+
throw error$1;
|
|
1563
|
+
}
|
|
1329
1564
|
const mastraError = new error.MastraError(
|
|
1330
1565
|
{
|
|
1331
|
-
id: storage.createStorageErrorId("MSSQL", "
|
|
1566
|
+
id: storage.createStorageErrorId("MSSQL", "LIST_THREADS", "FAILED"),
|
|
1332
1567
|
domain: error.ErrorDomain.STORAGE,
|
|
1333
1568
|
category: error.ErrorCategory.THIRD_PARTY,
|
|
1334
1569
|
details: {
|
|
1335
|
-
resourceId,
|
|
1570
|
+
...filter?.resourceId && { resourceId: filter.resourceId },
|
|
1571
|
+
hasMetadataFilter: !!filter?.metadata,
|
|
1336
1572
|
page
|
|
1337
1573
|
}
|
|
1338
1574
|
},
|
|
@@ -2214,6 +2450,22 @@ var ObservabilityMSSQL = class _ObservabilityMSSQL extends storage.Observability
|
|
|
2214
2450
|
async dangerouslyClearAll() {
|
|
2215
2451
|
await this.db.clearTable({ tableName: storage.TABLE_SPANS });
|
|
2216
2452
|
}
|
|
2453
|
+
/**
|
|
2454
|
+
* Manually run the spans migration to deduplicate and add the unique constraint.
|
|
2455
|
+
* This is intended to be called from the CLI when duplicates are detected.
|
|
2456
|
+
*
|
|
2457
|
+
* @returns Migration result with status and details
|
|
2458
|
+
*/
|
|
2459
|
+
async migrateSpans() {
|
|
2460
|
+
return this.db.migrateSpans();
|
|
2461
|
+
}
|
|
2462
|
+
/**
|
|
2463
|
+
* Check migration status for the spans table.
|
|
2464
|
+
* Returns information about whether migration is needed.
|
|
2465
|
+
*/
|
|
2466
|
+
async checkSpansMigrationStatus() {
|
|
2467
|
+
return this.db.checkSpansMigrationStatus();
|
|
2468
|
+
}
|
|
2217
2469
|
get tracingStrategy() {
|
|
2218
2470
|
return {
|
|
2219
2471
|
preferred: "batch-with-updates",
|
|
@@ -3613,7 +3865,7 @@ var WorkflowsMSSQL = class _WorkflowsMSSQL extends storage.WorkflowsStorage {
|
|
|
3613
3865
|
var isPoolConfig = (config) => {
|
|
3614
3866
|
return "pool" in config;
|
|
3615
3867
|
};
|
|
3616
|
-
var MSSQLStore = class extends storage.
|
|
3868
|
+
var MSSQLStore = class extends storage.MastraCompositeStore {
|
|
3617
3869
|
pool;
|
|
3618
3870
|
schema;
|
|
3619
3871
|
isConnected = null;
|
|
@@ -3684,6 +3936,9 @@ var MSSQLStore = class extends storage.MastraStorage {
|
|
|
3684
3936
|
await super.init();
|
|
3685
3937
|
} catch (error$1) {
|
|
3686
3938
|
this.isConnected = null;
|
|
3939
|
+
if (error$1 instanceof error.MastraError) {
|
|
3940
|
+
throw error$1;
|
|
3941
|
+
}
|
|
3687
3942
|
throw new error.MastraError(
|
|
3688
3943
|
{
|
|
3689
3944
|
id: storage.createStorageErrorId("MSSQL", "INIT", "FAILED"),
|