@mastra/pg 1.22.0-alpha.3 → 1.22.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @mastra/pg
2
2
 
3
+ ## 1.22.0-alpha.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Stop observability filter discovery from re-scanning all history on every refresh ([#22136](https://github.com/mastra-ai/mastra/pull/22136))
8
+
9
+ Discovery queries that build Studio's Traces, Logs, and Metrics filter suggestions scanned every span, metric, and log event each time the cache went stale, which grew unbounded with retained data. Refreshes are now bounded to the last 30 days by default (configurable via `observability.discovery.lookbackSeconds`, `0` restores the previous unbounded behaviour), so the planner can prune partitions instead of reading all of them. Only one process refreshes a given cache entry at a time, so running several server instances no longer multiplies the work, and Studio holds discovery results for five minutes instead of refetching on every page mount.
10
+
11
+ - Experiment results now include isolated metadata snapshots from the dataset items that ran. ([#22005](https://github.com/mastra-ai/mastra/pull/22005))
12
+
13
+ - Fix `PgFactoryStorage` reads of `json` columns holding a JSON value that isn't an object. ([#22258](https://github.com/mastra-ai/mastra/pull/22258))
14
+
15
+ node-pg parses JSONB through its own type parsers, so the value reaching row deserialization is already a JS value. Deserialization parsed it a second time when it was a string, which threw and failed the whole read. Objects and arrays came back as JS objects and never hit that branch, so the defect stayed hidden until a caller stored a string.
16
+
17
+ This surfaced through Factory credential encryption, which stores secrets as an opaque envelope string: saving or reading any credential on Postgres threw `Unexpected token ... is not valid JSON`, affecting model provider credentials, integrations, and custom providers. libsql was unaffected, since it stores `json` columns as text where parsing on read is correct.
18
+
19
+ - Enforce atomic conditional background task state updates so cancellation cannot be overwritten during dispatch. Background task storage is no longer exposed by Cloudflare KV or ClickHouse, which cannot provide the required compare-and-set semantics. ([#22228](https://github.com/mastra-ai/mastra/pull/22228))
20
+
21
+ - Updated dependencies [[`aa3a85d`](https://github.com/mastra-ai/mastra/commit/aa3a85daf094c683bb97efdf4b6a696d2e474af5), [`d29d06f`](https://github.com/mastra-ai/mastra/commit/d29d06fe00bbd35b4571150ea04c59d2ed783c71), [`e6516df`](https://github.com/mastra-ai/mastra/commit/e6516dfcdae4f4ac0e7971d84359a81385ee602f), [`0b2a3d1`](https://github.com/mastra-ai/mastra/commit/0b2a3d1783875c5b97b7b36ab3d03d7360e0dde7), [`6bb5d71`](https://github.com/mastra-ai/mastra/commit/6bb5d7193fe9166b219f0fccae17db7a5ae86e65), [`57de7d6`](https://github.com/mastra-ai/mastra/commit/57de7d644ba7146edb4e9e6111ec4fa98c3a59e9), [`e8e299c`](https://github.com/mastra-ai/mastra/commit/e8e299cc6abdfc39947e2fec25803493015d3882), [`edfc548`](https://github.com/mastra-ai/mastra/commit/edfc548886bc7bae17b681f8b6b41a47eb32bcd2), [`a8a4871`](https://github.com/mastra-ai/mastra/commit/a8a4871215f51da95c47129602157ce5372f634a), [`5165cdc`](https://github.com/mastra-ai/mastra/commit/5165cdcdcf50e144bb8113278535196cc9b07065), [`6bb5d71`](https://github.com/mastra-ai/mastra/commit/6bb5d7193fe9166b219f0fccae17db7a5ae86e65), [`9ee8120`](https://github.com/mastra-ai/mastra/commit/9ee8120ce17f76b9f617489e05a283353742690a), [`d975e92`](https://github.com/mastra-ai/mastra/commit/d975e924d4936f46c386bd3dee39c671720289f6), [`1cfa878`](https://github.com/mastra-ai/mastra/commit/1cfa8784d8da0dfaa0317e5048bc48b6084a5ea5), [`c118318`](https://github.com/mastra-ai/mastra/commit/c1183181c9804303db4b511c2e2648f8b714712b), [`fc07c64`](https://github.com/mastra-ai/mastra/commit/fc07c6465043e08e99193a6751a01c56ffc2e7a1), [`542dee2`](https://github.com/mastra-ai/mastra/commit/542dee254167f974ff8cbbbfc0ce10f9a2616a7b), [`a58483c`](https://github.com/mastra-ai/mastra/commit/a58483cff1a9d41fce7c931843f48cb0ac450f64), [`a58483c`](https://github.com/mastra-ai/mastra/commit/a58483cff1a9d41fce7c931843f48cb0ac450f64), [`895e9df`](https://github.com/mastra-ai/mastra/commit/895e9dfc17d6f34299eca64e317ded9e5f5e5ef8)]:
22
+ - @mastra/core@1.62.0-alpha.8
23
+
3
24
  ## 1.22.0-alpha.3
4
25
 
5
26
  ### Minor Changes
@@ -3,7 +3,7 @@ name: mastra-pg
3
3
  description: Documentation for @mastra/pg. Use when working with @mastra/pg APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/pg"
6
- version: "1.22.0-alpha.3"
6
+ version: "1.22.0-alpha.4"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.22.0-alpha.3",
2
+ "version": "1.22.0-alpha.4",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -197,6 +197,7 @@ Each provider page includes installation instructions, configuration parameters,
197
197
  - [Convex](https://mastra.ai/integrations/databases/convex)
198
198
  - [DuckDB](https://mastra.ai/integrations/databases/duckdb)
199
199
  - [DynamoDB](https://mastra.ai/integrations/databases/dynamodb)
200
+ - [Elasticsearch](https://mastra.ai/integrations/databases/elasticsearch)
200
201
  - [Google Cloud Spanner](https://mastra.ai/integrations/databases/spanner)
201
202
  - [LanceDB](https://mastra.ai/integrations/databases/lancedb)
202
203
  - [libSQL](https://mastra.ai/integrations/databases/libsql)
@@ -148,6 +148,30 @@ PostgreSQL supports observability and can handle low trace volumes. Throughput c
148
148
 
149
149
  `PostgresStoreVNext` uses the `event-sourced` [tracing strategy](https://mastra.ai/docs/observability/integrations/exporters/mastra-storage) instead. It writes one row when a span starts and another when it ends, never updating a row in place, and collapses those rows when a trace is read. Writes stay append-only, and traces appear in Studio while the run is still executing.
150
150
 
151
+ #### Filter suggestions
152
+
153
+ Studio's Traces, Logs, and Metrics pages offer filter values (tags, service names, environments, entity names, metric names and labels) discovered from your observability data. Those values are cached in the database and recomputed in the background when the cache goes stale.
154
+
155
+ To keep the refresh cheap on large datasets, it only scans events from the last 30 days. Values that appear exclusively in older events won't be suggested, though filtering by them still works. Use `discovery` to change the window or how often it refreshes:
156
+
157
+ ```typescript
158
+ import { PostgresStoreVNext } from '@mastra/pg'
159
+
160
+ const storage = new PostgresStoreVNext({
161
+ id: 'pg-storage',
162
+ connectionString: process.env.DATABASE_URL,
163
+ observability: {
164
+ connectionString: process.env.OBSERVABILITY_DATABASE_URL,
165
+ discovery: {
166
+ lookbackSeconds: 7 * 24 * 60 * 60, // scan the last 7 days; 0 scans all history
167
+ ttlSeconds: 15 * 60, // refresh at most every 15 minutes
168
+ },
169
+ },
170
+ })
171
+ ```
172
+
173
+ Lower `lookbackSeconds` if refreshes are slow, and raise `ttlSeconds` if they run more often than your filter values change. Only one process refreshes a given cache entry at a time, so adding server instances doesn't multiply the work.
174
+
151
175
  ### Initialization
152
176
 
153
177
  When you pass storage to the Mastra class, `init()` is called automatically before any storage operation:
package/dist/index.cjs CHANGED
@@ -5045,7 +5045,7 @@ var BackgroundTasksPG = class BackgroundTasksPG extends _mastra_core_storage.Bac
5045
5045
  }
5046
5046
  });
5047
5047
  }
5048
- async updateTask(taskId, update) {
5048
+ async updateTask(taskId, update, options) {
5049
5049
  const setClauses = [];
5050
5050
  const params = [];
5051
5051
  let paramIdx = 1;
@@ -5087,10 +5087,15 @@ var BackgroundTasksPG = class BackgroundTasksPG extends _mastra_core_storage.Bac
5087
5087
  const val = update.completedAt?.toISOString() ?? null;
5088
5088
  params.push(val, val);
5089
5089
  }
5090
- if (setClauses.length === 0) return;
5090
+ if (setClauses.length === 0) return false;
5091
5091
  const table = getTableName$4(getSchemaName$4(this.#schema));
5092
5092
  params.push(taskId);
5093
- await this.#db.client.none(`UPDATE ${table} SET ${setClauses.join(", ")} WHERE "id" = $${paramIdx}`, params);
5093
+ let where = `"id" = $${paramIdx++}`;
5094
+ if (options?.expectedStatus) {
5095
+ where += ` AND "status" = $${paramIdx}`;
5096
+ params.push(options.expectedStatus);
5097
+ }
5098
+ return ((await this.#db.client.query(`UPDATE ${table} SET ${setClauses.join(", ")} WHERE ${where}`, params)).rowCount ?? 0) > 0;
5094
5099
  }
5095
5100
  async getTask(taskId) {
5096
5101
  const table = getTableName$4(getSchemaName$4(this.#schema));
@@ -6735,6 +6740,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
6735
6740
  "tags",
6736
6741
  "comment",
6737
6742
  "toolMockReport",
6743
+ "metadata",
6738
6744
  "organizationId",
6739
6745
  "projectId",
6740
6746
  "attempt"
@@ -6931,6 +6937,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
6931
6937
  input: (0, _mastra_core_storage.safelyParseJSON)(row.input),
6932
6938
  output: row.output ? (0, _mastra_core_storage.safelyParseJSON)(row.output) : null,
6933
6939
  groundTruth: row.groundTruth ? (0, _mastra_core_storage.safelyParseJSON)(row.groundTruth) : null,
6940
+ metadata: row.metadata ? (0, _mastra_core_storage.safelyParseJSON)(row.metadata) : null,
6934
6941
  error: row.error ? (0, _mastra_core_storage.safelyParseJSON)(row.error) : null,
6935
6942
  startedAt: (0, _mastra_core_storage.ensureDate)(row.startedAtZ || row.startedAt),
6936
6943
  completedAt: (0, _mastra_core_storage.ensureDate)(row.completedAtZ || row.completedAt),
@@ -7239,6 +7246,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
7239
7246
  input: input.input,
7240
7247
  output: input.output ?? null,
7241
7248
  groundTruth: input.groundTruth ?? null,
7249
+ metadata: input.metadata ?? null,
7242
7250
  error: input.error ?? null,
7243
7251
  startedAt: input.startedAt.toISOString(),
7244
7252
  completedAt: input.completedAt.toISOString(),
@@ -7261,6 +7269,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
7261
7269
  input: input.input,
7262
7270
  output: input.output ?? null,
7263
7271
  groundTruth: input.groundTruth ?? null,
7272
+ metadata: input.metadata ?? null,
7264
7273
  error: input.error ?? null,
7265
7274
  startedAt: input.startedAt,
7266
7275
  completedAt: input.completedAt,
@@ -7297,9 +7306,9 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
7297
7306
  const id = crypto.randomUUID();
7298
7307
  return t.one(`INSERT INTO ${tableName} (
7299
7308
  "id", "experimentId", "itemId", "itemDatasetVersion", "organizationId", "projectId",
7300
- "input", "output", "groundTruth", "error", "startedAt", "completedAt",
7309
+ "input", "output", "groundTruth", "metadata", "error", "startedAt", "completedAt",
7301
7310
  "retryCount", "attempt", "traceId", "status", "tags", "toolMockReport", "createdAt"
7302
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
7311
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20)
7303
7312
  RETURNING *`, [
7304
7313
  id,
7305
7314
  input.experimentId,
@@ -7310,6 +7319,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
7310
7319
  JSON.stringify(input.input),
7311
7320
  input.output != null ? JSON.stringify(input.output) : null,
7312
7321
  input.groundTruth != null ? JSON.stringify(input.groundTruth) : null,
7322
+ input.metadata != null ? JSON.stringify(input.metadata) : null,
7313
7323
  input.error != null ? JSON.stringify(input.error) : null,
7314
7324
  input.startedAt.toISOString(),
7315
7325
  input.completedAt.toISOString(),
@@ -7324,9 +7334,9 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
7324
7334
  }
7325
7335
  return t.one(`UPDATE ${tableName} SET
7326
7336
  "itemDatasetVersion" = $2, "organizationId" = $3, "projectId" = $4,
7327
- "input" = $5, "output" = $6, "groundTruth" = $7, "error" = $8,
7328
- "startedAt" = $9, "completedAt" = $10, "retryCount" = $11, "attempt" = $12,
7329
- "traceId" = $13, "status" = $14, "tags" = $15, "toolMockReport" = $16
7337
+ "input" = $5, "output" = $6, "groundTruth" = $7, "metadata" = $8, "error" = $9,
7338
+ "startedAt" = $10, "completedAt" = $11, "retryCount" = $12, "attempt" = $13,
7339
+ "traceId" = $14, "status" = $15, "tags" = $16, "toolMockReport" = $17
7330
7340
  WHERE "id" = $1 RETURNING *`, [
7331
7341
  existing.id,
7332
7342
  input.itemDatasetVersion ?? null,
@@ -7335,6 +7345,7 @@ var ExperimentsPG = class ExperimentsPG extends _mastra_core_storage.Experiments
7335
7345
  JSON.stringify(input.input),
7336
7346
  input.output != null ? JSON.stringify(input.output) : null,
7337
7347
  input.groundTruth != null ? JSON.stringify(input.groundTruth) : null,
7348
+ input.metadata != null ? JSON.stringify(input.metadata) : null,
7338
7349
  input.error != null ? JSON.stringify(input.error) : null,
7339
7350
  input.startedAt.toISOString(),
7340
7351
  input.completedAt.toISOString(),
@@ -14441,6 +14452,7 @@ function allIndexDDL(schema) {
14441
14452
  //#endregion
14442
14453
  //#region src/storage/domains/observability/v-next/discovery.ts
14443
14454
  const DEFAULT_TTL_SECONDS = 300;
14455
+ const DEFAULT_LOOKBACK_SECONDS = 720 * 60 * 60;
14444
14456
  /** All signal tables that contain a column. Used by cross-signal discovery. */
14445
14457
  const SIGNAL_TABLES_WITH_CONTEXT = [
14446
14458
  TABLE_SPAN_EVENTS,
@@ -14505,22 +14517,71 @@ function startOrJoinRefresh(dedupeKey, cacheKey, refresh, upsert, logger) {
14505
14517
  async function readWithRefresh(client, schema, cacheKey, refresh, ttlSeconds, logger) {
14506
14518
  const table = qualifiedTable(schema, TABLE_DISCOVERY);
14507
14519
  const row = await client.oneOrNone(`SELECT "values", "refreshedAt" FROM ${table} WHERE "cacheKey" = $1`, [cacheKey]);
14508
- const refreshedAtMs = row ? new Date(row.refreshedAt).getTime() : 0;
14509
- if (!(!row || Date.now() - refreshedAtMs > ttlSeconds * 1e3)) return row.values;
14510
- const refreshing = startOrJoinRefresh(`${schema}:${cacheKey}`, cacheKey, refresh, (values) => upsertCache(client, schema, cacheKey, values), logger);
14520
+ const dedupeKey = `${schema}:${cacheKey}`;
14521
+ const startRefresh = () => startOrJoinRefresh(dedupeKey, cacheKey, refresh, (values) => upsertCache(client, schema, cacheKey, values), logger);
14511
14522
  if (ttlSeconds <= 0) try {
14512
- return await refreshing;
14523
+ return await startRefresh();
14513
14524
  } catch {
14514
14525
  return row?.values ?? [];
14515
14526
  }
14527
+ const refreshedAtMs = row ? new Date(row.refreshedAt).getTime() : 0;
14528
+ if (!(!row || Date.now() - refreshedAtMs > ttlSeconds * 1e3)) return row.values;
14516
14529
  if (!row) try {
14517
- return await refreshing;
14530
+ return await startRefresh();
14518
14531
  } catch {
14519
14532
  return [];
14520
14533
  }
14521
- refreshing.catch(() => {});
14534
+ const previousRefreshedAt = row.refreshedAt;
14535
+ claimRefresh(client, schema, cacheKey, ttlSeconds).then((claimedAt) => {
14536
+ if (!claimedAt) return;
14537
+ return startRefresh().then(() => {}, () => releaseClaim(client, schema, cacheKey, previousRefreshedAt, claimedAt));
14538
+ }).catch(() => {});
14522
14539
  return row.values;
14523
14540
  }
14541
+ /**
14542
+ * Try to become the process that refreshes `cacheKey`.
14543
+ *
14544
+ * The conditional UPDATE re-checks staleness in the database and stamps
14545
+ * `refreshedAt` in the same statement, so exactly one caller can transition a
14546
+ * given cache row out of the stale window — losers see zero rows updated and
14547
+ * skip their refresh. This is the cross-process counterpart to
14548
+ * `inFlightRefreshes`: N frontends going stale together run one refresh
14549
+ * instead of N.
14550
+ *
14551
+ * It reuses the cache table rather than an advisory lock because `DbClient`
14552
+ * is pool-backed, so a transaction-scoped lock would mean threading explicit
14553
+ * transaction handling through the discovery read path.
14554
+ *
14555
+ * The stamp is provisional in both directions: a completed refresh overwrites
14556
+ * it via `upsertCache`, and a failed one restores it via `releaseClaim`.
14557
+ *
14558
+ * Returns the exact provisional stamp written by the claim (or `null` when
14559
+ * the claim was lost) so `releaseClaim` can restore the row only while it
14560
+ * still holds this claim's stamp.
14561
+ */
14562
+ async function claimRefresh(client, schema, cacheKey, ttlSeconds) {
14563
+ const table = qualifiedTable(schema, TABLE_DISCOVERY);
14564
+ return (await client.oneOrNone(`UPDATE ${table} SET "refreshedAt" = NOW()
14565
+ WHERE "cacheKey" = $1 AND "refreshedAt" <= NOW() - ($2 || ' seconds')::interval
14566
+ RETURNING "refreshedAt"::text AS "refreshedAt"`, [cacheKey, Math.floor(ttlSeconds)]))?.refreshedAt ?? null;
14567
+ }
14568
+ /**
14569
+ * Hand back a claim whose refresh failed by restoring the timestamp the
14570
+ * claiming reader observed. Guarded on the row still holding this claim's
14571
+ * exact provisional stamp so a refresh that completed in the meantime
14572
+ * (another process, or a later claim) isn't dragged backwards into staleness.
14573
+ */
14574
+ async function releaseClaim(client, schema, cacheKey, previousRefreshedAt, claimedAt) {
14575
+ const table = qualifiedTable(schema, TABLE_DISCOVERY);
14576
+ try {
14577
+ await client.query(`UPDATE ${table} SET "refreshedAt" = $2
14578
+ WHERE "cacheKey" = $1 AND "refreshedAt" = $3::timestamptz`, [
14579
+ cacheKey,
14580
+ previousRefreshedAt,
14581
+ claimedAt
14582
+ ]);
14583
+ } catch {}
14584
+ }
14524
14585
  async function upsertCache(client, schema, cacheKey, values) {
14525
14586
  const table = qualifiedTable(schema, TABLE_DISCOVERY);
14526
14587
  await client.query(`INSERT INTO ${table} ("cacheKey", "refreshedAt", "values")
@@ -14529,61 +14590,95 @@ async function upsertCache(client, schema, cacheKey, values) {
14529
14590
  "refreshedAt" = EXCLUDED."refreshedAt",
14530
14591
  "values" = EXCLUDED."values"`, [cacheKey, JSON.stringify(values)]);
14531
14592
  }
14532
- async function distinctAcrossTables(client, schema, column, tables, filterSql = "", filterParams = []) {
14593
+ /**
14594
+ * Time predicate that bounds a discovery refresh to recent events.
14595
+ *
14596
+ * Each signal table is range-partitioned on its own time column
14597
+ * (`SIGNAL_TIME_COLUMN`: `endedAt` for spans, `timestamp` for everything
14598
+ * else), so the predicate has to be built per table rather than shared across
14599
+ * a UNION. Bounding on that exact column is what lets the planner prune
14600
+ * partitions/chunks instead of scanning all history.
14601
+ *
14602
+ * The interval is interpolated rather than bound to a parameter on purpose:
14603
+ * the UNION branches in `distinctAcrossTables` and `getTags` all reference the
14604
+ * same positional placeholders, so adding a per-branch parameter would break
14605
+ * `$N` numbering. Safety comes from coercing to a finite integer here — the
14606
+ * value originates from `DiscoveryConfig`, never from user input.
14607
+ */
14608
+ function lookbackPredicate(table, lookbackSeconds) {
14609
+ if (!Number.isFinite(lookbackSeconds) || lookbackSeconds <= 0) return "";
14610
+ const seconds = Math.floor(lookbackSeconds);
14611
+ const timeColumn = SIGNAL_TIME_COLUMN[table];
14612
+ if (!timeColumn) return "";
14613
+ return `AND "${timeColumn}" >= NOW() - INTERVAL '${seconds} seconds'`;
14614
+ }
14615
+ async function distinctAcrossTables(client, schema, column, tables, lookbackSeconds, filterSql = "", filterParams = []) {
14533
14616
  const safeColumn = (0, _mastra_core_utils.parseSqlIdentifier)(column, "column name");
14534
- const unions = tables.map((t) => `SELECT DISTINCT "${safeColumn}" AS v FROM ${qualifiedTable(schema, t)} WHERE "${safeColumn}" IS NOT NULL AND "${safeColumn}" <> '' ${filterSql}`).join(" UNION ");
14617
+ const unions = tables.map((t) => `SELECT DISTINCT "${safeColumn}" AS v FROM ${qualifiedTable(schema, t)} WHERE "${safeColumn}" IS NOT NULL AND "${safeColumn}" <> '' ${filterSql} ${lookbackPredicate(t, lookbackSeconds)}`).join(" UNION ");
14535
14618
  return (await client.manyOrNone(`SELECT v FROM (${unions}) sub ORDER BY v`, filterParams)).map((r) => r.v);
14536
14619
  }
14537
14620
  async function getEntityTypes(client, schema, _args, config) {
14538
- return { entityTypes: await readWithRefresh(client, schema, "entity_types", () => distinctAcrossTables(client, schema, "entityType", ENTITY_DISCOVERY_TABLES), config.ttlSeconds ?? DEFAULT_TTL_SECONDS, config.logger) };
14621
+ const ttl = config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
14622
+ const lookback = config.lookbackSeconds ?? DEFAULT_LOOKBACK_SECONDS;
14623
+ return { entityTypes: await readWithRefresh(client, schema, "entity_types", () => distinctAcrossTables(client, schema, "entityType", ENTITY_DISCOVERY_TABLES, lookback), ttl, config.logger) };
14539
14624
  }
14540
14625
  async function getEntityNames(client, schema, args, config) {
14541
14626
  const ttl = config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
14627
+ const lookback = config.lookbackSeconds ?? DEFAULT_LOOKBACK_SECONDS;
14542
14628
  const cacheKey = args.entityType ? `entity_names:${args.entityType}` : "entity_names";
14543
14629
  const filterSql = args.entityType ? `AND "entityType" = $1` : "";
14544
14630
  const filterParams = args.entityType ? [args.entityType] : [];
14545
- return { names: await readWithRefresh(client, schema, cacheKey, () => distinctAcrossTables(client, schema, "entityName", ENTITY_DISCOVERY_TABLES, filterSql, filterParams), ttl, config.logger) };
14631
+ return { names: await readWithRefresh(client, schema, cacheKey, () => distinctAcrossTables(client, schema, "entityName", ENTITY_DISCOVERY_TABLES, lookback, filterSql, filterParams), ttl, config.logger) };
14546
14632
  }
14547
14633
  async function getServiceNames(client, schema, _args, config) {
14548
- return { serviceNames: await readWithRefresh(client, schema, "service_names", () => distinctAcrossTables(client, schema, "serviceName", SIGNAL_TABLES_WITH_CONTEXT), config.ttlSeconds ?? DEFAULT_TTL_SECONDS, config.logger) };
14634
+ const ttl = config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
14635
+ const lookback = config.lookbackSeconds ?? DEFAULT_LOOKBACK_SECONDS;
14636
+ return { serviceNames: await readWithRefresh(client, schema, "service_names", () => distinctAcrossTables(client, schema, "serviceName", SIGNAL_TABLES_WITH_CONTEXT, lookback), ttl, config.logger) };
14549
14637
  }
14550
14638
  async function getEnvironments(client, schema, _args, config) {
14551
- return { environments: await readWithRefresh(client, schema, "environments", () => distinctAcrossTables(client, schema, "environment", SIGNAL_TABLES_WITH_CONTEXT), config.ttlSeconds ?? DEFAULT_TTL_SECONDS, config.logger) };
14639
+ const ttl = config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
14640
+ const lookback = config.lookbackSeconds ?? DEFAULT_LOOKBACK_SECONDS;
14641
+ return { environments: await readWithRefresh(client, schema, "environments", () => distinctAcrossTables(client, schema, "environment", SIGNAL_TABLES_WITH_CONTEXT, lookback), ttl, config.logger) };
14552
14642
  }
14553
14643
  async function getTags(client, schema, args, config) {
14554
14644
  const ttl = config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
14645
+ const lookback = config.lookbackSeconds ?? DEFAULT_LOOKBACK_SECONDS;
14555
14646
  const cacheKey = args.entityType ? `tags:${args.entityType}` : "tags";
14556
14647
  const refresh = async () => {
14557
14648
  const filter = args.entityType ? `AND "entityType" = $1` : "";
14558
14649
  const params = args.entityType ? [args.entityType] : [];
14559
- const unions = ENTITY_DISCOVERY_TABLES.map((t) => `SELECT DISTINCT UNNEST("tags") AS v FROM ${qualifiedTable(schema, t)} WHERE array_length("tags", 1) > 0 ${filter}`).join(" UNION ");
14650
+ const unions = ENTITY_DISCOVERY_TABLES.map((t) => `SELECT DISTINCT UNNEST("tags") AS v FROM ${qualifiedTable(schema, t)} WHERE array_length("tags", 1) > 0 ${filter} ${lookbackPredicate(t, lookback)}`).join(" UNION ");
14560
14651
  return (await client.manyOrNone(`SELECT v FROM (${unions}) sub WHERE v IS NOT NULL AND v <> '' ORDER BY v`, params)).map((r) => r.v);
14561
14652
  };
14562
14653
  return { tags: await readWithRefresh(client, schema, cacheKey, refresh, ttl, config.logger) };
14563
14654
  }
14564
14655
  async function getMetricNames(client, schema, args, config) {
14656
+ const ttl = config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
14657
+ const lookback = config.lookbackSeconds ?? DEFAULT_LOOKBACK_SECONDS;
14565
14658
  let filtered = await readWithRefresh(client, schema, "metric_names", async () => {
14566
14659
  return (await client.manyOrNone(`SELECT DISTINCT "name" AS v FROM ${qualifiedTable(schema, TABLE_METRIC_EVENTS)}
14567
- WHERE "name" IS NOT NULL AND "name" <> '' ORDER BY "name"`)).map((r) => r.v);
14568
- }, config.ttlSeconds ?? DEFAULT_TTL_SECONDS, config.logger);
14660
+ WHERE "name" IS NOT NULL AND "name" <> '' ${lookbackPredicate(TABLE_METRIC_EVENTS, lookback)} ORDER BY "name"`)).map((r) => r.v);
14661
+ }, ttl, config.logger);
14569
14662
  if (args.prefix) filtered = filtered.filter((v) => v.startsWith(args.prefix));
14570
14663
  if (args.limit) filtered = filtered.slice(0, args.limit);
14571
14664
  return { names: filtered };
14572
14665
  }
14573
14666
  async function getMetricLabelKeys(client, schema, args, config) {
14574
14667
  const ttl = config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
14668
+ const lookback = config.lookbackSeconds ?? DEFAULT_LOOKBACK_SECONDS;
14575
14669
  return { keys: await readWithRefresh(client, schema, `metric_label_keys:${args.metricName}`, async () => {
14576
14670
  return (await client.manyOrNone(`SELECT DISTINCT k AS v
14577
14671
  FROM ${qualifiedTable(schema, TABLE_METRIC_EVENTS)}, jsonb_object_keys("labels") k
14578
- WHERE "name" = $1 ORDER BY k`, [args.metricName])).map((r) => r.v);
14672
+ WHERE "name" = $1 ${lookbackPredicate(TABLE_METRIC_EVENTS, lookback)} ORDER BY k`, [args.metricName])).map((r) => r.v);
14579
14673
  }, ttl, config.logger) };
14580
14674
  }
14581
14675
  async function getMetricLabelValues(client, schema, args, config) {
14582
14676
  const ttl = config.ttlSeconds ?? DEFAULT_TTL_SECONDS;
14677
+ const lookback = config.lookbackSeconds ?? DEFAULT_LOOKBACK_SECONDS;
14583
14678
  let filtered = await readWithRefresh(client, schema, `metric_label_values:${args.metricName}:${args.labelKey}`, async () => {
14584
14679
  return (await client.manyOrNone(`SELECT DISTINCT "labels" ->> $2 AS v
14585
14680
  FROM ${qualifiedTable(schema, TABLE_METRIC_EVENTS)}
14586
- WHERE "name" = $1 AND "labels" ? $2
14681
+ WHERE "name" = $1 AND "labels" ? $2 ${lookbackPredicate(TABLE_METRIC_EVENTS, lookback)}
14587
14682
  ORDER BY v`, [args.metricName, args.labelKey])).map((r) => r.v).filter((v) => v != null && v !== "");
14588
14683
  }, ttl, config.logger);
14589
14684
  if (args.prefix) filtered = filtered.filter((v) => v.startsWith(args.prefix));
@@ -21931,7 +22026,7 @@ var PgFactoryStorageOps = class {
21931
22026
  row[name] = Boolean(value);
21932
22027
  break;
21933
22028
  case "json":
21934
- row[name] = typeof value === "string" ? JSON.parse(value) : value;
22029
+ row[name] = value;
21935
22030
  break;
21936
22031
  case "bigint":
21937
22032
  case "integer":