@mastra/pg 1.21.1 → 1.22.0-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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # @mastra/pg
2
2
 
3
+ ## 1.22.0-alpha.1
4
+
5
+ ### Minor Changes
6
+
7
+ - Added native application collection counts so totals no longer load matching rows. ([#22021](https://github.com/mastra-ai/mastra/pull/22021))
8
+
9
+ **Before**
10
+
11
+ ```ts
12
+ const total = (await storage.ops.findMany('jobs', { status: 'failed' })).length;
13
+ ```
14
+
15
+ **After**
16
+
17
+ ```ts
18
+ const total = await storage.ops.count?.('jobs', { status: 'failed' });
19
+ ```
20
+
21
+ ### Patch Changes
22
+
23
+ - 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))
24
+
25
+ - Fix PostgreSQL observability writes failing on NUL characters and unpaired Unicode surrogates ([#21728](https://github.com/mastra-ai/mastra/pull/21728))
26
+
27
+ 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.
28
+
29
+ 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.
30
+
31
+ - Updated dependencies [[`2c85f42`](https://github.com/mastra-ai/mastra/commit/2c85f428e04ccd63ea31a7ec80b5b327afdad555), [`11bbeb9`](https://github.com/mastra-ai/mastra/commit/11bbeb9b108ef2264e05acefc6dafb9cbb342921), [`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), [`575e343`](https://github.com/mastra-ai/mastra/commit/575e343900451021d96110916497d334af7bc252), [`cacb839`](https://github.com/mastra-ai/mastra/commit/cacb8392d9e74189b56d857290b0615f98a2683d), [`b47b26e`](https://github.com/mastra-ai/mastra/commit/b47b26e6fe95cb8a3482be2c5e52de157fe59d0b), [`0d37487`](https://github.com/mastra-ai/mastra/commit/0d37487d9f349388a3f1cef6a536cf9dcc4b6273), [`c46eb09`](https://github.com/mastra-ai/mastra/commit/c46eb09ce4987509af57a0ac582c61241a6dd2f1), [`30ed33e`](https://github.com/mastra-ai/mastra/commit/30ed33ee14084a26019aba15fceadda6d6ddefaf), [`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), [`c5eaec5`](https://github.com/mastra-ai/mastra/commit/c5eaec5a860d80d0e3805e67db0414b87ac8cbed), [`e66b2ba`](https://github.com/mastra-ai/mastra/commit/e66b2ba100db63eaeab6e21e1ea34b113f2ec781)]:
32
+ - @mastra/core@1.62.0-alpha.3
33
+
34
+ ## 1.22.0-alpha.0
35
+
36
+ ### Minor Changes
37
+
38
+ - Added support for filtering scores by metadata key-value pairs in listScores. ([#22047](https://github.com/mastra-ai/mastra/pull/22047))
39
+
40
+ ```typescript
41
+ const result = await storage.listScores({
42
+ filters: { metadata: { env: 'prod' } },
43
+ });
44
+ ```
45
+
46
+ 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.
47
+
48
+ ### Patch Changes
49
+
50
+ - Updated dependencies [[`e737014`](https://github.com/mastra-ai/mastra/commit/e737014e0fc7035759762bb5b48baef1d6c0f6a7), [`d6ce34a`](https://github.com/mastra-ai/mastra/commit/d6ce34aeceb06ddf3d595a1eed5cc74f481a46a1), [`e6f8450`](https://github.com/mastra-ai/mastra/commit/e6f845074d478527026b18d85031b23353e1d0a4)]:
51
+ - @mastra/core@1.62.0-alpha.2
52
+
3
53
  ## 1.21.1
4
54
 
5
55
  ### Patch 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.21.1"
6
+ version: "1.22.0-alpha.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.21.1",
2
+ "version": "1.22.0-alpha.1",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -15260,6 +15260,20 @@ const COMPLEX_GROUP_BY_EXCLUDED = /* @__PURE__ */ new Set([
15260
15260
  "requestContext"
15261
15261
  ]);
15262
15262
  //#endregion
15263
+ //#region src/storage/db/sanitize-json.ts
15264
+ /**
15265
+ * Sanitizes JSON string for PostgreSQL jsonb:
15266
+ * - Removes problematic Unicode sequences:
15267
+ * - \u0000 (null character) - causes error 22P05 "unsupported Unicode escape sequence"
15268
+ * - \uD800-\uDFFF (unpaired surrogates) - causes "Unicode low surrogate must follow a high surrogate"
15269
+ * - \\uD800 (escaped-backslash + surrogate, e.g. from JS regex literals like [^\ud800-\udfff]):
15270
+ * removing just \uXXXX would leave a dangling backslash that creates a new invalid escape (e.g. \-)
15271
+ * - Escapes any remaining invalid JSON escape sequences (e.g. \v, \k, \-)
15272
+ */
15273
+ function sanitizeJsonForPg(jsonString) {
15274
+ return jsonString.replace(/\\\\?u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})/g, "").replace(/(^|[^\\])(\\(?!["\\/bfnrtu]))/g, "$1\\\\");
15275
+ }
15276
+ //#endregion
15263
15277
  //#region src/storage/domains/observability/v-next/sql.ts
15264
15278
  /**
15265
15279
  * SQL helpers for the v-next Postgres observability domain.
@@ -15272,10 +15286,16 @@ const COMPLEX_GROUP_BY_EXCLUDED = /* @__PURE__ */ new Set([
15272
15286
  * Encode a JS value for a `$N::jsonb` cast. Always `JSON.stringify` so a
15273
15287
  * plain string like `"hello"` becomes `"hello"` (a valid JSON scalar) and
15274
15288
  * not the bare word `hello`, which Postgres rejects when cast to jsonb.
15289
+ *
15290
+ * The result is sanitized because Postgres rejects some sequences that are
15291
+ * legal in JSON: NUL (`\u0000`) fails with 22P05 and unpaired UTF-16
15292
+ * surrogates fail with 22P02. Inserts here are batched into a single
15293
+ * multi-row statement, so one bad value would otherwise reject the whole
15294
+ * batch of observability events.
15275
15295
  */
15276
15296
  function encodeJsonb(value) {
15277
15297
  if (value === null || value === void 0) return null;
15278
- return JSON.stringify(value);
15298
+ return sanitizeJsonForPg(JSON.stringify(value));
15279
15299
  }
15280
15300
  /**
15281
15301
  * Build a multi-row INSERT with explicit column types and ON CONFLICT DO NOTHING.
@@ -16112,6 +16132,10 @@ function applyScoreFilters(acc, filters) {
16112
16132
  acc.conditions.push(`"scoreSource" = $${acc.next++}`);
16113
16133
  acc.params.push(filters.scoreSource ?? filters.source);
16114
16134
  }
16135
+ if (filters?.metadata && Object.keys(filters.metadata).length > 0) for (const [key, value] of Object.entries(filters.metadata)) {
16136
+ acc.conditions.push(`"metadata"->($${acc.next++}::text) = $${acc.next++}::jsonb`);
16137
+ acc.params.push(key, JSON.stringify(value ?? null));
16138
+ }
16115
16139
  }
16116
16140
  /** OLAP queries take an explicit scorerId / scoreSource pair as identity. */
16117
16141
  function pushScoreIdentity(acc, scorerId, scoreSource) {
@@ -20497,18 +20521,6 @@ function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
20497
20521
  schemaName: getSchemaName(schemaName)
20498
20522
  })} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
20499
20523
  }
20500
- /**
20501
- * Sanitizes JSON string for PostgreSQL jsonb:
20502
- * - Removes problematic Unicode sequences:
20503
- * - \u0000 (null character) - causes error 22P05 "unsupported Unicode escape sequence"
20504
- * - \uD800-\uDFFF (unpaired surrogates) - causes "Unicode low surrogate must follow a high surrogate"
20505
- * - \\uD800 (escaped-backslash + surrogate, e.g. from JS regex literals like [^\ud800-\udfff]):
20506
- * removing just \uXXXX would leave a dangling backslash that creates a new invalid escape (e.g. \-)
20507
- * - Escapes any remaining invalid JSON escape sequences (e.g. \v, \k, \-)
20508
- */
20509
- function sanitizeJsonForPg(jsonString) {
20510
- return jsonString.replace(/\\\\?u(0000|[Dd][89A-Fa-f][0-9A-Fa-f]{2})/g, "").replace(/(^|[^\\])(\\(?!["\\/bfnrtu]))/g, "$1\\\\");
20511
- }
20512
20524
  var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorage {
20513
20525
  #db;
20514
20526
  #schema;
@@ -20788,11 +20800,11 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
20788
20800
  await this.#db.client.none(`INSERT INTO ${getTableName({
20789
20801
  indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
20790
20802
  schemaName: getSchemaName(this.#schema)
20791
- })}
20803
+ })} AS t
20792
20804
  (workflow_name, run_id, "resourceId", snapshot, "createdAt", "updatedAt", "createdAtZ", "updatedAtZ")
20793
20805
  VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
20794
20806
  ON CONFLICT (workflow_name, run_id) DO UPDATE
20795
- SET "resourceId" = $3, snapshot = $4, "updatedAt" = $6, "updatedAtZ" = $8`, [
20807
+ SET "resourceId" = COALESCE($3, t."resourceId"), snapshot = $4, "updatedAt" = $6, "updatedAtZ" = $8`, [
20796
20808
  workflowName,
20797
20809
  runId,
20798
20810
  resourceId,
@@ -21750,6 +21762,12 @@ var PgFactoryStorageOps = class {
21750
21762
  async findMany(collection, where, opts) {
21751
21763
  return this.#select(this.#queryable, collection, where, opts);
21752
21764
  }
21765
+ async count(collection, where) {
21766
+ const schema = this.#schema(collection);
21767
+ const filter = this.#buildWhere(schema, where);
21768
+ const result = await this.#queryable.query(`SELECT COUNT(*) AS count FROM "${schema.name}" WHERE ${filter.sql}`, filter.args);
21769
+ return Number(result.rows[0]?.count ?? 0);
21770
+ }
21753
21771
  async insertOne(collection, row) {
21754
21772
  const schema = this.#schema(collection);
21755
21773
  const pk = primaryKeyOf(schema);