@mastra/pg 1.31.1 → 1.31.2-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/docs/SKILL.md +1 -1
  2. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  3. package/dist/index.cjs +456 -147
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +457 -148
  6. package/dist/index.js.map +1 -1
  7. package/dist/storage/db/sanitize-json.d.ts +2 -0
  8. package/dist/storage/db/sanitize-json.d.ts.map +1 -1
  9. package/dist/storage/domains/notifications/index.d.ts +9 -0
  10. package/dist/storage/domains/notifications/index.d.ts.map +1 -1
  11. package/dist/storage/domains/observability/v-next/ddl.d.ts.map +1 -1
  12. package/dist/storage/domains/observability/v-next/helpers.d.ts +7 -0
  13. package/dist/storage/domains/observability/v-next/helpers.d.ts.map +1 -1
  14. package/dist/storage/domains/observability/v-next/index.d.ts +2 -1
  15. package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
  16. package/dist/storage/domains/observability/v-next/preview-compat.d.ts +47 -0
  17. package/dist/storage/domains/observability/v-next/preview-compat.d.ts.map +1 -0
  18. package/dist/storage/domains/observability/v-next/signal-schema.d.ts +12 -0
  19. package/dist/storage/domains/observability/v-next/signal-schema.d.ts.map +1 -1
  20. package/dist/storage/domains/observability/v-next/span-query.d.ts +2 -0
  21. package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -1
  22. package/dist/storage/domains/observability/v-next/sql.d.ts +11 -0
  23. package/dist/storage/domains/observability/v-next/sql.d.ts.map +1 -1
  24. package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
  25. package/dist/storage/domains/observability/v-next/traces.d.ts +3 -1
  26. package/dist/storage/domains/observability/v-next/traces.d.ts.map +1 -1
  27. package/package.json +4 -4
package/dist/index.cjs CHANGED
@@ -2472,6 +2472,10 @@ function repairJson(value) {
2472
2472
  }
2473
2473
  return value;
2474
2474
  }
2475
+ /** Repair text for a PostgreSQL `text` column, which rejects NUL characters. */
2476
+ function toPgText(value) {
2477
+ return repair(value);
2478
+ }
2475
2479
  /** Serialize JSON values without emitting Unicode sequences PostgreSQL rejects. */
2476
2480
  function toPgJson(value) {
2477
2481
  const json = JSON.stringify(value);
@@ -14563,6 +14567,7 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
14563
14567
  };
14564
14568
  //#endregion
14565
14569
  //#region src/storage/domains/notifications/index.ts
14570
+ const COMPOSITE_PRIMARY_KEY$1 = _mastra_core_storage.TABLE_CONFIGS[_mastra_core_storage.TABLE_NOTIFICATIONS].compositePrimaryKey;
14566
14571
  const statusTimestamp = (status, now) => {
14567
14572
  if (status === "delivered") return { deliveredAt: now };
14568
14573
  if (status === "seen") return { seenAt: now };
@@ -14674,11 +14679,58 @@ var NotificationsPG = class NotificationsPG extends _mastra_core_storage.Notific
14674
14679
  async init() {
14675
14680
  await this.#db.createTable({
14676
14681
  tableName: _mastra_core_storage.TABLE_NOTIFICATIONS,
14677
- schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_NOTIFICATIONS]
14682
+ schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_NOTIFICATIONS],
14683
+ compositePrimaryKey: COMPOSITE_PRIMARY_KEY$1
14678
14684
  });
14685
+ await this.ensurePrimaryKey();
14679
14686
  await this.createDefaultIndexes();
14680
14687
  await this.createCustomIndexes();
14681
14688
  }
14689
+ async #hasPrimaryKey() {
14690
+ return !!(await this.#db.client.oneOrNone(`SELECT EXISTS (
14691
+ SELECT 1 FROM pg_index i
14692
+ JOIN pg_class c ON c.oid = i.indrelid
14693
+ JOIN pg_namespace n ON n.oid = c.relnamespace
14694
+ WHERE c.relname = $1 AND n.nspname = $2 AND i.indisprimary
14695
+ ) AS exists`, [_mastra_core_storage.TABLE_NOTIFICATIONS, this.#schema]))?.exists;
14696
+ }
14697
+ /**
14698
+ * Tables created before the composite primary key was declared have no key at
14699
+ * all, which allows duplicate ids and leaves the table without a replica
14700
+ * identity (UPDATE/DELETE fail under logical replication). `createTable` is a
14701
+ * no-op for existing tables, so add the key here. If duplicate rows already
14702
+ * exist, or the role does not own the table, the key cannot be added; warn with
14703
+ * remediation instead of blocking init.
14704
+ */
14705
+ async ensurePrimaryKey() {
14706
+ if (await this.#hasPrimaryKey()) return;
14707
+ const tableName = getTableName$5({
14708
+ indexName: _mastra_core_storage.TABLE_NOTIFICATIONS,
14709
+ schemaName: getSchemaName$5(this.#schema)
14710
+ });
14711
+ const keyColumns = COMPOSITE_PRIMARY_KEY$1.map((column) => `"${(0, _mastra_core_utils.parseSqlIdentifier)(column, "column name")}"`).join(", ");
14712
+ const remediation = `ALTER TABLE ${tableName} ADD PRIMARY KEY (${keyColumns});`;
14713
+ if (await this.#db.client.oneOrNone(`SELECT 1 FROM ${tableName} GROUP BY ${keyColumns} HAVING count(*) > 1 LIMIT 1`)) {
14714
+ this.logger?.warn?.(`${tableName} contains duplicate (${keyColumns}) rows, so its PRIMARY KEY could not be added. Remove the duplicates, then run: ${remediation}`);
14715
+ return;
14716
+ }
14717
+ try {
14718
+ await this.#db.client.none(`ALTER TABLE ${tableName} ADD PRIMARY KEY (${keyColumns})`);
14719
+ this.logger?.info?.(`Added PRIMARY KEY (${keyColumns}) to ${tableName}`);
14720
+ } catch (error) {
14721
+ if (await this.#hasPrimaryKey()) return;
14722
+ if (error?.code === "42501") {
14723
+ this.logger?.warn?.(`Insufficient privilege to add PRIMARY KEY (${keyColumns}) to ${tableName}. Ask the table owner to run: ${remediation}`);
14724
+ return;
14725
+ }
14726
+ throw new _mastra_core_error.MastraError({
14727
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "ADD_NOTIFICATIONS_PRIMARY_KEY", "FAILED"),
14728
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
14729
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
14730
+ details: { tableName }
14731
+ }, error);
14732
+ }
14733
+ }
14682
14734
  /**
14683
14735
  * Lazily ensures a btree index exists on each configured policy's retention
14684
14736
  * anchor column so age-based `prune()` deletes stay fast on large tables.
@@ -14762,6 +14814,7 @@ var NotificationsPG = class NotificationsPG extends _mastra_core_storage.Notific
14762
14814
  tableName: _mastra_core_storage.TABLE_NOTIFICATIONS,
14763
14815
  schema: _mastra_core_storage.TABLE_SCHEMAS[_mastra_core_storage.TABLE_NOTIFICATIONS],
14764
14816
  schemaName,
14817
+ compositePrimaryKey: COMPOSITE_PRIMARY_KEY$1,
14765
14818
  includeAllConstraints: true
14766
14819
  }));
14767
14820
  for (const idx of NotificationsPG.getDefaultIndexDefs(schemaPrefix)) statements.push(generateIndexSQL(idx, schemaName));
@@ -16026,6 +16079,21 @@ const SPAN_EVENT_COLUMNS = [
16026
16079
  name: "requestContext",
16027
16080
  type: "jsonb",
16028
16081
  nullable: true
16082
+ },
16083
+ {
16084
+ name: "inputPreview",
16085
+ type: "text",
16086
+ nullable: true
16087
+ },
16088
+ {
16089
+ name: "outputPreview",
16090
+ type: "text",
16091
+ nullable: true
16092
+ },
16093
+ {
16094
+ name: "errorPreview",
16095
+ type: "text",
16096
+ nullable: true
16029
16097
  }
16030
16098
  ];
16031
16099
  const METRIC_EVENT_COLUMNS = [
@@ -16681,15 +16749,27 @@ function allTableDDL(schema, mode) {
16681
16749
  * default does not rewrite the table, and the ALTER cascades to partitions.
16682
16750
  */
16683
16751
  function additiveColumns(schema) {
16684
- return [{
16685
- table: TABLE_SPAN_EVENTS,
16686
- column: "isPending",
16687
- ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "isPending" boolean NOT NULL DEFAULT false`
16688
- }, {
16689
- table: TABLE_FEEDBACK_EVENTS,
16690
- column: "reviewStatus",
16691
- ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)} ADD COLUMN IF NOT EXISTS "reviewStatus" text NOT NULL DEFAULT 'needs-review'`
16692
- }];
16752
+ return [
16753
+ {
16754
+ table: TABLE_SPAN_EVENTS,
16755
+ column: "isPending",
16756
+ ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "isPending" boolean NOT NULL DEFAULT false`
16757
+ },
16758
+ ...[
16759
+ "inputPreview",
16760
+ "outputPreview",
16761
+ "errorPreview"
16762
+ ].map((column) => ({
16763
+ table: TABLE_SPAN_EVENTS,
16764
+ column,
16765
+ ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "${column}" text`
16766
+ })),
16767
+ {
16768
+ table: TABLE_FEEDBACK_EVENTS,
16769
+ column: "reviewStatus",
16770
+ ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)} ADD COLUMN IF NOT EXISTS "reviewStatus" text NOT NULL DEFAULT 'needs-review'`
16771
+ }
16772
+ ];
16693
16773
  }
16694
16774
  /** Existence probe for an additive column. Params: schema, table, column. */
16695
16775
  const columnExistsSQL = `SELECT 1 FROM information_schema.columns
@@ -17011,6 +17091,46 @@ function whereOrEmpty(acc) {
17011
17091
  return acc.conditions.length ? `WHERE ${acc.conditions.join(" AND ")}` : "";
17012
17092
  }
17013
17093
  //#endregion
17094
+ //#region src/storage/domains/observability/v-next/preview-compat.ts
17095
+ const generator = _mastra_core_storage.buildStoredPreview;
17096
+ const canGeneratePreviews = typeof generator === "function";
17097
+ /** SQL detection only; preview formatting stays in TypeScript. Empty strings are intentional. */
17098
+ function missingPreviewSql(column) {
17099
+ return canGeneratePreviews ? `${column} IS NULL` : "FALSE";
17100
+ }
17101
+ function needsPreview(value) {
17102
+ return canGeneratePreviews && value == null;
17103
+ }
17104
+ function storedPreview(preview, value, serialized) {
17105
+ return canGeneratePreviews ? generator(preview, value, serialized) : preview ?? null;
17106
+ }
17107
+ function readPreview(preview, payload, jsonEncoded = false) {
17108
+ const supplied = preview;
17109
+ if (!canGeneratePreviews) return supplied ?? null;
17110
+ if (supplied != null) return supplied.trim() || null;
17111
+ let value = payload;
17112
+ if (needsPreview(preview) && jsonEncoded && typeof payload === "string") try {
17113
+ value = JSON.parse(payload);
17114
+ } catch {
17115
+ value = null;
17116
+ }
17117
+ return storedPreview(supplied, value, jsonEncoded && typeof payload === "string" ? payload : void 0) || null;
17118
+ }
17119
+ /** Older response builders may shorten previews or turn empty strings into null. */
17120
+ function buildSpanQueryResponse(...args) {
17121
+ if (canGeneratePreviews) return _mastra_core_storage.buildSpanQueryResponse(...args);
17122
+ const [plan, selected, payloads, metrics] = args;
17123
+ const response = _mastra_core_storage.buildSpanQueryResponse(plan, selected, [], metrics);
17124
+ const byIdentity = new Map(payloads.map((payload) => [_mastra_core_storage.spanQueryIdentityKey(payload), payload]));
17125
+ for (const span of response.spans) {
17126
+ const payload = byIdentity.get(_mastra_core_storage.spanQueryIdentityKey(span));
17127
+ span.inputPreview = payload?.inputPreview ?? null;
17128
+ span.outputPreview = payload?.outputPreview ?? null;
17129
+ span.errorPreview = payload?.errorPreview ?? null;
17130
+ }
17131
+ return response;
17132
+ }
17133
+ //#endregion
17014
17134
  //#region src/storage/domains/observability/v-next/review-status.ts
17015
17135
  const FEEDBACK_REVIEW_STATUSES = ["needs-review", "reviewed"];
17016
17136
  function isFeedbackReviewStatus(value) {
@@ -17036,6 +17156,137 @@ function parseUpdateFeedbackReviewStatusArgs(args) {
17036
17156
  };
17037
17157
  }
17038
17158
  //#endregion
17159
+ //#region src/storage/domains/observability/v-next/sql.ts
17160
+ /**
17161
+ * SQL helpers for the v-next Postgres observability domain.
17162
+ *
17163
+ * Provides a multi-row INSERT builder with a configurable conflict clause
17164
+ * (`ON CONFLICT DO NOTHING` by default, for retry idempotency), and explicit
17165
+ * jsonb / text[] casts so the pg driver doesn't have to guess column types.
17166
+ */
17167
+ /**
17168
+ * Encode a JS value for a `$N::jsonb` cast. Always `JSON.stringify` so a
17169
+ * plain string like `"hello"` becomes `"hello"` (a valid JSON scalar) and
17170
+ * not the bare word `hello`, which Postgres rejects when cast to jsonb.
17171
+ *
17172
+ * The result is sanitized because Postgres rejects some sequences that are
17173
+ * legal in JSON: NUL (`\u0000`) fails with 22P05 and unpaired UTF-16
17174
+ * surrogates fail with 22P02. Inserts here are batched into a single
17175
+ * multi-row statement, so one bad value would otherwise reject the whole
17176
+ * batch of observability events.
17177
+ */
17178
+ function encodeJsonb(value) {
17179
+ if (value === null || value === void 0) return null;
17180
+ if (value instanceof EncodedJsonb) return value.json;
17181
+ return toPgJson(value);
17182
+ }
17183
+ /**
17184
+ * A jsonb value the caller already serialized with `toPgJson`, so `buildInsert`
17185
+ * sends it as is instead of serializing it again. Span writes serialize their
17186
+ * payloads up front because the stored previews are built from that same text.
17187
+ */
17188
+ var EncodedJsonb = class {
17189
+ json;
17190
+ constructor(json) {
17191
+ this.json = json;
17192
+ }
17193
+ };
17194
+ /** Serialize a value for a jsonb column once, for callers that also need the text. */
17195
+ function encodeJsonbValue(value) {
17196
+ if (value === null || value === void 0) return null;
17197
+ const json = toPgJson(value);
17198
+ return json === void 0 ? null : new EncodedJsonb(json);
17199
+ }
17200
+ /**
17201
+ * Build a multi-row INSERT with explicit column types and ON CONFLICT DO NOTHING.
17202
+ *
17203
+ * @param schema Schema name.
17204
+ * @param table Table name.
17205
+ * @param records Array of records (each is a column-name → value object).
17206
+ * All records must have identical key sets.
17207
+ * @returns { text, values } ready to pass to `client.query`.
17208
+ */
17209
+ function buildInsert(schema, table, records, onConflict = "ON CONFLICT DO NOTHING") {
17210
+ if (records.length === 0) return null;
17211
+ const columns = Object.keys(records[0]).map((c) => (0, _mastra_core_utils.parseSqlIdentifier)(c, "column name"));
17212
+ const quotedColumns = columns.map((c) => `"${c}"`).join(", ");
17213
+ const values = [];
17214
+ const rowPlaceholders = [];
17215
+ let p = 1;
17216
+ for (const record of records) {
17217
+ const placeholders = columns.map((col) => {
17218
+ const raw = record[col];
17219
+ if (JSONB_COLUMNS.has(col)) {
17220
+ values.push(encodeJsonb(raw));
17221
+ return `$${p++}::jsonb`;
17222
+ }
17223
+ if (TEXT_ARRAY_COLUMNS.has(col)) {
17224
+ values.push(Array.isArray(raw) ? raw : []);
17225
+ return `$${p++}::text[]`;
17226
+ }
17227
+ values.push(raw === void 0 ? null : raw);
17228
+ return `$${p++}`;
17229
+ });
17230
+ rowPlaceholders.push(`(${placeholders.join(", ")})`);
17231
+ }
17232
+ return {
17233
+ text: `INSERT INTO ${qualifiedTable(schema, table)} (${quotedColumns})
17234
+ VALUES ${rowPlaceholders.join(", ")}
17235
+ ${onConflict}`,
17236
+ values
17237
+ };
17238
+ }
17239
+ /** Primary key of the span table; also the conflict target for span upserts. */
17240
+ const SPAN_KEY_COLUMNS = [
17241
+ "traceId",
17242
+ "spanId",
17243
+ "endedAt"
17244
+ ];
17245
+ /**
17246
+ * Conflict clause for span writes.
17247
+ *
17248
+ * A span is written twice under the event-sourced strategy: once when it starts
17249
+ * (`isPending = true`, `endedAt` synthesized from `startedAt`) and once when it
17250
+ * ends. Those two rows normally differ in `endedAt` and so coexist under the
17251
+ * primary key. A zero-duration span is the exception — its real `endedAt`
17252
+ * equals its `startedAt`, so the end row collides with the pending row. Letting
17253
+ * `DO NOTHING` win there would strand the span as permanently running, so the
17254
+ * end row overwrites the pending one instead.
17255
+ *
17256
+ * The `WHERE` guard keeps the clause a no-op for every other conflict, which
17257
+ * preserves insert-only retry idempotency: a replayed end row cannot clobber
17258
+ * the row it duplicates, and a late-arriving start row cannot revert an ended
17259
+ * span to pending.
17260
+ */
17261
+ function spanConflictClause(columns) {
17262
+ return `ON CONFLICT ("traceId", "spanId", "endedAt") DO UPDATE SET ${columns.filter((column) => !SPAN_KEY_COLUMNS.includes(column)).map((column) => `"${column}" = EXCLUDED."${column}"`).join(", ")}
17263
+ WHERE ${TABLE_SPAN_EVENTS}."isPending" AND NOT EXCLUDED."isPending"`;
17264
+ }
17265
+ /**
17266
+ * Ordering that picks the winning row for a span under the event-sourced write
17267
+ * model. An ended row always beats a pending row for the same span; among rows
17268
+ * of the same kind the latest write wins, with `cursorId` breaking ties between
17269
+ * rows that share a timestamp. Combine with `DISTINCT ON` (or `LIMIT 1`) after
17270
+ * the grouping key to collapse a span's rows down to one.
17271
+ */
17272
+ const SPAN_COLLAPSE_ORDER = "\"isPending\" ASC, \"endedAt\" DESC, \"cursorId\" DESC";
17273
+ /**
17274
+ * Standard SELECT column list for tracing tables. The select projects every
17275
+ * column the row→record converters expect.
17276
+ */
17277
+ const SPAN_SELECT_COLUMNS = `
17278
+ ${buildSelectColumns(SPAN_EVENT_COLUMNS)}`;
17279
+ const SPAN_LIGHT_SELECT_COLUMNS = `
17280
+ ${buildNamedSelectColumns(SPAN_LIGHT_SELECT_COLUMN_NAMES)}`;
17281
+ const METRIC_SELECT_COLUMNS = `
17282
+ ${buildSelectColumns(METRIC_EVENT_COLUMNS)}`;
17283
+ const LOG_SELECT_COLUMNS = `
17284
+ ${buildSelectColumns(LOG_EVENT_COLUMNS)}`;
17285
+ const SCORE_SELECT_COLUMNS = `
17286
+ ${buildSelectColumns(SCORE_EVENT_COLUMNS)}`;
17287
+ const FEEDBACK_SELECT_COLUMNS = `
17288
+ ${buildSelectColumns(FEEDBACK_EVENT_COLUMNS)}`;
17289
+ //#endregion
17039
17290
  //#region src/storage/domains/observability/v-next/helpers.ts
17040
17291
  const PROMOTED_KEYS = /* @__PURE__ */ new Set([
17041
17292
  "experimentId",
@@ -17185,10 +17436,26 @@ function commonContextToRow(record) {
17185
17436
  serviceName: record.serviceName ?? null
17186
17437
  };
17187
17438
  }
17439
+ /**
17440
+ * Serialize a span payload for its jsonb column and build its stored preview from the same value.
17441
+ * Nonblank upstream previews win (trimmed and capped like generated ones). The preview goes into a `text` column, which
17442
+ * rejects NUL, so it gets the same repair `toPgJson` applies to the payload.
17443
+ */
17444
+ function spanPayload(value, upstream) {
17445
+ const encoded = encodeJsonbValue(value);
17446
+ const preview = storedPreview(upstream, value, encoded?.json);
17447
+ return {
17448
+ encoded,
17449
+ preview: preview === null ? null : toPgText(preview)
17450
+ };
17451
+ }
17188
17452
  function spanRecordToRow(span) {
17189
17453
  const isPending = !span.isEvent && span.endedAt == null;
17190
17454
  const endedAt = span.isEvent ? span.startedAt : span.endedAt ?? span.startedAt;
17191
17455
  const metadata = span.metadata ?? null;
17456
+ const input = spanPayload(span.input, span.inputPreview);
17457
+ const output = spanPayload(span.output, span.outputPreview);
17458
+ const error = spanPayload(span.error, span.errorPreview);
17192
17459
  return {
17193
17460
  ...commonContextToRow(span),
17194
17461
  traceId: span.traceId,
@@ -17206,10 +17473,13 @@ function spanRecordToRow(span) {
17206
17473
  scope: jsonField(span.scope),
17207
17474
  attributes: jsonField(span.attributes),
17208
17475
  links: jsonField(span.links),
17209
- input: jsonField(span.input),
17210
- output: jsonField(span.output),
17211
- error: jsonField(span.error),
17212
- requestContext: jsonField(span.requestContext)
17476
+ input: input.encoded,
17477
+ output: output.encoded,
17478
+ error: error.encoded,
17479
+ requestContext: jsonField(span.requestContext),
17480
+ inputPreview: input.preview,
17481
+ outputPreview: output.preview,
17482
+ errorPreview: error.preview
17213
17483
  };
17214
17484
  }
17215
17485
  /**
@@ -17276,6 +17546,32 @@ function rowToLightSpanRecord(row) {
17276
17546
  updatedAt: null
17277
17547
  };
17278
17548
  }
17549
+ /** A stored preview as lists return it: `''` (nothing to preview) and NULL become null. */
17550
+ function listPreview(value) {
17551
+ return readPreview(value);
17552
+ }
17553
+ /**
17554
+ * Build a trace-list {@link LightSpanRecord} from a row that projected the light trace-list columns
17555
+ * (see `TRACE_LIGHT_SELECT_COLUMNS` in traces.ts): the timeline fields plus the memory context,
17556
+ * metadata and the stored previews. Callers fill in previews for rows written before they were
17557
+ * stored first.
17558
+ */
17559
+ function rowToLightTraceRecord(row) {
17560
+ const record = rowToLightSpanRecord(row);
17561
+ return {
17562
+ ...record,
17563
+ status: (0, _mastra_core_storage.computeTraceStatus)({
17564
+ error: record.error,
17565
+ endedAt: record.endedAt
17566
+ }),
17567
+ threadId: row.threadId == null ? null : String(row.threadId),
17568
+ resourceId: row.resourceId == null ? null : String(row.resourceId),
17569
+ metadata: parsedJson(row.metadataRaw) ?? void 0,
17570
+ inputPreview: listPreview(row.inputPreview),
17571
+ outputPreview: listPreview(row.outputPreview),
17572
+ errorPreview: listPreview(row.errorPreview)
17573
+ };
17574
+ }
17279
17575
  function logRecordToRow(log) {
17280
17576
  return {
17281
17577
  ...commonContextToRow(log),
@@ -17791,119 +18087,6 @@ const COMPLEX_GROUP_BY_EXCLUDED = /* @__PURE__ */ new Set([
17791
18087
  "requestContext"
17792
18088
  ]);
17793
18089
  //#endregion
17794
- //#region src/storage/domains/observability/v-next/sql.ts
17795
- /**
17796
- * SQL helpers for the v-next Postgres observability domain.
17797
- *
17798
- * Provides a multi-row INSERT builder with a configurable conflict clause
17799
- * (`ON CONFLICT DO NOTHING` by default, for retry idempotency), and explicit
17800
- * jsonb / text[] casts so the pg driver doesn't have to guess column types.
17801
- */
17802
- /**
17803
- * Encode a JS value for a `$N::jsonb` cast. Always `JSON.stringify` so a
17804
- * plain string like `"hello"` becomes `"hello"` (a valid JSON scalar) and
17805
- * not the bare word `hello`, which Postgres rejects when cast to jsonb.
17806
- *
17807
- * The result is sanitized because Postgres rejects some sequences that are
17808
- * legal in JSON: NUL (`\u0000`) fails with 22P05 and unpaired UTF-16
17809
- * surrogates fail with 22P02. Inserts here are batched into a single
17810
- * multi-row statement, so one bad value would otherwise reject the whole
17811
- * batch of observability events.
17812
- */
17813
- function encodeJsonb(value) {
17814
- if (value === null || value === void 0) return null;
17815
- return toPgJson(value);
17816
- }
17817
- /**
17818
- * Build a multi-row INSERT with explicit column types and ON CONFLICT DO NOTHING.
17819
- *
17820
- * @param schema Schema name.
17821
- * @param table Table name.
17822
- * @param records Array of records (each is a column-name → value object).
17823
- * All records must have identical key sets.
17824
- * @returns { text, values } ready to pass to `client.query`.
17825
- */
17826
- function buildInsert(schema, table, records, onConflict = "ON CONFLICT DO NOTHING") {
17827
- if (records.length === 0) return null;
17828
- const columns = Object.keys(records[0]).map((c) => (0, _mastra_core_utils.parseSqlIdentifier)(c, "column name"));
17829
- const quotedColumns = columns.map((c) => `"${c}"`).join(", ");
17830
- const values = [];
17831
- const rowPlaceholders = [];
17832
- let p = 1;
17833
- for (const record of records) {
17834
- const placeholders = columns.map((col) => {
17835
- const raw = record[col];
17836
- if (JSONB_COLUMNS.has(col)) {
17837
- values.push(encodeJsonb(raw));
17838
- return `$${p++}::jsonb`;
17839
- }
17840
- if (TEXT_ARRAY_COLUMNS.has(col)) {
17841
- values.push(Array.isArray(raw) ? raw : []);
17842
- return `$${p++}::text[]`;
17843
- }
17844
- values.push(raw === void 0 ? null : raw);
17845
- return `$${p++}`;
17846
- });
17847
- rowPlaceholders.push(`(${placeholders.join(", ")})`);
17848
- }
17849
- return {
17850
- text: `INSERT INTO ${qualifiedTable(schema, table)} (${quotedColumns})
17851
- VALUES ${rowPlaceholders.join(", ")}
17852
- ${onConflict}`,
17853
- values
17854
- };
17855
- }
17856
- /** Primary key of the span table; also the conflict target for span upserts. */
17857
- const SPAN_KEY_COLUMNS = [
17858
- "traceId",
17859
- "spanId",
17860
- "endedAt"
17861
- ];
17862
- /**
17863
- * Conflict clause for span writes.
17864
- *
17865
- * A span is written twice under the event-sourced strategy: once when it starts
17866
- * (`isPending = true`, `endedAt` synthesized from `startedAt`) and once when it
17867
- * ends. Those two rows normally differ in `endedAt` and so coexist under the
17868
- * primary key. A zero-duration span is the exception — its real `endedAt`
17869
- * equals its `startedAt`, so the end row collides with the pending row. Letting
17870
- * `DO NOTHING` win there would strand the span as permanently running, so the
17871
- * end row overwrites the pending one instead.
17872
- *
17873
- * The `WHERE` guard keeps the clause a no-op for every other conflict, which
17874
- * preserves insert-only retry idempotency: a replayed end row cannot clobber
17875
- * the row it duplicates, and a late-arriving start row cannot revert an ended
17876
- * span to pending.
17877
- */
17878
- function spanConflictClause(columns) {
17879
- return `ON CONFLICT ("traceId", "spanId", "endedAt") DO UPDATE SET ${columns.filter((column) => !SPAN_KEY_COLUMNS.includes(column)).map((column) => `"${column}" = EXCLUDED."${column}"`).join(", ")}
17880
- WHERE ${TABLE_SPAN_EVENTS}."isPending" AND NOT EXCLUDED."isPending"`;
17881
- }
17882
- /**
17883
- * Ordering that picks the winning row for a span under the event-sourced write
17884
- * model. An ended row always beats a pending row for the same span; among rows
17885
- * of the same kind the latest write wins, with `cursorId` breaking ties between
17886
- * rows that share a timestamp. Combine with `DISTINCT ON` (or `LIMIT 1`) after
17887
- * the grouping key to collapse a span's rows down to one.
17888
- */
17889
- const SPAN_COLLAPSE_ORDER = "\"isPending\" ASC, \"endedAt\" DESC, \"cursorId\" DESC";
17890
- /**
17891
- * Standard SELECT column list for tracing tables. The select projects every
17892
- * column the row→record converters expect.
17893
- */
17894
- const SPAN_SELECT_COLUMNS = `
17895
- ${buildSelectColumns(SPAN_EVENT_COLUMNS)}`;
17896
- const SPAN_LIGHT_SELECT_COLUMNS = `
17897
- ${buildNamedSelectColumns(SPAN_LIGHT_SELECT_COLUMN_NAMES)}`;
17898
- const METRIC_SELECT_COLUMNS = `
17899
- ${buildSelectColumns(METRIC_EVENT_COLUMNS)}`;
17900
- const LOG_SELECT_COLUMNS = `
17901
- ${buildSelectColumns(LOG_EVENT_COLUMNS)}`;
17902
- const SCORE_SELECT_COLUMNS = `
17903
- ${buildSelectColumns(SCORE_EVENT_COLUMNS)}`;
17904
- const FEEDBACK_SELECT_COLUMNS = `
17905
- ${buildSelectColumns(FEEDBACK_EVENT_COLUMNS)}`;
17906
- //#endregion
17907
18090
  //#region src/storage/domains/observability/v-next/feedback.ts
17908
18091
  /**
17909
18092
  * Feedback operations for the v-next Postgres observability domain.
@@ -19062,7 +19245,10 @@ const TRACE_SELECT = `
19062
19245
  r."entityId" AS "entityId",
19063
19246
  r."parentSpanId" AS "parentSpanId",
19064
19247
  r."metadataRaw" AS "metadata",
19065
- r."input" AS "input",
19248
+ r."inputPreview" AS "inputPreview",
19249
+ r."outputPreview" AS "outputPreview",
19250
+ r."errorPreview" AS "errorPreview",
19251
+ r."cursorId" AS "cursorId",
19066
19252
  r."threadId" AS "threadId",
19067
19253
  r."resourceId" AS "resourceId",
19068
19254
  r."startedAt" AS "startedAt",
@@ -19520,7 +19706,7 @@ function compilePostgresTraceQuery(schema, plan, mode = "data", safeHorizon) {
19520
19706
  safeHorizon
19521
19707
  };
19522
19708
  }
19523
- const { ctes, values } = compilePostgresTraceCandidates(schema, plan, `${TRACE_SELECT}${plan.paginationMode === "delta" ? ", r.\"xactId\", r.\"cursorId\"" : ""}`, deltaWindow);
19709
+ const { ctes, values } = compilePostgresTraceCandidates(schema, plan, `${TRACE_SELECT}${plan.paginationMode === "delta" ? ", r.\"xactId\"" : ""}`, deltaWindow);
19524
19710
  const candidates = `WITH ${ctes.join(",\n")}`;
19525
19711
  if (plan.paginationMode === "page" && mode === "count") return {
19526
19712
  text: `${candidates}
@@ -19762,6 +19948,34 @@ async function setRemainingTimeout(transaction, deadline) {
19762
19948
  if (remainingTimeoutMs <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
19763
19949
  await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
19764
19950
  }
19951
+ const PREVIEW_PAYLOADS$2 = [
19952
+ ["inputPreview", "input"],
19953
+ ["outputPreview", "output"],
19954
+ ["errorPreview", "error"]
19955
+ ];
19956
+ /**
19957
+ * Fill the previews of root rows written before the preview columns existed (stored preview NULL)
19958
+ * by reading just those rows' payloads, and only the payloads whose preview is missing. Rows are
19959
+ * keyed by `cursorId`, which identifies the exact root row the list picked.
19960
+ *
19961
+ * jsonb comes back decoded, so the preview is built with `buildPreview(value)`. jsonb also
19962
+ * normalizes object key order, so a compact-JSON fallback preview can differ from the one the
19963
+ * writer would have stored from the original value. That is acceptable for these old rows.
19964
+ */
19965
+ async function fillMissingTracePreviews(transaction, schema, rows, deadline) {
19966
+ const missing = rows.filter((row) => PREVIEW_PAYLOADS$2.some(([preview]) => needsPreview(row[preview])));
19967
+ if (!missing.length) return;
19968
+ await setRemainingTimeout(transaction, deadline);
19969
+ const payloads = await transaction.any(`SELECT e."cursorId"::text AS "cursorId",
19970
+ ${PREVIEW_PAYLOADS$2.map(([preview, payload]) => `${missing.some((row) => needsPreview(row[preview])) ? `e."${payload}"` : "NULL"} AS "${payload}"`).join(",\n ")}
19971
+ FROM ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} e
19972
+ WHERE (e."traceId", e."cursorId") IN (SELECT * FROM unnest($1::text[], $2::bigint[]))`, [missing.map((row) => String(row.traceId)), missing.map((row) => String(row.cursorId))]);
19973
+ const byCursor = new Map(payloads.map((payload) => [String(payload.cursorId), payload]));
19974
+ for (const row of missing) {
19975
+ const payload = byCursor.get(String(row.cursorId));
19976
+ for (const [preview, column] of PREVIEW_PAYLOADS$2) if (needsPreview(row[preview])) row[preview] = readPreview(row[preview], payload?.[column]);
19977
+ }
19978
+ }
19765
19979
  function traceRowToResult(row) {
19766
19980
  return {
19767
19981
  traceId: String(row.traceId),
@@ -19771,7 +19985,9 @@ function traceRowToResult(row) {
19771
19985
  parentSpanId: row.parentSpanId ?? null,
19772
19986
  createdAt: asIsoTimestamp$1(row.startedAt),
19773
19987
  metadata: row.metadata ?? null,
19774
- inputPreview: _mastra_core_storage.buildInputPreview(row.input) ?? null,
19988
+ inputPreview: readPreview(row.inputPreview),
19989
+ outputPreview: readPreview(row.outputPreview),
19990
+ errorPreview: readPreview(row.errorPreview),
19775
19991
  threadId: row.threadId == null ? null : String(row.threadId),
19776
19992
  resourceId: row.resourceId == null ? null : String(row.resourceId),
19777
19993
  startedAt: asIsoTimestamp$1(row.startedAt),
@@ -19799,6 +20015,7 @@ async function queryTraces(client, schema, plan, timeoutMs) {
19799
20015
  rows = await transaction.any(query.text, query.values);
19800
20016
  }
19801
20017
  const visible = rows.slice(0, plan.limit);
20018
+ await fillMissingTracePreviews(transaction, schema, visible, deadline);
19802
20019
  const last = visible.at(-1);
19803
20020
  return _mastra_core_storage.traceQueryResponseSchema.parse({
19804
20021
  traces: visible.map(traceRowToResult),
@@ -19828,6 +20045,7 @@ async function queryTraces(client, schema, plan, timeoutMs) {
19828
20045
  if (remainingTimeoutMs <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
19829
20046
  await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
19830
20047
  const rows = await transaction.any(dataQuery.text, dataQuery.values);
20048
+ await fillMissingTracePreviews(transaction, schema, rows, deadline);
19831
20049
  return {
19832
20050
  total: Number(countRows[0]?.count ?? 0),
19833
20051
  rows,
@@ -19847,7 +20065,12 @@ async function queryTraces(client, schema, plan, timeoutMs) {
19847
20065
  });
19848
20066
  }
19849
20067
  const query = compilePostgresTraceQuery(schema, plan);
19850
- const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, (transaction) => transaction.any(query.text, query.values));
20068
+ const deadline = performance.now() + _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
20069
+ const rows = await runWithPostgresTraceQueryTimeout(client, timeoutMs, async (transaction) => {
20070
+ const rows = await transaction.any(query.text, query.values);
20071
+ if (plan.result === "traces") await fillMissingTracePreviews(transaction, schema, rows.slice(0, plan.limit), deadline);
20072
+ return rows;
20073
+ });
19851
20074
  const visibleRows = rows.slice(0, plan.limit);
19852
20075
  if (plan.result === "groups") {
19853
20076
  const groups = visibleRows.map((row) => ({ threadId: String(row.threadId) }));
@@ -20004,6 +20227,29 @@ function compilePostgresSpanQuery(schema, plan) {
20004
20227
  values
20005
20228
  };
20006
20229
  }
20230
+ const PREVIEW_PAYLOADS$1 = [
20231
+ ["inputPreview", "input"],
20232
+ ["outputPreview", "output"],
20233
+ ["errorPreview", "error"]
20234
+ ];
20235
+ /**
20236
+ * Stored previews of a hydrated row, built from the payload where the stored preview is NULL.
20237
+ * jsonb comes back decoded, so the fallback uses `buildPreview(value)`. jsonb normalizes object key
20238
+ * order, so a compact-JSON fallback preview can differ from what the writer would have stored from
20239
+ * the original value; that is acceptable for rows written before the preview columns existed.
20240
+ */
20241
+ function toSpanQueryPayload(row) {
20242
+ const [inputPreview, outputPreview, errorPreview] = PREVIEW_PAYLOADS$1.map(([preview, payload]) => readPreview(row[preview], row[payload]));
20243
+ return {
20244
+ organizationId: row.organizationId ?? null,
20245
+ resourceId: row.resourceId ?? null,
20246
+ traceId: String(row.traceId),
20247
+ spanId: String(row.spanId),
20248
+ inputPreview,
20249
+ outputPreview,
20250
+ errorPreview
20251
+ };
20252
+ }
20007
20253
  async function querySpans(client, schema, plan, timeoutMs) {
20008
20254
  const deadline = performance.now() + _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
20009
20255
  return runWithPostgresTraceQueryTimeout(client, timeoutMs, async (tx) => {
@@ -20028,9 +20274,9 @@ async function querySpans(client, schema, plan, timeoutMs) {
20028
20274
  payloadValues.push(row.version);
20029
20275
  return `(${key} AND "cursorId" = $${payloadValues.length}::bigint)`;
20030
20276
  }).join(" OR ");
20031
- const payloads = await tx.any(`SELECT ${identities},
20032
- left(input::text, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}) AS "inputPreview", left(output::text, ${_mastra_core_storage.SPAN_QUERY_MAX_PREVIEW_CHARACTERS + 1}) AS "outputPreview"
20033
- FROM ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} WHERE ${payloadWhere}`, payloadValues);
20277
+ const payloads = (await tx.any(`SELECT ${identities},
20278
+ ${PREVIEW_PAYLOADS$1.map(([preview, payload]) => `"${preview}", CASE WHEN ${missingPreviewSql(`"${preview}"`)} THEN "${payload}" END AS "${payload}"`).join(",\n ")}
20279
+ FROM ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} WHERE ${payloadWhere}`, payloadValues)).map(toSpanQueryPayload);
20034
20280
  await remaining();
20035
20281
  const metricValues = [];
20036
20282
  const metricWhere = selectedKeys(page, metricValues);
@@ -20041,7 +20287,7 @@ async function querySpans(client, schema, plan, timeoutMs) {
20041
20287
  ORDER BY ${identities}, "metricId", "cursorId" DESC) m
20042
20288
  LIMIT ${_mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS + 1}`, metricValues);
20043
20289
  if (metrics.length > _mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS) throw new _mastra_core_storage.TraceQueryResourceLimitError();
20044
- return _mastra_core_storage.buildSpanQueryResponse(plan, selected, payloads, metrics);
20290
+ return buildSpanQueryResponse(plan, selected, payloads, metrics);
20045
20291
  }, { repeatableRead: true });
20046
20292
  }
20047
20293
  //#endregion
@@ -20506,16 +20752,73 @@ function buildListTracesFilters(filters, spanTable, nextParamIdx) {
20506
20752
  * array-of-strings representation here.
20507
20753
  */
20508
20754
  const SPAN_SELECT_COLUMNS_ALIASED = SPAN_SELECT_COLUMNS.replace(/\n/g, " ").split(",").map((c) => `r.${c.trim()}`).join(", ");
20755
+ const FULL_PROJECTION = {
20756
+ columns: SPAN_SELECT_COLUMNS_ALIASED,
20757
+ mapRows: (_client, _span, rows) => (0, _mastra_core_storage.toTraceSpans)(rows.map(rowToSpanRecord))
20758
+ };
20759
+ /**
20760
+ * Columns a light trace list renders: the timeline fields, memory context, metadata and the stored
20761
+ * previews. The `input` / `output` payloads stay off the read path; rows written before the
20762
+ * previews were stored get theirs from the payloads, read for just those rows.
20763
+ */
20764
+ const TRACE_LIGHT_SELECT_COLUMNS = [
20765
+ ...SPAN_LIGHT_SELECT_COLUMN_NAMES,
20766
+ "threadId",
20767
+ "resourceId",
20768
+ "metadataRaw",
20769
+ "inputPreview",
20770
+ "outputPreview",
20771
+ "errorPreview"
20772
+ ].map((column) => `r."${column}"`).join(", ");
20773
+ const PREVIEW_PAYLOADS = [
20774
+ ["inputPreview", "input"],
20775
+ ["outputPreview", "output"],
20776
+ ["errorPreview", "error"]
20777
+ ];
20778
+ /**
20779
+ * Builds the previews of rows written before previews were stored (NULL preview) from those rows'
20780
+ * payloads. Only the flagged rows' payloads are read, so this fades out as old rows age out.
20781
+ *
20782
+ * `jsonb` comes back decoded with its keys reordered, so a compact-JSON preview built here can
20783
+ * differ in key order from the one the writer would have stored. Acceptable for old rows.
20784
+ */
20785
+ async function fillMissingPreviews(client, span, rows) {
20786
+ const missing = rows.filter((row) => PREVIEW_PAYLOADS.some(([preview]) => needsPreview(row[preview])));
20787
+ if (!missing.length) return;
20788
+ const payloads = await client.manyOrNone(`SELECT e."cursorId"::text AS "cursorId",
20789
+ ${PREVIEW_PAYLOADS.map(([preview, payload]) => `${missing.some((row) => needsPreview(row[preview])) ? `e."${payload}"` : "NULL"} AS "${payload}"`).join(",\n ")}
20790
+ FROM ${span} e
20791
+ WHERE (e."traceId", e."cursorId") IN (SELECT * FROM unnest($1::text[], $2::bigint[]))`, [missing.map((row) => String(row.traceId)), missing.map((row) => String(row.cursorId))]);
20792
+ const byCursor = new Map(payloads.map((payload) => [String(payload.cursorId), payload]));
20793
+ for (const row of missing) {
20794
+ const payload = byCursor.get(String(row.cursorId));
20795
+ for (const [preview, column] of PREVIEW_PAYLOADS) if (needsPreview(row[preview])) row[preview] = readPreview(row[preview], payload?.[column]);
20796
+ }
20797
+ }
20798
+ const LIGHT_PROJECTION = {
20799
+ columns: TRACE_LIGHT_SELECT_COLUMNS,
20800
+ mapRows: async (client, span, rows) => {
20801
+ await fillMissingPreviews(client, span, rows);
20802
+ return rows.map(rowToLightTraceRecord);
20803
+ }
20804
+ };
20509
20805
  async function listTraces(client, schema, args) {
20806
+ return listTraceRows(client, schema, args, FULL_PROJECTION);
20807
+ }
20808
+ /** Trace list projecting only the columns a list renders, with stored previews instead of payloads. */
20809
+ async function listTracesLight(client, schema, args) {
20810
+ return listTraceRows(client, schema, args, LIGHT_PROJECTION);
20811
+ }
20812
+ async function listTraceRows(client, schema, args, projection) {
20510
20813
  const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listTracesArgsSchema.parse(args);
20511
20814
  const span = qualifiedTable(schema, TABLE_SPAN_EVENTS);
20512
20815
  if (mode === "delta") {
20513
20816
  assertDeltaPollingEnabled();
20514
- return listTracesDelta(client, span, filters, after, limit);
20817
+ return listTracesDelta(client, span, projection, filters, after, limit);
20515
20818
  }
20516
- return listTracesPage(client, span, filters, pagination.page, pagination.perPage, orderBy.field, orderBy.direction);
20819
+ return listTracesPage(client, span, projection, filters, pagination.page, pagination.perPage, orderBy.field, orderBy.direction);
20517
20820
  }
20518
- async function listTracesPage(client, span, filters, page, perPage, orderField, orderDir) {
20821
+ async function listTracesPage(client, span, projection, filters, page, perPage, orderField, orderDir) {
20519
20822
  const { conditions, params, nextParamIdx } = buildListTracesFilters(filters, span, 1);
20520
20823
  let i = nextParamIdx;
20521
20824
  const whereClause = `WHERE ${conditions.join(" AND ")}`;
@@ -20523,15 +20826,18 @@ async function listTracesPage(client, span, filters, page, perPage, orderField,
20523
20826
  const countRow = await client.oneOrNone(`SELECT COUNT(*)::text AS count FROM ${span} r ${whereClause}`, params);
20524
20827
  const count = Number(countRow?.count ?? 0);
20525
20828
  let spans = [];
20526
- if (count > 0) spans = (await client.manyOrNone(`SELECT ${SPAN_SELECT_COLUMNS_ALIASED}
20829
+ if (count > 0) {
20830
+ const rows = await client.manyOrNone(`SELECT ${projection.columns}
20527
20831
  FROM ${span} r
20528
20832
  ${whereClause}
20529
20833
  ${orderClause}
20530
20834
  LIMIT $${i++} OFFSET $${i++}`, [
20531
- ...params,
20532
- perPage,
20533
- page * perPage
20534
- ])).map(rowToSpanRecord);
20835
+ ...params,
20836
+ perPage,
20837
+ page * perPage
20838
+ ]);
20839
+ spans = await projection.mapRows(client, span, rows);
20840
+ }
20535
20841
  const deltaCursor = deltaPollingFeatureEnabled() ? await readTracesStreamHeadCursor(client, span, filters) : void 0;
20536
20842
  return {
20537
20843
  pagination: {
@@ -20540,11 +20846,11 @@ async function listTracesPage(client, span, filters, page, perPage, orderField,
20540
20846
  perPage,
20541
20847
  hasMore: (page + 1) * perPage < count
20542
20848
  },
20543
- spans: (0, _mastra_core_storage.toTraceSpans)(spans),
20849
+ spans,
20544
20850
  ...deltaCursor !== void 0 ? { deltaCursor } : {}
20545
20851
  };
20546
20852
  }
20547
- async function listTracesDelta(client, span, filters, after, limit) {
20853
+ async function listTracesDelta(client, span, projection, filters, after, limit) {
20548
20854
  if (after === void 0) {
20549
20855
  const deltaCursor = await readTracesStreamHeadCursor(client, span, filters);
20550
20856
  return {
@@ -20564,7 +20870,7 @@ async function listTracesDelta(client, span, filters, after, limit) {
20564
20870
  params.push(afterCursor.xactId, afterCursor.cursorId);
20565
20871
  conditions.push(`r."xactId" < $${i++}::xid8`);
20566
20872
  params.push(safeHorizon);
20567
- const rows = await client.manyOrNone(`SELECT ${SPAN_SELECT_COLUMNS_ALIASED}
20873
+ const rows = await client.manyOrNone(`SELECT ${projection.columns}
20568
20874
  FROM ${span} r
20569
20875
  WHERE ${conditions.join(" AND ")}
20570
20876
  ORDER BY r."xactId" ASC, r."cursorId" ASC
@@ -20573,7 +20879,7 @@ async function listTracesDelta(client, span, filters, after, limit) {
20573
20879
  const visible = rows.slice(0, limit);
20574
20880
  const deltaCursor = visible.length > 0 ? encodeDeltaCursor(visible[visible.length - 1].xactId, visible[visible.length - 1].cursorId) : encodeDeltaCursor(safeHorizon, 0);
20575
20881
  return {
20576
- spans: (0, _mastra_core_storage.toTraceSpans)(visible.map(rowToSpanRecord)),
20882
+ spans: await projection.mapRows(client, span, visible),
20577
20883
  delta: {
20578
20884
  limit,
20579
20885
  hasMore
@@ -21284,6 +21590,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21284
21590
  async listTraces(args) {
21285
21591
  return this.#run("LIST_TRACES", () => listTraces(this.#readClient, this.#schema, args));
21286
21592
  }
21593
+ async listTracesLight(args) {
21594
+ return this.#run("LIST_TRACES_LIGHT", () => listTracesLight(this.#readClient, this.#schema, args));
21595
+ }
21287
21596
  async querySpans(plan) {
21288
21597
  return this.#run("QUERY_SPANS", () => querySpans(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
21289
21598
  }