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