@mastra/pg 1.30.0 → 1.31.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.
@@ -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.30.0"
6
+ version: "1.31.0-alpha.1"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.30.0",
2
+ "version": "1.31.0-alpha.1",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
@@ -411,11 +411,11 @@ PostgreSQL supports both IVFFlat and HNSW indexes. By default, Mastra creates an
411
411
 
412
412
  ```typescript
413
413
  import { Memory } from '@mastra/memory'
414
- import { PgStore, PgVector } from '@mastra/pg'
414
+ import { PostgresStore, PgVector } from '@mastra/pg'
415
415
 
416
416
  const agent = new Agent({
417
417
  memory: new Memory({
418
- storage: new PgStore({
418
+ storage: new PostgresStore({
419
419
  id: 'agent-storage',
420
420
  connectionString: process.env.DATABASE_URL,
421
421
  }),
@@ -272,6 +272,22 @@ Schema-based working memory uses **merge semantics**, meaning the agent only nee
272
272
  - **Set a field to `null` to delete it:** This explicitly removes the field from memory
273
273
  - **Arrays are replaced entirely:** When an array field is provided, it replaces the existing array (arrays aren't merged element-by-element)
274
274
 
275
+ ### Concurrent updates
276
+
277
+ With `scope: 'resource'` and a storage adapter that supports atomic merges (currently `@mastra/pg`), each merge runs inside a row-locked database transaction. Concurrent updates to different fields from separate processes are all kept. Other adapters merge in the application, which only serializes writers inside a single process.
278
+
279
+ To merge a partial update from your own code, call `memory.mergeWorkingMemory()`:
280
+
281
+ ```typescript
282
+ await memory.mergeWorkingMemory({
283
+ threadId: 'thread-1',
284
+ resourceId: 'user-123',
285
+ workingMemory: { preferences: { tone: 'formal' } },
286
+ })
287
+ ```
288
+
289
+ It throws if working memory isn't schema-based and resource-scoped, or if the storage adapter doesn't support atomic merges. Check `await memory.supportsAtomicWorkingMemoryMerge()` first if you need to fall back.
290
+
275
291
  ## Choosing between template and schema
276
292
 
277
293
  - Use a **template** (Markdown) if you want the agent to maintain memory as a free-form text block, such as a user profile or scratchpad. Templates use **replace semantics**: the agent must provide the complete memory content on each update.
@@ -101,14 +101,14 @@ export const agent = new Agent({
101
101
  import { Memory } from '@mastra/memory'
102
102
  import { Agent } from '@mastra/core/agent'
103
103
  import { ModelRouterEmbeddingModel } from '@mastra/core/llm'
104
- import { PgStore, PgVector } from '@mastra/pg'
104
+ import { PostgresStore, PgVector } from '@mastra/pg'
105
105
 
106
106
  export const agent = new Agent({
107
107
  name: 'pg-agent',
108
108
  instructions: 'You are an agent with optimized PostgreSQL memory.',
109
109
  model: 'openai/gpt-5.6-sol',
110
110
  memory: new Memory({
111
- storage: new PgStore({
111
+ storage: new PostgresStore({
112
112
  id: 'pg-agent-storage',
113
113
  connectionString: process.env.DATABASE_URL,
114
114
  }),
@@ -40,9 +40,10 @@ const processor = new MessageHistory({
40
40
  ```typescript
41
41
  import { Agent } from '@mastra/core/agent'
42
42
  import { MessageHistory } from '@mastra/core/processors'
43
- import { PostgresStorage } from '@mastra/pg'
43
+ import { PostgresStore } from '@mastra/pg'
44
44
 
45
- const storage = new PostgresStorage({
45
+ const storage = new PostgresStore({
46
+ id: 'pg-storage',
46
47
  connectionString: process.env.DATABASE_URL,
47
48
  })
48
49
 
@@ -59,11 +59,10 @@ const processor = new SemanticRecall({
59
59
  ```typescript
60
60
  import { Agent } from '@mastra/core/agent'
61
61
  import { SemanticRecall, MessageHistory } from '@mastra/core/processors'
62
- import { PostgresStorage } from '@mastra/pg'
63
- import { PgVector } from '@mastra/pg'
62
+ import { PostgresStore, PgVector } from '@mastra/pg'
64
63
  import { openai } from '@ai-sdk/openai'
65
64
 
66
- const storage = new PostgresStorage({
65
+ const storage = new PostgresStore({
67
66
  id: 'pg-storage',
68
67
  connectionString: process.env.DATABASE_URL,
69
68
  })
@@ -62,9 +62,10 @@ const processor = new WorkingMemory({
62
62
  ```typescript
63
63
  import { Agent } from '@mastra/core/agent'
64
64
  import { WorkingMemory, MessageHistory } from '@mastra/core/processors'
65
- import { PostgresStorage } from '@mastra/pg'
65
+ import { PostgresStore } from '@mastra/pg'
66
66
 
67
- const storage = new PostgresStorage({
67
+ const storage = new PostgresStore({
68
+ id: 'pg-storage',
68
69
  connectionString: process.env.DATABASE_URL,
69
70
  })
70
71
 
@@ -231,7 +231,7 @@ await store.createIndex({
231
231
  - [libSQL](https://mastra.ai/reference/vectors/libsql)
232
232
  - [MongoDB](https://mastra.ai/reference/vectors/mongodb)
233
233
  - [OracleDB](https://mastra.ai/reference/vectors/oracledb)
234
- - [PgStore](https://mastra.ai/reference/vectors/pg)
234
+ - [PgVector](https://mastra.ai/reference/vectors/pg)
235
235
  - [Pinecone](https://mastra.ai/reference/vectors/pinecone)
236
236
  - [Qdrant](https://mastra.ai/reference/vectors/qdrant)
237
237
  - [Upstash](https://mastra.ai/reference/vectors/upstash)
@@ -230,7 +230,7 @@ await store.upsert({
230
230
  **libSQL**:
231
231
 
232
232
  ```ts
233
- import { LibSQLVector } from '@mastra/core/vector/libsql'
233
+ import { LibSQLVector } from '@mastra/libsql'
234
234
 
235
235
  const store = new LibSQLVector({
236
236
  id: 'libsql-vector',
package/dist/index.cjs CHANGED
@@ -2507,7 +2507,7 @@ async function loadSchemaSnapshot(client, schemaName) {
2507
2507
  AND c.relkind IN ('r', 'p')
2508
2508
  AND a.attnum > 0
2509
2509
  AND NOT a.attisdropped`, [schema]),
2510
- client.manyOrNone(`SELECT c.relname AS indexname, i.indisreplident AS is_replica_identity, i.indisprimary AS is_primary
2510
+ client.manyOrNone(`SELECT c.relname AS indexname, i.indisreplident AS is_replica_identity, i.indisprimary AS is_primary, i.indisvalid AS is_valid
2511
2511
  FROM pg_catalog.pg_index i
2512
2512
  JOIN pg_catalog.pg_class c ON c.oid = i.indexrelid
2513
2513
  JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
@@ -2532,8 +2532,10 @@ async function loadSchemaSnapshot(client, schemaName) {
2532
2532
  const indexes = /* @__PURE__ */ new Set();
2533
2533
  const replicaIdentityIndexes = /* @__PURE__ */ new Set();
2534
2534
  const primaryKeyIndexes = /* @__PURE__ */ new Set();
2535
+ const invalidIndexes = /* @__PURE__ */ new Set();
2535
2536
  for (const row of indexRows) {
2536
- indexes.add(row.indexname);
2537
+ if (row.is_valid) indexes.add(row.indexname);
2538
+ else invalidIndexes.add(row.indexname);
2537
2539
  if (row.is_replica_identity) replicaIdentityIndexes.add(row.indexname);
2538
2540
  if (row.is_primary) primaryKeyIndexes.add(row.indexname.toLowerCase());
2539
2541
  }
@@ -2544,7 +2546,8 @@ async function loadSchemaSnapshot(client, schemaName) {
2544
2546
  columnTypes,
2545
2547
  indexes,
2546
2548
  replicaIdentityIndexes,
2547
- primaryKeyIndexes
2549
+ primaryKeyIndexes,
2550
+ invalidIndexes
2548
2551
  };
2549
2552
  }
2550
2553
  //#endregion
@@ -3692,11 +3695,8 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3692
3695
  schemaName: getSchemaName$6(this.schemaName)
3693
3696
  });
3694
3697
  const snapshot = this.schemaSnapshot;
3695
- if (snapshot) {
3696
- if (snapshot.indexes.has(name)) return;
3697
- } else if (await this.client.oneOrNone(`SELECT 1 FROM pg_indexes
3698
- WHERE indexname = $1
3699
- AND schemaname = $2`, [name, schemaName])) return;
3698
+ if (snapshot?.indexes.has(name)) return;
3699
+ if (!await this.prepareIndexBuild(name, schemaName)) return;
3700
3700
  const uniqueStr = unique ? "UNIQUE " : "";
3701
3701
  const concurrentStr = concurrent ? "CONCURRENTLY " : "";
3702
3702
  const methodStr = method !== "btree" ? `USING ${method} ` : "";
@@ -3715,16 +3715,9 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3715
3715
  let withStr = "";
3716
3716
  if (storage && Object.keys(storage).length > 0) withStr = ` WITH (${Object.entries(storage).map(([key, value]) => `${key} = ${value}`).join(", ")})`;
3717
3717
  const sql = `CREATE ${uniqueStr}INDEX ${concurrentStr}${`"${(0, _mastra_core_utils.parseSqlIdentifier)(name, "index name")}"`} ON ${fullTableName} ${methodStr}(${columnsStr})${withStr}${tablespaceStr}${whereStr}`;
3718
- await this.client.none(sql);
3718
+ await this.timedIndexBuild(name, sql);
3719
3719
  snapshot?.indexes.add(name);
3720
3720
  } catch (error) {
3721
- if (error instanceof Error && error.message.includes("CONCURRENTLY")) {
3722
- const retryOptions = {
3723
- ...options,
3724
- concurrent: false
3725
- };
3726
- return this.createIndex(retryOptions);
3727
- }
3728
3721
  throw new _mastra_core_error.MastraError({
3729
3722
  id: (0, _mastra_core_storage.createStorageErrorId)("PG", "INDEX_CREATE", "FAILED"),
3730
3723
  domain: _mastra_core_error.ErrorDomain.STORAGE,
@@ -3748,9 +3741,43 @@ MIGRATION REQUIRED: Duplicate spans detected in ${duplicateInfo.tableName}\n====
3748
3741
  async createIndexFromStatement(indexName, sql) {
3749
3742
  const snapshot = this.schemaSnapshot;
3750
3743
  if (snapshot?.indexes.has(indexName)) return;
3751
- await this.client.none(sql);
3744
+ if (!await this.prepareIndexBuild(indexName, this.schemaName || "public")) return;
3745
+ await this.timedIndexBuild(indexName, sql);
3752
3746
  snapshot?.indexes.add(indexName);
3753
3747
  }
3748
+ /**
3749
+ * Decides whether `indexName` still needs building. Returns false when a valid
3750
+ * index already exists, or when another session is currently building it.
3751
+ * An invalid index left by an interrupted `CREATE INDEX CONCURRENTLY` is
3752
+ * dropped (concurrently) so the caller rebuilds it — `IF NOT EXISTS` would
3753
+ * otherwise keep the broken index forever.
3754
+ */
3755
+ async prepareIndexBuild(indexName, schemaName) {
3756
+ const snapshot = this.schemaSnapshot;
3757
+ if (snapshot && !snapshot.invalidIndexes.has(indexName)) return true;
3758
+ const existing = await this.client.oneOrNone(`SELECT i.indisvalid AS is_valid,
3759
+ EXISTS (SELECT 1 FROM pg_stat_progress_create_index p WHERE p.index_relid = c.oid) AS building
3760
+ FROM pg_catalog.pg_index i
3761
+ JOIN pg_catalog.pg_class c ON c.oid = i.indexrelid
3762
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
3763
+ WHERE c.relname = $1 AND n.nspname = $2`, [indexName, schemaName]);
3764
+ if (!existing) return true;
3765
+ if (existing.is_valid) return false;
3766
+ if (existing.building) {
3767
+ this.logger.warn(`Index ${indexName} is being built by another session; skipping`);
3768
+ return false;
3769
+ }
3770
+ this.logger.warn(`Dropping invalid index ${indexName} left by an interrupted build; rebuilding it`);
3771
+ const quotedIndexName = `"${(0, _mastra_core_utils.parseSqlIdentifier)(indexName, "index name")}"`;
3772
+ await this.client.none(`DROP INDEX CONCURRENTLY IF EXISTS ${getSchemaName$6(this.schemaName)}.${quotedIndexName}`);
3773
+ return true;
3774
+ }
3775
+ async timedIndexBuild(indexName, sql) {
3776
+ const startedAt = Date.now();
3777
+ this.logger.info(`Creating index ${indexName}`);
3778
+ await this.client.none(sql);
3779
+ this.logger.info(`Created index ${indexName} in ${Date.now() - startedAt}ms`);
3780
+ }
3754
3781
  async dropIndex(indexName) {
3755
3782
  try {
3756
3783
  const schemaName = this.schemaName || "public";
@@ -10495,6 +10522,7 @@ function dedupeMessagesForSave(messages) {
10495
10522
  var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
10496
10523
  supportsPartialThreadUpdate = true;
10497
10524
  supportsObservationalMemory = true;
10525
+ supportsAtomicWorkingMemoryMerge = true;
10498
10526
  supportsObservationalMemoryHistorySearch = true;
10499
10527
  /**
10500
10528
  * Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
@@ -11841,6 +11869,57 @@ var MemoryPG = class MemoryPG extends _mastra_core_storage.MemoryStorage {
11841
11869
  metadata: metadataJson ? JSON.parse(metadataJson) : resource.metadata
11842
11870
  };
11843
11871
  }
11872
+ async mergeResourceWorkingMemory({ resourceId, merge }) {
11873
+ const tableName = getTableName$3({
11874
+ indexName: _mastra_core_storage.TABLE_RESOURCES,
11875
+ schemaName: getSchemaName$3(this.#schema)
11876
+ });
11877
+ const callbackError = {};
11878
+ try {
11879
+ return await this.#db.client.tx(async (t) => {
11880
+ const now = (/* @__PURE__ */ new Date()).toISOString();
11881
+ await t.none(`INSERT INTO ${tableName} (id, metadata, "createdAt", "createdAtZ", "updatedAt", "updatedAtZ")
11882
+ VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (id) DO NOTHING`, [
11883
+ resourceId,
11884
+ toPgJson({}),
11885
+ now,
11886
+ now,
11887
+ now,
11888
+ now
11889
+ ]);
11890
+ await t.one(`SELECT id FROM ${tableName} WHERE id = $1 FOR UPDATE`, [resourceId]);
11891
+ const existing = await this.#getResourceById(t, resourceId);
11892
+ let workingMemory;
11893
+ try {
11894
+ workingMemory = merge(existing.workingMemory ?? void 0);
11895
+ } catch (error) {
11896
+ callbackError.error = error;
11897
+ throw error;
11898
+ }
11899
+ const updatedAt = /* @__PURE__ */ new Date();
11900
+ const updatedAtStr = updatedAt.toISOString();
11901
+ await t.none(`UPDATE ${tableName} SET "workingMemory" = $1, "updatedAt" = $2, "updatedAtZ" = $3 WHERE id = $4`, [
11902
+ workingMemory,
11903
+ updatedAtStr,
11904
+ updatedAtStr,
11905
+ resourceId
11906
+ ]);
11907
+ return {
11908
+ ...existing,
11909
+ workingMemory,
11910
+ updatedAt
11911
+ };
11912
+ });
11913
+ } catch (error) {
11914
+ if (error === callbackError.error || error instanceof _mastra_core_error.MastraError) throw error;
11915
+ throw new _mastra_core_error.MastraError({
11916
+ id: (0, _mastra_core_storage.createStorageErrorId)("PG", "MERGE_RESOURCE_WORKING_MEMORY", "FAILED"),
11917
+ domain: _mastra_core_error.ErrorDomain.STORAGE,
11918
+ category: _mastra_core_error.ErrorCategory.THIRD_PARTY,
11919
+ details: { resourceId }
11920
+ }, error);
11921
+ }
11922
+ }
11844
11923
  async updateResource({ resourceId, workingMemory, metadata }) {
11845
11924
  const existingResource = await this.#getResourceById(this.#db.client, resourceId);
11846
11925
  if (!existingResource) {
@@ -17540,6 +17619,35 @@ function isMetadataField(field) {
17540
17619
  function placeholders(values, offset) {
17541
17620
  return values.map((_, index) => `$${offset + index}`).join(", ");
17542
17621
  }
17622
+ /**
17623
+ * Unicode letters, marks, and digits as PostgreSQL bracket ranges, so `[^<ranges>]` splits words
17624
+ * like the planner's `[^\p{L}\p{M}\p{N}]`. PostgreSQL regexes have no `\p{…}` classes, and
17625
+ * `[[:alnum:]]` depends on the locale: it leaves out digits such as `² ½ ₂ ①`, and glibc files
17626
+ * some marks, such as the Devanagari virama, under punct. Derived once from the runtime's own
17627
+ * Unicode tables. A run may also absorb unassigned code points, which are never stored.
17628
+ * Private-use code points are not absorbed: the other stores treat them as separators.
17629
+ */
17630
+ let pgWordRanges;
17631
+ function getPgWordRanges() {
17632
+ if (pgWordRanges !== void 0) return pgWordRanges;
17633
+ const escape = (cp) => cp > 65535 ? `\\U${cp.toString(16).padStart(8, "0")}` : `\\u${cp.toString(16).padStart(4, "0")}`;
17634
+ const chunks = [];
17635
+ for (let start = 0; start <= 1114111; start += 8192) {
17636
+ const codePoints = [];
17637
+ for (let cp = start; cp < start + 8192 && cp <= 1114111; cp++) if (cp < 55296 || cp > 57343) codePoints.push(cp);
17638
+ chunks.push(String.fromCodePoint(...codePoints));
17639
+ }
17640
+ const text = chunks.join("");
17641
+ const ranges = [];
17642
+ for (const match of text.matchAll(/[\p{L}\p{M}\p{N}\p{Cn}]+/gu)) {
17643
+ const first = match[0].codePointAt(0);
17644
+ const lastUnit = match.index + match[0].length - 1;
17645
+ const last = text.codePointAt(text.charCodeAt(lastUnit) >= 56320 && text.charCodeAt(lastUnit) <= 57343 ? lastUnit - 1 : lastUnit);
17646
+ ranges.push(first === last ? escape(first) : `${escape(first)}-${escape(last)}`);
17647
+ }
17648
+ pgWordRanges = ranges.join("");
17649
+ return pgWordRanges;
17650
+ }
17543
17651
  function compileScalarPredicate(predicate, registry, parameterOffset, allowMetadata = false) {
17544
17652
  if (predicate.type === "boolean") {
17545
17653
  const values = [];
@@ -17583,6 +17691,13 @@ function compileScalarPredicate(predicate, registry, parameterOffset, allowMetad
17583
17691
  values: fieldValues
17584
17692
  };
17585
17693
  }
17694
+ if (predicate.type === "text") {
17695
+ const found = `strpos(${`' ' || replace(lower(regexp_replace(normalize(${field}), '[^${getPgWordRanges()}]+', ' ', 'g')), 'ς', 'σ') || ' '`}, $${parameterOffset}) > 0`;
17696
+ return {
17697
+ sql: `COALESCE(${predicate.operator === "matches" ? found : `NOT (${found})`}, false)`,
17698
+ values: [...fieldValues, ` ${predicate.value} `]
17699
+ };
17700
+ }
17586
17701
  if (predicate.type === "membership") {
17587
17702
  const list = placeholders(predicate.values, parameterOffset);
17588
17703
  if (predicate.operator === "in") return {
@@ -23172,12 +23287,17 @@ function workflowSnapshotStatusIndexName(schemaName) {
23172
23287
  /**
23173
23288
  * Expression index on `(workflow_name, snapshot->>'status', "createdAt" DESC)` so
23174
23289
  * listWorkflowRuns() status filters can use an index instead of scanning every snapshot.
23290
+ *
23291
+ * `concurrently` is set by init, which may build the index on a large existing table while
23292
+ * other processes keep writing to it. The exported DDL stays plain because migration
23293
+ * tooling often wraps statements in a transaction, where CONCURRENTLY is rejected.
23175
23294
  */
23176
- function workflowSnapshotStatusIndexSQL(indexName, schemaName) {
23177
- return `CREATE INDEX IF NOT EXISTS "${indexName}" ON ${getTableName({
23295
+ function workflowSnapshotStatusIndexSQL(indexName, schemaName, concurrently = false) {
23296
+ const tableName = getTableName({
23178
23297
  indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
23179
23298
  schemaName: getSchemaName(schemaName)
23180
- })} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
23299
+ });
23300
+ return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} (workflow_name, (snapshot ->> 'status'), "createdAt" DESC)`;
23181
23301
  }
23182
23302
  /** Base name (before any schema prefix) of the expression index backing the threadId filter. */
23183
23303
  const WORKFLOW_SNAPSHOT_THREAD_ID_INDEX = "mastra_workflow_snapshot_threadid_idx";
@@ -23214,12 +23334,14 @@ const WORKFLOW_SNAPSHOT_THREAD_ID_EXPR = `COALESCE(jsonb_path_query_first(snapsh
23214
23334
  /**
23215
23335
  * Expression index on the snapshot-embedded thread id so listWorkflowRuns() threadId filters
23216
23336
  * (Agent.listSuspendedRuns) can use an index instead of detoasting every snapshot.
23337
+ * See {@link workflowSnapshotStatusIndexSQL} for `concurrently`.
23217
23338
  */
23218
- function workflowSnapshotThreadIdIndexSQL(indexName, schemaName) {
23219
- return `CREATE INDEX IF NOT EXISTS "${indexName}" ON ${getTableName({
23339
+ function workflowSnapshotThreadIdIndexSQL(indexName, schemaName, concurrently = false) {
23340
+ const tableName = getTableName({
23220
23341
  indexName: _mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT,
23221
23342
  schemaName: getSchemaName(schemaName)
23222
- })} ((${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR}))`;
23343
+ });
23344
+ return `CREATE INDEX ${concurrently ? "CONCURRENTLY " : ""}IF NOT EXISTS "${indexName}" ON ${tableName} ((${WORKFLOW_SNAPSHOT_THREAD_ID_EXPR}))`;
23223
23345
  }
23224
23346
  var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorage {
23225
23347
  #db;
@@ -23316,13 +23438,13 @@ var WorkflowsPG = class WorkflowsPG extends _mastra_core_storage.WorkflowsStorag
23316
23438
  if (await this.#db.getColumnType(_mastra_core_storage.TABLE_WORKFLOW_SNAPSHOT, "snapshot") !== "jsonb") return;
23317
23439
  const indexName = workflowSnapshotStatusIndexName(this.#schema);
23318
23440
  try {
23319
- await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema));
23441
+ await this.#db.createIndexFromStatement(indexName, workflowSnapshotStatusIndexSQL(indexName, this.#schema, true));
23320
23442
  } catch (error) {
23321
23443
  this.logger?.warn?.(`Failed to create index ${indexName}:`, error);
23322
23444
  }
23323
23445
  const threadIdIndexName = workflowSnapshotThreadIdIndexName(this.#schema);
23324
23446
  try {
23325
- await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema));
23447
+ await this.#db.createIndexFromStatement(threadIdIndexName, workflowSnapshotThreadIdIndexSQL(threadIdIndexName, this.#schema, true));
23326
23448
  } catch (error) {
23327
23449
  this.logger?.warn?.(`Failed to create index ${threadIdIndexName}:`, error);
23328
23450
  }