@mastra/pg 1.31.2-alpha.0 → 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 (25) 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 +406 -146
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.js +407 -147
  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/observability/v-next/ddl.d.ts.map +1 -1
  10. package/dist/storage/domains/observability/v-next/helpers.d.ts +7 -0
  11. package/dist/storage/domains/observability/v-next/helpers.d.ts.map +1 -1
  12. package/dist/storage/domains/observability/v-next/index.d.ts +2 -1
  13. package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
  14. package/dist/storage/domains/observability/v-next/preview-compat.d.ts +47 -0
  15. package/dist/storage/domains/observability/v-next/preview-compat.d.ts.map +1 -0
  16. package/dist/storage/domains/observability/v-next/signal-schema.d.ts +12 -0
  17. package/dist/storage/domains/observability/v-next/signal-schema.d.ts.map +1 -1
  18. package/dist/storage/domains/observability/v-next/span-query.d.ts +2 -0
  19. package/dist/storage/domains/observability/v-next/span-query.d.ts.map +1 -1
  20. package/dist/storage/domains/observability/v-next/sql.d.ts +11 -0
  21. package/dist/storage/domains/observability/v-next/sql.d.ts.map +1 -1
  22. package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
  23. package/dist/storage/domains/observability/v-next/traces.d.ts +3 -1
  24. package/dist/storage/domains/observability/v-next/traces.d.ts.map +1 -1
  25. 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);
@@ -16075,6 +16079,21 @@ const SPAN_EVENT_COLUMNS = [
16075
16079
  name: "requestContext",
16076
16080
  type: "jsonb",
16077
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
16078
16097
  }
16079
16098
  ];
16080
16099
  const METRIC_EVENT_COLUMNS = [
@@ -16730,15 +16749,27 @@ function allTableDDL(schema, mode) {
16730
16749
  * default does not rewrite the table, and the ALTER cascades to partitions.
16731
16750
  */
16732
16751
  function additiveColumns(schema) {
16733
- return [{
16734
- table: TABLE_SPAN_EVENTS,
16735
- column: "isPending",
16736
- ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_SPAN_EVENTS)} ADD COLUMN IF NOT EXISTS "isPending" boolean NOT NULL DEFAULT false`
16737
- }, {
16738
- table: TABLE_FEEDBACK_EVENTS,
16739
- column: "reviewStatus",
16740
- ddl: `ALTER TABLE ${qualifiedTable(schema, TABLE_FEEDBACK_EVENTS)} ADD COLUMN IF NOT EXISTS "reviewStatus" text NOT NULL DEFAULT 'needs-review'`
16741
- }];
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
+ ];
16742
16773
  }
16743
16774
  /** Existence probe for an additive column. Params: schema, table, column. */
16744
16775
  const columnExistsSQL = `SELECT 1 FROM information_schema.columns
@@ -17060,6 +17091,46 @@ function whereOrEmpty(acc) {
17060
17091
  return acc.conditions.length ? `WHERE ${acc.conditions.join(" AND ")}` : "";
17061
17092
  }
17062
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
17063
17134
  //#region src/storage/domains/observability/v-next/review-status.ts
17064
17135
  const FEEDBACK_REVIEW_STATUSES = ["needs-review", "reviewed"];
17065
17136
  function isFeedbackReviewStatus(value) {
@@ -17085,6 +17156,137 @@ function parseUpdateFeedbackReviewStatusArgs(args) {
17085
17156
  };
17086
17157
  }
17087
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
17088
17290
  //#region src/storage/domains/observability/v-next/helpers.ts
17089
17291
  const PROMOTED_KEYS = /* @__PURE__ */ new Set([
17090
17292
  "experimentId",
@@ -17234,10 +17436,26 @@ function commonContextToRow(record) {
17234
17436
  serviceName: record.serviceName ?? null
17235
17437
  };
17236
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
+ }
17237
17452
  function spanRecordToRow(span) {
17238
17453
  const isPending = !span.isEvent && span.endedAt == null;
17239
17454
  const endedAt = span.isEvent ? span.startedAt : span.endedAt ?? span.startedAt;
17240
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);
17241
17459
  return {
17242
17460
  ...commonContextToRow(span),
17243
17461
  traceId: span.traceId,
@@ -17255,10 +17473,13 @@ function spanRecordToRow(span) {
17255
17473
  scope: jsonField(span.scope),
17256
17474
  attributes: jsonField(span.attributes),
17257
17475
  links: jsonField(span.links),
17258
- input: jsonField(span.input),
17259
- output: jsonField(span.output),
17260
- error: jsonField(span.error),
17261
- 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
17262
17483
  };
17263
17484
  }
17264
17485
  /**
@@ -17325,6 +17546,32 @@ function rowToLightSpanRecord(row) {
17325
17546
  updatedAt: null
17326
17547
  };
17327
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
+ }
17328
17575
  function logRecordToRow(log) {
17329
17576
  return {
17330
17577
  ...commonContextToRow(log),
@@ -17840,119 +18087,6 @@ const COMPLEX_GROUP_BY_EXCLUDED = /* @__PURE__ */ new Set([
17840
18087
  "requestContext"
17841
18088
  ]);
17842
18089
  //#endregion
17843
- //#region src/storage/domains/observability/v-next/sql.ts
17844
- /**
17845
- * SQL helpers for the v-next Postgres observability domain.
17846
- *
17847
- * Provides a multi-row INSERT builder with a configurable conflict clause
17848
- * (`ON CONFLICT DO NOTHING` by default, for retry idempotency), and explicit
17849
- * jsonb / text[] casts so the pg driver doesn't have to guess column types.
17850
- */
17851
- /**
17852
- * Encode a JS value for a `$N::jsonb` cast. Always `JSON.stringify` so a
17853
- * plain string like `"hello"` becomes `"hello"` (a valid JSON scalar) and
17854
- * not the bare word `hello`, which Postgres rejects when cast to jsonb.
17855
- *
17856
- * The result is sanitized because Postgres rejects some sequences that are
17857
- * legal in JSON: NUL (`\u0000`) fails with 22P05 and unpaired UTF-16
17858
- * surrogates fail with 22P02. Inserts here are batched into a single
17859
- * multi-row statement, so one bad value would otherwise reject the whole
17860
- * batch of observability events.
17861
- */
17862
- function encodeJsonb(value) {
17863
- if (value === null || value === void 0) return null;
17864
- return toPgJson(value);
17865
- }
17866
- /**
17867
- * Build a multi-row INSERT with explicit column types and ON CONFLICT DO NOTHING.
17868
- *
17869
- * @param schema Schema name.
17870
- * @param table Table name.
17871
- * @param records Array of records (each is a column-name → value object).
17872
- * All records must have identical key sets.
17873
- * @returns { text, values } ready to pass to `client.query`.
17874
- */
17875
- function buildInsert(schema, table, records, onConflict = "ON CONFLICT DO NOTHING") {
17876
- if (records.length === 0) return null;
17877
- const columns = Object.keys(records[0]).map((c) => (0, _mastra_core_utils.parseSqlIdentifier)(c, "column name"));
17878
- const quotedColumns = columns.map((c) => `"${c}"`).join(", ");
17879
- const values = [];
17880
- const rowPlaceholders = [];
17881
- let p = 1;
17882
- for (const record of records) {
17883
- const placeholders = columns.map((col) => {
17884
- const raw = record[col];
17885
- if (JSONB_COLUMNS.has(col)) {
17886
- values.push(encodeJsonb(raw));
17887
- return `$${p++}::jsonb`;
17888
- }
17889
- if (TEXT_ARRAY_COLUMNS.has(col)) {
17890
- values.push(Array.isArray(raw) ? raw : []);
17891
- return `$${p++}::text[]`;
17892
- }
17893
- values.push(raw === void 0 ? null : raw);
17894
- return `$${p++}`;
17895
- });
17896
- rowPlaceholders.push(`(${placeholders.join(", ")})`);
17897
- }
17898
- return {
17899
- text: `INSERT INTO ${qualifiedTable(schema, table)} (${quotedColumns})
17900
- VALUES ${rowPlaceholders.join(", ")}
17901
- ${onConflict}`,
17902
- values
17903
- };
17904
- }
17905
- /** Primary key of the span table; also the conflict target for span upserts. */
17906
- const SPAN_KEY_COLUMNS = [
17907
- "traceId",
17908
- "spanId",
17909
- "endedAt"
17910
- ];
17911
- /**
17912
- * Conflict clause for span writes.
17913
- *
17914
- * A span is written twice under the event-sourced strategy: once when it starts
17915
- * (`isPending = true`, `endedAt` synthesized from `startedAt`) and once when it
17916
- * ends. Those two rows normally differ in `endedAt` and so coexist under the
17917
- * primary key. A zero-duration span is the exception — its real `endedAt`
17918
- * equals its `startedAt`, so the end row collides with the pending row. Letting
17919
- * `DO NOTHING` win there would strand the span as permanently running, so the
17920
- * end row overwrites the pending one instead.
17921
- *
17922
- * The `WHERE` guard keeps the clause a no-op for every other conflict, which
17923
- * preserves insert-only retry idempotency: a replayed end row cannot clobber
17924
- * the row it duplicates, and a late-arriving start row cannot revert an ended
17925
- * span to pending.
17926
- */
17927
- function spanConflictClause(columns) {
17928
- return `ON CONFLICT ("traceId", "spanId", "endedAt") DO UPDATE SET ${columns.filter((column) => !SPAN_KEY_COLUMNS.includes(column)).map((column) => `"${column}" = EXCLUDED."${column}"`).join(", ")}
17929
- WHERE ${TABLE_SPAN_EVENTS}."isPending" AND NOT EXCLUDED."isPending"`;
17930
- }
17931
- /**
17932
- * Ordering that picks the winning row for a span under the event-sourced write
17933
- * model. An ended row always beats a pending row for the same span; among rows
17934
- * of the same kind the latest write wins, with `cursorId` breaking ties between
17935
- * rows that share a timestamp. Combine with `DISTINCT ON` (or `LIMIT 1`) after
17936
- * the grouping key to collapse a span's rows down to one.
17937
- */
17938
- const SPAN_COLLAPSE_ORDER = "\"isPending\" ASC, \"endedAt\" DESC, \"cursorId\" DESC";
17939
- /**
17940
- * Standard SELECT column list for tracing tables. The select projects every
17941
- * column the row→record converters expect.
17942
- */
17943
- const SPAN_SELECT_COLUMNS = `
17944
- ${buildSelectColumns(SPAN_EVENT_COLUMNS)}`;
17945
- const SPAN_LIGHT_SELECT_COLUMNS = `
17946
- ${buildNamedSelectColumns(SPAN_LIGHT_SELECT_COLUMN_NAMES)}`;
17947
- const METRIC_SELECT_COLUMNS = `
17948
- ${buildSelectColumns(METRIC_EVENT_COLUMNS)}`;
17949
- const LOG_SELECT_COLUMNS = `
17950
- ${buildSelectColumns(LOG_EVENT_COLUMNS)}`;
17951
- const SCORE_SELECT_COLUMNS = `
17952
- ${buildSelectColumns(SCORE_EVENT_COLUMNS)}`;
17953
- const FEEDBACK_SELECT_COLUMNS = `
17954
- ${buildSelectColumns(FEEDBACK_EVENT_COLUMNS)}`;
17955
- //#endregion
17956
18090
  //#region src/storage/domains/observability/v-next/feedback.ts
17957
18091
  /**
17958
18092
  * Feedback operations for the v-next Postgres observability domain.
@@ -19111,7 +19245,10 @@ const TRACE_SELECT = `
19111
19245
  r."entityId" AS "entityId",
19112
19246
  r."parentSpanId" AS "parentSpanId",
19113
19247
  r."metadataRaw" AS "metadata",
19114
- r."input" AS "input",
19248
+ r."inputPreview" AS "inputPreview",
19249
+ r."outputPreview" AS "outputPreview",
19250
+ r."errorPreview" AS "errorPreview",
19251
+ r."cursorId" AS "cursorId",
19115
19252
  r."threadId" AS "threadId",
19116
19253
  r."resourceId" AS "resourceId",
19117
19254
  r."startedAt" AS "startedAt",
@@ -19569,7 +19706,7 @@ function compilePostgresTraceQuery(schema, plan, mode = "data", safeHorizon) {
19569
19706
  safeHorizon
19570
19707
  };
19571
19708
  }
19572
- 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);
19573
19710
  const candidates = `WITH ${ctes.join(",\n")}`;
19574
19711
  if (plan.paginationMode === "page" && mode === "count") return {
19575
19712
  text: `${candidates}
@@ -19811,6 +19948,34 @@ async function setRemainingTimeout(transaction, deadline) {
19811
19948
  if (remainingTimeoutMs <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
19812
19949
  await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
19813
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
+ }
19814
19979
  function traceRowToResult(row) {
19815
19980
  return {
19816
19981
  traceId: String(row.traceId),
@@ -19820,7 +19985,9 @@ function traceRowToResult(row) {
19820
19985
  parentSpanId: row.parentSpanId ?? null,
19821
19986
  createdAt: asIsoTimestamp$1(row.startedAt),
19822
19987
  metadata: row.metadata ?? null,
19823
- inputPreview: _mastra_core_storage.buildInputPreview(row.input) ?? null,
19988
+ inputPreview: readPreview(row.inputPreview),
19989
+ outputPreview: readPreview(row.outputPreview),
19990
+ errorPreview: readPreview(row.errorPreview),
19824
19991
  threadId: row.threadId == null ? null : String(row.threadId),
19825
19992
  resourceId: row.resourceId == null ? null : String(row.resourceId),
19826
19993
  startedAt: asIsoTimestamp$1(row.startedAt),
@@ -19848,6 +20015,7 @@ async function queryTraces(client, schema, plan, timeoutMs) {
19848
20015
  rows = await transaction.any(query.text, query.values);
19849
20016
  }
19850
20017
  const visible = rows.slice(0, plan.limit);
20018
+ await fillMissingTracePreviews(transaction, schema, visible, deadline);
19851
20019
  const last = visible.at(-1);
19852
20020
  return _mastra_core_storage.traceQueryResponseSchema.parse({
19853
20021
  traces: visible.map(traceRowToResult),
@@ -19877,6 +20045,7 @@ async function queryTraces(client, schema, plan, timeoutMs) {
19877
20045
  if (remainingTimeoutMs <= 0) throw new _mastra_core_storage.TraceQueryExecutionError();
19878
20046
  await transaction.query(`SELECT set_config('statement_timeout', $1, true)`, [`${remainingTimeoutMs}ms`]);
19879
20047
  const rows = await transaction.any(dataQuery.text, dataQuery.values);
20048
+ await fillMissingTracePreviews(transaction, schema, rows, deadline);
19880
20049
  return {
19881
20050
  total: Number(countRows[0]?.count ?? 0),
19882
20051
  rows,
@@ -19896,7 +20065,12 @@ async function queryTraces(client, schema, plan, timeoutMs) {
19896
20065
  });
19897
20066
  }
19898
20067
  const query = compilePostgresTraceQuery(schema, plan);
19899
- 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
+ });
19900
20074
  const visibleRows = rows.slice(0, plan.limit);
19901
20075
  if (plan.result === "groups") {
19902
20076
  const groups = visibleRows.map((row) => ({ threadId: String(row.threadId) }));
@@ -20053,6 +20227,29 @@ function compilePostgresSpanQuery(schema, plan) {
20053
20227
  values
20054
20228
  };
20055
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
+ }
20056
20253
  async function querySpans(client, schema, plan, timeoutMs) {
20057
20254
  const deadline = performance.now() + _mastra_core_storage.resolveTraceQueryTimeoutMs(timeoutMs);
20058
20255
  return runWithPostgresTraceQueryTimeout(client, timeoutMs, async (tx) => {
@@ -20077,9 +20274,9 @@ async function querySpans(client, schema, plan, timeoutMs) {
20077
20274
  payloadValues.push(row.version);
20078
20275
  return `(${key} AND "cursorId" = $${payloadValues.length}::bigint)`;
20079
20276
  }).join(" OR ");
20080
- const payloads = await tx.any(`SELECT ${identities},
20081
- 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"
20082
- 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);
20083
20280
  await remaining();
20084
20281
  const metricValues = [];
20085
20282
  const metricWhere = selectedKeys(page, metricValues);
@@ -20090,7 +20287,7 @@ async function querySpans(client, schema, plan, timeoutMs) {
20090
20287
  ORDER BY ${identities}, "metricId", "cursorId" DESC) m
20091
20288
  LIMIT ${_mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS + 1}`, metricValues);
20092
20289
  if (metrics.length > _mastra_core_storage.SPAN_QUERY_MAX_COST_METRICS) throw new _mastra_core_storage.TraceQueryResourceLimitError();
20093
- return _mastra_core_storage.buildSpanQueryResponse(plan, selected, payloads, metrics);
20290
+ return buildSpanQueryResponse(plan, selected, payloads, metrics);
20094
20291
  }, { repeatableRead: true });
20095
20292
  }
20096
20293
  //#endregion
@@ -20555,16 +20752,73 @@ function buildListTracesFilters(filters, spanTable, nextParamIdx) {
20555
20752
  * array-of-strings representation here.
20556
20753
  */
20557
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
+ };
20558
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) {
20559
20813
  const { mode, filters, pagination, orderBy, after, limit } = _mastra_core_storage.listTracesArgsSchema.parse(args);
20560
20814
  const span = qualifiedTable(schema, TABLE_SPAN_EVENTS);
20561
20815
  if (mode === "delta") {
20562
20816
  assertDeltaPollingEnabled();
20563
- return listTracesDelta(client, span, filters, after, limit);
20817
+ return listTracesDelta(client, span, projection, filters, after, limit);
20564
20818
  }
20565
- 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);
20566
20820
  }
20567
- async function listTracesPage(client, span, filters, page, perPage, orderField, orderDir) {
20821
+ async function listTracesPage(client, span, projection, filters, page, perPage, orderField, orderDir) {
20568
20822
  const { conditions, params, nextParamIdx } = buildListTracesFilters(filters, span, 1);
20569
20823
  let i = nextParamIdx;
20570
20824
  const whereClause = `WHERE ${conditions.join(" AND ")}`;
@@ -20572,15 +20826,18 @@ async function listTracesPage(client, span, filters, page, perPage, orderField,
20572
20826
  const countRow = await client.oneOrNone(`SELECT COUNT(*)::text AS count FROM ${span} r ${whereClause}`, params);
20573
20827
  const count = Number(countRow?.count ?? 0);
20574
20828
  let spans = [];
20575
- 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}
20576
20831
  FROM ${span} r
20577
20832
  ${whereClause}
20578
20833
  ${orderClause}
20579
20834
  LIMIT $${i++} OFFSET $${i++}`, [
20580
- ...params,
20581
- perPage,
20582
- page * perPage
20583
- ])).map(rowToSpanRecord);
20835
+ ...params,
20836
+ perPage,
20837
+ page * perPage
20838
+ ]);
20839
+ spans = await projection.mapRows(client, span, rows);
20840
+ }
20584
20841
  const deltaCursor = deltaPollingFeatureEnabled() ? await readTracesStreamHeadCursor(client, span, filters) : void 0;
20585
20842
  return {
20586
20843
  pagination: {
@@ -20589,11 +20846,11 @@ async function listTracesPage(client, span, filters, page, perPage, orderField,
20589
20846
  perPage,
20590
20847
  hasMore: (page + 1) * perPage < count
20591
20848
  },
20592
- spans: (0, _mastra_core_storage.toTraceSpans)(spans),
20849
+ spans,
20593
20850
  ...deltaCursor !== void 0 ? { deltaCursor } : {}
20594
20851
  };
20595
20852
  }
20596
- async function listTracesDelta(client, span, filters, after, limit) {
20853
+ async function listTracesDelta(client, span, projection, filters, after, limit) {
20597
20854
  if (after === void 0) {
20598
20855
  const deltaCursor = await readTracesStreamHeadCursor(client, span, filters);
20599
20856
  return {
@@ -20613,7 +20870,7 @@ async function listTracesDelta(client, span, filters, after, limit) {
20613
20870
  params.push(afterCursor.xactId, afterCursor.cursorId);
20614
20871
  conditions.push(`r."xactId" < $${i++}::xid8`);
20615
20872
  params.push(safeHorizon);
20616
- const rows = await client.manyOrNone(`SELECT ${SPAN_SELECT_COLUMNS_ALIASED}
20873
+ const rows = await client.manyOrNone(`SELECT ${projection.columns}
20617
20874
  FROM ${span} r
20618
20875
  WHERE ${conditions.join(" AND ")}
20619
20876
  ORDER BY r."xactId" ASC, r."cursorId" ASC
@@ -20622,7 +20879,7 @@ async function listTracesDelta(client, span, filters, after, limit) {
20622
20879
  const visible = rows.slice(0, limit);
20623
20880
  const deltaCursor = visible.length > 0 ? encodeDeltaCursor(visible[visible.length - 1].xactId, visible[visible.length - 1].cursorId) : encodeDeltaCursor(safeHorizon, 0);
20624
20881
  return {
20625
- spans: (0, _mastra_core_storage.toTraceSpans)(visible.map(rowToSpanRecord)),
20882
+ spans: await projection.mapRows(client, span, visible),
20626
20883
  delta: {
20627
20884
  limit,
20628
20885
  hasMore
@@ -21333,6 +21590,9 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21333
21590
  async listTraces(args) {
21334
21591
  return this.#run("LIST_TRACES", () => listTraces(this.#readClient, this.#schema, args));
21335
21592
  }
21593
+ async listTracesLight(args) {
21594
+ return this.#run("LIST_TRACES_LIGHT", () => listTracesLight(this.#readClient, this.#schema, args));
21595
+ }
21336
21596
  async querySpans(plan) {
21337
21597
  return this.#run("QUERY_SPANS", () => querySpans(this.#readClient, this.#schema, plan, this.#traceQueryTimeoutMs));
21338
21598
  }