@mastra/pg 1.22.0-alpha.3 → 1.22.0
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 +116 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-semantic-recall.md +19 -0
- package/dist/docs/references/docs-storage.md +1 -0
- package/dist/docs/references/integrations-databases-postgresql.md +24 -0
- package/dist/index.cjs +121 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +121 -26
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/background-tasks/index.d.ts +3 -1
- package/dist/storage/domains/background-tasks/index.d.ts.map +1 -1
- package/dist/storage/domains/experiments/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/discovery.d.ts +14 -0
- package/dist/storage/domains/observability/v-next/discovery.d.ts.map +1 -1
- package/dist/storage/factory-storage.d.ts.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,121 @@
|
|
|
1
1
|
# @mastra/pg
|
|
2
2
|
|
|
3
|
+
## 1.22.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **In-progress traces now appear in Studio with `PostgresStoreVNext`** ([#22137](https://github.com/mastra-ai/mastra/pull/22137))
|
|
8
|
+
|
|
9
|
+
`PostgresStoreVNext` previously persisted a span only after it finished, so a long agent run stayed invisible until it completed. It now uses the `event-sourced` tracing strategy: one row is written when a span starts and another when it ends, and reads collapse those rows back into a single span. Traces show up in Studio while the run is executing, and filtering by `running` status works.
|
|
10
|
+
|
|
11
|
+
This also fixes duplicate traces from durable runs. A run that suspends and resumes opens a second root span on the same trace, which used to render as two separate entries; the trace list now shows the current root only.
|
|
12
|
+
|
|
13
|
+
Writes stay append-only, so throughput is unchanged. The span table gains an `isPending` column, added automatically on `init()` — no manual migration needed. Closes #22054.
|
|
14
|
+
|
|
15
|
+
- Added native application collection counts so totals no longer load matching rows. ([#22021](https://github.com/mastra-ai/mastra/pull/22021))
|
|
16
|
+
|
|
17
|
+
**Before**
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const total = (await storage.ops.findMany('jobs', { status: 'failed' })).length;
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**After**
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
const total = await storage.ops.count?.('jobs', { status: 'failed' });
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Added namespace isolation to PgVector operations so applications can safely reuse vector indexes across tenants. Existing vectors remain available in the default namespace. ([#22149](https://github.com/mastra-ai/mastra/pull/22149))
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
await pgVector.upsert({
|
|
33
|
+
indexName: 'documents',
|
|
34
|
+
vectors,
|
|
35
|
+
ids,
|
|
36
|
+
namespace: 'tenant-123',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const results = await pgVector.query({
|
|
40
|
+
indexName: 'documents',
|
|
41
|
+
queryVector,
|
|
42
|
+
namespace: 'tenant-123',
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- Added support for filtering scores by metadata key-value pairs in listScores. ([#22047](https://github.com/mastra-ai/mastra/pull/22047))
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
const result = await storage.listScores({
|
|
50
|
+
filters: { metadata: { env: 'prod' } },
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Each top-level metadata key is matched with exact equality against the stored value. Nested objects and arrays compare structurally (key order doesn't matter) with no partial/subset matching, and an empty metadata filter is a no-op.
|
|
55
|
+
|
|
56
|
+
### Patch Changes
|
|
57
|
+
|
|
58
|
+
- Workflow snapshot upserts no longer overwrite a previously stored `resourceId` with NULL when a run is re-persisted without one (for example during resume). ([#22105](https://github.com/mastra-ai/mastra/pull/22105))
|
|
59
|
+
|
|
60
|
+
- Stop observability filter discovery from re-scanning all history on every refresh ([#22136](https://github.com/mastra-ai/mastra/pull/22136))
|
|
61
|
+
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
- Fix PostgreSQL observability writes failing on NUL characters and unpaired Unicode surrogates ([#21728](https://github.com/mastra-ai/mastra/pull/21728))
|
|
65
|
+
|
|
66
|
+
Span serialization truncated strings by UTF-16 code unit, so a cut inside an emoji left a lone surrogate that PostgreSQL rejected on the jsonb cast (`22P02`). NUL characters were rejected as well (`22P05`). Because observability events are inserted as a single multi-row statement, one malformed field discarded the entire batch.
|
|
67
|
+
|
|
68
|
+
Truncation now preserves complete surrogate pairs, and the v-next PostgreSQL observability encoder sanitizes NUL and unpaired surrogates before the jsonb cast, using the same sanitizer that workflow snapshots already rely on. Valid Unicode, including complete emoji, is preserved.
|
|
69
|
+
|
|
70
|
+
- Experiment results now include isolated metadata snapshots from the dataset items that ran. ([#22005](https://github.com/mastra-ai/mastra/pull/22005))
|
|
71
|
+
|
|
72
|
+
- Fix `PgFactoryStorage` reads of `json` columns holding a JSON value that isn't an object. ([#22258](https://github.com/mastra-ai/mastra/pull/22258))
|
|
73
|
+
|
|
74
|
+
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.
|
|
75
|
+
|
|
76
|
+
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.
|
|
77
|
+
|
|
78
|
+
- Fixed `PgVector` scanning every vector table on startup. Constructing a `PgVector` warms an index cache in the background, and that warmup asked for full index statistics, which include `SELECT COUNT(*)` per table. On a large index that is a full table scan per index, per process start, and the warmup never used the count it paid for. `query()`, `upsert()`, `updateVector()` and the "has this index changed?" check in `createIndex()` paid for the same count. ([#22180](https://github.com/mastra-ai/mastra/pull/22180))
|
|
79
|
+
|
|
80
|
+
These paths now read only the index metadata they use (dimension, metric, index type, vector type, index configuration), all of which comes from the Postgres catalog at a cost that does not grow with the size of the table.
|
|
81
|
+
|
|
82
|
+
`describeIndex()` is unchanged and still returns an exact `count`:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const stats = await pgVector.describeIndex({ indexName: 'embeddings' });
|
|
86
|
+
console.log(stats.count); // exact row count, as before
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Concurrent callers on a cold cache also no longer duplicate the lookup: the first call is shared with everyone waiting on it, and a failed lookup is not cached.
|
|
90
|
+
|
|
91
|
+
Fixes [#21952](https://github.com/mastra-ai/mastra/issues/21952).
|
|
92
|
+
|
|
93
|
+
- 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))
|
|
94
|
+
|
|
95
|
+
- Updated dependencies [[`79f04a7`](https://github.com/mastra-ai/mastra/commit/79f04a7f6c6829da541139f638f2f1d267916e08), [`65edab1`](https://github.com/mastra-ai/mastra/commit/65edab1c233d17b8f163bad12fca410d0e6f16b1), [`1e47b75`](https://github.com/mastra-ai/mastra/commit/1e47b7520cab4cfaa8daed52f17e2e6d14ff7539), [`ab20a38`](https://github.com/mastra-ai/mastra/commit/ab20a38d0275f8d85e0f3833bd87ef487bcc609f), [`fd4d5fe`](https://github.com/mastra-ai/mastra/commit/fd4d5fe4f943699b85db5e74404f190d5a6b8c2a), [`ae8790c`](https://github.com/mastra-ai/mastra/commit/ae8790c4bfaa088d2ab279d1dcc06f326b9fd109), [`2c85f42`](https://github.com/mastra-ai/mastra/commit/2c85f428e04ccd63ea31a7ec80b5b327afdad555), [`11bbeb9`](https://github.com/mastra-ai/mastra/commit/11bbeb9b108ef2264e05acefc6dafb9cbb342921), [`48ef1f1`](https://github.com/mastra-ai/mastra/commit/48ef1f1d24eedafbb07f64e659a81b52b67b8bf6), [`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), [`1a485f3`](https://github.com/mastra-ai/mastra/commit/1a485f3538f5ec64d58bd8b5e1e99de0c695c87b), [`0d37487`](https://github.com/mastra-ai/mastra/commit/0d37487d9f349388a3f1cef6a536cf9dcc4b6273), [`8661d7d`](https://github.com/mastra-ai/mastra/commit/8661d7d7179f0a024456aabdd8679bcecd09ac28), [`dbbfeb8`](https://github.com/mastra-ai/mastra/commit/dbbfeb85ec949dc9ebc0755e1ad262e4f5eba8db), [`575e343`](https://github.com/mastra-ai/mastra/commit/575e343900451021d96110916497d334af7bc252), [`0b2a3d1`](https://github.com/mastra-ai/mastra/commit/0b2a3d1783875c5b97b7b36ab3d03d7360e0dde7), [`6bb5d71`](https://github.com/mastra-ai/mastra/commit/6bb5d7193fe9166b219f0fccae17db7a5ae86e65), [`3cc9d00`](https://github.com/mastra-ai/mastra/commit/3cc9d00b2b4333e0377a5e9df5eff92c17ce7630), [`cacb839`](https://github.com/mastra-ai/mastra/commit/cacb8392d9e74189b56d857290b0615f98a2683d), [`57de7d6`](https://github.com/mastra-ai/mastra/commit/57de7d644ba7146edb4e9e6111ec4fa98c3a59e9), [`c8e4cea`](https://github.com/mastra-ai/mastra/commit/c8e4ceac9a390d78c8327dff3cdb2861dd71957f), [`ed01e9a`](https://github.com/mastra-ai/mastra/commit/ed01e9a807514a904374bf687a7b8f18750f6f78), [`b47b26e`](https://github.com/mastra-ai/mastra/commit/b47b26e6fe95cb8a3482be2c5e52de157fe59d0b), [`0d37487`](https://github.com/mastra-ai/mastra/commit/0d37487d9f349388a3f1cef6a536cf9dcc4b6273), [`733a537`](https://github.com/mastra-ai/mastra/commit/733a537489a858b5880b2e98809334fba895a221), [`e8e299c`](https://github.com/mastra-ai/mastra/commit/e8e299cc6abdfc39947e2fec25803493015d3882), [`edfc548`](https://github.com/mastra-ai/mastra/commit/edfc548886bc7bae17b681f8b6b41a47eb32bcd2), [`b05f486`](https://github.com/mastra-ai/mastra/commit/b05f48612984d5fe2447ea2d6cdd5c604d285b97), [`a8a4871`](https://github.com/mastra-ai/mastra/commit/a8a4871215f51da95c47129602157ce5372f634a), [`eb9ecaa`](https://github.com/mastra-ai/mastra/commit/eb9ecaa89c36e889749e3b825cfc507ce7f7980b), [`4ff3ee2`](https://github.com/mastra-ai/mastra/commit/4ff3ee2bff7ed07528b4817f8f49639031c72a4d), [`9207dfa`](https://github.com/mastra-ai/mastra/commit/9207dfab8062e5fc68b751684797ff86fe0b4e70), [`5165cdc`](https://github.com/mastra-ai/mastra/commit/5165cdcdcf50e144bb8113278535196cc9b07065), [`e737014`](https://github.com/mastra-ai/mastra/commit/e737014e0fc7035759762bb5b48baef1d6c0f6a7), [`6bb5d71`](https://github.com/mastra-ai/mastra/commit/6bb5d7193fe9166b219f0fccae17db7a5ae86e65), [`f591643`](https://github.com/mastra-ai/mastra/commit/f591643becdf0be9bddce6ba1748e64bc30d77f1), [`63796ba`](https://github.com/mastra-ai/mastra/commit/63796ba0fda60253be17535e68f6bbbf1e6ffa09), [`b1ad324`](https://github.com/mastra-ai/mastra/commit/b1ad324d657f3544b0701332aef7eb10e9a36258), [`61c566d`](https://github.com/mastra-ai/mastra/commit/61c566dd2f2cde2b23ed8f139924e530d4202214), [`c24754c`](https://github.com/mastra-ai/mastra/commit/c24754c1fb6fe144e5051e536e98c8a18b0214ac), [`12c61d2`](https://github.com/mastra-ai/mastra/commit/12c61d280c8cb208bc3c8dbcbe5dcc60cf9d1cd0), [`c46eb09`](https://github.com/mastra-ai/mastra/commit/c46eb09ce4987509af57a0ac582c61241a6dd2f1), [`9ee8120`](https://github.com/mastra-ai/mastra/commit/9ee8120ce17f76b9f617489e05a283353742690a), [`d975e92`](https://github.com/mastra-ai/mastra/commit/d975e924d4936f46c386bd3dee39c671720289f6), [`45dd6ee`](https://github.com/mastra-ai/mastra/commit/45dd6ee089bd7df0d0c98a10098e483fd388e04a), [`4e9a228`](https://github.com/mastra-ai/mastra/commit/4e9a2283d5fd6ed1b70a2751eb3dc2cbf82ada20), [`d6ce34a`](https://github.com/mastra-ai/mastra/commit/d6ce34aeceb06ddf3d595a1eed5cc74f481a46a1), [`f95f468`](https://github.com/mastra-ai/mastra/commit/f95f468cf1e7c2b924a13826494f98b8f2ccd581), [`30ed33e`](https://github.com/mastra-ai/mastra/commit/30ed33ee14084a26019aba15fceadda6d6ddefaf), [`04a815f`](https://github.com/mastra-ai/mastra/commit/04a815fc8971d29e97fcdcc5008a1eb472fc00ff), [`1cfa878`](https://github.com/mastra-ai/mastra/commit/1cfa8784d8da0dfaa0317e5048bc48b6084a5ea5), [`9a12ef3`](https://github.com/mastra-ai/mastra/commit/9a12ef3fccf3f4186db0f294f4ee1f02cf4d8db2), [`32d3583`](https://github.com/mastra-ai/mastra/commit/32d358332cb8ac2306b83b73cf3536e74dbd435e), [`7960688`](https://github.com/mastra-ai/mastra/commit/7960688828e04eaf3106e34f7758fa580257eef6), [`91ad69d`](https://github.com/mastra-ai/mastra/commit/91ad69d64994c89199b0c55399e64ed91c61df2f), [`8dc408d`](https://github.com/mastra-ai/mastra/commit/8dc408d34438f9e13297f792c11a5cfd6cf952e1), [`c92def1`](https://github.com/mastra-ai/mastra/commit/c92def10a13c822972c96f0a4ca6ffc1f4258aed), [`63041eb`](https://github.com/mastra-ai/mastra/commit/63041eb4c50b520a0a80e03d4cd6ea99f67715a0), [`c118318`](https://github.com/mastra-ai/mastra/commit/c1183181c9804303db4b511c2e2648f8b714712b), [`c5eaec5`](https://github.com/mastra-ai/mastra/commit/c5eaec5a860d80d0e3805e67db0414b87ac8cbed), [`fc07c64`](https://github.com/mastra-ai/mastra/commit/fc07c6465043e08e99193a6751a01c56ffc2e7a1), [`cced745`](https://github.com/mastra-ai/mastra/commit/cced745a056ec2225c5bc702e32d848847aa8b65), [`542dee2`](https://github.com/mastra-ai/mastra/commit/542dee254167f974ff8cbbbfc0ce10f9a2616a7b), [`3c19dce`](https://github.com/mastra-ai/mastra/commit/3c19dcef8e73062a80627a4927eae3ec11145afd), [`aca2869`](https://github.com/mastra-ai/mastra/commit/aca2869b2031982f3c4a2f52525c9be7cf123ef8), [`a58483c`](https://github.com/mastra-ai/mastra/commit/a58483cff1a9d41fce7c931843f48cb0ac450f64), [`a58483c`](https://github.com/mastra-ai/mastra/commit/a58483cff1a9d41fce7c931843f48cb0ac450f64), [`e6f8450`](https://github.com/mastra-ai/mastra/commit/e6f845074d478527026b18d85031b23353e1d0a4), [`895e9df`](https://github.com/mastra-ai/mastra/commit/895e9dfc17d6f34299eca64e317ded9e5f5e5ef8), [`e66b2ba`](https://github.com/mastra-ai/mastra/commit/e66b2ba100db63eaeab6e21e1ea34b113f2ec781), [`3e8727e`](https://github.com/mastra-ai/mastra/commit/3e8727e11ec1a5d733acedb5c872896394be18c1)]:
|
|
96
|
+
- @mastra/core@1.62.0
|
|
97
|
+
|
|
98
|
+
## 1.22.0-alpha.4
|
|
99
|
+
|
|
100
|
+
### Patch Changes
|
|
101
|
+
|
|
102
|
+
- Stop observability filter discovery from re-scanning all history on every refresh ([#22136](https://github.com/mastra-ai/mastra/pull/22136))
|
|
103
|
+
|
|
104
|
+
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.
|
|
105
|
+
|
|
106
|
+
- Experiment results now include isolated metadata snapshots from the dataset items that ran. ([#22005](https://github.com/mastra-ai/mastra/pull/22005))
|
|
107
|
+
|
|
108
|
+
- Fix `PgFactoryStorage` reads of `json` columns holding a JSON value that isn't an object. ([#22258](https://github.com/mastra-ai/mastra/pull/22258))
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
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.
|
|
113
|
+
|
|
114
|
+
- 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))
|
|
115
|
+
|
|
116
|
+
- 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)]:
|
|
117
|
+
- @mastra/core@1.62.0-alpha.8
|
|
118
|
+
|
|
3
119
|
## 1.22.0-alpha.3
|
|
4
120
|
|
|
5
121
|
### Minor Changes
|
package/dist/docs/SKILL.md
CHANGED
|
@@ -350,6 +350,25 @@ const agent = new Agent({
|
|
|
350
350
|
})
|
|
351
351
|
```
|
|
352
352
|
|
|
353
|
+
FastEmbed also exposes the multilingual E5 model for non-English content. E5 is asymmetric, so it's exposed as two models: `multilingualE5LargePassage` for text you index and `multilingualE5LargeQuery` for search text.
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
import { Memory } from '@mastra/memory'
|
|
357
|
+
import { Agent } from '@mastra/core/agent'
|
|
358
|
+
import { fastembed } from '@mastra/fastembed'
|
|
359
|
+
|
|
360
|
+
const agent = new Agent({
|
|
361
|
+
id: 'agent',
|
|
362
|
+
memory: new Memory({
|
|
363
|
+
embedder: fastembed.multilingualE5LargePassage,
|
|
364
|
+
}),
|
|
365
|
+
})
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Memory uses a single embedder for both storing and recalling messages, so pick one of the two models and use it consistently. Use the paired `multilingualE5LargeQuery` model only where you control both sides of the pipeline, such as a RAG workflow that indexes with the passage model and searches with the query model.
|
|
369
|
+
|
|
370
|
+
Multilingual E5 produces 1024-dimensional vectors. Your vector index must be created with matching dimensions, and E5 vectors can't be mixed with vectors from another embedder in the same index.
|
|
371
|
+
|
|
353
372
|
## PostgreSQL index optimization
|
|
354
373
|
|
|
355
374
|
When using PostgreSQL as your vector store, you can optimize semantic recall performance by configuring the vector index. This is particularly important for large-scale deployments with thousands of messages.
|
|
@@ -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
|
-
|
|
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, "
|
|
7328
|
-
"startedAt" = $
|
|
7329
|
-
"traceId" = $
|
|
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
|
|
14509
|
-
|
|
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
|
|
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
|
|
14530
|
+
return await startRefresh();
|
|
14518
14531
|
} catch {
|
|
14519
14532
|
return [];
|
|
14520
14533
|
}
|
|
14521
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
},
|
|
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] =
|
|
22029
|
+
row[name] = value;
|
|
21935
22030
|
break;
|
|
21936
22031
|
case "bigint":
|
|
21937
22032
|
case "integer":
|