@mastra/dsql 0.0.0-ag-example-20260516005230

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE.md +30 -0
  3. package/README.md +357 -0
  4. package/dist/index.cjs +4478 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +4469 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/shared/batch.d.ts +55 -0
  11. package/dist/shared/batch.d.ts.map +1 -0
  12. package/dist/shared/config.d.ts +129 -0
  13. package/dist/shared/config.d.ts.map +1 -0
  14. package/dist/shared/retry.d.ts +207 -0
  15. package/dist/shared/retry.d.ts.map +1 -0
  16. package/dist/storage/client.d.ts +91 -0
  17. package/dist/storage/client.d.ts.map +1 -0
  18. package/dist/storage/db/index.d.ts +179 -0
  19. package/dist/storage/db/index.d.ts.map +1 -0
  20. package/dist/storage/domains/agents/index.d.ts +47 -0
  21. package/dist/storage/domains/agents/index.d.ts.map +1 -0
  22. package/dist/storage/domains/memory/index.d.ts +80 -0
  23. package/dist/storage/domains/memory/index.d.ts.map +1 -0
  24. package/dist/storage/domains/observability/index.d.ts +37 -0
  25. package/dist/storage/domains/observability/index.d.ts.map +1 -0
  26. package/dist/storage/domains/scores/index.d.ts +53 -0
  27. package/dist/storage/domains/scores/index.d.ts.map +1 -0
  28. package/dist/storage/domains/utils.d.ts +14 -0
  29. package/dist/storage/domains/utils.d.ts.map +1 -0
  30. package/dist/storage/domains/workflows/index.d.ts +61 -0
  31. package/dist/storage/domains/workflows/index.d.ts.map +1 -0
  32. package/dist/storage/index.d.ts +46 -0
  33. package/dist/storage/index.d.ts.map +1 -0
  34. package/dist/storage/performance-indexes/performance-test.d.ts +47 -0
  35. package/dist/storage/performance-indexes/performance-test.d.ts.map +1 -0
  36. package/dist/storage/test-utils.d.ts +14 -0
  37. package/dist/storage/test-utils.d.ts.map +1 -0
  38. package/package.json +69 -0
package/dist/index.js ADDED
@@ -0,0 +1,4469 @@
1
+ import { AuroraDSQLClient } from '@aws/aurora-dsql-node-postgres-connector';
2
+ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
3
+ import { AgentsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_SCHEMAS, createStorageErrorId, normalizePerPage, calculatePagination, MemoryStorage, TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES, ObservabilityStorage, TABLE_SPANS, listTracesArgsSchema, toTraceSpans, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MastraStorage, TraceStatus, getSqlType, getDefaultValue, transformScoreRow as transformScoreRow$1 } from '@mastra/core/storage';
4
+ import { Pool } from 'pg';
5
+ import { MastraBase } from '@mastra/core/base';
6
+ import { parseSqlIdentifier } from '@mastra/core/utils';
7
+ import { MessageList } from '@mastra/core/agent';
8
+ import { saveScorePayloadSchema } from '@mastra/core/evals';
9
+
10
+ // src/storage/index.ts
11
+
12
+ // src/shared/config.ts
13
+ var DSQL_POOL_DEFAULTS = {
14
+ /** Maximum connections in the pool */
15
+ max: 10,
16
+ /** Minimum connections in the pool */
17
+ min: 0,
18
+ /** Close idle connections after 10 minutes */
19
+ idleTimeoutMillis: 6e5,
20
+ /** Force connection rotation before DSQL's 60-minute limit */
21
+ maxLifetimeSeconds: 3300,
22
+ /** Connection acquisition timeout */
23
+ connectionTimeoutMillis: 5e3,
24
+ /** Allow process to exit when idle */
25
+ allowExitOnIdle: true
26
+ };
27
+ var isPoolConfig = (cfg) => {
28
+ return "pool" in cfg;
29
+ };
30
+ var isHostConfig = (cfg) => {
31
+ return "host" in cfg;
32
+ };
33
+ var validateConfig = (config) => {
34
+ if (!config.id || typeof config.id !== "string" || config.id.trim() === "") {
35
+ throw new Error("DSQLStore: id must be provided and cannot be empty.");
36
+ }
37
+ if (isPoolConfig(config)) {
38
+ if (!config.pool) {
39
+ throw new Error("DSQLStore: pool must be provided when using pool config.");
40
+ }
41
+ return;
42
+ }
43
+ if (!isHostConfig(config)) {
44
+ throw new Error("DSQLStore: host must be provided and cannot be empty.");
45
+ }
46
+ if (!config.host || config.host.trim() === "") {
47
+ throw new Error("DSQLStore: host must be provided and cannot be empty.");
48
+ }
49
+ if (config.maxLifetimeSeconds !== void 0 && config.maxLifetimeSeconds >= 3600) {
50
+ throw new Error(
51
+ "DSQLStore: maxLifetimeSeconds must be less than 3600 (60 minutes) due to Aurora DSQL connection duration limit."
52
+ );
53
+ }
54
+ };
55
+ var extractRegionFromHost = (host) => {
56
+ const match = host.match(/\.dsql\.([a-z0-9-]+)\.on\.aws$/);
57
+ return match?.[1];
58
+ };
59
+ var getEffectiveRegion = (config) => {
60
+ if (config.region) {
61
+ return config.region;
62
+ }
63
+ const extractedRegion = extractRegionFromHost(config.host);
64
+ if (extractedRegion) {
65
+ return extractedRegion;
66
+ }
67
+ throw new Error(
68
+ "DSQLStore: region could not be determined. Provide region in config or use a standard DSQL endpoint."
69
+ );
70
+ };
71
+
72
+ // src/storage/client.ts
73
+ function truncateQuery(query, maxLength = 100) {
74
+ const normalized = query.replace(/\s+/g, " ").trim();
75
+ if (normalized.length <= maxLength) {
76
+ return normalized;
77
+ }
78
+ return normalized.slice(0, maxLength) + "...";
79
+ }
80
+ var PoolAdapter = class {
81
+ constructor($pool) {
82
+ this.$pool = $pool;
83
+ }
84
+ $pool;
85
+ connect() {
86
+ return this.$pool.connect();
87
+ }
88
+ async none(query, values) {
89
+ await this.$pool.query(query, values);
90
+ return null;
91
+ }
92
+ async one(query, values) {
93
+ const result = await this.$pool.query(query, values);
94
+ if (result.rows.length === 0) {
95
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
96
+ }
97
+ if (result.rows.length > 1) {
98
+ throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
99
+ }
100
+ return result.rows[0];
101
+ }
102
+ async oneOrNone(query, values) {
103
+ const result = await this.$pool.query(query, values);
104
+ if (result.rows.length === 0) {
105
+ return null;
106
+ }
107
+ if (result.rows.length > 1) {
108
+ throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
109
+ }
110
+ return result.rows[0];
111
+ }
112
+ async any(query, values) {
113
+ const result = await this.$pool.query(query, values);
114
+ return result.rows;
115
+ }
116
+ async manyOrNone(query, values) {
117
+ return this.any(query, values);
118
+ }
119
+ async many(query, values) {
120
+ const result = await this.$pool.query(query, values);
121
+ if (result.rows.length === 0) {
122
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
123
+ }
124
+ return result.rows;
125
+ }
126
+ async query(query, values) {
127
+ return this.$pool.query(query, values);
128
+ }
129
+ async tx(callback) {
130
+ const client = await this.$pool.connect();
131
+ try {
132
+ await client.query("BEGIN");
133
+ const txClient = new TransactionClient(client);
134
+ const result = await callback(txClient);
135
+ await client.query("COMMIT");
136
+ return result;
137
+ } catch (error) {
138
+ try {
139
+ await client.query("ROLLBACK");
140
+ } catch (rollbackError) {
141
+ console.error("Transaction rollback failed:", rollbackError);
142
+ }
143
+ throw error;
144
+ } finally {
145
+ client.release();
146
+ }
147
+ }
148
+ };
149
+ var TransactionClient = class {
150
+ constructor(client) {
151
+ this.client = client;
152
+ }
153
+ client;
154
+ async none(query, values) {
155
+ await this.client.query(query, values);
156
+ return null;
157
+ }
158
+ async one(query, values) {
159
+ const result = await this.client.query(query, values);
160
+ if (result.rows.length === 0) {
161
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
162
+ }
163
+ if (result.rows.length > 1) {
164
+ throw new Error(`Multiple rows returned when one was expected: ${truncateQuery(query)}`);
165
+ }
166
+ return result.rows[0];
167
+ }
168
+ async oneOrNone(query, values) {
169
+ const result = await this.client.query(query, values);
170
+ if (result.rows.length === 0) {
171
+ return null;
172
+ }
173
+ if (result.rows.length > 1) {
174
+ throw new Error(`Multiple rows returned when one or none was expected: ${truncateQuery(query)}`);
175
+ }
176
+ return result.rows[0];
177
+ }
178
+ async any(query, values) {
179
+ const result = await this.client.query(query, values);
180
+ return result.rows;
181
+ }
182
+ async manyOrNone(query, values) {
183
+ return this.any(query, values);
184
+ }
185
+ async many(query, values) {
186
+ const result = await this.client.query(query, values);
187
+ if (result.rows.length === 0) {
188
+ throw new Error(`No data returned from query: ${truncateQuery(query)}`);
189
+ }
190
+ return result.rows;
191
+ }
192
+ async query(query, values) {
193
+ return this.client.query(query, values);
194
+ }
195
+ async batch(promises) {
196
+ return Promise.all(promises);
197
+ }
198
+ };
199
+
200
+ // src/shared/retry.ts
201
+ var RETRIABLE_ERROR_CODES = {
202
+ /** Serialization failure - OCC conflict in Aurora DSQL (default retriable) */
203
+ SERIALIZATION_FAILURE: "40001"};
204
+ var DEFAULT_RETRIABLE_SQLSTATES = /* @__PURE__ */ new Set([RETRIABLE_ERROR_CODES.SERIALIZATION_FAILURE]);
205
+ var DEFAULT_RETRY_OPTIONS = {
206
+ maxAttempts: 5,
207
+ initialDelayMs: 100,
208
+ maxDelayMs: 2e3,
209
+ backoffMultiplier: 2,
210
+ jitter: true
211
+ };
212
+ function isPostgresError(error) {
213
+ return error instanceof Error && "code" in error && typeof error.code === "string";
214
+ }
215
+ function getErrorCode(error) {
216
+ if (isPostgresError(error)) {
217
+ return error.code;
218
+ }
219
+ return void 0;
220
+ }
221
+ var SQLSTATE_PATTERN = /^[0-9A-Z]{5}$/;
222
+ function getPostgresSqlStateCode(error) {
223
+ const raw = getErrorCode(error);
224
+ if (!raw) return void 0;
225
+ const code = raw.toUpperCase();
226
+ if (SQLSTATE_PATTERN.test(code)) {
227
+ return code;
228
+ }
229
+ return void 0;
230
+ }
231
+ function isRetriableError(error) {
232
+ const sqlstate = getPostgresSqlStateCode(error);
233
+ if (!sqlstate) {
234
+ return false;
235
+ }
236
+ return DEFAULT_RETRIABLE_SQLSTATES.has(sqlstate);
237
+ }
238
+ function calculateRetryDelay(attempt, options = {}) {
239
+ const {
240
+ initialDelayMs = DEFAULT_RETRY_OPTIONS.initialDelayMs,
241
+ maxDelayMs = DEFAULT_RETRY_OPTIONS.maxDelayMs,
242
+ backoffMultiplier = DEFAULT_RETRY_OPTIONS.backoffMultiplier,
243
+ jitter = DEFAULT_RETRY_OPTIONS.jitter
244
+ } = options;
245
+ const baseDelay = initialDelayMs * Math.pow(backoffMultiplier, attempt - 1);
246
+ const cappedDelay = Math.min(baseDelay, maxDelayMs);
247
+ if (jitter) {
248
+ return Math.floor(Math.random() * (cappedDelay + 1));
249
+ }
250
+ return cappedDelay;
251
+ }
252
+ function sleep(ms) {
253
+ return new Promise((resolve) => setTimeout(resolve, ms));
254
+ }
255
+ function validateRetryOptions(options) {
256
+ const { maxAttempts, initialDelayMs, maxDelayMs, backoffMultiplier } = options;
257
+ if (maxAttempts < 1) {
258
+ throw new Error(`Invalid retry option: maxAttempts must be >= 1, got ${maxAttempts}`);
259
+ }
260
+ if (initialDelayMs < 0) {
261
+ throw new Error(`Invalid retry option: initialDelayMs must be >= 0, got ${initialDelayMs}`);
262
+ }
263
+ if (maxDelayMs <= 0) {
264
+ throw new Error(`Invalid retry option: maxDelayMs must be > 0, got ${maxDelayMs}`);
265
+ }
266
+ if (backoffMultiplier < 1) {
267
+ throw new Error(`Invalid retry option: backoffMultiplier must be >= 1, got ${backoffMultiplier}`);
268
+ }
269
+ if (maxDelayMs < initialDelayMs) {
270
+ throw new Error(`Invalid retry option: maxDelayMs (${maxDelayMs}) must be >= initialDelayMs (${initialDelayMs})`);
271
+ }
272
+ }
273
+ async function withRetry(fn, options = {}) {
274
+ const {
275
+ maxAttempts = DEFAULT_RETRY_OPTIONS.maxAttempts,
276
+ initialDelayMs = DEFAULT_RETRY_OPTIONS.initialDelayMs,
277
+ maxDelayMs = DEFAULT_RETRY_OPTIONS.maxDelayMs,
278
+ backoffMultiplier = DEFAULT_RETRY_OPTIONS.backoffMultiplier,
279
+ jitter = DEFAULT_RETRY_OPTIONS.jitter,
280
+ onRetry,
281
+ isRetriable = isRetriableError
282
+ } = options;
283
+ validateRetryOptions({ maxAttempts, initialDelayMs, maxDelayMs, backoffMultiplier });
284
+ const startTime = Date.now();
285
+ let lastError;
286
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
287
+ try {
288
+ const result = await fn();
289
+ return {
290
+ result,
291
+ attempts: attempt,
292
+ totalTimeMs: Date.now() - startTime
293
+ };
294
+ } catch (error) {
295
+ lastError = error instanceof Error ? error : new Error(String(error));
296
+ if (attempt === maxAttempts || !isRetriable(error)) {
297
+ throw lastError;
298
+ }
299
+ const delay = calculateRetryDelay(attempt, {
300
+ initialDelayMs,
301
+ maxDelayMs,
302
+ backoffMultiplier,
303
+ jitter
304
+ });
305
+ if (onRetry) {
306
+ onRetry(lastError, attempt, delay);
307
+ }
308
+ await sleep(delay);
309
+ }
310
+ }
311
+ throw lastError;
312
+ }
313
+
314
+ // src/shared/batch.ts
315
+ var DEFAULT_MAX_ROWS_PER_BATCH = 3e3;
316
+ function splitIntoBatches(records, options = {}) {
317
+ const maxRows = options.maxRows ?? DEFAULT_MAX_ROWS_PER_BATCH;
318
+ if (records.length === 0) {
319
+ return {
320
+ batches: [],
321
+ totalRecords: 0,
322
+ batchCount: 0
323
+ };
324
+ }
325
+ if (maxRows <= 0) {
326
+ throw new Error(`maxRows must be a positive number, got: ${maxRows}`);
327
+ }
328
+ const batches = [];
329
+ for (let i = 0; i < records.length; i += maxRows) {
330
+ batches.push(records.slice(i, i + maxRows));
331
+ }
332
+ return {
333
+ batches,
334
+ totalRecords: records.length,
335
+ batchCount: batches.length
336
+ };
337
+ }
338
+
339
+ // src/storage/db/index.ts
340
+ function resolveDsqlConfig(config) {
341
+ if ("client" in config) {
342
+ return {
343
+ client: config.client,
344
+ schemaName: config.schemaName,
345
+ skipDefaultIndexes: config.skipDefaultIndexes,
346
+ indexes: config.indexes
347
+ };
348
+ }
349
+ if ("pool" in config) {
350
+ return {
351
+ client: new PoolAdapter(config.pool),
352
+ schemaName: config.schemaName,
353
+ skipDefaultIndexes: config.skipDefaultIndexes,
354
+ indexes: config.indexes
355
+ };
356
+ }
357
+ const pool = new Pool({
358
+ host: config.host,
359
+ database: config.database,
360
+ user: config.user,
361
+ Client: AuroraDSQLClient
362
+ });
363
+ return {
364
+ client: new PoolAdapter(pool),
365
+ schemaName: config.schemaName,
366
+ skipDefaultIndexes: config.skipDefaultIndexes,
367
+ indexes: config.indexes
368
+ };
369
+ }
370
+ function getSchemaName(schema) {
371
+ return schema ? `"${parseSqlIdentifier(schema, "schema name")}"` : '"public"';
372
+ }
373
+ function getTableName({ indexName, schemaName }) {
374
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
375
+ const quotedIndexName = `"${parsedIndexName}"`;
376
+ const quotedSchemaName = schemaName;
377
+ return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
378
+ }
379
+ var schemaSetupRegistry = /* @__PURE__ */ new Map();
380
+ var DsqlDB = class extends MastraBase {
381
+ client;
382
+ schemaName;
383
+ skipDefaultIndexes;
384
+ constructor(config) {
385
+ super({
386
+ component: "STORAGE",
387
+ name: "DSQL_DB_LAYER"
388
+ });
389
+ this.client = config.client;
390
+ this.schemaName = config.schemaName;
391
+ this.skipDefaultIndexes = config.skipDefaultIndexes;
392
+ }
393
+ async hasColumn(table, column) {
394
+ const schema = this.schemaName || "public";
395
+ const result = await this.client.oneOrNone(
396
+ `SELECT 1 FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND (column_name = $3 OR column_name = $4)`,
397
+ [schema, table, column, column.toLowerCase()]
398
+ );
399
+ return !!result;
400
+ }
401
+ /**
402
+ * Prepares values for insertion, handling TEXT columns storing JSON by stringifying them.
403
+ * Aurora DSQL does not support JSONB natively, so we store JSON as TEXT.
404
+ */
405
+ prepareValuesForInsert(record, tableName) {
406
+ return Object.entries(record).map(([key, value]) => {
407
+ const schema = TABLE_SCHEMAS[tableName];
408
+ const columnSchema = schema?.[key];
409
+ if (columnSchema?.type === "jsonb" && value !== null && value !== void 0 && typeof value === "object") {
410
+ return JSON.stringify(value);
411
+ }
412
+ return value;
413
+ });
414
+ }
415
+ /**
416
+ * Adds timestamp Z columns to a record if timestamp columns exist
417
+ */
418
+ addTimestampZColumns(record) {
419
+ if (record.createdAt) {
420
+ record.createdAtZ = record.createdAt;
421
+ }
422
+ if (record.created_at) {
423
+ record.created_atZ = record.created_at;
424
+ }
425
+ if (record.updatedAt) {
426
+ record.updatedAtZ = record.updatedAt;
427
+ }
428
+ }
429
+ /**
430
+ * Prepares a value for database operations, handling Date objects and JSON serialization.
431
+ * This is schema-aware and stringifies objects for TEXT columns storing JSON.
432
+ * Aurora DSQL: We use TEXT instead of JSONB and cast to ::jsonb when filtering.
433
+ */
434
+ prepareValue(value, columnName, tableName) {
435
+ if (value === null || value === void 0) {
436
+ return value;
437
+ }
438
+ if (value instanceof Date) {
439
+ return value.toISOString();
440
+ }
441
+ const schema = TABLE_SCHEMAS[tableName];
442
+ const columnSchema = schema?.[columnName];
443
+ if (columnSchema?.type === "jsonb") {
444
+ if (typeof value === "object") {
445
+ return JSON.stringify(value);
446
+ }
447
+ }
448
+ if (typeof value === "object") {
449
+ return JSON.stringify(value);
450
+ }
451
+ return value;
452
+ }
453
+ async setupSchema() {
454
+ if (!this.schemaName) {
455
+ return;
456
+ }
457
+ let registryEntry = schemaSetupRegistry.get(this.schemaName);
458
+ if (registryEntry?.complete) {
459
+ return;
460
+ }
461
+ const quotedSchemaName = getSchemaName(this.schemaName);
462
+ if (!registryEntry?.promise) {
463
+ const schemaNameCapture = this.schemaName;
464
+ const setupPromise = (async () => {
465
+ try {
466
+ const schemaExists = await this.client.oneOrNone(
467
+ `
468
+ SELECT EXISTS (
469
+ SELECT 1 FROM information_schema.schemata
470
+ WHERE schema_name = $1
471
+ )
472
+ `,
473
+ [schemaNameCapture]
474
+ );
475
+ if (!schemaExists?.exists) {
476
+ try {
477
+ await this.client.none(`CREATE SCHEMA IF NOT EXISTS ${quotedSchemaName}`);
478
+ this.logger.info(`Schema "${schemaNameCapture}" created successfully`);
479
+ } catch (error) {
480
+ this.logger.error(`Failed to create schema "${schemaNameCapture}"`, { error });
481
+ throw new Error(
482
+ `Unable to create schema "${schemaNameCapture}". This requires CREATE privilege on the database. Either create the schema manually or grant CREATE privilege to the user.`
483
+ );
484
+ }
485
+ }
486
+ const entry = schemaSetupRegistry.get(schemaNameCapture);
487
+ if (entry) {
488
+ entry.complete = true;
489
+ }
490
+ this.logger.debug(`Schema "${quotedSchemaName}" is ready for use`);
491
+ } catch (error) {
492
+ schemaSetupRegistry.delete(schemaNameCapture);
493
+ throw error;
494
+ }
495
+ })();
496
+ schemaSetupRegistry.set(this.schemaName, { promise: setupPromise, complete: false });
497
+ registryEntry = schemaSetupRegistry.get(this.schemaName);
498
+ }
499
+ await registryEntry.promise;
500
+ }
501
+ /**
502
+ * Override getSqlType to map JSONB to TEXT for Aurora DSQL compatibility.
503
+ * Aurora DSQL does not fully support native JSONB, so we store JSON as TEXT
504
+ * and cast to ::jsonb only when filtering/querying.
505
+ */
506
+ getSqlType(type) {
507
+ switch (type) {
508
+ case "jsonb":
509
+ return "TEXT";
510
+ case "uuid":
511
+ return "UUID";
512
+ case "boolean":
513
+ return "BOOLEAN";
514
+ default:
515
+ return getSqlType(type);
516
+ }
517
+ }
518
+ getDefaultValue(type) {
519
+ switch (type) {
520
+ case "timestamp":
521
+ return "DEFAULT NOW()";
522
+ case "jsonb":
523
+ return "DEFAULT '{}'";
524
+ default:
525
+ return getDefaultValue(type);
526
+ }
527
+ }
528
+ async insert({ tableName, record }) {
529
+ this.addTimestampZColumns(record);
530
+ const schemaName = getSchemaName(this.schemaName);
531
+ const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
532
+ const values = this.prepareValuesForInsert(record, tableName);
533
+ const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
534
+ await withRetry(
535
+ async () => {
536
+ await this.client.none(
537
+ `INSERT INTO ${getTableName({ indexName: tableName, schemaName })} (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`,
538
+ values
539
+ );
540
+ },
541
+ {
542
+ onRetry: (error, attempt, delay) => {
543
+ this.logger?.warn?.(`insert retry ${attempt} for table ${tableName} after ${delay}ms: ${error.message}`);
544
+ }
545
+ }
546
+ ).catch((error) => {
547
+ throw new MastraError(
548
+ {
549
+ id: createStorageErrorId("DSQL", "INSERT", "FAILED"),
550
+ domain: ErrorDomain.STORAGE,
551
+ category: ErrorCategory.THIRD_PARTY,
552
+ details: {
553
+ tableName
554
+ }
555
+ },
556
+ error
557
+ );
558
+ });
559
+ }
560
+ async clearTable({ tableName }) {
561
+ try {
562
+ await withRetry(
563
+ async () => {
564
+ const schemaName = getSchemaName(this.schemaName);
565
+ const tableNameWithSchema = getTableName({ indexName: tableName, schemaName });
566
+ await this.client.none(`DELETE FROM ${tableNameWithSchema}`);
567
+ },
568
+ {
569
+ onRetry: (error, attempt, delay) => {
570
+ this.logger?.warn?.(`clearTable retry ${attempt} for ${tableName} after ${delay}ms: ${error.message}`);
571
+ }
572
+ }
573
+ );
574
+ } catch (error) {
575
+ throw new MastraError(
576
+ {
577
+ id: createStorageErrorId("DSQL", "CLEAR_TABLE", "FAILED"),
578
+ domain: ErrorDomain.STORAGE,
579
+ category: ErrorCategory.THIRD_PARTY,
580
+ details: {
581
+ tableName
582
+ }
583
+ },
584
+ error
585
+ );
586
+ }
587
+ }
588
+ async createTable({
589
+ tableName,
590
+ schema
591
+ }) {
592
+ await withRetry(
593
+ async () => {
594
+ const timeZColumnNames = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => name);
595
+ const timeZColumns = Object.entries(schema).filter(([_, def]) => def.type === "timestamp").map(([name]) => {
596
+ const parsedName = parseSqlIdentifier(name, "column name");
597
+ return `"${parsedName}Z" TIMESTAMPTZ DEFAULT NOW()`;
598
+ });
599
+ const columns = Object.entries(schema).map(([name, def]) => {
600
+ const parsedName = parseSqlIdentifier(name, "column name");
601
+ const constraints = [];
602
+ if (def.primaryKey) constraints.push("PRIMARY KEY");
603
+ if (!def.nullable) constraints.push("NOT NULL");
604
+ const sqlType = this.getSqlType(def.type);
605
+ return `"${parsedName}" ${sqlType} ${constraints.join(" ")}`;
606
+ });
607
+ if (this.schemaName) {
608
+ await this.setupSchema();
609
+ }
610
+ const finalColumns = [...columns, ...timeZColumns].join(",\n");
611
+ const constraintPrefix = this.schemaName ? `${this.schemaName}_` : "";
612
+ const schemaName = getSchemaName(this.schemaName);
613
+ const createTableSql = `
614
+ CREATE TABLE IF NOT EXISTS ${getTableName({ indexName: tableName, schemaName })} (
615
+ ${finalColumns}
616
+ );
617
+ `;
618
+ await this.client.none(createTableSql);
619
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
620
+ const indexName = `${constraintPrefix}mastra_workflow_snapshot_workflow_name_run_id_key`;
621
+ const fullTableName = getTableName({ indexName: tableName, schemaName });
622
+ try {
623
+ const indexExists = await this.client.oneOrNone(`SELECT 1 FROM pg_indexes WHERE indexname = $1`, [
624
+ indexName
625
+ ]);
626
+ if (!indexExists) {
627
+ const result = await this.client.oneOrNone(
628
+ `CREATE UNIQUE INDEX ASYNC "${indexName}" ON ${fullTableName} ("workflow_name", "run_id")`
629
+ );
630
+ if (result?.job_uuid) {
631
+ await this.waitForDSQLJob(result.job_uuid);
632
+ }
633
+ this.logger?.debug?.(`Created unique index ${indexName} on ${fullTableName}`);
634
+ }
635
+ } catch (error) {
636
+ this.logger?.warn?.(`Failed to create unique index ${indexName}:`, error);
637
+ }
638
+ }
639
+ await this.alterTable({
640
+ tableName,
641
+ schema,
642
+ ifNotExists: timeZColumnNames
643
+ });
644
+ if (tableName === TABLE_SPANS) {
645
+ await this.setupTimestampTriggers(tableName);
646
+ await this.migrateSpansTable();
647
+ }
648
+ },
649
+ {
650
+ onRetry: (error, attempt, delay) => {
651
+ this.logger?.warn?.(`createTable retry ${attempt} for ${tableName} after ${delay}ms: ${error.message}`);
652
+ }
653
+ }
654
+ ).catch((error) => {
655
+ throw new MastraError(
656
+ {
657
+ id: createStorageErrorId("DSQL", "CREATE_TABLE", "FAILED"),
658
+ domain: ErrorDomain.STORAGE,
659
+ category: ErrorCategory.THIRD_PARTY,
660
+ details: {
661
+ tableName
662
+ }
663
+ },
664
+ error
665
+ );
666
+ });
667
+ }
668
+ /**
669
+ * Set up timestamp triggers for a table to automatically manage createdAt/updatedAt
670
+ * Note: Aurora DSQL doesn't support triggers, PL/pgSQL, or CREATE FUNCTION.
671
+ * Timestamps are managed at the application level in insert/update operations.
672
+ * This method is kept as a no-op for API compatibility.
673
+ */
674
+ async setupTimestampTriggers(_tableName) {
675
+ return;
676
+ }
677
+ /**
678
+ * Migrates the spans table schema from OLD_SPAN_SCHEMA to current SPAN_SCHEMA.
679
+ * This adds new columns that don't exist in old schema.
680
+ */
681
+ async migrateSpansTable() {
682
+ const fullTableName = getTableName({ indexName: TABLE_SPANS, schemaName: getSchemaName(this.schemaName) });
683
+ const schema = TABLE_SCHEMAS[TABLE_SPANS];
684
+ try {
685
+ for (const [columnName, columnDef] of Object.entries(schema)) {
686
+ const columnExists = await this.hasColumn(TABLE_SPANS, columnName);
687
+ if (!columnExists) {
688
+ const parsedColumnName = parseSqlIdentifier(columnName, "column name");
689
+ const sqlType = this.getSqlType(columnDef.type);
690
+ const nullable = columnDef.nullable ? "" : "NOT NULL";
691
+ const defaultValue = !columnDef.nullable ? this.getDefaultValue(columnDef.type) : "";
692
+ const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType} ${nullable} ${defaultValue}`.trim();
693
+ await this.client.none(alterSql);
694
+ this.logger?.debug?.(`Added column '${columnName}' to ${fullTableName}`);
695
+ if (sqlType === "TIMESTAMP") {
696
+ const timestampZSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}Z" TIMESTAMPTZ DEFAULT NOW()`.trim();
697
+ await this.client.none(timestampZSql);
698
+ this.logger?.debug?.(`Added timezone column '${columnName}Z' to ${fullTableName}`);
699
+ }
700
+ }
701
+ }
702
+ for (const [columnName, columnDef] of Object.entries(schema)) {
703
+ if (columnDef.type === "timestamp") {
704
+ const tzColumnName = `${columnName}Z`;
705
+ const tzColumnExists = await this.hasColumn(TABLE_SPANS, tzColumnName);
706
+ if (!tzColumnExists) {
707
+ const parsedTzColumnName = parseSqlIdentifier(tzColumnName, "column name");
708
+ const timestampZSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedTzColumnName}" TIMESTAMPTZ DEFAULT NOW()`.trim();
709
+ await this.client.none(timestampZSql);
710
+ this.logger?.debug?.(`Added timezone column '${tzColumnName}' to ${fullTableName}`);
711
+ }
712
+ }
713
+ }
714
+ this.logger?.info?.(`Migration completed for ${fullTableName}`);
715
+ } catch (error) {
716
+ this.logger?.warn?.(`Failed to migrate spans table ${fullTableName}:`, error);
717
+ }
718
+ }
719
+ /**
720
+ * Alters table schema to add columns if they don't exist
721
+ * @param tableName Name of the table
722
+ * @param schema Schema of the table
723
+ * @param ifNotExists Array of column names to add if they don't exist
724
+ */
725
+ async alterTable({
726
+ tableName,
727
+ schema,
728
+ ifNotExists
729
+ }) {
730
+ const fullTableName = getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) });
731
+ try {
732
+ for (const columnName of ifNotExists) {
733
+ if (schema[columnName]) {
734
+ const columnDef = schema[columnName];
735
+ const sqlType = this.getSqlType(columnDef.type);
736
+ const parsedColumnName = parseSqlIdentifier(columnName, "column name");
737
+ const alterSql = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}" ${sqlType}`;
738
+ await this.client.none(alterSql);
739
+ if (sqlType === "TIMESTAMP") {
740
+ const alterSqlZ = `ALTER TABLE ${fullTableName} ADD COLUMN IF NOT EXISTS "${parsedColumnName}Z" TIMESTAMPTZ`;
741
+ await this.client.none(alterSqlZ);
742
+ }
743
+ this.logger?.debug?.(`Ensured column ${parsedColumnName} exists in table ${fullTableName}`);
744
+ }
745
+ }
746
+ } catch (error) {
747
+ throw new MastraError(
748
+ {
749
+ id: createStorageErrorId("DSQL", "ALTER_TABLE", "FAILED"),
750
+ domain: ErrorDomain.STORAGE,
751
+ category: ErrorCategory.THIRD_PARTY,
752
+ details: {
753
+ tableName
754
+ }
755
+ },
756
+ error
757
+ );
758
+ }
759
+ }
760
+ async load({ tableName, keys }) {
761
+ try {
762
+ const keyEntries = Object.entries(keys).map(([key, value]) => [parseSqlIdentifier(key, "column name"), value]);
763
+ const conditions = keyEntries.map(([key], index) => `"${key}" = $${index + 1}`).join(" AND ");
764
+ const values = keyEntries.map(([_, value]) => value);
765
+ const result = await this.client.oneOrNone(
766
+ `SELECT * FROM ${getTableName({ indexName: tableName, schemaName: getSchemaName(this.schemaName) })} WHERE ${conditions} ORDER BY "createdAt" DESC LIMIT 1`,
767
+ values
768
+ );
769
+ if (!result) {
770
+ return null;
771
+ }
772
+ if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
773
+ const snapshot = result;
774
+ if (typeof snapshot.snapshot === "string") {
775
+ snapshot.snapshot = JSON.parse(snapshot.snapshot);
776
+ }
777
+ return snapshot;
778
+ }
779
+ return result;
780
+ } catch (error) {
781
+ throw new MastraError(
782
+ {
783
+ id: createStorageErrorId("DSQL", "LOAD", "FAILED"),
784
+ domain: ErrorDomain.STORAGE,
785
+ category: ErrorCategory.THIRD_PARTY,
786
+ details: {
787
+ tableName
788
+ }
789
+ },
790
+ error
791
+ );
792
+ }
793
+ }
794
+ async batchInsert({ tableName, records }) {
795
+ if (records.length === 0) {
796
+ return;
797
+ }
798
+ try {
799
+ const { batches } = splitIntoBatches(records, { maxRows: DEFAULT_MAX_ROWS_PER_BATCH });
800
+ for (const batch of batches) {
801
+ await withRetry(
802
+ async () => {
803
+ await this.client.tx(async (t) => {
804
+ for (const record of batch) {
805
+ this.addTimestampZColumns(record);
806
+ const schemaName = getSchemaName(this.schemaName);
807
+ const columns = Object.keys(record).map((col) => parseSqlIdentifier(col, "column name"));
808
+ const values = this.prepareValuesForInsert(record, tableName);
809
+ const placeholders = values.map((_, i) => `$${i + 1}`).join(", ");
810
+ await t.none(
811
+ `INSERT INTO ${getTableName({ indexName: tableName, schemaName })} (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`,
812
+ values
813
+ );
814
+ }
815
+ });
816
+ },
817
+ {
818
+ onRetry: (error, attempt, delay) => {
819
+ this.logger?.warn?.(
820
+ `Batch insert retry ${attempt} for table ${tableName} after ${delay}ms: ${error.message}`
821
+ );
822
+ }
823
+ }
824
+ );
825
+ }
826
+ } catch (error) {
827
+ throw new MastraError(
828
+ {
829
+ id: createStorageErrorId("DSQL", "BATCH_INSERT", "FAILED"),
830
+ domain: ErrorDomain.STORAGE,
831
+ category: ErrorCategory.THIRD_PARTY,
832
+ details: {
833
+ tableName,
834
+ numberOfRecords: records.length
835
+ }
836
+ },
837
+ error
838
+ );
839
+ }
840
+ }
841
+ async dropTable({ tableName }) {
842
+ try {
843
+ const schemaName = getSchemaName(this.schemaName);
844
+ const tableNameWithSchema = getTableName({ indexName: tableName, schemaName });
845
+ await this.client.none(`DROP TABLE IF EXISTS ${tableNameWithSchema}`);
846
+ } catch (error) {
847
+ throw new MastraError(
848
+ {
849
+ id: createStorageErrorId("DSQL", "DROP_TABLE", "FAILED"),
850
+ domain: ErrorDomain.STORAGE,
851
+ category: ErrorCategory.THIRD_PARTY,
852
+ details: {
853
+ tableName
854
+ }
855
+ },
856
+ error
857
+ );
858
+ }
859
+ }
860
+ /**
861
+ * Wait for an asynchronous DSQL job to complete.
862
+ * Aurora DSQL requires CREATE INDEX ASYNC and sys.wait_for_job() to wait for completion.
863
+ */
864
+ async waitForDSQLJob(jobUuid, timeoutMs = 6e4) {
865
+ const pollIntervalMs = 1e3;
866
+ const startTime = Date.now();
867
+ while (Date.now() - startTime < timeoutMs) {
868
+ const result = await this.client.oneOrNone(`SELECT sys.wait_for_job($1, 1) as status`, [
869
+ jobUuid
870
+ ]);
871
+ if (result?.status === "COMPLETED") {
872
+ return;
873
+ }
874
+ if (result?.status === "FAILED") {
875
+ throw new Error(`DSQL async job ${jobUuid} failed`);
876
+ }
877
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
878
+ }
879
+ throw new Error(`DSQL async job ${jobUuid} timed out after ${timeoutMs}ms`);
880
+ }
881
+ async createIndex(options) {
882
+ try {
883
+ const {
884
+ name,
885
+ table,
886
+ columns,
887
+ unique = false,
888
+ // Note: 'concurrent' option is ignored in DSQL - always uses ASYNC
889
+ where,
890
+ method = "btree",
891
+ opclass,
892
+ storage,
893
+ tablespace
894
+ } = options;
895
+ const schemaName = this.schemaName || "public";
896
+ const fullTableName = getTableName({
897
+ indexName: table,
898
+ schemaName: getSchemaName(this.schemaName)
899
+ });
900
+ const indexExists = await this.client.oneOrNone(
901
+ `SELECT 1 as exists FROM pg_indexes
902
+ WHERE indexname = $1
903
+ AND schemaname = $2`,
904
+ [name, schemaName]
905
+ );
906
+ if (indexExists) {
907
+ return;
908
+ }
909
+ const uniqueStr = unique ? "UNIQUE " : "";
910
+ const methodStr = method !== "btree" ? `USING ${method} ` : "";
911
+ const columnsStr = columns.map((col) => {
912
+ const colName = col.replace(/\s+(DESC|ASC)$/i, "").trim();
913
+ const quotedCol = `"${parseSqlIdentifier(colName, "column name")}"`;
914
+ return opclass ? `${quotedCol} ${opclass}` : quotedCol;
915
+ }).join(", ");
916
+ const whereStr = where ? ` WHERE ${where}` : "";
917
+ const tablespaceStr = tablespace ? ` TABLESPACE ${tablespace}` : "";
918
+ let withStr = "";
919
+ if (storage && Object.keys(storage).length > 0) {
920
+ const storageParams = Object.entries(storage).map(([key, value]) => `${key} = ${value}`).join(", ");
921
+ withStr = ` WITH (${storageParams})`;
922
+ }
923
+ const quotedIndexName = `"${parseSqlIdentifier(name, "index name")}"`;
924
+ const sql = `CREATE ${uniqueStr}INDEX ASYNC ${quotedIndexName} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
925
+ const result = await this.client.oneOrNone(sql);
926
+ if (result?.job_uuid) {
927
+ await this.waitForDSQLJob(result.job_uuid);
928
+ }
929
+ } catch (error) {
930
+ throw new MastraError(
931
+ {
932
+ id: createStorageErrorId("DSQL", "INDEX_CREATE", "FAILED"),
933
+ domain: ErrorDomain.STORAGE,
934
+ category: ErrorCategory.THIRD_PARTY,
935
+ details: {
936
+ indexName: options.name,
937
+ tableName: options.table
938
+ }
939
+ },
940
+ error
941
+ );
942
+ }
943
+ }
944
+ async dropIndex(indexName) {
945
+ const schemaName = this.schemaName || "public";
946
+ await withRetry(
947
+ async () => {
948
+ const indexExists = await this.client.oneOrNone(
949
+ `SELECT 1 FROM pg_indexes
950
+ WHERE indexname = $1
951
+ AND schemaname = $2`,
952
+ [indexName, schemaName]
953
+ );
954
+ if (!indexExists) {
955
+ return;
956
+ }
957
+ const quotedIndexName = `"${parseSqlIdentifier(indexName, "index name")}"`;
958
+ const sql = `DROP INDEX IF EXISTS ${getSchemaName(this.schemaName)}.${quotedIndexName}`;
959
+ await this.client.none(sql);
960
+ },
961
+ {
962
+ onRetry: (error, attempt, delay) => {
963
+ this.logger?.warn?.(`dropIndex retry ${attempt} for ${indexName} after ${delay}ms: ${error.message}`);
964
+ }
965
+ }
966
+ ).catch((error) => {
967
+ throw new MastraError(
968
+ {
969
+ id: createStorageErrorId("DSQL", "INDEX_DROP", "FAILED"),
970
+ domain: ErrorDomain.STORAGE,
971
+ category: ErrorCategory.THIRD_PARTY,
972
+ details: {
973
+ indexName
974
+ }
975
+ },
976
+ error
977
+ );
978
+ });
979
+ }
980
+ async listIndexes(tableName) {
981
+ try {
982
+ const schemaName = this.schemaName || "public";
983
+ let query;
984
+ let params;
985
+ if (tableName) {
986
+ query = `
987
+ SELECT
988
+ i.indexname as name,
989
+ i.tablename as table,
990
+ i.indexdef as definition,
991
+ ix.indisunique as is_unique,
992
+ pg_size_pretty(pg_relation_size(c.oid)) as size,
993
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
994
+ FROM pg_indexes i
995
+ JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
996
+ JOIN pg_index ix ON ix.indexrelid = c.oid
997
+ JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
998
+ WHERE i.schemaname = $1
999
+ AND i.tablename = $2
1000
+ GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
1001
+ `;
1002
+ params = [schemaName, tableName];
1003
+ } else {
1004
+ query = `
1005
+ SELECT
1006
+ i.indexname as name,
1007
+ i.tablename as table,
1008
+ i.indexdef as definition,
1009
+ ix.indisunique as is_unique,
1010
+ pg_size_pretty(pg_relation_size(c.oid)) as size,
1011
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns
1012
+ FROM pg_indexes i
1013
+ JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
1014
+ JOIN pg_index ix ON ix.indexrelid = c.oid
1015
+ JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
1016
+ WHERE i.schemaname = $1
1017
+ GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid
1018
+ `;
1019
+ params = [schemaName];
1020
+ }
1021
+ const results = await this.client.manyOrNone(query, params);
1022
+ return results.map((row) => {
1023
+ let columns = [];
1024
+ if (typeof row.columns === "string" && row.columns.startsWith("{") && row.columns.endsWith("}")) {
1025
+ const arrayContent = row.columns.slice(1, -1);
1026
+ columns = arrayContent ? arrayContent.split(",") : [];
1027
+ } else if (Array.isArray(row.columns)) {
1028
+ columns = row.columns;
1029
+ }
1030
+ return {
1031
+ name: row.name,
1032
+ table: row.table,
1033
+ columns,
1034
+ unique: row.is_unique || false,
1035
+ size: row.size || "0",
1036
+ definition: row.definition || ""
1037
+ };
1038
+ });
1039
+ } catch (error) {
1040
+ throw new MastraError(
1041
+ {
1042
+ id: createStorageErrorId("DSQL", "INDEX_LIST", "FAILED"),
1043
+ domain: ErrorDomain.STORAGE,
1044
+ category: ErrorCategory.THIRD_PARTY,
1045
+ details: tableName ? {
1046
+ tableName
1047
+ } : {}
1048
+ },
1049
+ error
1050
+ );
1051
+ }
1052
+ }
1053
+ async describeIndex(indexName) {
1054
+ try {
1055
+ const schemaName = this.schemaName || "public";
1056
+ const query = `
1057
+ SELECT
1058
+ i.indexname as name,
1059
+ i.tablename as table,
1060
+ i.indexdef as definition,
1061
+ ix.indisunique as is_unique,
1062
+ pg_size_pretty(pg_relation_size(c.oid)) as size,
1063
+ array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns,
1064
+ am.amname as method,
1065
+ s.idx_scan as scans,
1066
+ s.idx_tup_read as tuples_read,
1067
+ s.idx_tup_fetch as tuples_fetched
1068
+ FROM pg_indexes i
1069
+ JOIN pg_class c ON c.relname = i.indexname AND c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = i.schemaname)
1070
+ JOIN pg_index ix ON ix.indexrelid = c.oid
1071
+ JOIN pg_attribute a ON a.attrelid = ix.indrelid AND a.attnum = ANY(ix.indkey)
1072
+ JOIN pg_am am ON c.relam = am.oid
1073
+ LEFT JOIN pg_stat_user_indexes s ON s.indexrelname = i.indexname AND s.schemaname = i.schemaname
1074
+ WHERE i.schemaname = $1
1075
+ AND i.indexname = $2
1076
+ GROUP BY i.indexname, i.tablename, i.indexdef, ix.indisunique, c.oid, am.amname, s.idx_scan, s.idx_tup_read, s.idx_tup_fetch
1077
+ `;
1078
+ const result = await this.client.oneOrNone(query, [schemaName, indexName]);
1079
+ if (!result) {
1080
+ throw new Error(`Index "${indexName}" not found in schema "${schemaName}"`);
1081
+ }
1082
+ let columns = [];
1083
+ if (typeof result.columns === "string" && result.columns.startsWith("{") && result.columns.endsWith("}")) {
1084
+ const arrayContent = result.columns.slice(1, -1);
1085
+ columns = arrayContent ? arrayContent.split(",") : [];
1086
+ } else if (Array.isArray(result.columns)) {
1087
+ columns = result.columns;
1088
+ }
1089
+ const normalizedMethod = result.method === "btree_index" ? "btree" : result.method || "btree";
1090
+ return {
1091
+ name: result.name,
1092
+ table: result.table,
1093
+ columns,
1094
+ unique: result.is_unique || false,
1095
+ size: result.size || "0",
1096
+ definition: result.definition || "",
1097
+ method: normalizedMethod,
1098
+ scans: parseInt(result.scans) || 0,
1099
+ tuples_read: parseInt(result.tuples_read) || 0,
1100
+ tuples_fetched: parseInt(result.tuples_fetched) || 0
1101
+ };
1102
+ } catch (error) {
1103
+ throw new MastraError(
1104
+ {
1105
+ id: createStorageErrorId("DSQL", "INDEX_DESCRIBE", "FAILED"),
1106
+ domain: ErrorDomain.STORAGE,
1107
+ category: ErrorCategory.THIRD_PARTY,
1108
+ details: {
1109
+ indexName
1110
+ }
1111
+ },
1112
+ error
1113
+ );
1114
+ }
1115
+ }
1116
+ async update({
1117
+ tableName,
1118
+ keys,
1119
+ data
1120
+ }) {
1121
+ const setColumns = [];
1122
+ const setValues = [];
1123
+ let paramIndex = 1;
1124
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1125
+ const dataWithTimestamp = {
1126
+ ...data,
1127
+ updatedAt: now,
1128
+ updatedAtZ: now
1129
+ };
1130
+ Object.entries(dataWithTimestamp).forEach(([key, value]) => {
1131
+ const parsedKey = parseSqlIdentifier(key, "column name");
1132
+ setColumns.push(`"${parsedKey}" = $${paramIndex++}`);
1133
+ setValues.push(this.prepareValue(value, key, tableName));
1134
+ });
1135
+ const whereConditions = [];
1136
+ const whereValues = [];
1137
+ Object.entries(keys).forEach(([key, value]) => {
1138
+ const parsedKey = parseSqlIdentifier(key, "column name");
1139
+ whereConditions.push(`"${parsedKey}" = $${paramIndex++}`);
1140
+ whereValues.push(this.prepareValue(value, key, tableName));
1141
+ });
1142
+ const tableName_ = getTableName({
1143
+ indexName: tableName,
1144
+ schemaName: getSchemaName(this.schemaName)
1145
+ });
1146
+ const sql = `UPDATE ${tableName_} SET ${setColumns.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
1147
+ const values = [...setValues, ...whereValues];
1148
+ await withRetry(
1149
+ async () => {
1150
+ await this.client.none(sql, values);
1151
+ },
1152
+ {
1153
+ onRetry: (error, attempt, delay) => {
1154
+ this.logger?.warn?.(`update retry ${attempt} for table ${tableName} after ${delay}ms: ${error.message}`);
1155
+ }
1156
+ }
1157
+ ).catch((error) => {
1158
+ throw new MastraError(
1159
+ {
1160
+ id: createStorageErrorId("DSQL", "UPDATE", "FAILED"),
1161
+ domain: ErrorDomain.STORAGE,
1162
+ category: ErrorCategory.THIRD_PARTY,
1163
+ details: {
1164
+ tableName
1165
+ }
1166
+ },
1167
+ error
1168
+ );
1169
+ });
1170
+ }
1171
+ async batchUpdate({
1172
+ tableName,
1173
+ updates
1174
+ }) {
1175
+ if (updates.length === 0) {
1176
+ return;
1177
+ }
1178
+ try {
1179
+ const { batches } = splitIntoBatches(updates, { maxRows: DEFAULT_MAX_ROWS_PER_BATCH });
1180
+ for (const batch of batches) {
1181
+ await withRetry(
1182
+ async () => {
1183
+ await this.client.tx(async (t) => {
1184
+ for (const { keys, data } of batch) {
1185
+ const setClauses = [];
1186
+ const whereConditions = [];
1187
+ const values = [];
1188
+ let paramIndex = 1;
1189
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1190
+ const dataWithTimestamp = {
1191
+ ...data,
1192
+ updatedAt: now,
1193
+ updatedAtZ: now
1194
+ };
1195
+ Object.entries(dataWithTimestamp).forEach(([key, value]) => {
1196
+ const parsedKey = parseSqlIdentifier(key, "column name");
1197
+ const preparedValue = this.prepareValue(value, key, tableName);
1198
+ setClauses.push(`"${parsedKey}" = $${paramIndex++}`);
1199
+ values.push(preparedValue);
1200
+ });
1201
+ Object.entries(keys).forEach(([key, value]) => {
1202
+ const parsedKey = parseSqlIdentifier(key, "column name");
1203
+ whereConditions.push(`"${parsedKey}" = $${paramIndex++}`);
1204
+ values.push(value);
1205
+ });
1206
+ const tableName_ = getTableName({
1207
+ indexName: tableName,
1208
+ schemaName: getSchemaName(this.schemaName)
1209
+ });
1210
+ const sql = `UPDATE ${tableName_} SET ${setClauses.join(", ")} WHERE ${whereConditions.join(" AND ")}`;
1211
+ await t.none(sql, values);
1212
+ }
1213
+ });
1214
+ },
1215
+ {
1216
+ onRetry: (error, attempt, delay) => {
1217
+ this.logger?.warn?.(
1218
+ `Batch update retry ${attempt} for table ${tableName} after ${delay}ms: ${error.message}`
1219
+ );
1220
+ }
1221
+ }
1222
+ );
1223
+ }
1224
+ } catch (error) {
1225
+ throw new MastraError(
1226
+ {
1227
+ id: createStorageErrorId("DSQL", "BATCH_UPDATE", "FAILED"),
1228
+ domain: ErrorDomain.STORAGE,
1229
+ category: ErrorCategory.THIRD_PARTY,
1230
+ details: {
1231
+ tableName,
1232
+ numberOfRecords: updates.length
1233
+ }
1234
+ },
1235
+ error
1236
+ );
1237
+ }
1238
+ }
1239
+ async batchDelete({ tableName, keys }) {
1240
+ if (keys.length === 0) {
1241
+ return;
1242
+ }
1243
+ try {
1244
+ const tableName_ = getTableName({
1245
+ indexName: tableName,
1246
+ schemaName: getSchemaName(this.schemaName)
1247
+ });
1248
+ const { batches } = splitIntoBatches(keys, { maxRows: DEFAULT_MAX_ROWS_PER_BATCH });
1249
+ for (const batch of batches) {
1250
+ await withRetry(
1251
+ async () => {
1252
+ await this.client.tx(async (t) => {
1253
+ for (const keySet of batch) {
1254
+ const conditions = [];
1255
+ const values = [];
1256
+ let paramIndex = 1;
1257
+ Object.entries(keySet).forEach(([key, value]) => {
1258
+ const parsedKey = parseSqlIdentifier(key, "column name");
1259
+ conditions.push(`"${parsedKey}" = $${paramIndex++}`);
1260
+ values.push(value);
1261
+ });
1262
+ const sql = `DELETE FROM ${tableName_} WHERE ${conditions.join(" AND ")}`;
1263
+ await t.none(sql, values);
1264
+ }
1265
+ });
1266
+ },
1267
+ {
1268
+ onRetry: (error, attempt, delay) => {
1269
+ this.logger?.warn?.(
1270
+ `Batch delete retry ${attempt} for table ${tableName} after ${delay}ms: ${error.message}`
1271
+ );
1272
+ }
1273
+ }
1274
+ );
1275
+ }
1276
+ } catch (error) {
1277
+ throw new MastraError(
1278
+ {
1279
+ id: createStorageErrorId("DSQL", "BATCH_DELETE", "FAILED"),
1280
+ domain: ErrorDomain.STORAGE,
1281
+ category: ErrorCategory.THIRD_PARTY,
1282
+ details: {
1283
+ tableName,
1284
+ numberOfRecords: keys.length
1285
+ }
1286
+ },
1287
+ error
1288
+ );
1289
+ }
1290
+ }
1291
+ /**
1292
+ * Delete all data from a table (alias for clearTable for consistency with other stores)
1293
+ */
1294
+ async deleteData({ tableName }) {
1295
+ return this.clearTable({ tableName });
1296
+ }
1297
+ };
1298
+ function getSchemaName2(schema) {
1299
+ return schema ? `"${parseSqlIdentifier(schema, "schema name")}"` : void 0;
1300
+ }
1301
+ function getTableName2({ indexName, schemaName }) {
1302
+ const parsedIndexName = parseSqlIdentifier(indexName, "index name");
1303
+ const quotedIndexName = `"${parsedIndexName}"`;
1304
+ const quotedSchemaName = schemaName;
1305
+ return quotedSchemaName ? `${quotedSchemaName}.${quotedIndexName}` : quotedIndexName;
1306
+ }
1307
+ function transformFromSqlRow({
1308
+ tableName,
1309
+ sqlRow
1310
+ }) {
1311
+ const schema = TABLE_SCHEMAS[tableName];
1312
+ const result = {};
1313
+ Object.entries(sqlRow).forEach(([key, value]) => {
1314
+ const columnSchema = schema?.[key];
1315
+ if (columnSchema?.type === "jsonb" && typeof value === "string") {
1316
+ try {
1317
+ result[key] = JSON.parse(value);
1318
+ } catch {
1319
+ result[key] = value;
1320
+ }
1321
+ } else if (columnSchema?.type === "timestamp" && value && typeof value === "string") {
1322
+ result[key] = new Date(value);
1323
+ } else if (columnSchema?.type === "timestamp" && value instanceof Date) {
1324
+ result[key] = value;
1325
+ } else if (columnSchema?.type === "boolean") {
1326
+ result[key] = Boolean(value);
1327
+ } else {
1328
+ result[key] = value;
1329
+ }
1330
+ });
1331
+ return result;
1332
+ }
1333
+
1334
+ // src/storage/domains/agents/index.ts
1335
+ var AgentsDSQL = class _AgentsDSQL extends AgentsStorage {
1336
+ #db;
1337
+ #schema;
1338
+ #skipDefaultIndexes;
1339
+ #indexes;
1340
+ /** Tables managed by this domain */
1341
+ static MANAGED_TABLES = [TABLE_AGENTS, TABLE_AGENT_VERSIONS];
1342
+ constructor(config) {
1343
+ super();
1344
+ const { client, schemaName, skipDefaultIndexes, indexes } = resolveDsqlConfig(config);
1345
+ this.#db = new DsqlDB({ client, schemaName, skipDefaultIndexes });
1346
+ this.#schema = schemaName || "public";
1347
+ this.#skipDefaultIndexes = skipDefaultIndexes;
1348
+ this.#indexes = indexes?.filter((idx) => _AgentsDSQL.MANAGED_TABLES.includes(idx.table));
1349
+ }
1350
+ /**
1351
+ * Returns default index definitions for the agents domain tables.
1352
+ * Currently no default indexes are defined for agents.
1353
+ */
1354
+ getDefaultIndexDefinitions() {
1355
+ return [];
1356
+ }
1357
+ /**
1358
+ * Creates default indexes for optimal query performance.
1359
+ * Currently no default indexes are defined for agents.
1360
+ */
1361
+ async createDefaultIndexes() {
1362
+ if (this.#skipDefaultIndexes) {
1363
+ return;
1364
+ }
1365
+ }
1366
+ async init() {
1367
+ await this.#db.createTable({ tableName: TABLE_AGENTS, schema: TABLE_SCHEMAS[TABLE_AGENTS] });
1368
+ await this.#db.createTable({ tableName: TABLE_AGENT_VERSIONS, schema: TABLE_SCHEMAS[TABLE_AGENT_VERSIONS] });
1369
+ await this.createDefaultIndexes();
1370
+ await this.createCustomIndexes();
1371
+ }
1372
+ /**
1373
+ * Creates custom user-defined indexes for this domain's tables.
1374
+ */
1375
+ async createCustomIndexes() {
1376
+ if (!this.#indexes || this.#indexes.length === 0) {
1377
+ return;
1378
+ }
1379
+ for (const indexDef of this.#indexes) {
1380
+ try {
1381
+ await this.#db.createIndex(indexDef);
1382
+ } catch (error) {
1383
+ this.logger?.warn?.(`Failed to create custom index ${indexDef.name}:`, error);
1384
+ }
1385
+ }
1386
+ }
1387
+ async dangerouslyClearAll() {
1388
+ await this.#db.clearTable({ tableName: TABLE_AGENT_VERSIONS });
1389
+ await this.#db.clearTable({ tableName: TABLE_AGENTS });
1390
+ }
1391
+ parseJson(value, fieldName) {
1392
+ if (!value) return void 0;
1393
+ if (typeof value !== "string") return value;
1394
+ try {
1395
+ return JSON.parse(value);
1396
+ } catch (error) {
1397
+ const details = {
1398
+ value: value.length > 100 ? value.substring(0, 100) + "..." : value
1399
+ };
1400
+ if (fieldName) {
1401
+ details.field = fieldName;
1402
+ }
1403
+ throw new MastraError(
1404
+ {
1405
+ id: createStorageErrorId("DSQL", "PARSE_JSON", "INVALID_JSON"),
1406
+ domain: ErrorDomain.STORAGE,
1407
+ category: ErrorCategory.SYSTEM,
1408
+ text: `Failed to parse JSON${fieldName ? ` for field "${fieldName}"` : ""}: ${error instanceof Error ? error.message : "Unknown error"}`,
1409
+ details
1410
+ },
1411
+ error
1412
+ );
1413
+ }
1414
+ }
1415
+ parseRow(row) {
1416
+ return {
1417
+ id: row.id,
1418
+ status: row.status,
1419
+ activeVersionId: row.activeVersionId,
1420
+ authorId: row.authorId,
1421
+ metadata: this.parseJson(row.metadata, "metadata"),
1422
+ createdAt: row.createdAtZ || row.createdAt,
1423
+ updatedAt: row.updatedAtZ || row.updatedAt
1424
+ };
1425
+ }
1426
+ async getById(id) {
1427
+ try {
1428
+ const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
1429
+ const result = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [id]);
1430
+ if (!result) {
1431
+ return null;
1432
+ }
1433
+ return this.parseRow(result);
1434
+ } catch (error) {
1435
+ if (error instanceof MastraError) throw error;
1436
+ throw new MastraError(
1437
+ {
1438
+ id: createStorageErrorId("DSQL", "GET_AGENT_BY_ID", "FAILED"),
1439
+ domain: ErrorDomain.STORAGE,
1440
+ category: ErrorCategory.THIRD_PARTY,
1441
+ details: { agentId: id }
1442
+ },
1443
+ error
1444
+ );
1445
+ }
1446
+ }
1447
+ async create(input) {
1448
+ const { agent } = input;
1449
+ const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
1450
+ const now = /* @__PURE__ */ new Date();
1451
+ const nowIso = now.toISOString();
1452
+ try {
1453
+ await withRetry(
1454
+ () => this.#db.client.none(
1455
+ `INSERT INTO ${tableName} (
1456
+ id, status, "authorId", metadata,
1457
+ "activeVersionId",
1458
+ "createdAt", "createdAtZ", "updatedAt", "updatedAtZ"
1459
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
1460
+ [
1461
+ agent.id,
1462
+ "draft",
1463
+ agent.authorId ?? null,
1464
+ agent.metadata ? JSON.stringify(agent.metadata) : null,
1465
+ null,
1466
+ // activeVersionId starts as null
1467
+ nowIso,
1468
+ nowIso,
1469
+ nowIso,
1470
+ nowIso
1471
+ ]
1472
+ )
1473
+ );
1474
+ const { id: _id, authorId: _authorId, metadata: _metadata, ...snapshotConfig } = agent;
1475
+ const versionId = crypto.randomUUID();
1476
+ await this.createVersion({
1477
+ id: versionId,
1478
+ agentId: agent.id,
1479
+ versionNumber: 1,
1480
+ ...snapshotConfig,
1481
+ changedFields: Object.keys(snapshotConfig),
1482
+ changeMessage: "Initial version"
1483
+ });
1484
+ return {
1485
+ id: agent.id,
1486
+ status: "draft",
1487
+ activeVersionId: void 0,
1488
+ authorId: agent.authorId,
1489
+ metadata: agent.metadata,
1490
+ createdAt: now,
1491
+ updatedAt: now
1492
+ };
1493
+ } catch (error) {
1494
+ if (error instanceof MastraError) throw error;
1495
+ try {
1496
+ await this.#db.client.none(
1497
+ `DELETE FROM ${tableName} WHERE id = $1 AND status = 'draft' AND "activeVersionId" IS NULL`,
1498
+ [agent.id]
1499
+ );
1500
+ } catch {
1501
+ }
1502
+ throw new MastraError(
1503
+ {
1504
+ id: createStorageErrorId("DSQL", "CREATE_AGENT", "FAILED"),
1505
+ domain: ErrorDomain.STORAGE,
1506
+ category: ErrorCategory.THIRD_PARTY,
1507
+ details: { agentId: agent.id }
1508
+ },
1509
+ error
1510
+ );
1511
+ }
1512
+ }
1513
+ async update(input) {
1514
+ const { id, ...updates } = input;
1515
+ try {
1516
+ const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
1517
+ const existingAgent = await this.getById(id);
1518
+ if (!existingAgent) {
1519
+ throw new MastraError({
1520
+ id: createStorageErrorId("DSQL", "UPDATE_AGENT", "NOT_FOUND"),
1521
+ domain: ErrorDomain.STORAGE,
1522
+ category: ErrorCategory.USER,
1523
+ text: `Agent ${id} not found`,
1524
+ details: { agentId: id }
1525
+ });
1526
+ }
1527
+ const { authorId, activeVersionId, metadata, status } = updates;
1528
+ const setClauses = [];
1529
+ const values = [];
1530
+ let paramIndex = 1;
1531
+ if (authorId !== void 0) {
1532
+ setClauses.push(`"authorId" = $${paramIndex++}`);
1533
+ values.push(authorId);
1534
+ }
1535
+ if (activeVersionId !== void 0) {
1536
+ setClauses.push(`"activeVersionId" = $${paramIndex++}`);
1537
+ values.push(activeVersionId);
1538
+ }
1539
+ if (status !== void 0) {
1540
+ setClauses.push(`status = $${paramIndex++}`);
1541
+ values.push(status);
1542
+ }
1543
+ if (metadata !== void 0) {
1544
+ setClauses.push(`metadata = $${paramIndex++}`);
1545
+ values.push(JSON.stringify(metadata));
1546
+ }
1547
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1548
+ setClauses.push(`"updatedAt" = $${paramIndex++}`);
1549
+ values.push(now);
1550
+ setClauses.push(`"updatedAtZ" = $${paramIndex++}`);
1551
+ values.push(now);
1552
+ values.push(id);
1553
+ await withRetry(
1554
+ () => this.#db.client.none(`UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE id = ${paramIndex}`, values)
1555
+ );
1556
+ const updatedAgent = await this.getById(id);
1557
+ if (!updatedAgent) {
1558
+ throw new MastraError({
1559
+ id: createStorageErrorId("DSQL", "UPDATE_AGENT", "NOT_FOUND_AFTER_UPDATE"),
1560
+ domain: ErrorDomain.STORAGE,
1561
+ category: ErrorCategory.SYSTEM,
1562
+ text: `Agent ${id} not found after update`,
1563
+ details: { agentId: id }
1564
+ });
1565
+ }
1566
+ return updatedAgent;
1567
+ } catch (error) {
1568
+ if (error instanceof MastraError) throw error;
1569
+ throw new MastraError(
1570
+ {
1571
+ id: createStorageErrorId("DSQL", "UPDATE_AGENT", "FAILED"),
1572
+ domain: ErrorDomain.STORAGE,
1573
+ category: ErrorCategory.THIRD_PARTY,
1574
+ details: { agentId: id }
1575
+ },
1576
+ error
1577
+ );
1578
+ }
1579
+ }
1580
+ async delete(id) {
1581
+ try {
1582
+ const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
1583
+ await this.deleteVersionsByParentId(id);
1584
+ await this.#db.client.none(`DELETE FROM ${tableName} WHERE id = $1`, [id]);
1585
+ } catch (error) {
1586
+ if (error instanceof MastraError) throw error;
1587
+ throw new MastraError(
1588
+ {
1589
+ id: createStorageErrorId("DSQL", "DELETE_AGENT", "FAILED"),
1590
+ domain: ErrorDomain.STORAGE,
1591
+ category: ErrorCategory.THIRD_PARTY,
1592
+ details: { agentId: id }
1593
+ },
1594
+ error
1595
+ );
1596
+ }
1597
+ }
1598
+ async list(args) {
1599
+ const { page = 0, perPage: perPageInput, orderBy, authorId, metadata, status } = args || {};
1600
+ const { field, direction } = this.parseOrderBy(orderBy);
1601
+ if (page < 0) {
1602
+ throw new MastraError(
1603
+ {
1604
+ id: createStorageErrorId("DSQL", "LIST_AGENTS", "INVALID_PAGE"),
1605
+ domain: ErrorDomain.STORAGE,
1606
+ category: ErrorCategory.USER,
1607
+ details: { page }
1608
+ },
1609
+ new Error("page must be >= 0")
1610
+ );
1611
+ }
1612
+ const perPage = normalizePerPage(perPageInput, 100);
1613
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1614
+ try {
1615
+ const tableName = getTableName2({ indexName: TABLE_AGENTS, schemaName: getSchemaName2(this.#schema) });
1616
+ const conditions = [];
1617
+ const queryParams = [];
1618
+ let paramIdx = 1;
1619
+ if (status) {
1620
+ conditions.push(`status = $${paramIdx++}`);
1621
+ queryParams.push(status);
1622
+ }
1623
+ if (authorId !== void 0) {
1624
+ conditions.push(`"authorId" = $${paramIdx++}`);
1625
+ queryParams.push(authorId);
1626
+ }
1627
+ if (metadata && Object.keys(metadata).length > 0) {
1628
+ conditions.push(`metadata::text = $${paramIdx++}`);
1629
+ queryParams.push(JSON.stringify(metadata));
1630
+ }
1631
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
1632
+ const countResult = await this.#db.client.one(
1633
+ `SELECT COUNT(*) as count FROM ${tableName} ${whereClause}`,
1634
+ queryParams
1635
+ );
1636
+ const total = parseInt(countResult.count, 10);
1637
+ if (total === 0) {
1638
+ return {
1639
+ agents: [],
1640
+ total: 0,
1641
+ page,
1642
+ perPage: perPageForResponse,
1643
+ hasMore: false
1644
+ };
1645
+ }
1646
+ const limitValue = perPageInput === false ? total : perPage;
1647
+ const dataResult = await this.#db.client.manyOrNone(
1648
+ `SELECT * FROM ${tableName} ${whereClause} ORDER BY "${field}" ${direction} LIMIT $${paramIdx++} OFFSET $${paramIdx++}`,
1649
+ [...queryParams, limitValue, offset]
1650
+ );
1651
+ const agents = (dataResult || []).map((row) => this.parseRow(row));
1652
+ return {
1653
+ agents,
1654
+ total,
1655
+ page,
1656
+ perPage: perPageForResponse,
1657
+ hasMore: perPageInput === false ? false : offset + perPage < total
1658
+ };
1659
+ } catch (error) {
1660
+ if (error instanceof MastraError) throw error;
1661
+ throw new MastraError(
1662
+ {
1663
+ id: createStorageErrorId("DSQL", "LIST_AGENTS", "FAILED"),
1664
+ domain: ErrorDomain.STORAGE,
1665
+ category: ErrorCategory.THIRD_PARTY
1666
+ },
1667
+ error
1668
+ );
1669
+ }
1670
+ }
1671
+ // ==========================================================================
1672
+ // Agent Version Methods
1673
+ // ==========================================================================
1674
+ async createVersion(input) {
1675
+ try {
1676
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
1677
+ const now = /* @__PURE__ */ new Date();
1678
+ const nowIso = now.toISOString();
1679
+ await withRetry(
1680
+ () => this.#db.client.none(
1681
+ `INSERT INTO ${tableName} (
1682
+ id, "agentId", "versionNumber",
1683
+ name, description, instructions, model, tools,
1684
+ "defaultOptions", workflows, agents, "integrationTools",
1685
+ "inputProcessors", "outputProcessors", memory, scorers,
1686
+ "mcpClients", "requestContextSchema", workspace, skills, "skillsFormat",
1687
+ "changedFields", "changeMessage",
1688
+ "createdAt", "createdAtZ"
1689
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)`,
1690
+ [
1691
+ input.id,
1692
+ input.agentId,
1693
+ input.versionNumber,
1694
+ input.name,
1695
+ input.description ?? null,
1696
+ this.serializeInstructions(input.instructions),
1697
+ JSON.stringify(input.model),
1698
+ input.tools ? JSON.stringify(input.tools) : null,
1699
+ input.defaultOptions ? JSON.stringify(input.defaultOptions) : null,
1700
+ input.workflows ? JSON.stringify(input.workflows) : null,
1701
+ input.agents ? JSON.stringify(input.agents) : null,
1702
+ input.integrationTools ? JSON.stringify(input.integrationTools) : null,
1703
+ input.inputProcessors ? JSON.stringify(input.inputProcessors) : null,
1704
+ input.outputProcessors ? JSON.stringify(input.outputProcessors) : null,
1705
+ input.memory ? JSON.stringify(input.memory) : null,
1706
+ input.scorers ? JSON.stringify(input.scorers) : null,
1707
+ input.mcpClients ? JSON.stringify(input.mcpClients) : null,
1708
+ input.requestContextSchema ? JSON.stringify(input.requestContextSchema) : null,
1709
+ input.workspace ? JSON.stringify(input.workspace) : null,
1710
+ input.skills ? JSON.stringify(input.skills) : null,
1711
+ input.skillsFormat ?? null,
1712
+ input.changedFields ? JSON.stringify(input.changedFields) : null,
1713
+ input.changeMessage ?? null,
1714
+ nowIso,
1715
+ nowIso
1716
+ ]
1717
+ )
1718
+ );
1719
+ return {
1720
+ ...input,
1721
+ createdAt: now
1722
+ };
1723
+ } catch (error) {
1724
+ if (error instanceof MastraError) throw error;
1725
+ throw new MastraError(
1726
+ {
1727
+ id: createStorageErrorId("DSQL", "CREATE_VERSION", "FAILED"),
1728
+ domain: ErrorDomain.STORAGE,
1729
+ category: ErrorCategory.THIRD_PARTY,
1730
+ details: { versionId: input.id, agentId: input.agentId }
1731
+ },
1732
+ error
1733
+ );
1734
+ }
1735
+ }
1736
+ async getVersion(id) {
1737
+ try {
1738
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
1739
+ const result = await this.#db.client.oneOrNone(`SELECT * FROM ${tableName} WHERE id = $1`, [id]);
1740
+ if (!result) {
1741
+ return null;
1742
+ }
1743
+ return this.parseVersionRow(result);
1744
+ } catch (error) {
1745
+ if (error instanceof MastraError) throw error;
1746
+ throw new MastraError(
1747
+ {
1748
+ id: createStorageErrorId("DSQL", "GET_VERSION", "FAILED"),
1749
+ domain: ErrorDomain.STORAGE,
1750
+ category: ErrorCategory.THIRD_PARTY,
1751
+ details: { versionId: id }
1752
+ },
1753
+ error
1754
+ );
1755
+ }
1756
+ }
1757
+ async getVersionByNumber(agentId, versionNumber) {
1758
+ try {
1759
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
1760
+ const result = await this.#db.client.oneOrNone(
1761
+ `SELECT * FROM ${tableName} WHERE "agentId" = $1 AND "versionNumber" = $2`,
1762
+ [agentId, versionNumber]
1763
+ );
1764
+ if (!result) {
1765
+ return null;
1766
+ }
1767
+ return this.parseVersionRow(result);
1768
+ } catch (error) {
1769
+ if (error instanceof MastraError) throw error;
1770
+ throw new MastraError(
1771
+ {
1772
+ id: createStorageErrorId("DSQL", "GET_VERSION_BY_NUMBER", "FAILED"),
1773
+ domain: ErrorDomain.STORAGE,
1774
+ category: ErrorCategory.THIRD_PARTY,
1775
+ details: { agentId, versionNumber }
1776
+ },
1777
+ error
1778
+ );
1779
+ }
1780
+ }
1781
+ async getLatestVersion(agentId) {
1782
+ try {
1783
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
1784
+ const result = await this.#db.client.oneOrNone(
1785
+ `SELECT * FROM ${tableName} WHERE "agentId" = $1 ORDER BY "versionNumber" DESC LIMIT 1`,
1786
+ [agentId]
1787
+ );
1788
+ if (!result) {
1789
+ return null;
1790
+ }
1791
+ return this.parseVersionRow(result);
1792
+ } catch (error) {
1793
+ if (error instanceof MastraError) throw error;
1794
+ throw new MastraError(
1795
+ {
1796
+ id: createStorageErrorId("DSQL", "GET_LATEST_VERSION", "FAILED"),
1797
+ domain: ErrorDomain.STORAGE,
1798
+ category: ErrorCategory.THIRD_PARTY,
1799
+ details: { agentId }
1800
+ },
1801
+ error
1802
+ );
1803
+ }
1804
+ }
1805
+ async listVersions(input) {
1806
+ const { agentId, page = 0, perPage: perPageInput, orderBy } = input;
1807
+ const perPage = normalizePerPage(perPageInput, 100);
1808
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
1809
+ const sortField = orderBy?.field || "versionNumber";
1810
+ const sortDirection = orderBy?.direction || "DESC";
1811
+ try {
1812
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
1813
+ const countResult = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${tableName} WHERE "agentId" = $1`, [
1814
+ agentId
1815
+ ]);
1816
+ const total = parseInt(countResult.count, 10);
1817
+ if (total === 0) {
1818
+ return {
1819
+ versions: [],
1820
+ total: 0,
1821
+ page,
1822
+ perPage: perPageForResponse,
1823
+ hasMore: false
1824
+ };
1825
+ }
1826
+ const limitValue = perPageInput === false ? total : perPage;
1827
+ const rows = await this.#db.client.manyOrNone(
1828
+ `SELECT * FROM ${tableName} WHERE "agentId" = $1 ORDER BY "${sortField}" ${sortDirection} LIMIT $2 OFFSET $3`,
1829
+ [agentId, limitValue, offset]
1830
+ );
1831
+ const versions = (rows || []).map((row) => this.parseVersionRow(row));
1832
+ return {
1833
+ versions,
1834
+ total,
1835
+ page,
1836
+ perPage: perPageForResponse,
1837
+ hasMore: perPageInput === false ? false : offset + perPage < total
1838
+ };
1839
+ } catch (error) {
1840
+ if (error instanceof MastraError) throw error;
1841
+ throw new MastraError(
1842
+ {
1843
+ id: createStorageErrorId("DSQL", "LIST_VERSIONS", "FAILED"),
1844
+ domain: ErrorDomain.STORAGE,
1845
+ category: ErrorCategory.THIRD_PARTY,
1846
+ details: { agentId }
1847
+ },
1848
+ error
1849
+ );
1850
+ }
1851
+ }
1852
+ async deleteVersion(id) {
1853
+ try {
1854
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
1855
+ await this.#db.client.none(`DELETE FROM ${tableName} WHERE id = $1`, [id]);
1856
+ } catch (error) {
1857
+ if (error instanceof MastraError) throw error;
1858
+ throw new MastraError(
1859
+ {
1860
+ id: createStorageErrorId("DSQL", "DELETE_VERSION", "FAILED"),
1861
+ domain: ErrorDomain.STORAGE,
1862
+ category: ErrorCategory.THIRD_PARTY,
1863
+ details: { versionId: id }
1864
+ },
1865
+ error
1866
+ );
1867
+ }
1868
+ }
1869
+ async deleteVersionsByParentId(agentId) {
1870
+ try {
1871
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
1872
+ await this.#db.client.none(`DELETE FROM ${tableName} WHERE "agentId" = $1`, [agentId]);
1873
+ } catch (error) {
1874
+ if (error instanceof MastraError) throw error;
1875
+ throw new MastraError(
1876
+ {
1877
+ id: createStorageErrorId("DSQL", "DELETE_VERSIONS_BY_PARENT", "FAILED"),
1878
+ domain: ErrorDomain.STORAGE,
1879
+ category: ErrorCategory.THIRD_PARTY,
1880
+ details: { agentId }
1881
+ },
1882
+ error
1883
+ );
1884
+ }
1885
+ }
1886
+ async countVersions(agentId) {
1887
+ try {
1888
+ const tableName = getTableName2({ indexName: TABLE_AGENT_VERSIONS, schemaName: getSchemaName2(this.#schema) });
1889
+ const result = await this.#db.client.one(`SELECT COUNT(*) as count FROM ${tableName} WHERE "agentId" = $1`, [
1890
+ agentId
1891
+ ]);
1892
+ return parseInt(result.count, 10);
1893
+ } catch (error) {
1894
+ if (error instanceof MastraError) throw error;
1895
+ throw new MastraError(
1896
+ {
1897
+ id: createStorageErrorId("DSQL", "COUNT_VERSIONS", "FAILED"),
1898
+ domain: ErrorDomain.STORAGE,
1899
+ category: ErrorCategory.THIRD_PARTY,
1900
+ details: { agentId }
1901
+ },
1902
+ error
1903
+ );
1904
+ }
1905
+ }
1906
+ // ==========================================================================
1907
+ // Private Helpers
1908
+ // ==========================================================================
1909
+ serializeInstructions(instructions) {
1910
+ if (instructions == null) return void 0;
1911
+ return Array.isArray(instructions) ? JSON.stringify(instructions) : instructions;
1912
+ }
1913
+ deserializeInstructions(raw) {
1914
+ if (!raw) return "";
1915
+ try {
1916
+ const parsed = JSON.parse(raw);
1917
+ if (Array.isArray(parsed)) return parsed;
1918
+ } catch {
1919
+ }
1920
+ return raw;
1921
+ }
1922
+ parseVersionRow(row) {
1923
+ return {
1924
+ id: row.id,
1925
+ agentId: row.agentId,
1926
+ versionNumber: row.versionNumber,
1927
+ name: row.name,
1928
+ description: row.description,
1929
+ instructions: this.deserializeInstructions(row.instructions),
1930
+ model: this.parseJson(row.model, "model"),
1931
+ tools: this.parseJson(row.tools, "tools"),
1932
+ defaultOptions: this.parseJson(row.defaultOptions, "defaultOptions"),
1933
+ workflows: this.parseJson(row.workflows, "workflows"),
1934
+ agents: this.parseJson(row.agents, "agents"),
1935
+ integrationTools: this.parseJson(row.integrationTools, "integrationTools"),
1936
+ inputProcessors: this.parseJson(row.inputProcessors, "inputProcessors"),
1937
+ outputProcessors: this.parseJson(row.outputProcessors, "outputProcessors"),
1938
+ memory: this.parseJson(row.memory, "memory"),
1939
+ scorers: this.parseJson(row.scorers, "scorers"),
1940
+ mcpClients: this.parseJson(row.mcpClients, "mcpClients"),
1941
+ requestContextSchema: this.parseJson(row.requestContextSchema, "requestContextSchema"),
1942
+ workspace: this.parseJson(row.workspace, "workspace"),
1943
+ skills: this.parseJson(row.skills, "skills"),
1944
+ skillsFormat: row.skillsFormat,
1945
+ changedFields: this.parseJson(row.changedFields, "changedFields"),
1946
+ changeMessage: row.changeMessage,
1947
+ createdAt: row.createdAtZ || row.createdAt
1948
+ };
1949
+ }
1950
+ };
1951
+ function inPlaceholders(count, startIndex = 1) {
1952
+ return Array.from({ length: count }, (_, i) => `$${i + startIndex}`).join(", ");
1953
+ }
1954
+ var MemoryDSQL = class _MemoryDSQL extends MemoryStorage {
1955
+ #db;
1956
+ #schema;
1957
+ #skipDefaultIndexes;
1958
+ #indexes;
1959
+ /** Tables managed by this domain */
1960
+ static MANAGED_TABLES = [TABLE_THREADS, TABLE_MESSAGES, TABLE_RESOURCES];
1961
+ constructor(config) {
1962
+ super();
1963
+ const { client, schemaName, skipDefaultIndexes, indexes } = resolveDsqlConfig(config);
1964
+ this.#db = new DsqlDB({ client, schemaName, skipDefaultIndexes });
1965
+ this.#schema = schemaName || "public";
1966
+ this.#skipDefaultIndexes = skipDefaultIndexes;
1967
+ this.#indexes = indexes?.filter((idx) => _MemoryDSQL.MANAGED_TABLES.includes(idx.table));
1968
+ }
1969
+ async init() {
1970
+ await this.#db.createTable({ tableName: TABLE_THREADS, schema: TABLE_SCHEMAS[TABLE_THREADS] });
1971
+ await this.#db.createTable({ tableName: TABLE_MESSAGES, schema: TABLE_SCHEMAS[TABLE_MESSAGES] });
1972
+ await this.#db.createTable({ tableName: TABLE_RESOURCES, schema: TABLE_SCHEMAS[TABLE_RESOURCES] });
1973
+ await this.#db.alterTable({
1974
+ tableName: TABLE_MESSAGES,
1975
+ schema: TABLE_SCHEMAS[TABLE_MESSAGES],
1976
+ ifNotExists: ["resourceId"]
1977
+ });
1978
+ await this.createDefaultIndexes();
1979
+ await this.createCustomIndexes();
1980
+ }
1981
+ /**
1982
+ * Returns default index definitions for the memory domain tables.
1983
+ * Note: Aurora DSQL does not support ASC/DESC in index columns.
1984
+ */
1985
+ getDefaultIndexDefinitions() {
1986
+ const schemaPrefix = this.#schema !== "public" ? `${this.#schema}_` : "";
1987
+ return [
1988
+ {
1989
+ name: `${schemaPrefix}mastra_threads_resourceid_createdat_idx`,
1990
+ table: TABLE_THREADS,
1991
+ columns: ["resourceId", "createdAt"]
1992
+ },
1993
+ {
1994
+ name: `${schemaPrefix}mastra_messages_thread_id_createdat_idx`,
1995
+ table: TABLE_MESSAGES,
1996
+ columns: ["thread_id", "createdAt"]
1997
+ }
1998
+ ];
1999
+ }
2000
+ /**
2001
+ * Creates default indexes for optimal query performance.
2002
+ */
2003
+ async createDefaultIndexes() {
2004
+ if (this.#skipDefaultIndexes) {
2005
+ return;
2006
+ }
2007
+ for (const indexDef of this.getDefaultIndexDefinitions()) {
2008
+ try {
2009
+ await this.#db.createIndex(indexDef);
2010
+ } catch (error) {
2011
+ this.logger?.warn?.(`Failed to create index ${indexDef.name}:`, error);
2012
+ }
2013
+ }
2014
+ }
2015
+ /**
2016
+ * Creates custom user-defined indexes for this domain's tables.
2017
+ */
2018
+ async createCustomIndexes() {
2019
+ if (!this.#indexes || this.#indexes.length === 0) {
2020
+ return;
2021
+ }
2022
+ for (const indexDef of this.#indexes) {
2023
+ try {
2024
+ await this.#db.createIndex(indexDef);
2025
+ } catch (error) {
2026
+ this.logger?.warn?.(`Failed to create custom index ${indexDef.name}:`, error);
2027
+ }
2028
+ }
2029
+ }
2030
+ async dangerouslyClearAll() {
2031
+ await this.#db.clearTable({ tableName: TABLE_MESSAGES });
2032
+ await this.#db.clearTable({ tableName: TABLE_THREADS });
2033
+ await this.#db.clearTable({ tableName: TABLE_RESOURCES });
2034
+ }
2035
+ /**
2036
+ * Normalizes message row from database by applying createdAtZ fallback
2037
+ */
2038
+ normalizeMessageRow(row) {
2039
+ return {
2040
+ id: row.id,
2041
+ content: row.content,
2042
+ role: row.role,
2043
+ type: row.type,
2044
+ createdAt: row.createdAtZ || row.createdAt,
2045
+ threadId: row.threadId,
2046
+ resourceId: row.resourceId
2047
+ };
2048
+ }
2049
+ async getThreadById({ threadId }) {
2050
+ try {
2051
+ const tableName = getTableName2({ indexName: TABLE_THREADS, schemaName: getSchemaName2(this.#schema) });
2052
+ const thread = await this.#db.client.oneOrNone(
2053
+ `SELECT * FROM ${tableName} WHERE id = $1`,
2054
+ [threadId]
2055
+ );
2056
+ if (!thread) {
2057
+ return null;
2058
+ }
2059
+ return {
2060
+ id: thread.id,
2061
+ resourceId: thread.resourceId,
2062
+ title: thread.title,
2063
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
2064
+ createdAt: thread.createdAtZ || thread.createdAt,
2065
+ updatedAt: thread.updatedAtZ || thread.updatedAt
2066
+ };
2067
+ } catch (error) {
2068
+ throw new MastraError(
2069
+ {
2070
+ id: createStorageErrorId("DSQL", "GET_THREAD_BY_ID", "FAILED"),
2071
+ domain: ErrorDomain.STORAGE,
2072
+ category: ErrorCategory.THIRD_PARTY,
2073
+ details: {
2074
+ threadId
2075
+ }
2076
+ },
2077
+ error
2078
+ );
2079
+ }
2080
+ }
2081
+ async listThreads(args) {
2082
+ const { page = 0, perPage: perPageInput, orderBy, filter } = args;
2083
+ try {
2084
+ this.validatePaginationInput(page, perPageInput ?? 100);
2085
+ } catch (error) {
2086
+ throw new MastraError({
2087
+ id: createStorageErrorId("DSQL", "LIST_THREADS", "INVALID_PAGE"),
2088
+ domain: ErrorDomain.STORAGE,
2089
+ category: ErrorCategory.USER,
2090
+ text: error instanceof Error ? error.message : "Invalid pagination parameters",
2091
+ details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } }
2092
+ });
2093
+ }
2094
+ const perPage = normalizePerPage(perPageInput, 100);
2095
+ try {
2096
+ this.validateMetadataKeys(filter?.metadata);
2097
+ } catch (error) {
2098
+ throw new MastraError({
2099
+ id: createStorageErrorId("DSQL", "LIST_THREADS", "INVALID_METADATA_KEY"),
2100
+ domain: ErrorDomain.STORAGE,
2101
+ category: ErrorCategory.USER,
2102
+ text: error instanceof Error ? error.message : "Invalid metadata key",
2103
+ details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" }
2104
+ });
2105
+ }
2106
+ const { field, direction } = this.parseOrderBy(orderBy);
2107
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2108
+ try {
2109
+ const tableName = getTableName2({ indexName: TABLE_THREADS, schemaName: getSchemaName2(this.#schema) });
2110
+ const whereClauses = [];
2111
+ const queryParams = [];
2112
+ let paramIndex = 1;
2113
+ if (filter?.resourceId) {
2114
+ whereClauses.push(`"resourceId" = ${paramIndex}`);
2115
+ queryParams.push(filter.resourceId);
2116
+ paramIndex++;
2117
+ }
2118
+ if (filter?.metadata && Object.keys(filter.metadata).length > 0) {
2119
+ for (const [key, value] of Object.entries(filter.metadata)) {
2120
+ whereClauses.push(`metadata::jsonb @> ${paramIndex}::jsonb`);
2121
+ queryParams.push(JSON.stringify({ [key]: value }));
2122
+ paramIndex++;
2123
+ }
2124
+ }
2125
+ const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
2126
+ const baseQuery = `FROM ${tableName} ${whereClause}`;
2127
+ const countQuery = `SELECT COUNT(*) ${baseQuery}`;
2128
+ const countResult = await this.#db.client.one(countQuery, queryParams);
2129
+ const total = parseInt(countResult.count, 10);
2130
+ if (total === 0) {
2131
+ return {
2132
+ threads: [],
2133
+ total: 0,
2134
+ page,
2135
+ perPage: perPageForResponse,
2136
+ hasMore: false
2137
+ };
2138
+ }
2139
+ const limitValue = perPageInput === false ? total : perPage;
2140
+ const dataQuery = `SELECT id, "resourceId", title, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ" ${baseQuery} ORDER BY "${field}" ${direction} LIMIT ${paramIndex} OFFSET ${paramIndex + 1}`;
2141
+ const rows = await this.#db.client.manyOrNone(
2142
+ dataQuery,
2143
+ [...queryParams, limitValue, offset]
2144
+ );
2145
+ const threads = (rows || []).map((thread) => ({
2146
+ id: thread.id,
2147
+ resourceId: thread.resourceId,
2148
+ title: thread.title,
2149
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
2150
+ createdAt: thread.createdAtZ || thread.createdAt,
2151
+ updatedAt: thread.updatedAtZ || thread.updatedAt
2152
+ }));
2153
+ return {
2154
+ threads,
2155
+ total,
2156
+ page,
2157
+ perPage: perPageForResponse,
2158
+ hasMore: perPageInput === false ? false : offset + perPage < total
2159
+ };
2160
+ } catch (error) {
2161
+ const mastraError = new MastraError(
2162
+ {
2163
+ id: createStorageErrorId("DSQL", "LIST_THREADS", "FAILED"),
2164
+ domain: ErrorDomain.STORAGE,
2165
+ category: ErrorCategory.THIRD_PARTY,
2166
+ details: {
2167
+ ...filter?.resourceId && { resourceId: filter.resourceId },
2168
+ hasMetadataFilter: !!filter?.metadata,
2169
+ page
2170
+ }
2171
+ },
2172
+ error
2173
+ );
2174
+ this.logger?.error?.(mastraError.toString());
2175
+ this.logger?.trackException(mastraError);
2176
+ return {
2177
+ threads: [],
2178
+ total: 0,
2179
+ page,
2180
+ perPage: perPageForResponse,
2181
+ hasMore: false
2182
+ };
2183
+ }
2184
+ }
2185
+ async saveThread({ thread }) {
2186
+ const tableName = getTableName2({ indexName: TABLE_THREADS, schemaName: getSchemaName2(this.#schema) });
2187
+ await withRetry(
2188
+ async () => {
2189
+ await this.#db.client.none(
2190
+ `INSERT INTO ${tableName} (
2191
+ id,
2192
+ "resourceId",
2193
+ title,
2194
+ metadata,
2195
+ "createdAt",
2196
+ "createdAtZ",
2197
+ "updatedAt",
2198
+ "updatedAtZ"
2199
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
2200
+ ON CONFLICT (id) DO UPDATE SET
2201
+ "resourceId" = EXCLUDED."resourceId",
2202
+ title = EXCLUDED.title,
2203
+ metadata = EXCLUDED.metadata,
2204
+ "createdAt" = EXCLUDED."createdAt",
2205
+ "createdAtZ" = EXCLUDED."createdAtZ",
2206
+ "updatedAt" = EXCLUDED."updatedAt",
2207
+ "updatedAtZ" = EXCLUDED."updatedAtZ"`,
2208
+ [
2209
+ thread.id,
2210
+ thread.resourceId,
2211
+ thread.title,
2212
+ thread.metadata ? JSON.stringify(thread.metadata) : null,
2213
+ thread.createdAt,
2214
+ thread.createdAt,
2215
+ thread.updatedAt,
2216
+ thread.updatedAt
2217
+ ]
2218
+ );
2219
+ },
2220
+ {
2221
+ onRetry: (error, attempt, delay) => {
2222
+ this.logger?.warn?.(`saveThread retry ${attempt} for ${thread.id} after ${delay}ms: ${error.message}`);
2223
+ }
2224
+ }
2225
+ ).catch((error) => {
2226
+ throw new MastraError(
2227
+ {
2228
+ id: createStorageErrorId("DSQL", "SAVE_THREAD", "FAILED"),
2229
+ domain: ErrorDomain.STORAGE,
2230
+ category: ErrorCategory.THIRD_PARTY,
2231
+ details: {
2232
+ threadId: thread.id
2233
+ }
2234
+ },
2235
+ error
2236
+ );
2237
+ });
2238
+ return thread;
2239
+ }
2240
+ async updateThread({
2241
+ id,
2242
+ title,
2243
+ metadata
2244
+ }) {
2245
+ const threadTableName = getTableName2({ indexName: TABLE_THREADS, schemaName: getSchemaName2(this.#schema) });
2246
+ const { result } = await withRetry(
2247
+ async () => {
2248
+ const existingThread = await this.getThreadById({ threadId: id });
2249
+ if (!existingThread) {
2250
+ throw new MastraError({
2251
+ id: createStorageErrorId("DSQL", "UPDATE_THREAD", "NOT_FOUND"),
2252
+ domain: ErrorDomain.STORAGE,
2253
+ category: ErrorCategory.USER,
2254
+ text: `Thread ${id} not found`,
2255
+ details: {
2256
+ threadId: id,
2257
+ title
2258
+ }
2259
+ });
2260
+ }
2261
+ const mergedMetadata = {
2262
+ ...existingThread.metadata,
2263
+ ...metadata
2264
+ };
2265
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2266
+ const thread = await this.#db.client.one(
2267
+ `UPDATE ${threadTableName}
2268
+ SET
2269
+ title = $1,
2270
+ metadata = $2,
2271
+ "updatedAt" = $3::timestamp,
2272
+ "updatedAtZ" = $4::timestamptz
2273
+ WHERE id = $5
2274
+ RETURNING *
2275
+ `,
2276
+ [title, JSON.stringify(mergedMetadata), now, now, id]
2277
+ );
2278
+ return {
2279
+ id: thread.id,
2280
+ resourceId: thread.resourceId,
2281
+ title: thread.title,
2282
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata,
2283
+ createdAt: thread.createdAtZ || thread.createdAt,
2284
+ updatedAt: thread.updatedAtZ || thread.updatedAt
2285
+ };
2286
+ },
2287
+ {
2288
+ onRetry: (error, attempt, delay) => {
2289
+ this.logger?.warn?.(`updateThread retry ${attempt} for ${id} after ${delay}ms: ${error.message}`);
2290
+ }
2291
+ }
2292
+ ).catch((error) => {
2293
+ if (error instanceof MastraError) {
2294
+ throw error;
2295
+ }
2296
+ throw new MastraError(
2297
+ {
2298
+ id: createStorageErrorId("DSQL", "UPDATE_THREAD", "FAILED"),
2299
+ domain: ErrorDomain.STORAGE,
2300
+ category: ErrorCategory.THIRD_PARTY,
2301
+ details: {
2302
+ threadId: id,
2303
+ title
2304
+ }
2305
+ },
2306
+ error
2307
+ );
2308
+ });
2309
+ return result;
2310
+ }
2311
+ async deleteThread({ threadId }) {
2312
+ const tableName = getTableName2({ indexName: TABLE_MESSAGES, schemaName: getSchemaName2(this.#schema) });
2313
+ const threadTableName = getTableName2({ indexName: TABLE_THREADS, schemaName: getSchemaName2(this.#schema) });
2314
+ await withRetry(
2315
+ async () => {
2316
+ await this.#db.client.tx(async (t) => {
2317
+ await t.none(`DELETE FROM ${tableName} WHERE thread_id = $1`, [threadId]);
2318
+ await t.none(`DELETE FROM ${threadTableName} WHERE id = $1`, [threadId]);
2319
+ });
2320
+ },
2321
+ {
2322
+ onRetry: (error, attempt, delay) => {
2323
+ this.logger?.warn?.(`deleteThread retry ${attempt} for ${threadId} after ${delay}ms: ${error.message}`);
2324
+ }
2325
+ }
2326
+ ).catch((error) => {
2327
+ throw new MastraError(
2328
+ {
2329
+ id: createStorageErrorId("DSQL", "DELETE_THREAD", "FAILED"),
2330
+ domain: ErrorDomain.STORAGE,
2331
+ category: ErrorCategory.THIRD_PARTY,
2332
+ details: {
2333
+ threadId
2334
+ }
2335
+ },
2336
+ error
2337
+ );
2338
+ });
2339
+ }
2340
+ async _getIncludedMessages({ include }) {
2341
+ if (!include || include.length === 0) return null;
2342
+ const unionQueries = [];
2343
+ const params = [];
2344
+ let paramIdx = 1;
2345
+ const tableName = getTableName2({ indexName: TABLE_MESSAGES, schemaName: getSchemaName2(this.#schema) });
2346
+ for (const inc of include) {
2347
+ const { id, withPreviousMessages = 0, withNextMessages = 0 } = inc;
2348
+ unionQueries.push(
2349
+ `
2350
+ SELECT * FROM (
2351
+ WITH target_thread AS (
2352
+ SELECT thread_id FROM ${tableName} WHERE id = $${paramIdx}
2353
+ ),
2354
+ ordered_messages AS (
2355
+ SELECT
2356
+ *,
2357
+ ROW_NUMBER() OVER (ORDER BY "createdAt" ASC) as row_num
2358
+ FROM ${tableName}
2359
+ WHERE thread_id = (SELECT thread_id FROM target_thread)
2360
+ )
2361
+ SELECT
2362
+ m.id,
2363
+ m.content,
2364
+ m.role,
2365
+ m.type,
2366
+ m."createdAt",
2367
+ m."createdAtZ",
2368
+ m.thread_id AS "threadId",
2369
+ m."resourceId"
2370
+ FROM ordered_messages m
2371
+ WHERE m.id = $${paramIdx}
2372
+ OR EXISTS (
2373
+ SELECT 1 FROM ordered_messages target
2374
+ WHERE target.id = $${paramIdx}
2375
+ AND (
2376
+ (m.row_num < target.row_num AND m.row_num >= target.row_num - $${paramIdx + 1})
2377
+ OR
2378
+ (m.row_num > target.row_num AND m.row_num <= target.row_num + $${paramIdx + 2})
2379
+ )
2380
+ )
2381
+ ) AS query_${paramIdx}
2382
+ `
2383
+ );
2384
+ params.push(id, withPreviousMessages, withNextMessages);
2385
+ paramIdx += 3;
2386
+ }
2387
+ const finalQuery = unionQueries.join(" UNION ALL ") + ' ORDER BY "createdAt" ASC';
2388
+ const includedRows = await this.#db.client.manyOrNone(finalQuery, params);
2389
+ const seen = /* @__PURE__ */ new Set();
2390
+ const dedupedRows = includedRows.filter((row) => {
2391
+ if (seen.has(row.id)) return false;
2392
+ seen.add(row.id);
2393
+ return true;
2394
+ });
2395
+ return dedupedRows;
2396
+ }
2397
+ parseRow(row) {
2398
+ const normalized = this.normalizeMessageRow(row);
2399
+ let content = normalized.content;
2400
+ try {
2401
+ content = JSON.parse(normalized.content);
2402
+ } catch {
2403
+ }
2404
+ return {
2405
+ id: normalized.id,
2406
+ content,
2407
+ role: normalized.role,
2408
+ createdAt: new Date(normalized.createdAt),
2409
+ threadId: normalized.threadId,
2410
+ resourceId: normalized.resourceId,
2411
+ ...normalized.type && normalized.type !== "v2" ? { type: normalized.type } : {}
2412
+ };
2413
+ }
2414
+ async listMessagesById({ messageIds }) {
2415
+ if (messageIds.length === 0) return { messages: [] };
2416
+ const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
2417
+ try {
2418
+ const tableName = getTableName2({ indexName: TABLE_MESSAGES, schemaName: getSchemaName2(this.#schema) });
2419
+ const query = `
2420
+ ${selectStatement} FROM ${tableName}
2421
+ WHERE id IN (${inPlaceholders(messageIds.length)})
2422
+ ORDER BY "createdAt" DESC
2423
+ `;
2424
+ const resultRows = await this.#db.client.manyOrNone(query, messageIds);
2425
+ const list = new MessageList().add(
2426
+ resultRows.map((row) => this.parseRow(row)),
2427
+ "memory"
2428
+ );
2429
+ return { messages: list.get.all.db() };
2430
+ } catch (error) {
2431
+ const mastraError = new MastraError(
2432
+ {
2433
+ id: createStorageErrorId("DSQL", "LIST_MESSAGES_BY_ID", "FAILED"),
2434
+ domain: ErrorDomain.STORAGE,
2435
+ category: ErrorCategory.THIRD_PARTY,
2436
+ details: {
2437
+ messageIds: JSON.stringify(messageIds)
2438
+ }
2439
+ },
2440
+ error
2441
+ );
2442
+ this.logger?.error?.(mastraError.toString());
2443
+ this.logger?.trackException(mastraError);
2444
+ return { messages: [] };
2445
+ }
2446
+ }
2447
+ async listMessages(args) {
2448
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
2449
+ const threadIds = (Array.isArray(threadId) ? threadId : [threadId]).filter(
2450
+ (id) => typeof id === "string"
2451
+ );
2452
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
2453
+ throw new MastraError(
2454
+ {
2455
+ id: createStorageErrorId("DSQL", "LIST_MESSAGES", "INVALID_THREAD_ID"),
2456
+ domain: ErrorDomain.STORAGE,
2457
+ category: ErrorCategory.USER,
2458
+ details: { threadId: Array.isArray(threadId) ? String(threadId) : String(threadId) }
2459
+ },
2460
+ new Error("threadId must be a non-empty string or array of non-empty strings")
2461
+ );
2462
+ }
2463
+ if (page < 0) {
2464
+ throw new MastraError({
2465
+ id: createStorageErrorId("DSQL", "LIST_MESSAGES", "INVALID_PAGE"),
2466
+ domain: ErrorDomain.STORAGE,
2467
+ category: ErrorCategory.USER,
2468
+ text: "Page number must be non-negative",
2469
+ details: {
2470
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
2471
+ page
2472
+ }
2473
+ });
2474
+ }
2475
+ const perPage = normalizePerPage(perPageInput, 40);
2476
+ const { offset, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
2477
+ try {
2478
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
2479
+ const orderByStatement = `ORDER BY "${field}" ${direction}`;
2480
+ const selectStatement = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId"`;
2481
+ const tableName = getTableName2({ indexName: TABLE_MESSAGES, schemaName: getSchemaName2(this.#schema) });
2482
+ const conditions = [`thread_id IN (${inPlaceholders(threadIds.length)})`];
2483
+ const queryParams = [...threadIds];
2484
+ let paramIndex = threadIds.length + 1;
2485
+ if (resourceId) {
2486
+ conditions.push(`"resourceId" = $${paramIndex++}`);
2487
+ queryParams.push(resourceId);
2488
+ }
2489
+ if (filter?.dateRange?.start) {
2490
+ conditions.push(`"createdAtZ" >= $${paramIndex++}::timestamptz`);
2491
+ queryParams.push(filter.dateRange.start);
2492
+ }
2493
+ if (filter?.dateRange?.end) {
2494
+ conditions.push(`"createdAtZ" <= $${paramIndex++}::timestamptz`);
2495
+ queryParams.push(filter.dateRange.end);
2496
+ }
2497
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2498
+ const countQuery = `SELECT COUNT(*) FROM ${tableName} ${whereClause}`;
2499
+ const countResult = await this.#db.client.one(countQuery, queryParams);
2500
+ const total = parseInt(countResult.count, 10);
2501
+ const limitValue = perPageInput === false ? total : perPage;
2502
+ const dataQuery = `${selectStatement} FROM ${tableName} ${whereClause} ${orderByStatement} LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
2503
+ const rows = await this.#db.client.manyOrNone(dataQuery, [...queryParams, limitValue, offset]);
2504
+ const messages = [...rows || []];
2505
+ if (total === 0 && messages.length === 0 && (!include || include.length === 0)) {
2506
+ return {
2507
+ messages: [],
2508
+ total: 0,
2509
+ page,
2510
+ perPage: perPageForResponse,
2511
+ hasMore: false
2512
+ };
2513
+ }
2514
+ const messageIds = new Set(messages.map((m) => m.id));
2515
+ if (include && include.length > 0) {
2516
+ const includeMessages = await this._getIncludedMessages({ include });
2517
+ if (includeMessages) {
2518
+ for (const includeMsg of includeMessages) {
2519
+ if (!messageIds.has(includeMsg.id)) {
2520
+ messages.push(includeMsg);
2521
+ messageIds.add(includeMsg.id);
2522
+ }
2523
+ }
2524
+ }
2525
+ }
2526
+ const messagesWithParsedContent = messages.map((row) => this.parseRow(row));
2527
+ const list = new MessageList().add(messagesWithParsedContent, "memory");
2528
+ let finalMessages = list.get.all.db();
2529
+ finalMessages = finalMessages.sort((a, b) => {
2530
+ const aValue = field === "createdAt" ? new Date(a.createdAt).getTime() : a[field];
2531
+ const bValue = field === "createdAt" ? new Date(b.createdAt).getTime() : b[field];
2532
+ if (aValue == null && bValue == null) return a.id.localeCompare(b.id);
2533
+ if (aValue == null) return 1;
2534
+ if (bValue == null) return -1;
2535
+ if (aValue === bValue) {
2536
+ return a.id.localeCompare(b.id);
2537
+ }
2538
+ if (typeof aValue === "number" && typeof bValue === "number") {
2539
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
2540
+ }
2541
+ return direction === "ASC" ? String(aValue).localeCompare(String(bValue)) : String(bValue).localeCompare(String(aValue));
2542
+ });
2543
+ const threadIdSet = new Set(threadIds);
2544
+ const returnedThreadMessageIds = new Set(
2545
+ finalMessages.filter((m) => m.threadId && threadIdSet.has(m.threadId)).map((m) => m.id)
2546
+ );
2547
+ const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
2548
+ const hasMore = perPageInput !== false && !allThreadMessagesReturned && offset + perPage < total;
2549
+ return {
2550
+ messages: finalMessages,
2551
+ total,
2552
+ page,
2553
+ perPage: perPageForResponse,
2554
+ hasMore
2555
+ };
2556
+ } catch (error) {
2557
+ const mastraError = new MastraError(
2558
+ {
2559
+ id: createStorageErrorId("DSQL", "LIST_MESSAGES", "FAILED"),
2560
+ domain: ErrorDomain.STORAGE,
2561
+ category: ErrorCategory.THIRD_PARTY,
2562
+ details: {
2563
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
2564
+ resourceId: resourceId ?? ""
2565
+ }
2566
+ },
2567
+ error
2568
+ );
2569
+ this.logger?.error?.(mastraError.toString());
2570
+ this.logger?.trackException(mastraError);
2571
+ return {
2572
+ messages: [],
2573
+ total: 0,
2574
+ page,
2575
+ perPage: perPageForResponse,
2576
+ hasMore: false
2577
+ };
2578
+ }
2579
+ }
2580
+ async saveMessages({ messages }) {
2581
+ if (messages.length === 0) return { messages: [] };
2582
+ const threadId = messages[0]?.threadId;
2583
+ if (!threadId) {
2584
+ throw new MastraError({
2585
+ id: createStorageErrorId("DSQL", "SAVE_MESSAGES", "FAILED"),
2586
+ domain: ErrorDomain.STORAGE,
2587
+ category: ErrorCategory.USER,
2588
+ text: `Thread ID is required`
2589
+ });
2590
+ }
2591
+ const thread = await this.getThreadById({ threadId });
2592
+ if (!thread) {
2593
+ throw new MastraError({
2594
+ id: createStorageErrorId("DSQL", "SAVE_MESSAGES", "FAILED"),
2595
+ domain: ErrorDomain.STORAGE,
2596
+ category: ErrorCategory.USER,
2597
+ text: `Thread ${threadId} not found`,
2598
+ details: {
2599
+ threadId
2600
+ }
2601
+ });
2602
+ }
2603
+ const tableName = getTableName2({ indexName: TABLE_MESSAGES, schemaName: getSchemaName2(this.#schema) });
2604
+ const threadTableName = getTableName2({ indexName: TABLE_THREADS, schemaName: getSchemaName2(this.#schema) });
2605
+ await withRetry(
2606
+ async () => {
2607
+ await this.#db.client.tx(async (t) => {
2608
+ const messageInserts = messages.map((message) => {
2609
+ if (!message.threadId) {
2610
+ throw new Error(
2611
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
2612
+ );
2613
+ }
2614
+ if (!message.resourceId) {
2615
+ throw new Error(
2616
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
2617
+ );
2618
+ }
2619
+ const createdAtIso = message.createdAt ? new Date(message.createdAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
2620
+ return t.none(
2621
+ `INSERT INTO ${tableName} (id, thread_id, content, "createdAt", "createdAtZ", role, type, "resourceId")
2622
+ VALUES ($1, $2, $3, $4::timestamp, $5::timestamptz, $6, $7, $8)
2623
+ ON CONFLICT (id) DO UPDATE SET
2624
+ thread_id = EXCLUDED.thread_id,
2625
+ content = EXCLUDED.content,
2626
+ role = EXCLUDED.role,
2627
+ type = EXCLUDED.type,
2628
+ "resourceId" = EXCLUDED."resourceId"`,
2629
+ [
2630
+ message.id,
2631
+ message.threadId,
2632
+ typeof message.content === "string" ? message.content : JSON.stringify(message.content),
2633
+ createdAtIso,
2634
+ createdAtIso,
2635
+ message.role,
2636
+ message.type || "v2",
2637
+ message.resourceId
2638
+ ]
2639
+ );
2640
+ });
2641
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
2642
+ const threadUpdate = t.none(
2643
+ `UPDATE ${threadTableName}
2644
+ SET
2645
+ "updatedAt" = $1::timestamp,
2646
+ "updatedAtZ" = $2::timestamptz
2647
+ WHERE id = $3
2648
+ `,
2649
+ [nowIso, nowIso, threadId]
2650
+ );
2651
+ await Promise.all([...messageInserts, threadUpdate]);
2652
+ });
2653
+ },
2654
+ {
2655
+ onRetry: (error, attempt, delay) => {
2656
+ this.logger?.warn?.(
2657
+ `saveMessages retry ${attempt} for thread ${threadId} after ${delay}ms: ${error.message}`
2658
+ );
2659
+ }
2660
+ }
2661
+ ).catch((error) => {
2662
+ throw new MastraError(
2663
+ {
2664
+ id: createStorageErrorId("DSQL", "SAVE_MESSAGES", "FAILED"),
2665
+ domain: ErrorDomain.STORAGE,
2666
+ category: ErrorCategory.THIRD_PARTY,
2667
+ details: {
2668
+ threadId
2669
+ }
2670
+ },
2671
+ error
2672
+ );
2673
+ });
2674
+ const messagesWithParsedContent = messages.map((message) => {
2675
+ if (typeof message.content === "string") {
2676
+ try {
2677
+ return { ...message, content: JSON.parse(message.content) };
2678
+ } catch {
2679
+ return message;
2680
+ }
2681
+ }
2682
+ return message;
2683
+ });
2684
+ const list = new MessageList().add(messagesWithParsedContent, "memory");
2685
+ return { messages: list.get.all.db() };
2686
+ }
2687
+ async updateMessages({
2688
+ messages
2689
+ }) {
2690
+ if (messages.length === 0) {
2691
+ return [];
2692
+ }
2693
+ const messageIds = messages.map((m) => m.id);
2694
+ const selectQuery = `SELECT id, content, role, type, "createdAt", "createdAtZ", thread_id AS "threadId", "resourceId" FROM ${getTableName2({ indexName: TABLE_MESSAGES, schemaName: getSchemaName2(this.#schema) })} WHERE id IN (${inPlaceholders(messageIds.length)})`;
2695
+ const existingMessagesDb = await this.#db.client.manyOrNone(selectQuery, messageIds);
2696
+ if (existingMessagesDb.length === 0) {
2697
+ return [];
2698
+ }
2699
+ const existingMessages = existingMessagesDb.map((msg) => {
2700
+ if (typeof msg.content === "string") {
2701
+ try {
2702
+ msg.content = JSON.parse(msg.content);
2703
+ } catch {
2704
+ }
2705
+ }
2706
+ return msg;
2707
+ });
2708
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
2709
+ await withRetry(
2710
+ async () => {
2711
+ await this.#db.client.tx(async (t) => {
2712
+ const queries = [];
2713
+ const columnMapping = {
2714
+ threadId: "thread_id"
2715
+ };
2716
+ for (const existingMessage of existingMessages) {
2717
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
2718
+ if (!updatePayload) continue;
2719
+ const { id, ...fieldsToUpdate } = updatePayload;
2720
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
2721
+ threadIdsToUpdate.add(existingMessage.threadId);
2722
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
2723
+ threadIdsToUpdate.add(updatePayload.threadId);
2724
+ }
2725
+ const setClauses = [];
2726
+ const values = [];
2727
+ let paramIndex = 1;
2728
+ const updatableFields = { ...fieldsToUpdate };
2729
+ if (updatableFields.content) {
2730
+ const newContent = {
2731
+ ...existingMessage.content,
2732
+ ...updatableFields.content,
2733
+ ...existingMessage.content?.metadata && updatableFields.content.metadata ? {
2734
+ metadata: {
2735
+ ...existingMessage.content.metadata,
2736
+ ...updatableFields.content.metadata
2737
+ }
2738
+ } : {}
2739
+ };
2740
+ setClauses.push(`content = $${paramIndex++}`);
2741
+ values.push(newContent);
2742
+ delete updatableFields.content;
2743
+ }
2744
+ for (const key in updatableFields) {
2745
+ if (Object.prototype.hasOwnProperty.call(updatableFields, key)) {
2746
+ const dbColumn = columnMapping[key] || key;
2747
+ setClauses.push(`"${dbColumn}" = $${paramIndex++}`);
2748
+ values.push(updatableFields[key]);
2749
+ }
2750
+ }
2751
+ if (setClauses.length > 0) {
2752
+ values.push(id);
2753
+ const sql = `UPDATE ${getTableName2({ indexName: TABLE_MESSAGES, schemaName: getSchemaName2(this.#schema) })} SET ${setClauses.join(", ")} WHERE id = $${paramIndex}`;
2754
+ queries.push(t.none(sql, values));
2755
+ }
2756
+ }
2757
+ if (threadIdsToUpdate.size > 0) {
2758
+ const threadIds = Array.from(threadIdsToUpdate);
2759
+ queries.push(
2760
+ t.none(
2761
+ `UPDATE ${getTableName2({ indexName: TABLE_THREADS, schemaName: getSchemaName2(this.#schema) })} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id IN (${inPlaceholders(threadIds.length)})`,
2762
+ threadIds
2763
+ )
2764
+ );
2765
+ }
2766
+ if (queries.length > 0) {
2767
+ await t.batch(queries);
2768
+ }
2769
+ });
2770
+ },
2771
+ {
2772
+ onRetry: (error, attempt, delay) => {
2773
+ this.logger?.warn?.(
2774
+ `updateMessages retry ${attempt} for ${messageIds.length} messages after ${delay}ms: ${error.message}`
2775
+ );
2776
+ }
2777
+ }
2778
+ ).catch((error) => {
2779
+ throw new MastraError(
2780
+ {
2781
+ id: createStorageErrorId("DSQL", "UPDATE_MESSAGES", "FAILED"),
2782
+ domain: ErrorDomain.STORAGE,
2783
+ category: ErrorCategory.THIRD_PARTY,
2784
+ details: {
2785
+ messageIdsLength: messageIds.length
2786
+ }
2787
+ },
2788
+ error
2789
+ );
2790
+ });
2791
+ const updatedMessages = await this.#db.client.manyOrNone(selectQuery, messageIds);
2792
+ return (updatedMessages || []).map((row) => {
2793
+ const message = this.normalizeMessageRow(row);
2794
+ if (typeof message.content === "string") {
2795
+ try {
2796
+ return { ...message, content: JSON.parse(message.content) };
2797
+ } catch {
2798
+ }
2799
+ }
2800
+ return message;
2801
+ });
2802
+ }
2803
+ async deleteMessages(messageIds) {
2804
+ if (!messageIds || messageIds.length === 0) {
2805
+ return;
2806
+ }
2807
+ const messageTableName = getTableName2({ indexName: TABLE_MESSAGES, schemaName: getSchemaName2(this.#schema) });
2808
+ const threadTableName = getTableName2({ indexName: TABLE_THREADS, schemaName: getSchemaName2(this.#schema) });
2809
+ await withRetry(
2810
+ async () => {
2811
+ await this.#db.client.tx(async (t) => {
2812
+ const placeholders = messageIds.map((_, idx) => `$${idx + 1}`).join(",");
2813
+ const messages = await t.manyOrNone(
2814
+ `SELECT DISTINCT thread_id FROM ${messageTableName} WHERE id IN (${placeholders})`,
2815
+ messageIds
2816
+ );
2817
+ const threadIds = messages?.map((msg) => msg.thread_id).filter(Boolean) || [];
2818
+ await t.none(`DELETE FROM ${messageTableName} WHERE id IN (${placeholders})`, messageIds);
2819
+ if (threadIds.length > 0) {
2820
+ const updatePromises = threadIds.map(
2821
+ (threadId) => t.none(`UPDATE ${threadTableName} SET "updatedAt" = NOW(), "updatedAtZ" = NOW() WHERE id = $1`, [
2822
+ threadId
2823
+ ])
2824
+ );
2825
+ await Promise.all(updatePromises);
2826
+ }
2827
+ });
2828
+ },
2829
+ {
2830
+ onRetry: (error, attempt, delay) => {
2831
+ this.logger?.warn?.(
2832
+ `deleteMessages retry ${attempt} for ${messageIds.length} messages after ${delay}ms: ${error.message}`
2833
+ );
2834
+ }
2835
+ }
2836
+ ).catch((error) => {
2837
+ throw new MastraError(
2838
+ {
2839
+ id: createStorageErrorId("DSQL", "DELETE_MESSAGES", "FAILED"),
2840
+ domain: ErrorDomain.STORAGE,
2841
+ category: ErrorCategory.THIRD_PARTY,
2842
+ details: { messageIds: messageIds.join(", ") }
2843
+ },
2844
+ error
2845
+ );
2846
+ });
2847
+ }
2848
+ async getResourceById({ resourceId }) {
2849
+ try {
2850
+ const tableName = getTableName2({ indexName: TABLE_RESOURCES, schemaName: getSchemaName2(this.#schema) });
2851
+ const result = await this.#db.client.oneOrNone(
2852
+ `SELECT * FROM ${tableName} WHERE id = $1`,
2853
+ [resourceId]
2854
+ );
2855
+ if (!result) {
2856
+ return null;
2857
+ }
2858
+ return {
2859
+ id: result.id,
2860
+ createdAt: result.createdAtZ || result.createdAt,
2861
+ updatedAt: result.updatedAtZ || result.updatedAt,
2862
+ workingMemory: result.workingMemory,
2863
+ metadata: typeof result.metadata === "string" ? JSON.parse(result.metadata) : result.metadata
2864
+ };
2865
+ } catch (error) {
2866
+ throw new MastraError(
2867
+ {
2868
+ id: createStorageErrorId("DSQL", "GET_RESOURCE_BY_ID", "FAILED"),
2869
+ domain: ErrorDomain.STORAGE,
2870
+ category: ErrorCategory.THIRD_PARTY,
2871
+ details: { resourceId }
2872
+ },
2873
+ error
2874
+ );
2875
+ }
2876
+ }
2877
+ async saveResource({ resource }) {
2878
+ await this.#db.insert({
2879
+ tableName: TABLE_RESOURCES,
2880
+ record: {
2881
+ ...resource,
2882
+ metadata: JSON.stringify(resource.metadata)
2883
+ }
2884
+ });
2885
+ return resource;
2886
+ }
2887
+ async updateResource({
2888
+ resourceId,
2889
+ workingMemory,
2890
+ metadata
2891
+ }) {
2892
+ const tableName = getTableName2({ indexName: TABLE_RESOURCES, schemaName: getSchemaName2(this.#schema) });
2893
+ const { result } = await withRetry(
2894
+ async () => {
2895
+ const existingResource = await this.getResourceById({ resourceId });
2896
+ if (!existingResource) {
2897
+ const newResource = {
2898
+ id: resourceId,
2899
+ workingMemory,
2900
+ metadata: metadata || {},
2901
+ createdAt: /* @__PURE__ */ new Date(),
2902
+ updatedAt: /* @__PURE__ */ new Date()
2903
+ };
2904
+ return this.saveResource({ resource: newResource });
2905
+ }
2906
+ const updatedResource = {
2907
+ ...existingResource,
2908
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
2909
+ metadata: {
2910
+ ...existingResource.metadata,
2911
+ ...metadata
2912
+ },
2913
+ updatedAt: /* @__PURE__ */ new Date()
2914
+ };
2915
+ const updates = [];
2916
+ const values = [];
2917
+ let paramIndex = 1;
2918
+ if (workingMemory !== void 0) {
2919
+ updates.push(`"workingMemory" = $${paramIndex}`);
2920
+ values.push(workingMemory);
2921
+ paramIndex++;
2922
+ }
2923
+ if (metadata) {
2924
+ updates.push(`metadata = $${paramIndex}`);
2925
+ values.push(JSON.stringify(updatedResource.metadata));
2926
+ paramIndex++;
2927
+ }
2928
+ updates.push(`"updatedAt" = $${paramIndex}`);
2929
+ values.push(updatedResource.updatedAt.toISOString());
2930
+ paramIndex++;
2931
+ updates.push(`"updatedAtZ" = $${paramIndex}`);
2932
+ values.push(updatedResource.updatedAt.toISOString());
2933
+ paramIndex++;
2934
+ values.push(resourceId);
2935
+ await this.#db.client.none(`UPDATE ${tableName} SET ${updates.join(", ")} WHERE id = $${paramIndex}`, values);
2936
+ return updatedResource;
2937
+ },
2938
+ {
2939
+ onRetry: (error, attempt, delay) => {
2940
+ this.logger?.warn?.(`updateResource retry ${attempt} for ${resourceId} after ${delay}ms: ${error.message}`);
2941
+ }
2942
+ }
2943
+ ).catch((error) => {
2944
+ if (error instanceof MastraError) {
2945
+ throw error;
2946
+ }
2947
+ throw new MastraError(
2948
+ {
2949
+ id: createStorageErrorId("DSQL", "UPDATE_RESOURCE", "FAILED"),
2950
+ domain: ErrorDomain.STORAGE,
2951
+ category: ErrorCategory.THIRD_PARTY,
2952
+ details: {
2953
+ resourceId
2954
+ }
2955
+ },
2956
+ error
2957
+ );
2958
+ });
2959
+ return result;
2960
+ }
2961
+ };
2962
+ var ObservabilityDSQL = class _ObservabilityDSQL extends ObservabilityStorage {
2963
+ #db;
2964
+ #schema;
2965
+ #skipDefaultIndexes;
2966
+ #indexes;
2967
+ /** Tables managed by this domain */
2968
+ static MANAGED_TABLES = [TABLE_SPANS];
2969
+ constructor(config) {
2970
+ super();
2971
+ const { client, schemaName, skipDefaultIndexes, indexes } = resolveDsqlConfig(config);
2972
+ this.#db = new DsqlDB({ client, schemaName, skipDefaultIndexes });
2973
+ this.#schema = schemaName || "public";
2974
+ this.#skipDefaultIndexes = skipDefaultIndexes;
2975
+ this.#indexes = indexes?.filter((idx) => _ObservabilityDSQL.MANAGED_TABLES.includes(idx.table));
2976
+ }
2977
+ async init() {
2978
+ await this.#db.createTable({ tableName: TABLE_SPANS, schema: TABLE_SCHEMAS[TABLE_SPANS] });
2979
+ await this.createDefaultIndexes();
2980
+ await this.createCustomIndexes();
2981
+ }
2982
+ /**
2983
+ * Returns default index definitions for the observability domain tables.
2984
+ */
2985
+ getDefaultIndexDefinitions() {
2986
+ const schemaPrefix = this.#schema !== "public" ? `${this.#schema}_` : "";
2987
+ return [
2988
+ {
2989
+ name: `${schemaPrefix}mastra_ai_spans_traceid_startedat_idx`,
2990
+ table: TABLE_SPANS,
2991
+ columns: ["traceId", "startedAt"]
2992
+ },
2993
+ {
2994
+ name: `${schemaPrefix}mastra_ai_spans_parentspanid_startedat_idx`,
2995
+ table: TABLE_SPANS,
2996
+ columns: ["parentSpanId", "startedAt"]
2997
+ },
2998
+ {
2999
+ name: `${schemaPrefix}mastra_ai_spans_name_idx`,
3000
+ table: TABLE_SPANS,
3001
+ columns: ["name"]
3002
+ },
3003
+ {
3004
+ name: `${schemaPrefix}mastra_ai_spans_spantype_startedat_idx`,
3005
+ table: TABLE_SPANS,
3006
+ columns: ["spanType", "startedAt"]
3007
+ },
3008
+ // Entity identification indexes - common filtering patterns
3009
+ {
3010
+ name: `${schemaPrefix}mastra_ai_spans_entitytype_entityid_idx`,
3011
+ table: TABLE_SPANS,
3012
+ columns: ["entityType", "entityId"]
3013
+ },
3014
+ {
3015
+ name: `${schemaPrefix}mastra_ai_spans_entitytype_entityname_idx`,
3016
+ table: TABLE_SPANS,
3017
+ columns: ["entityType", "entityName"]
3018
+ },
3019
+ // Multi-tenant filtering - organizationId + userId
3020
+ {
3021
+ name: `${schemaPrefix}mastra_ai_spans_orgid_userid_idx`,
3022
+ table: TABLE_SPANS,
3023
+ columns: ["organizationId", "userId"]
3024
+ }
3025
+ ];
3026
+ }
3027
+ /**
3028
+ * Creates default indexes for optimal query performance.
3029
+ */
3030
+ async createDefaultIndexes() {
3031
+ if (this.#skipDefaultIndexes) {
3032
+ return;
3033
+ }
3034
+ for (const indexDef of this.getDefaultIndexDefinitions()) {
3035
+ try {
3036
+ await this.#db.createIndex(indexDef);
3037
+ } catch (error) {
3038
+ this.logger?.warn?.(`Failed to create index ${indexDef.name}:`, error);
3039
+ }
3040
+ }
3041
+ }
3042
+ /**
3043
+ * Creates custom user-defined indexes for this domain's tables.
3044
+ */
3045
+ async createCustomIndexes() {
3046
+ if (!this.#indexes || this.#indexes.length === 0) {
3047
+ return;
3048
+ }
3049
+ for (const indexDef of this.#indexes) {
3050
+ try {
3051
+ await this.#db.createIndex(indexDef);
3052
+ } catch (error) {
3053
+ this.logger?.warn?.(`Failed to create custom index ${indexDef.name}:`, error);
3054
+ }
3055
+ }
3056
+ }
3057
+ async dangerouslyClearAll() {
3058
+ await this.#db.clearTable({ tableName: TABLE_SPANS });
3059
+ }
3060
+ get tracingStrategy() {
3061
+ return {
3062
+ preferred: "batch-with-updates",
3063
+ supported: ["batch-with-updates", "insert-only"]
3064
+ };
3065
+ }
3066
+ async createSpan(args) {
3067
+ const { span } = args;
3068
+ try {
3069
+ const startedAt = span.startedAt instanceof Date ? span.startedAt.toISOString() : span.startedAt;
3070
+ const endedAt = span.endedAt instanceof Date ? span.endedAt.toISOString() : span.endedAt;
3071
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3072
+ const record = {
3073
+ ...span,
3074
+ startedAt,
3075
+ endedAt,
3076
+ startedAtZ: startedAt,
3077
+ endedAtZ: endedAt,
3078
+ // Aurora DSQL doesn't support triggers, so we set timestamps explicitly
3079
+ createdAt: now,
3080
+ updatedAt: now
3081
+ };
3082
+ await this.#db.insert({ tableName: TABLE_SPANS, record });
3083
+ } catch (error) {
3084
+ throw new MastraError(
3085
+ {
3086
+ id: createStorageErrorId("DSQL", "CREATE_SPAN", "FAILED"),
3087
+ domain: ErrorDomain.STORAGE,
3088
+ category: ErrorCategory.USER,
3089
+ details: {
3090
+ spanId: span.spanId,
3091
+ traceId: span.traceId,
3092
+ spanType: span.spanType,
3093
+ spanName: span.name
3094
+ }
3095
+ },
3096
+ error
3097
+ );
3098
+ }
3099
+ }
3100
+ async getSpan(args) {
3101
+ const { traceId, spanId } = args;
3102
+ try {
3103
+ const tableName = getTableName2({
3104
+ indexName: TABLE_SPANS,
3105
+ schemaName: getSchemaName2(this.#schema)
3106
+ });
3107
+ const row = await this.#db.client.oneOrNone(
3108
+ `SELECT
3109
+ "traceId", "spanId", "parentSpanId", "name",
3110
+ "entityType", "entityId", "entityName",
3111
+ "userId", "organizationId", "resourceId",
3112
+ "runId", "sessionId", "threadId", "requestId",
3113
+ "environment", "source", "serviceName", "scope",
3114
+ "spanType", "attributes", "metadata", "tags", "links",
3115
+ "input", "output", "error", "isEvent",
3116
+ "startedAtZ" as "startedAt", "endedAtZ" as "endedAt",
3117
+ "createdAtZ" as "createdAt", "updatedAtZ" as "updatedAt"
3118
+ FROM ${tableName}
3119
+ WHERE "traceId" = $1 AND "spanId" = $2`,
3120
+ [traceId, spanId]
3121
+ );
3122
+ if (!row) {
3123
+ return null;
3124
+ }
3125
+ return {
3126
+ span: transformFromSqlRow({
3127
+ tableName: TABLE_SPANS,
3128
+ sqlRow: row
3129
+ })
3130
+ };
3131
+ } catch (error) {
3132
+ throw new MastraError(
3133
+ {
3134
+ id: createStorageErrorId("DSQL", "GET_SPAN", "FAILED"),
3135
+ domain: ErrorDomain.STORAGE,
3136
+ category: ErrorCategory.USER,
3137
+ details: { traceId, spanId }
3138
+ },
3139
+ error
3140
+ );
3141
+ }
3142
+ }
3143
+ async getRootSpan(args) {
3144
+ const { traceId } = args;
3145
+ try {
3146
+ const tableName = getTableName2({
3147
+ indexName: TABLE_SPANS,
3148
+ schemaName: getSchemaName2(this.#schema)
3149
+ });
3150
+ const row = await this.#db.client.oneOrNone(
3151
+ `SELECT
3152
+ "traceId", "spanId", "parentSpanId", "name",
3153
+ "entityType", "entityId", "entityName",
3154
+ "userId", "organizationId", "resourceId",
3155
+ "runId", "sessionId", "threadId", "requestId",
3156
+ "environment", "source", "serviceName", "scope",
3157
+ "spanType", "attributes", "metadata", "tags", "links",
3158
+ "input", "output", "error", "isEvent",
3159
+ "startedAtZ" as "startedAt", "endedAtZ" as "endedAt",
3160
+ "createdAtZ" as "createdAt", "updatedAtZ" as "updatedAt"
3161
+ FROM ${tableName}
3162
+ WHERE "traceId" = $1 AND "parentSpanId" IS NULL`,
3163
+ [traceId]
3164
+ );
3165
+ if (!row) {
3166
+ return null;
3167
+ }
3168
+ return {
3169
+ span: transformFromSqlRow({
3170
+ tableName: TABLE_SPANS,
3171
+ sqlRow: row
3172
+ })
3173
+ };
3174
+ } catch (error) {
3175
+ throw new MastraError(
3176
+ {
3177
+ id: createStorageErrorId("DSQL", "GET_ROOT_SPAN", "FAILED"),
3178
+ domain: ErrorDomain.STORAGE,
3179
+ category: ErrorCategory.USER,
3180
+ details: { traceId }
3181
+ },
3182
+ error
3183
+ );
3184
+ }
3185
+ }
3186
+ async getTrace(args) {
3187
+ const { traceId } = args;
3188
+ try {
3189
+ const tableName = getTableName2({
3190
+ indexName: TABLE_SPANS,
3191
+ schemaName: getSchemaName2(this.#schema)
3192
+ });
3193
+ const spans = await this.#db.client.manyOrNone(
3194
+ `SELECT
3195
+ "traceId", "spanId", "parentSpanId", "name",
3196
+ "entityType", "entityId", "entityName",
3197
+ "userId", "organizationId", "resourceId",
3198
+ "runId", "sessionId", "threadId", "requestId",
3199
+ "environment", "source", "serviceName", "scope",
3200
+ "spanType", "attributes", "metadata", "tags", "links",
3201
+ "input", "output", "error", "isEvent",
3202
+ "startedAtZ" as "startedAt", "endedAtZ" as "endedAt",
3203
+ "createdAtZ" as "createdAt", "updatedAtZ" as "updatedAt"
3204
+ FROM ${tableName}
3205
+ WHERE "traceId" = $1
3206
+ ORDER BY "startedAtZ" ASC`,
3207
+ [traceId]
3208
+ );
3209
+ if (!spans || spans.length === 0) {
3210
+ return null;
3211
+ }
3212
+ return {
3213
+ traceId,
3214
+ spans: spans.map(
3215
+ (span) => transformFromSqlRow({
3216
+ tableName: TABLE_SPANS,
3217
+ sqlRow: span
3218
+ })
3219
+ )
3220
+ };
3221
+ } catch (error) {
3222
+ throw new MastraError(
3223
+ {
3224
+ id: createStorageErrorId("DSQL", "GET_TRACE", "FAILED"),
3225
+ domain: ErrorDomain.STORAGE,
3226
+ category: ErrorCategory.USER,
3227
+ details: {
3228
+ traceId
3229
+ }
3230
+ },
3231
+ error
3232
+ );
3233
+ }
3234
+ }
3235
+ async updateSpan(args) {
3236
+ const { traceId, spanId, updates } = args;
3237
+ try {
3238
+ const data = { ...updates };
3239
+ if (data.endedAt instanceof Date) {
3240
+ const endedAt = data.endedAt.toISOString();
3241
+ data.endedAt = endedAt;
3242
+ data.endedAtZ = endedAt;
3243
+ }
3244
+ if (data.startedAt instanceof Date) {
3245
+ const startedAt = data.startedAt.toISOString();
3246
+ data.startedAt = startedAt;
3247
+ data.startedAtZ = startedAt;
3248
+ }
3249
+ await this.#db.update({
3250
+ tableName: TABLE_SPANS,
3251
+ keys: { spanId, traceId },
3252
+ data
3253
+ });
3254
+ } catch (error) {
3255
+ throw new MastraError(
3256
+ {
3257
+ id: createStorageErrorId("DSQL", "UPDATE_SPAN", "FAILED"),
3258
+ domain: ErrorDomain.STORAGE,
3259
+ category: ErrorCategory.USER,
3260
+ details: {
3261
+ spanId,
3262
+ traceId
3263
+ }
3264
+ },
3265
+ error
3266
+ );
3267
+ }
3268
+ }
3269
+ async listTraces(args) {
3270
+ const { filters, pagination, orderBy } = listTracesArgsSchema.parse(args);
3271
+ const page = pagination?.page ?? 0;
3272
+ const perPage = pagination?.perPage ?? 100;
3273
+ const tableName = getTableName2({
3274
+ indexName: TABLE_SPANS,
3275
+ schemaName: getSchemaName2(this.#schema)
3276
+ });
3277
+ try {
3278
+ const conditions = ['r."parentSpanId" IS NULL'];
3279
+ const params = [];
3280
+ let paramIndex = 1;
3281
+ if (filters) {
3282
+ if (filters.startedAt?.start) {
3283
+ conditions.push(`r."startedAtZ" >= $${paramIndex++}`);
3284
+ params.push(filters.startedAt.start.toISOString());
3285
+ }
3286
+ if (filters.startedAt?.end) {
3287
+ conditions.push(`r."startedAtZ" <= $${paramIndex++}`);
3288
+ params.push(filters.startedAt.end.toISOString());
3289
+ }
3290
+ if (filters.endedAt?.start) {
3291
+ conditions.push(`r."endedAtZ" >= $${paramIndex++}`);
3292
+ params.push(filters.endedAt.start.toISOString());
3293
+ }
3294
+ if (filters.endedAt?.end) {
3295
+ conditions.push(`r."endedAtZ" <= $${paramIndex++}`);
3296
+ params.push(filters.endedAt.end.toISOString());
3297
+ }
3298
+ if (filters.spanType !== void 0) {
3299
+ conditions.push(`r."spanType" = $${paramIndex++}`);
3300
+ params.push(filters.spanType);
3301
+ }
3302
+ if (filters.entityType !== void 0) {
3303
+ conditions.push(`r."entityType" = $${paramIndex++}`);
3304
+ params.push(filters.entityType);
3305
+ }
3306
+ if (filters.entityId !== void 0) {
3307
+ conditions.push(`r."entityId" = $${paramIndex++}`);
3308
+ params.push(filters.entityId);
3309
+ }
3310
+ if (filters.entityName !== void 0) {
3311
+ conditions.push(`r."entityName" = $${paramIndex++}`);
3312
+ params.push(filters.entityName);
3313
+ }
3314
+ if (filters.userId !== void 0) {
3315
+ conditions.push(`r."userId" = $${paramIndex++}`);
3316
+ params.push(filters.userId);
3317
+ }
3318
+ if (filters.organizationId !== void 0) {
3319
+ conditions.push(`r."organizationId" = $${paramIndex++}`);
3320
+ params.push(filters.organizationId);
3321
+ }
3322
+ if (filters.resourceId !== void 0) {
3323
+ conditions.push(`r."resourceId" = $${paramIndex++}`);
3324
+ params.push(filters.resourceId);
3325
+ }
3326
+ if (filters.runId !== void 0) {
3327
+ conditions.push(`r."runId" = $${paramIndex++}`);
3328
+ params.push(filters.runId);
3329
+ }
3330
+ if (filters.sessionId !== void 0) {
3331
+ conditions.push(`r."sessionId" = $${paramIndex++}`);
3332
+ params.push(filters.sessionId);
3333
+ }
3334
+ if (filters.threadId !== void 0) {
3335
+ conditions.push(`r."threadId" = $${paramIndex++}`);
3336
+ params.push(filters.threadId);
3337
+ }
3338
+ if (filters.requestId !== void 0) {
3339
+ conditions.push(`r."requestId" = $${paramIndex++}`);
3340
+ params.push(filters.requestId);
3341
+ }
3342
+ if (filters.environment !== void 0) {
3343
+ conditions.push(`r."environment" = $${paramIndex++}`);
3344
+ params.push(filters.environment);
3345
+ }
3346
+ if (filters.source !== void 0) {
3347
+ conditions.push(`r."source" = $${paramIndex++}`);
3348
+ params.push(filters.source);
3349
+ }
3350
+ if (filters.serviceName !== void 0) {
3351
+ conditions.push(`r."serviceName" = $${paramIndex++}`);
3352
+ params.push(filters.serviceName);
3353
+ }
3354
+ if (filters.scope != null) {
3355
+ conditions.push(`r."scope"::text = $${paramIndex++}`);
3356
+ params.push(JSON.stringify(filters.scope));
3357
+ }
3358
+ if (filters.metadata != null) {
3359
+ conditions.push(`r."metadata"::text = $${paramIndex++}`);
3360
+ params.push(JSON.stringify(filters.metadata));
3361
+ }
3362
+ if (filters.tags != null && filters.tags.length > 0) {
3363
+ conditions.push(`r."tags"::text = $${paramIndex++}`);
3364
+ params.push(JSON.stringify(filters.tags));
3365
+ }
3366
+ if (filters.status !== void 0) {
3367
+ switch (filters.status) {
3368
+ case TraceStatus.ERROR:
3369
+ conditions.push(`r."error" IS NOT NULL`);
3370
+ break;
3371
+ case TraceStatus.RUNNING:
3372
+ conditions.push(`r."endedAtZ" IS NULL AND r."error" IS NULL`);
3373
+ break;
3374
+ case TraceStatus.SUCCESS:
3375
+ conditions.push(`r."endedAtZ" IS NOT NULL AND r."error" IS NULL`);
3376
+ break;
3377
+ }
3378
+ }
3379
+ if (filters.hasChildError !== void 0) {
3380
+ if (filters.hasChildError) {
3381
+ conditions.push(`EXISTS (
3382
+ SELECT 1 FROM ${tableName} c
3383
+ WHERE c."traceId" = r."traceId" AND c."error" IS NOT NULL
3384
+ )`);
3385
+ } else {
3386
+ conditions.push(`NOT EXISTS (
3387
+ SELECT 1 FROM ${tableName} c
3388
+ WHERE c."traceId" = r."traceId" AND c."error" IS NOT NULL
3389
+ )`);
3390
+ }
3391
+ }
3392
+ }
3393
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3394
+ const sortField = `${orderBy?.field ?? "startedAt"}Z`;
3395
+ const sortDirection = orderBy?.direction ?? "DESC";
3396
+ const orderClause = `ORDER BY r."${sortField}" ${sortDirection}`;
3397
+ const countResult = await this.#db.client.oneOrNone(
3398
+ `SELECT COUNT(*) FROM ${tableName} r ${whereClause}`,
3399
+ params
3400
+ );
3401
+ const count = Number(countResult?.count ?? 0);
3402
+ if (count === 0) {
3403
+ return {
3404
+ pagination: {
3405
+ total: 0,
3406
+ page,
3407
+ perPage,
3408
+ hasMore: false
3409
+ },
3410
+ spans: []
3411
+ };
3412
+ }
3413
+ const spans = await this.#db.client.manyOrNone(
3414
+ `SELECT
3415
+ r."traceId", r."spanId", r."parentSpanId", r."name",
3416
+ r."entityType", r."entityId", r."entityName",
3417
+ r."userId", r."organizationId", r."resourceId",
3418
+ r."runId", r."sessionId", r."threadId", r."requestId",
3419
+ r."environment", r."source", r."serviceName", r."scope",
3420
+ r."spanType", r."attributes", r."metadata", r."tags", r."links",
3421
+ r."input", r."output", r."error", r."isEvent",
3422
+ r."startedAtZ" as "startedAt", r."endedAtZ" as "endedAt",
3423
+ r."createdAtZ" as "createdAt", r."updatedAtZ" as "updatedAt"
3424
+ FROM ${tableName} r
3425
+ ${whereClause}
3426
+ ${orderClause}
3427
+ LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
3428
+ [...params, perPage, page * perPage]
3429
+ );
3430
+ return {
3431
+ pagination: {
3432
+ total: count,
3433
+ page,
3434
+ perPage,
3435
+ hasMore: (page + 1) * perPage < count
3436
+ },
3437
+ spans: toTraceSpans(
3438
+ spans.map(
3439
+ (span) => transformFromSqlRow({
3440
+ tableName: TABLE_SPANS,
3441
+ sqlRow: span
3442
+ })
3443
+ )
3444
+ )
3445
+ };
3446
+ } catch (error) {
3447
+ throw new MastraError(
3448
+ {
3449
+ id: createStorageErrorId("DSQL", "LIST_TRACES", "FAILED"),
3450
+ domain: ErrorDomain.STORAGE,
3451
+ category: ErrorCategory.USER
3452
+ },
3453
+ error
3454
+ );
3455
+ }
3456
+ }
3457
+ async batchCreateSpans(args) {
3458
+ try {
3459
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3460
+ const records = args.records.map((record) => {
3461
+ const startedAt = record.startedAt instanceof Date ? record.startedAt.toISOString() : record.startedAt;
3462
+ const endedAt = record.endedAt instanceof Date ? record.endedAt.toISOString() : record.endedAt;
3463
+ return {
3464
+ ...record,
3465
+ startedAt,
3466
+ endedAt,
3467
+ startedAtZ: startedAt,
3468
+ endedAtZ: endedAt,
3469
+ // Aurora DSQL doesn't support triggers, so we set timestamps explicitly
3470
+ createdAt: now,
3471
+ updatedAt: now
3472
+ };
3473
+ });
3474
+ await this.#db.batchInsert({
3475
+ tableName: TABLE_SPANS,
3476
+ records
3477
+ });
3478
+ } catch (error) {
3479
+ throw new MastraError(
3480
+ {
3481
+ id: createStorageErrorId("DSQL", "BATCH_CREATE_SPANS", "FAILED"),
3482
+ domain: ErrorDomain.STORAGE,
3483
+ category: ErrorCategory.USER
3484
+ },
3485
+ error
3486
+ );
3487
+ }
3488
+ }
3489
+ async batchUpdateSpans(args) {
3490
+ try {
3491
+ await this.#db.batchUpdate({
3492
+ tableName: TABLE_SPANS,
3493
+ updates: args.records.map((record) => {
3494
+ const data = { ...record.updates };
3495
+ if (data.endedAt instanceof Date) {
3496
+ const endedAt = data.endedAt.toISOString();
3497
+ data.endedAt = endedAt;
3498
+ data.endedAtZ = endedAt;
3499
+ }
3500
+ if (data.startedAt instanceof Date) {
3501
+ const startedAt = data.startedAt.toISOString();
3502
+ data.startedAt = startedAt;
3503
+ data.startedAtZ = startedAt;
3504
+ }
3505
+ return {
3506
+ keys: { spanId: record.spanId, traceId: record.traceId },
3507
+ data
3508
+ };
3509
+ })
3510
+ });
3511
+ } catch (error) {
3512
+ throw new MastraError(
3513
+ {
3514
+ id: createStorageErrorId("DSQL", "BATCH_UPDATE_SPANS", "FAILED"),
3515
+ domain: ErrorDomain.STORAGE,
3516
+ category: ErrorCategory.USER
3517
+ },
3518
+ error
3519
+ );
3520
+ }
3521
+ }
3522
+ async batchDeleteTraces(args) {
3523
+ const { batches } = splitIntoBatches(args.traceIds, { maxRows: DEFAULT_MAX_ROWS_PER_BATCH });
3524
+ const tableName = getTableName2({
3525
+ indexName: TABLE_SPANS,
3526
+ schemaName: getSchemaName2(this.#schema)
3527
+ });
3528
+ for (const batchTraceIds of batches) {
3529
+ const placeholders = batchTraceIds.map((_, i) => `$${i + 1}`).join(", ");
3530
+ await withRetry(
3531
+ async () => {
3532
+ await this.#db.client.none(`DELETE FROM ${tableName} WHERE "traceId" IN (${placeholders})`, batchTraceIds);
3533
+ },
3534
+ {
3535
+ onRetry: (error, attempt, delay) => {
3536
+ this.logger?.warn?.(
3537
+ `batchDeleteTraces retry ${attempt} for ${batchTraceIds.length} traces after ${delay}ms: ${error.message}`
3538
+ );
3539
+ }
3540
+ }
3541
+ ).catch((error) => {
3542
+ throw new MastraError(
3543
+ {
3544
+ id: createStorageErrorId("DSQL", "BATCH_DELETE_TRACES", "FAILED"),
3545
+ domain: ErrorDomain.STORAGE,
3546
+ category: ErrorCategory.USER
3547
+ },
3548
+ error
3549
+ );
3550
+ });
3551
+ }
3552
+ }
3553
+ };
3554
+ function transformScoreRow(row) {
3555
+ return transformScoreRow$1(row, {
3556
+ preferredTimestampFields: {
3557
+ createdAt: "createdAtZ",
3558
+ updatedAt: "updatedAtZ"
3559
+ }
3560
+ });
3561
+ }
3562
+ var ScoresDSQL = class _ScoresDSQL extends ScoresStorage {
3563
+ #db;
3564
+ #schema;
3565
+ #skipDefaultIndexes;
3566
+ #indexes;
3567
+ /** Tables managed by this domain */
3568
+ static MANAGED_TABLES = [TABLE_SCORERS];
3569
+ constructor(config) {
3570
+ super();
3571
+ const { client, schemaName, skipDefaultIndexes, indexes } = resolveDsqlConfig(config);
3572
+ this.#db = new DsqlDB({ client, schemaName, skipDefaultIndexes });
3573
+ this.#schema = schemaName || "public";
3574
+ this.#skipDefaultIndexes = skipDefaultIndexes;
3575
+ this.#indexes = indexes?.filter((idx) => _ScoresDSQL.MANAGED_TABLES.includes(idx.table));
3576
+ }
3577
+ async init() {
3578
+ await this.#db.createTable({ tableName: TABLE_SCORERS, schema: TABLE_SCHEMAS[TABLE_SCORERS] });
3579
+ await this.createDefaultIndexes();
3580
+ await this.createCustomIndexes();
3581
+ }
3582
+ /**
3583
+ * Returns default index definitions for the scores domain tables.
3584
+ * Note: Aurora DSQL does not support ASC/DESC in index columns.
3585
+ */
3586
+ getDefaultIndexDefinitions() {
3587
+ const schemaPrefix = this.#schema !== "public" ? `${this.#schema}_` : "";
3588
+ return [
3589
+ {
3590
+ name: `${schemaPrefix}mastra_scores_trace_id_span_id_created_at_idx`,
3591
+ table: TABLE_SCORERS,
3592
+ columns: ["traceId", "spanId", "createdAt"]
3593
+ }
3594
+ ];
3595
+ }
3596
+ /**
3597
+ * Creates default indexes for optimal query performance.
3598
+ */
3599
+ async createDefaultIndexes() {
3600
+ if (this.#skipDefaultIndexes) {
3601
+ return;
3602
+ }
3603
+ for (const indexDef of this.getDefaultIndexDefinitions()) {
3604
+ try {
3605
+ await this.#db.createIndex(indexDef);
3606
+ } catch (error) {
3607
+ this.logger?.warn?.(`Failed to create index ${indexDef.name}:`, error);
3608
+ }
3609
+ }
3610
+ }
3611
+ /**
3612
+ * Creates custom user-defined indexes for this domain's tables.
3613
+ */
3614
+ async createCustomIndexes() {
3615
+ if (!this.#indexes || this.#indexes.length === 0) {
3616
+ return;
3617
+ }
3618
+ for (const indexDef of this.#indexes) {
3619
+ try {
3620
+ await this.#db.createIndex(indexDef);
3621
+ } catch (error) {
3622
+ this.logger?.warn?.(`Failed to create custom index ${indexDef.name}:`, error);
3623
+ }
3624
+ }
3625
+ }
3626
+ async dangerouslyClearAll() {
3627
+ await this.#db.clearTable({ tableName: TABLE_SCORERS });
3628
+ }
3629
+ async getScoreById({ id }) {
3630
+ try {
3631
+ const result = await this.#db.client.oneOrNone(
3632
+ `SELECT * FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.#schema) })} WHERE id = $1`,
3633
+ [id]
3634
+ );
3635
+ return result ? transformScoreRow(result) : null;
3636
+ } catch (error) {
3637
+ throw new MastraError(
3638
+ {
3639
+ id: createStorageErrorId("DSQL", "GET_SCORE_BY_ID", "FAILED"),
3640
+ domain: ErrorDomain.STORAGE,
3641
+ category: ErrorCategory.THIRD_PARTY
3642
+ },
3643
+ error
3644
+ );
3645
+ }
3646
+ }
3647
+ async listScoresByScorerId({
3648
+ scorerId,
3649
+ pagination,
3650
+ entityId,
3651
+ entityType,
3652
+ source
3653
+ }) {
3654
+ try {
3655
+ const conditions = [`"scorerId" = $1`];
3656
+ const queryParams = [scorerId];
3657
+ let paramIndex = 2;
3658
+ if (entityId) {
3659
+ conditions.push(`"entityId" = $${paramIndex++}`);
3660
+ queryParams.push(entityId);
3661
+ }
3662
+ if (entityType) {
3663
+ conditions.push(`"entityType" = $${paramIndex++}`);
3664
+ queryParams.push(entityType);
3665
+ }
3666
+ if (source) {
3667
+ conditions.push(`"source" = $${paramIndex++}`);
3668
+ queryParams.push(source);
3669
+ }
3670
+ const whereClause = conditions.join(" AND ");
3671
+ const total = await this.#db.client.oneOrNone(
3672
+ `SELECT COUNT(*) FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.#schema) })} WHERE ${whereClause}`,
3673
+ queryParams
3674
+ );
3675
+ const { page, perPage: perPageInput } = pagination;
3676
+ const perPage = normalizePerPage(perPageInput, 100);
3677
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3678
+ if (total?.count === "0" || !total?.count) {
3679
+ return {
3680
+ pagination: {
3681
+ total: 0,
3682
+ page,
3683
+ perPage: perPageForResponse,
3684
+ hasMore: false
3685
+ },
3686
+ scores: []
3687
+ };
3688
+ }
3689
+ const limitValue = perPageInput === false ? Number(total?.count) : perPage;
3690
+ const end = perPageInput === false ? Number(total?.count) : start + perPage;
3691
+ const result = await this.#db.client.manyOrNone(
3692
+ `SELECT * FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.#schema) })} WHERE ${whereClause} ORDER BY "createdAt" DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
3693
+ [...queryParams, limitValue, start]
3694
+ );
3695
+ return {
3696
+ pagination: {
3697
+ total: Number(total?.count) || 0,
3698
+ page,
3699
+ perPage: perPageForResponse,
3700
+ hasMore: end < Number(total?.count)
3701
+ },
3702
+ scores: result.map(transformScoreRow)
3703
+ };
3704
+ } catch (error) {
3705
+ throw new MastraError(
3706
+ {
3707
+ id: createStorageErrorId("DSQL", "LIST_SCORES_BY_SCORER_ID", "FAILED"),
3708
+ domain: ErrorDomain.STORAGE,
3709
+ category: ErrorCategory.THIRD_PARTY
3710
+ },
3711
+ error
3712
+ );
3713
+ }
3714
+ }
3715
+ async saveScore(score) {
3716
+ let parsedScore;
3717
+ try {
3718
+ parsedScore = saveScorePayloadSchema.parse(score);
3719
+ } catch (error) {
3720
+ throw new MastraError(
3721
+ {
3722
+ id: createStorageErrorId("DSQL", "SAVE_SCORE", "VALIDATION_FAILED"),
3723
+ domain: ErrorDomain.STORAGE,
3724
+ category: ErrorCategory.USER,
3725
+ details: {
3726
+ scorer: typeof score.scorer?.id === "string" ? score.scorer.id : String(score.scorer?.id ?? "unknown"),
3727
+ entityId: score.entityId ?? "unknown",
3728
+ entityType: score.entityType ?? "unknown",
3729
+ traceId: score.traceId ?? "",
3730
+ spanId: score.spanId ?? ""
3731
+ }
3732
+ },
3733
+ error
3734
+ );
3735
+ }
3736
+ try {
3737
+ const id = crypto.randomUUID();
3738
+ const now = /* @__PURE__ */ new Date();
3739
+ const {
3740
+ scorer,
3741
+ preprocessStepResult,
3742
+ analyzeStepResult,
3743
+ metadata,
3744
+ input,
3745
+ output,
3746
+ additionalContext,
3747
+ requestContext,
3748
+ entity,
3749
+ ...rest
3750
+ } = parsedScore;
3751
+ await this.#db.insert({
3752
+ tableName: TABLE_SCORERS,
3753
+ record: {
3754
+ id,
3755
+ ...rest,
3756
+ input: JSON.stringify(input) || "",
3757
+ output: JSON.stringify(output) || "",
3758
+ scorer: scorer ? JSON.stringify(scorer) : null,
3759
+ preprocessStepResult: preprocessStepResult ? JSON.stringify(preprocessStepResult) : null,
3760
+ analyzeStepResult: analyzeStepResult ? JSON.stringify(analyzeStepResult) : null,
3761
+ metadata: metadata ? JSON.stringify(metadata) : null,
3762
+ additionalContext: additionalContext ? JSON.stringify(additionalContext) : null,
3763
+ requestContext: requestContext ? JSON.stringify(requestContext) : null,
3764
+ entity: entity ? JSON.stringify(entity) : null,
3765
+ createdAt: now.toISOString(),
3766
+ updatedAt: now.toISOString()
3767
+ }
3768
+ });
3769
+ return { score: { ...parsedScore, id, createdAt: now, updatedAt: now } };
3770
+ } catch (error) {
3771
+ throw new MastraError(
3772
+ {
3773
+ id: createStorageErrorId("DSQL", "SAVE_SCORE", "FAILED"),
3774
+ domain: ErrorDomain.STORAGE,
3775
+ category: ErrorCategory.THIRD_PARTY
3776
+ },
3777
+ error
3778
+ );
3779
+ }
3780
+ }
3781
+ async listScoresByRunId({
3782
+ runId,
3783
+ pagination
3784
+ }) {
3785
+ try {
3786
+ const total = await this.#db.client.oneOrNone(
3787
+ `SELECT COUNT(*) FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.#schema) })} WHERE "runId" = $1`,
3788
+ [runId]
3789
+ );
3790
+ const { page, perPage: perPageInput } = pagination;
3791
+ const perPage = normalizePerPage(perPageInput, 100);
3792
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3793
+ if (total?.count === "0" || !total?.count) {
3794
+ return {
3795
+ pagination: {
3796
+ total: 0,
3797
+ page,
3798
+ perPage: perPageForResponse,
3799
+ hasMore: false
3800
+ },
3801
+ scores: []
3802
+ };
3803
+ }
3804
+ const limitValue = perPageInput === false ? Number(total?.count) : perPage;
3805
+ const end = perPageInput === false ? Number(total?.count) : start + perPage;
3806
+ const result = await this.#db.client.manyOrNone(
3807
+ `SELECT * FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.#schema) })} WHERE "runId" = $1 ORDER BY "createdAt" DESC LIMIT $2 OFFSET $3`,
3808
+ [runId, limitValue, start]
3809
+ );
3810
+ return {
3811
+ pagination: {
3812
+ total: Number(total?.count) || 0,
3813
+ page,
3814
+ perPage: perPageForResponse,
3815
+ hasMore: end < Number(total?.count)
3816
+ },
3817
+ scores: result.map(transformScoreRow)
3818
+ };
3819
+ } catch (error) {
3820
+ throw new MastraError(
3821
+ {
3822
+ id: createStorageErrorId("DSQL", "LIST_SCORES_BY_RUN_ID", "FAILED"),
3823
+ domain: ErrorDomain.STORAGE,
3824
+ category: ErrorCategory.THIRD_PARTY
3825
+ },
3826
+ error
3827
+ );
3828
+ }
3829
+ }
3830
+ async listScoresByEntityId({
3831
+ entityId,
3832
+ entityType,
3833
+ pagination
3834
+ }) {
3835
+ try {
3836
+ const total = await this.#db.client.oneOrNone(
3837
+ `SELECT COUNT(*) FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.#schema) })} WHERE "entityId" = $1 AND "entityType" = $2`,
3838
+ [entityId, entityType]
3839
+ );
3840
+ const { page, perPage: perPageInput } = pagination;
3841
+ const perPage = normalizePerPage(perPageInput, 100);
3842
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3843
+ if (total?.count === "0" || !total?.count) {
3844
+ return {
3845
+ pagination: {
3846
+ total: 0,
3847
+ page,
3848
+ perPage: perPageForResponse,
3849
+ hasMore: false
3850
+ },
3851
+ scores: []
3852
+ };
3853
+ }
3854
+ const limitValue = perPageInput === false ? Number(total?.count) : perPage;
3855
+ const end = perPageInput === false ? Number(total?.count) : start + perPage;
3856
+ const result = await this.#db.client.manyOrNone(
3857
+ `SELECT * FROM ${getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.#schema) })} WHERE "entityId" = $1 AND "entityType" = $2 ORDER BY "createdAt" DESC LIMIT $3 OFFSET $4`,
3858
+ [entityId, entityType, limitValue, start]
3859
+ );
3860
+ return {
3861
+ pagination: {
3862
+ total: Number(total?.count) || 0,
3863
+ page,
3864
+ perPage: perPageForResponse,
3865
+ hasMore: end < Number(total?.count)
3866
+ },
3867
+ scores: result.map(transformScoreRow)
3868
+ };
3869
+ } catch (error) {
3870
+ throw new MastraError(
3871
+ {
3872
+ id: createStorageErrorId("DSQL", "LIST_SCORES_BY_ENTITY_ID", "FAILED"),
3873
+ domain: ErrorDomain.STORAGE,
3874
+ category: ErrorCategory.THIRD_PARTY
3875
+ },
3876
+ error
3877
+ );
3878
+ }
3879
+ }
3880
+ async listScoresBySpan({
3881
+ traceId,
3882
+ spanId,
3883
+ pagination
3884
+ }) {
3885
+ try {
3886
+ const tableName = getTableName2({ indexName: TABLE_SCORERS, schemaName: getSchemaName2(this.#schema) });
3887
+ const countSQLResult = await this.#db.client.oneOrNone(
3888
+ `SELECT COUNT(*) as count FROM ${tableName} WHERE "traceId" = $1 AND "spanId" = $2`,
3889
+ [traceId, spanId]
3890
+ );
3891
+ const total = Number(countSQLResult?.count ?? 0);
3892
+ const { page, perPage: perPageInput } = pagination;
3893
+ const perPage = normalizePerPage(perPageInput, 100);
3894
+ const { offset: start, perPage: perPageForResponse } = calculatePagination(page, perPageInput, perPage);
3895
+ const limitValue = perPageInput === false ? total : perPage;
3896
+ const end = perPageInput === false ? total : start + perPage;
3897
+ const result = await this.#db.client.manyOrNone(
3898
+ `SELECT * FROM ${tableName} WHERE "traceId" = $1 AND "spanId" = $2 ORDER BY "createdAt" DESC LIMIT $3 OFFSET $4`,
3899
+ [traceId, spanId, limitValue, start]
3900
+ );
3901
+ const hasMore = end < total;
3902
+ const scores = result.map((row) => transformScoreRow(row)) ?? [];
3903
+ return {
3904
+ scores,
3905
+ pagination: {
3906
+ total,
3907
+ page,
3908
+ perPage: perPageForResponse,
3909
+ hasMore
3910
+ }
3911
+ };
3912
+ } catch (error) {
3913
+ throw new MastraError(
3914
+ {
3915
+ id: createStorageErrorId("DSQL", "LIST_SCORES_BY_SPAN", "FAILED"),
3916
+ domain: ErrorDomain.STORAGE,
3917
+ category: ErrorCategory.THIRD_PARTY
3918
+ },
3919
+ error
3920
+ );
3921
+ }
3922
+ }
3923
+ };
3924
+ function parseWorkflowRun(row) {
3925
+ let parsedSnapshot = row.snapshot;
3926
+ if (typeof parsedSnapshot === "string") {
3927
+ try {
3928
+ parsedSnapshot = JSON.parse(row.snapshot);
3929
+ } catch (e) {
3930
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
3931
+ }
3932
+ }
3933
+ return {
3934
+ workflowName: row.workflow_name,
3935
+ runId: row.run_id,
3936
+ snapshot: parsedSnapshot,
3937
+ resourceId: row.resourceId,
3938
+ createdAt: new Date(row.createdAtZ || row.createdAt),
3939
+ updatedAt: new Date(row.updatedAtZ || row.updatedAt)
3940
+ };
3941
+ }
3942
+ var WorkflowsDSQL = class _WorkflowsDSQL extends WorkflowsStorage {
3943
+ #db;
3944
+ #schema;
3945
+ #skipDefaultIndexes;
3946
+ #indexes;
3947
+ /** Tables managed by this domain */
3948
+ static MANAGED_TABLES = [TABLE_WORKFLOW_SNAPSHOT];
3949
+ constructor(config) {
3950
+ super();
3951
+ const { client, schemaName, skipDefaultIndexes, indexes } = resolveDsqlConfig(config);
3952
+ this.#db = new DsqlDB({ client, schemaName });
3953
+ this.#schema = schemaName || "public";
3954
+ this.#skipDefaultIndexes = skipDefaultIndexes;
3955
+ this.#indexes = indexes?.filter((idx) => _WorkflowsDSQL.MANAGED_TABLES.includes(idx.table));
3956
+ }
3957
+ supportsConcurrentUpdates() {
3958
+ return true;
3959
+ }
3960
+ /**
3961
+ * Returns default index definitions for the workflows domain tables.
3962
+ * Currently no default indexes are defined for workflows.
3963
+ */
3964
+ getDefaultIndexDefinitions() {
3965
+ return [];
3966
+ }
3967
+ /**
3968
+ * Creates default indexes for optimal query performance.
3969
+ * Currently no default indexes are defined for workflows.
3970
+ */
3971
+ async createDefaultIndexes() {
3972
+ if (this.#skipDefaultIndexes) {
3973
+ return;
3974
+ }
3975
+ }
3976
+ async init() {
3977
+ await this.#db.createTable({ tableName: TABLE_WORKFLOW_SNAPSHOT, schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT] });
3978
+ await this.#db.alterTable({
3979
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
3980
+ schema: TABLE_SCHEMAS[TABLE_WORKFLOW_SNAPSHOT],
3981
+ ifNotExists: ["resourceId"]
3982
+ });
3983
+ await this.createDefaultIndexes();
3984
+ await this.createCustomIndexes();
3985
+ }
3986
+ /**
3987
+ * Creates custom user-defined indexes for this domain's tables.
3988
+ */
3989
+ async createCustomIndexes() {
3990
+ if (!this.#indexes || this.#indexes.length === 0) {
3991
+ return;
3992
+ }
3993
+ for (const indexDef of this.#indexes) {
3994
+ try {
3995
+ await this.#db.createIndex(indexDef);
3996
+ } catch (error) {
3997
+ this.logger?.warn?.(`Failed to create custom index ${indexDef.name}:`, error);
3998
+ }
3999
+ }
4000
+ }
4001
+ async dangerouslyClearAll() {
4002
+ await this.#db.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
4003
+ }
4004
+ async updateWorkflowResults({
4005
+ workflowName,
4006
+ runId,
4007
+ stepId,
4008
+ result,
4009
+ requestContext
4010
+ }) {
4011
+ try {
4012
+ const { result: context } = await withRetry(
4013
+ async () => {
4014
+ return this.#db.client.tx(async (t) => {
4015
+ const tableName = getTableName2({
4016
+ indexName: TABLE_WORKFLOW_SNAPSHOT,
4017
+ schemaName: getSchemaName2(this.#schema)
4018
+ });
4019
+ const existingSnapshotResult = await t.oneOrNone(
4020
+ `SELECT snapshot FROM ${tableName} WHERE workflow_name = $1 AND run_id = $2`,
4021
+ [workflowName, runId]
4022
+ );
4023
+ let snapshot;
4024
+ if (!existingSnapshotResult) {
4025
+ snapshot = {
4026
+ context: {},
4027
+ activePaths: [],
4028
+ timestamp: Date.now(),
4029
+ suspendedPaths: {},
4030
+ activeStepsPath: {},
4031
+ resumeLabels: {},
4032
+ serializedStepGraph: [],
4033
+ status: "pending",
4034
+ value: {},
4035
+ waitingPaths: {},
4036
+ runId,
4037
+ requestContext: {}
4038
+ };
4039
+ } else {
4040
+ const existingSnapshot = existingSnapshotResult.snapshot;
4041
+ snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
4042
+ }
4043
+ snapshot.context[stepId] = result;
4044
+ snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
4045
+ const now = /* @__PURE__ */ new Date();
4046
+ await t.none(
4047
+ `INSERT INTO ${tableName} (workflow_name, run_id, snapshot, "createdAt", "updatedAt")
4048
+ VALUES ($1, $2, $3, $4, $5)
4049
+ ON CONFLICT (workflow_name, run_id) DO UPDATE
4050
+ SET snapshot = $3, "updatedAt" = $5`,
4051
+ [workflowName, runId, JSON.stringify(snapshot), now, now]
4052
+ );
4053
+ return snapshot.context;
4054
+ });
4055
+ },
4056
+ {
4057
+ onRetry: (error, attempt, delay) => {
4058
+ this.logger?.warn?.(
4059
+ `updateWorkflowResults retry ${attempt} for workflow ${workflowName}/${runId} after ${delay}ms: ${error.message}`
4060
+ );
4061
+ }
4062
+ }
4063
+ );
4064
+ return context;
4065
+ } catch (error) {
4066
+ throw new MastraError(
4067
+ {
4068
+ id: createStorageErrorId("DSQL", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
4069
+ domain: ErrorDomain.STORAGE,
4070
+ category: ErrorCategory.THIRD_PARTY,
4071
+ details: {
4072
+ workflowName,
4073
+ runId,
4074
+ stepId
4075
+ }
4076
+ },
4077
+ error
4078
+ );
4079
+ }
4080
+ }
4081
+ async updateWorkflowState({
4082
+ workflowName,
4083
+ runId,
4084
+ opts
4085
+ }) {
4086
+ try {
4087
+ const { result } = await withRetry(
4088
+ async () => {
4089
+ return this.#db.client.tx(async (t) => {
4090
+ const tableName = getTableName2({
4091
+ indexName: TABLE_WORKFLOW_SNAPSHOT,
4092
+ schemaName: getSchemaName2(this.#schema)
4093
+ });
4094
+ const existingSnapshotResult = await t.oneOrNone(
4095
+ `SELECT snapshot FROM ${tableName} WHERE workflow_name = $1 AND run_id = $2`,
4096
+ [workflowName, runId]
4097
+ );
4098
+ if (!existingSnapshotResult) {
4099
+ return void 0;
4100
+ }
4101
+ const existingSnapshot = existingSnapshotResult.snapshot;
4102
+ const snapshot = typeof existingSnapshot === "string" ? JSON.parse(existingSnapshot) : existingSnapshot;
4103
+ if (!snapshot || !snapshot?.context) {
4104
+ throw new Error(`Snapshot not found for runId ${runId}`);
4105
+ }
4106
+ const updatedSnapshot = { ...snapshot, ...opts };
4107
+ await t.none(
4108
+ `UPDATE ${tableName} SET snapshot = $1, "updatedAt" = $2 WHERE workflow_name = $3 AND run_id = $4`,
4109
+ [JSON.stringify(updatedSnapshot), /* @__PURE__ */ new Date(), workflowName, runId]
4110
+ );
4111
+ return updatedSnapshot;
4112
+ });
4113
+ },
4114
+ {
4115
+ onRetry: (error, attempt, delay) => {
4116
+ this.logger?.warn?.(
4117
+ `updateWorkflowState retry ${attempt} for workflow ${workflowName}/${runId} after ${delay}ms: ${error.message}`
4118
+ );
4119
+ }
4120
+ }
4121
+ );
4122
+ return result;
4123
+ } catch (error) {
4124
+ throw new MastraError(
4125
+ {
4126
+ id: createStorageErrorId("DSQL", "UPDATE_WORKFLOW_STATE", "FAILED"),
4127
+ domain: ErrorDomain.STORAGE,
4128
+ category: ErrorCategory.THIRD_PARTY,
4129
+ details: {
4130
+ workflowName,
4131
+ runId
4132
+ }
4133
+ },
4134
+ error
4135
+ );
4136
+ }
4137
+ }
4138
+ async persistWorkflowSnapshot({
4139
+ workflowName,
4140
+ runId,
4141
+ resourceId,
4142
+ snapshot,
4143
+ createdAt,
4144
+ updatedAt
4145
+ }) {
4146
+ try {
4147
+ const now = /* @__PURE__ */ new Date();
4148
+ const createdAtValue = createdAt ? createdAt : now;
4149
+ const updatedAtValue = updatedAt ? updatedAt : now;
4150
+ await this.#db.client.none(
4151
+ `INSERT INTO ${getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.#schema) })} (workflow_name, run_id, "resourceId", snapshot, "createdAt", "updatedAt")
4152
+ VALUES ($1, $2, $3, $4, $5, $6)
4153
+ ON CONFLICT (workflow_name, run_id) DO UPDATE
4154
+ SET "resourceId" = $3, snapshot = $4, "updatedAt" = $6`,
4155
+ [
4156
+ workflowName,
4157
+ runId,
4158
+ resourceId,
4159
+ JSON.stringify(snapshot),
4160
+ createdAtValue.toISOString(),
4161
+ updatedAtValue.toISOString()
4162
+ ]
4163
+ );
4164
+ } catch (error) {
4165
+ throw new MastraError(
4166
+ {
4167
+ id: createStorageErrorId("DSQL", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
4168
+ domain: ErrorDomain.STORAGE,
4169
+ category: ErrorCategory.THIRD_PARTY
4170
+ },
4171
+ error
4172
+ );
4173
+ }
4174
+ }
4175
+ async loadWorkflowSnapshot({
4176
+ workflowName,
4177
+ runId
4178
+ }) {
4179
+ try {
4180
+ const result = await this.#db.load({
4181
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
4182
+ keys: { workflow_name: workflowName, run_id: runId }
4183
+ });
4184
+ return result ? result.snapshot : null;
4185
+ } catch (error) {
4186
+ throw new MastraError(
4187
+ {
4188
+ id: createStorageErrorId("DSQL", "LOAD_WORKFLOW_SNAPSHOT", "FAILED"),
4189
+ domain: ErrorDomain.STORAGE,
4190
+ category: ErrorCategory.THIRD_PARTY
4191
+ },
4192
+ error
4193
+ );
4194
+ }
4195
+ }
4196
+ async getWorkflowRunById({
4197
+ runId,
4198
+ workflowName
4199
+ }) {
4200
+ try {
4201
+ const conditions = [];
4202
+ const values = [];
4203
+ let paramIndex = 1;
4204
+ if (runId) {
4205
+ conditions.push(`run_id = $${paramIndex}`);
4206
+ values.push(runId);
4207
+ paramIndex++;
4208
+ }
4209
+ if (workflowName) {
4210
+ conditions.push(`workflow_name = $${paramIndex}`);
4211
+ values.push(workflowName);
4212
+ paramIndex++;
4213
+ }
4214
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
4215
+ const query = `
4216
+ SELECT * FROM ${getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.#schema) })}
4217
+ ${whereClause}
4218
+ ORDER BY "createdAt" DESC LIMIT 1
4219
+ `;
4220
+ const queryValues = values;
4221
+ const result = await this.#db.client.oneOrNone(query, queryValues);
4222
+ if (!result) {
4223
+ return null;
4224
+ }
4225
+ return parseWorkflowRun(result);
4226
+ } catch (error) {
4227
+ throw new MastraError(
4228
+ {
4229
+ id: createStorageErrorId("DSQL", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
4230
+ domain: ErrorDomain.STORAGE,
4231
+ category: ErrorCategory.THIRD_PARTY,
4232
+ details: {
4233
+ runId,
4234
+ workflowName: workflowName || ""
4235
+ }
4236
+ },
4237
+ error
4238
+ );
4239
+ }
4240
+ }
4241
+ async deleteWorkflowRunById({ runId, workflowName }) {
4242
+ try {
4243
+ await this.#db.client.none(
4244
+ `DELETE FROM ${getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.#schema) })} WHERE run_id = $1 AND workflow_name = $2`,
4245
+ [runId, workflowName]
4246
+ );
4247
+ } catch (error) {
4248
+ throw new MastraError(
4249
+ {
4250
+ id: createStorageErrorId("DSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
4251
+ domain: ErrorDomain.STORAGE,
4252
+ category: ErrorCategory.THIRD_PARTY,
4253
+ details: {
4254
+ runId,
4255
+ workflowName
4256
+ }
4257
+ },
4258
+ error
4259
+ );
4260
+ }
4261
+ }
4262
+ async listWorkflowRuns({
4263
+ workflowName,
4264
+ fromDate,
4265
+ toDate,
4266
+ perPage,
4267
+ page,
4268
+ resourceId,
4269
+ status
4270
+ } = {}) {
4271
+ try {
4272
+ const conditions = [];
4273
+ const values = [];
4274
+ let paramIndex = 1;
4275
+ if (workflowName) {
4276
+ conditions.push(`workflow_name = $${paramIndex}`);
4277
+ values.push(workflowName);
4278
+ paramIndex++;
4279
+ }
4280
+ if (status) {
4281
+ conditions.push(`snapshot::jsonb ->> 'status' = $${paramIndex}`);
4282
+ values.push(status);
4283
+ paramIndex++;
4284
+ }
4285
+ if (resourceId) {
4286
+ const hasResourceId = await this.#db.hasColumn(TABLE_WORKFLOW_SNAPSHOT, "resourceId");
4287
+ if (hasResourceId) {
4288
+ conditions.push(`"resourceId" = $${paramIndex}`);
4289
+ values.push(resourceId);
4290
+ paramIndex++;
4291
+ } else {
4292
+ console.warn(`[${TABLE_WORKFLOW_SNAPSHOT}] resourceId column not found. Skipping resourceId filter.`);
4293
+ }
4294
+ }
4295
+ if (fromDate) {
4296
+ conditions.push(`"createdAt" >= $${paramIndex}`);
4297
+ values.push(fromDate instanceof Date ? fromDate.toISOString() : fromDate);
4298
+ paramIndex++;
4299
+ }
4300
+ if (toDate) {
4301
+ conditions.push(`"createdAt" <= $${paramIndex}`);
4302
+ values.push(toDate instanceof Date ? toDate.toISOString() : toDate);
4303
+ paramIndex++;
4304
+ }
4305
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
4306
+ let total = 0;
4307
+ const usePagination = typeof perPage === "number" && typeof page === "number";
4308
+ if (usePagination) {
4309
+ const countResult = await this.#db.client.one(
4310
+ `SELECT COUNT(*) as count FROM ${getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.#schema) })} ${whereClause}`,
4311
+ values
4312
+ );
4313
+ total = Number(countResult.count);
4314
+ }
4315
+ const normalizedPerPage = usePagination ? normalizePerPage(perPage, Number.MAX_SAFE_INTEGER) : 0;
4316
+ const offset = usePagination ? page * normalizedPerPage : void 0;
4317
+ const query = `
4318
+ SELECT * FROM ${getTableName2({ indexName: TABLE_WORKFLOW_SNAPSHOT, schemaName: getSchemaName2(this.#schema) })}
4319
+ ${whereClause}
4320
+ ORDER BY "createdAt" DESC
4321
+ ${usePagination ? ` LIMIT $${paramIndex} OFFSET $${paramIndex + 1}` : ""}
4322
+ `;
4323
+ const queryValues = usePagination ? [...values, normalizedPerPage, offset] : values;
4324
+ const result = await this.#db.client.manyOrNone(query, queryValues);
4325
+ const runs = (result || []).map((row) => {
4326
+ return parseWorkflowRun(row);
4327
+ });
4328
+ return { runs, total: total || runs.length };
4329
+ } catch (error) {
4330
+ throw new MastraError(
4331
+ {
4332
+ id: createStorageErrorId("DSQL", "LIST_WORKFLOW_RUNS", "FAILED"),
4333
+ domain: ErrorDomain.STORAGE,
4334
+ category: ErrorCategory.THIRD_PARTY,
4335
+ details: {
4336
+ workflowName: workflowName || "all"
4337
+ }
4338
+ },
4339
+ error
4340
+ );
4341
+ }
4342
+ }
4343
+ };
4344
+
4345
+ // src/storage/index.ts
4346
+ var DSQLStore = class extends MastraStorage {
4347
+ #pool;
4348
+ #db;
4349
+ #ownsPool;
4350
+ schema;
4351
+ isInitialized = false;
4352
+ stores;
4353
+ constructor(config) {
4354
+ try {
4355
+ validateConfig(config);
4356
+ super({ id: config.id, name: "DSQLStore", disableInit: config.disableInit });
4357
+ this.schema = config.schemaName || "public";
4358
+ if (isPoolConfig(config)) {
4359
+ this.#pool = config.pool;
4360
+ this.#ownsPool = false;
4361
+ } else {
4362
+ this.#pool = this.createPool(config);
4363
+ this.#ownsPool = true;
4364
+ }
4365
+ this.#db = new PoolAdapter(this.#pool);
4366
+ const domainConfig = {
4367
+ client: this.#db,
4368
+ schemaName: this.schema,
4369
+ skipDefaultIndexes: config.skipDefaultIndexes,
4370
+ indexes: config.indexes
4371
+ };
4372
+ this.stores = {
4373
+ scores: new ScoresDSQL(domainConfig),
4374
+ workflows: new WorkflowsDSQL(domainConfig),
4375
+ memory: new MemoryDSQL(domainConfig),
4376
+ observability: new ObservabilityDSQL(domainConfig),
4377
+ agents: new AgentsDSQL(domainConfig)
4378
+ };
4379
+ } catch (e) {
4380
+ throw new MastraError(
4381
+ {
4382
+ id: createStorageErrorId("DSQL", "INITIALIZATION", "FAILED"),
4383
+ domain: ErrorDomain.STORAGE,
4384
+ category: ErrorCategory.USER
4385
+ },
4386
+ e
4387
+ );
4388
+ }
4389
+ }
4390
+ /**
4391
+ * Creates a connection pool with AuroraDSQLClient for IAM authentication.
4392
+ */
4393
+ createPool(config) {
4394
+ if (!isHostConfig(config)) {
4395
+ throw new Error("DSQLStore: Invalid configuration for creating pool.");
4396
+ }
4397
+ const region = getEffectiveRegion(config);
4398
+ const poolConfig = {
4399
+ host: config.host,
4400
+ user: config.user ?? "admin",
4401
+ database: config.database ?? "postgres",
4402
+ // Use AuroraDSQLClient for automatic IAM token generation
4403
+ Client: AuroraDSQLClient,
4404
+ // Pass region for IAM token generation
4405
+ region,
4406
+ // Custom credentials provider (optional)
4407
+ customCredentialsProvider: config.customCredentialsProvider,
4408
+ // Pool settings optimized for Aurora DSQL
4409
+ max: config.max ?? DSQL_POOL_DEFAULTS.max,
4410
+ min: config.min ?? DSQL_POOL_DEFAULTS.min,
4411
+ idleTimeoutMillis: config.idleTimeoutMillis ?? DSQL_POOL_DEFAULTS.idleTimeoutMillis,
4412
+ maxLifetimeSeconds: config.maxLifetimeSeconds ?? DSQL_POOL_DEFAULTS.maxLifetimeSeconds,
4413
+ connectionTimeoutMillis: config.connectionTimeoutMillis ?? DSQL_POOL_DEFAULTS.connectionTimeoutMillis,
4414
+ allowExitOnIdle: config.allowExitOnIdle ?? DSQL_POOL_DEFAULTS.allowExitOnIdle
4415
+ };
4416
+ return new Pool(poolConfig);
4417
+ }
4418
+ async init() {
4419
+ if (this.isInitialized) {
4420
+ return;
4421
+ }
4422
+ try {
4423
+ this.isInitialized = true;
4424
+ await super.init();
4425
+ } catch (error) {
4426
+ this.isInitialized = false;
4427
+ throw new MastraError(
4428
+ {
4429
+ id: createStorageErrorId("DSQL", "INIT", "FAILED"),
4430
+ domain: ErrorDomain.STORAGE,
4431
+ category: ErrorCategory.THIRD_PARTY
4432
+ },
4433
+ error
4434
+ );
4435
+ }
4436
+ }
4437
+ /**
4438
+ * Database client for executing queries.
4439
+ *
4440
+ * @example
4441
+ * ```typescript
4442
+ * const rows = await store.db.any('SELECT * FROM users WHERE active = $1', [true]);
4443
+ * const user = await store.db.one('SELECT * FROM users WHERE id = $1', [userId]);
4444
+ * ```
4445
+ */
4446
+ get db() {
4447
+ return this.#db;
4448
+ }
4449
+ /**
4450
+ * The underlying pg.Pool for direct database access or ORM integration.
4451
+ */
4452
+ get pool() {
4453
+ return this.#pool;
4454
+ }
4455
+ /**
4456
+ * Closes the connection pool if it was created by this store.
4457
+ * If a pool was passed in via config, it will not be closed.
4458
+ */
4459
+ async close() {
4460
+ if (this.#ownsPool) {
4461
+ await this.#pool.end();
4462
+ }
4463
+ this.isInitialized = false;
4464
+ }
4465
+ };
4466
+
4467
+ export { AgentsDSQL, DSQLStore, DSQL_POOL_DEFAULTS, MemoryDSQL, ObservabilityDSQL, PoolAdapter, ScoresDSQL, WorkflowsDSQL };
4468
+ //# sourceMappingURL=index.js.map
4469
+ //# sourceMappingURL=index.js.map