diff --git a/apps/server/src/orchestration-v2/ProjectionStore.ts b/apps/server/src/orchestration-v2/ProjectionStore.ts index 0c4976dc9059..8c35ce7a1de8 100644 --- a/apps/server/src/orchestration-v2/ProjectionStore.ts +++ b/apps/server/src/orchestration-v2/ProjectionStore.ts @@ -96,6 +96,7 @@ import type * as Statement from "effect/sql/Statement"; import { MCP_APP_OUTPUT_KEY } from "@t3tools/shared/mcpApp"; import { threadHtmlRenderAttachmentIds } from "../attachmentStore.ts"; +import { readableSearchText } from "./messageSearchIndex.ts"; import { isThreadHistoryUserTurn, isConversationHistoryItem, @@ -2594,7 +2595,8 @@ export const layer: Layer.Layer = streaming, created_at, updated_at, - payload_json + payload_json, + search_text ) VALUES ( ${event.payload.id}, @@ -2605,7 +2607,8 @@ export const layer: Layer.Layer = ${booleanInt(event.payload.streaming)}, ${createdAt}, ${updatedAt}, - ${payloadJson} + ${payloadJson}, + ${readableSearchText(event.payload.text)} ) ON CONFLICT(message_id) DO UPDATE SET @@ -2616,7 +2619,8 @@ export const layer: Layer.Layer = streaming = excluded.streaming, created_at = excluded.created_at, updated_at = excluded.updated_at, - payload_json = excluded.payload_json + payload_json = excluded.payload_json, + search_text = excluded.search_text `; break; } diff --git a/apps/server/src/orchestration-v2/ThreadSearch.test.ts b/apps/server/src/orchestration-v2/ThreadSearch.test.ts index e758280f9388..421e79f5a090 100644 --- a/apps/server/src/orchestration-v2/ThreadSearch.test.ts +++ b/apps/server/src/orchestration-v2/ThreadSearch.test.ts @@ -1,5 +1,6 @@ import { assert, it } from "@effect/vitest"; import { + EnvironmentId, EventId, MessageId, ProjectId, @@ -18,9 +19,11 @@ import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; import * as SqlClient from "effect/sql/SqlClient"; +import { serializeAssistantCitation } from "@t3tools/shared/assistantCitations"; import { ATTACHMENT_ONLY_BOOTSTRAP_PROMPT } from "@t3tools/shared/composerContextReferences"; import * as SqlitePersistence from "../persistence/Sqlite.ts"; +import { rebuildMessageSearch } from "./messageSearchIndex.ts"; import * as ProjectionStore from "./ProjectionStore.ts"; import * as ProjectStore from "./ProjectStore.ts"; import * as ThreadSearch from "./ThreadSearch.ts"; @@ -150,6 +153,20 @@ const message = ( }, }); +// The prefix and suffix are hidden selection context, not text the reader sees. +const quote = serializeAssistantCitation({ + version: 1, + environmentId: EnvironmentId.make("environment:local"), + threadId: ThreadId.make("thread:quoted"), + messageId: MessageId.make("quoted-answer"), + text: "A lighthouse guides ships safely through the darkness.", + comment: "what are you talking about", + start: 0, + end: 54, + prefix: "beacon01", + suffix: "harbor", +}); + it.effect.each([true, false])( "ThreadSearch excludes bootstrap text before the hit and candidate limits (indexed: %s)", (indexed) => @@ -349,6 +366,96 @@ it.layer(layerTest)("ThreadSearch", (it) => { }), ); + it.effect("matches the readable text of quotes and their comments", () => + Effect.gen(function* () { + const projections = yield* ProjectionStore.ProjectionStoreV2; + const search = yield* ThreadSearch.ThreadSearch; + const project = ProjectId.make("project:search-citation"); + const threadId = ThreadId.make("thread:citation"); + yield* createProject(project); + yield* Effect.forEach( + [ + thread(threadId, project), + message(threadId, "citation", "user", `${quote} quiet silver lantern`), + ], + projections.apply, + { discard: true }, + ); + const snippets = (query: string) => + Effect.map(search.search({ query }), (result) => + result.matches + .filter((match) => match.threadId === threadId) + .map((match) => match.snippet), + ); + + const readable = + "A lighthouse guides ships safely through the darkness. Comment: what are you talking about quiet silver lantern"; + assert.deepEqual(yield* snippets("what are you talking about"), [ + `…${readable.slice(readable.indexOf("the darkness"))}`, + ]); + assert.deepEqual(yield* snippets("ships safely through"), [readable]); + assert.deepEqual(yield* snippets("beacon01"), []); + assert.deepEqual(yield* snippets("harbor"), []); + + yield* projections.apply(message(threadId, "citation", "user", "quiet silver lantern")); + assert.deepEqual(yield* snippets("what are you talking about"), []); + assert.deepEqual(yield* snippets("lantern"), ["quiet silver lantern"]); + }), + ); + + it.effect("finds accented, CJK, and apostrophe words of a quote through the index", () => + Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + const projections = yield* ProjectionStore.ProjectionStoreV2; + const search = yield* ThreadSearch.ThreadSearch; + const project = ProjectId.make("project:search-citation-index"); + const threadId = ThreadId.make("thread:citation-index"); + // The stored link percent-encodes these words; the index must hold them as they read. + const encodedQuote = serializeAssistantCitation({ + version: 1, + environmentId: EnvironmentId.make("environment:local"), + threadId: ThreadId.make("thread:quoted"), + messageId: MessageId.make("quoted-answer"), + text: "The kittiwake's résumé mentions 灯台の光 twice.", + comment: "naïve question в логах", + start: 0, + end: 44, + prefix: "", + suffix: "", + }); + yield* createProject(project); + yield* Effect.forEach( + [thread(threadId, project), message(threadId, "citation-index", "user", encodedQuote)], + projections.apply, + { discard: true }, + ); + const index = yield* sql` + SELECT 1 FROM sqlite_master WHERE name = 'orchestration_v2_message_search' + `; + assert.lengthOf(index, 1); + const threadIds = (query: string) => + search.search({ query }).pipe(Effect.map(({ matches }) => matches.map((m) => m.threadId))); + + for (const query of [ + "kittiwake's", + "kittiwake’s", + "résumé", + "灯台の光", + "naïve", + "логах в", + ]) { + assert.deepEqual(yield* threadIds(query), [threadId], query); + } + // `t3 doctor` rebuilds the index after VACUUM; quotes must still read. + yield* rebuildMessageSearch(sql); + assert.deepEqual(yield* threadIds("résumé"), [threadId]); + + yield* projections.apply(message(threadId, "citation-index", "user", "plain words")); + assert.deepEqual(yield* threadIds("résumé"), []); + assert.deepEqual(yield* threadIds("plain"), [threadId]); + }), + ); + it.effect("indexes a message when it finishes streaming and follows later edits", () => Effect.gen(function* () { const projections = yield* ProjectionStore.ProjectionStoreV2; diff --git a/apps/server/src/orchestration-v2/ThreadSearch.ts b/apps/server/src/orchestration-v2/ThreadSearch.ts index 04f68ef64c34..d3c96a40ce2b 100644 --- a/apps/server/src/orchestration-v2/ThreadSearch.ts +++ b/apps/server/src/orchestration-v2/ThreadSearch.ts @@ -33,7 +33,11 @@ import { type ThreadSearchSubject, } from "@t3tools/shared/paletteSearch"; import { threadSearchPullRequests } from "@t3tools/shared/threadPullRequests"; -import { indexedMessageCondition, nonPlaceholderMessageCondition } from "./messageSearchIndex.ts"; +import { + indexedMessageCondition, + messageSearchText, + nonPlaceholderMessageCondition, +} from "./messageSearchIndex.ts"; import { loadOlderSettledThreadsPage } from "./ShellStream.ts"; // When the user last wrote in the thread aliased `threads`, as the clients @@ -203,7 +207,7 @@ export const messageHitsQuery = ( messages.thread_id AS "threadId", eligible.project_id AS "projectId", messages.role AS source, - json_extract(messages.payload_json, '$.text') AS text, + ${messageSearchText(sql)} AS text, messages.created_at AS "messageCreatedAt" FROM orchestration_v2_projection_messages AS messages INNER JOIN eligible ON eligible.thread_id = messages.thread_id @@ -220,7 +224,7 @@ export const messageHitsQuery = ( messages.thread_id AS "threadId", eligible.project_id AS "projectId", messages.role AS source, - json_extract(messages.payload_json, '$.text') AS text, + ${messageSearchText(sql)} AS text, messages.created_at AS "messageCreatedAt" FROM orchestration_v2_message_search INNER JOIN orchestration_v2_projection_messages AS messages @@ -241,13 +245,13 @@ export const messageHitsQuery = ( AND messages.message_id NOT LIKE 'async-answer:%' AND NOT EXISTS ( SELECT 1 FROM json_each(${substrings}) AS token - WHERE instr(lower(json_extract(messages.payload_json, '$.text')), token.value) = 0 + WHERE instr(lower(${messageSearchText(sql)}), token.value) = 0 ) AND NOT EXISTS ( SELECT 1 FROM json_each(${shortSpellings}) AS token WHERE NOT EXISTS ( SELECT 1 FROM json_each(token.value) AS spelling - WHERE instr(json_extract(messages.payload_json, '$.text'), spelling.value) > 0 + WHERE instr(${messageSearchText(sql)}, spelling.value) > 0 ) ) -- The index's own rowid, so FTS5 returns hits newest first and stops at diff --git a/apps/server/src/orchestration-v2/messageSearchIndex.ts b/apps/server/src/orchestration-v2/messageSearchIndex.ts index dbd3545e5906..87d7256b6809 100644 --- a/apps/server/src/orchestration-v2/messageSearchIndex.ts +++ b/apps/server/src/orchestration-v2/messageSearchIndex.ts @@ -1,3 +1,4 @@ +import { assistantCitationsToPlainText } from "@t3tools/shared/assistantCitations"; import { ATTACHMENT_ONLY_BOOTSTRAP_PROMPT } from "@t3tools/shared/composerContextReferences"; import * as Effect from "effect/Effect"; import type * as SqlClient from "effect/sql/SqlClient"; @@ -40,46 +41,83 @@ export const indexedMessageCondition = (sql: SqlClient.SqlClient, row = "message AND ${nonPlaceholderMessageCondition(sql, row)} `; +// Curly apostrophes straight, as search folds a query, since iOS keyboards +// type "’" and others type "'". +const foldApostrophes = (text: string) => `replace(replace(${text}, '’', ''''), '‘', '''')`; + +const readableText = (row: string) => + `COALESCE(${row}.search_text, json_extract(${row}.payload_json, '$.text'))`; + +/** + * What search_text holds for a message: its text as it reads, a quote's + * percent-encoded link replaced by its words and comment. NULL when that is + * the stored text, so most messages store nothing extra. + */ +export const readableSearchText = (text: string) => { + const readable = assistantCitationsToPlainText(text); + return readable === text ? null : readable; +}; + +/** A message's text as it reads, for thread search. */ +export const messageSearchText = (sql: SqlClient.SqlClient) => + sql.literal(readableText("messages")); + /** - * What the index stores for a message: its text with curly apostrophes - * straight, as search folds a query, since iOS keyboards type "’" and others - * type "'". Migration 69's triggers use it too. `payload` is the message's + * A message's stored text with apostrophes folded, what the index held before + * migration 81. Migration 69's backfill uses it. `payload` is the message's * JSON column. */ export const indexedMessageText = (sql: SqlClient.SqlClient, payload: string) => - sql.literal(`replace(replace(json_extract(${payload}, '$.text'), '’', ''''), '‘', '''')`); + sql.literal(foldApostrophes(`json_extract(${payload}, '$.text')`)); + +// What the index stores: a message's readable text with apostrophes folded. +// Migrations 69 and 74 create the triggers before migration 81 adds +// search_text, and `t3 doctor` can rebuild a database that has not migrated, +// so those fall back to the stored text. +const indexedText = Effect.fn("MessageSearchIndex.indexedText")(function* ( + sql: SqlClient.SqlClient, +) { + const column = yield* sql` + SELECT 1 FROM pragma_table_info('orchestration_v2_projection_messages') + WHERE name = 'search_text' + `; + return column.length > 0 + ? (row: string) => sql.literal(foldApostrophes(readableText(row))) + : (row: string) => indexedMessageText(sql, `${row}.payload_json`); +}); export const createMessageSearchTriggers = Effect.fn("MessageSearchIndex.createTriggers")( function* (sql: SqlClient.SqlClient) { + const text = yield* indexedText(sql); yield* sql` CREATE TRIGGER orchestration_v2_message_search_insert AFTER INSERT ON orchestration_v2_projection_messages WHEN ${indexedMessageCondition(sql, "new")} BEGIN INSERT INTO orchestration_v2_message_search (rowid, text) - SELECT new.rowid, ${indexedMessageText(sql, "new.payload_json")} + SELECT new.rowid, ${text("new")} WHERE json_extract(new.payload_json, '$.text') IS NOT NULL; END `; // Streaming deltas rewrite the row many times a second; skip them until the - // message settles. Upserts rewrite every column, so a finished message - // re-indexes only when what the index reads changed. Deleting a rowid the - // index never held is a no-op. + // message settles. Upserts rewrite every column, so re-index only when what + // the index stores changed; migration 81's backfill writes only search_text. + // Deleting a rowid the index never held is a no-op. yield* sql` CREATE TRIGGER orchestration_v2_message_search_update - AFTER UPDATE OF payload_json, streaming, role ON orchestration_v2_projection_messages + AFTER UPDATE ON orchestration_v2_projection_messages WHEN (old.streaming = 0 OR new.streaming = 0) AND ( old.streaming IS NOT new.streaming OR old.role IS NOT new.role - OR json_extract(old.payload_json, '$.text') IS NOT json_extract(new.payload_json, '$.text') + OR ${text("old")} IS NOT ${text("new")} OR json_extract(old.payload_json, '$.createdBy') IS NOT json_extract(new.payload_json, '$.createdBy') ) BEGIN DELETE FROM orchestration_v2_message_search WHERE rowid = old.rowid AND old.streaming = 0; INSERT INTO orchestration_v2_message_search (rowid, text) - SELECT new.rowid, ${indexedMessageText(sql, "new.payload_json")} + SELECT new.rowid, ${text("new")} WHERE ${indexedMessageCondition(sql, "new")} AND json_extract(new.payload_json, '$.text') IS NOT NULL; END @@ -95,6 +133,20 @@ export const createMessageSearchTriggers = Effect.fn("MessageSearchIndex.createT }, ); +/** Replaces the search triggers with their current definition, where the index exists. */ +export const recreateMessageSearchTriggers = Effect.fn("MessageSearchIndex.recreateTriggers")( + function* (sql: SqlClient.SqlClient) { + const index = yield* sql` + SELECT 1 FROM sqlite_master WHERE name = 'orchestration_v2_message_search' + `; + if (index.length === 0) return; + yield* sql`DROP TRIGGER orchestration_v2_message_search_insert`; + yield* sql`DROP TRIGGER orchestration_v2_message_search_update`; + yield* sql`DROP TRIGGER orchestration_v2_message_search_delete`; + yield* createMessageSearchTriggers(sql); + }, +); + /** * Refills the search index from the messages table. The index is keyed by * message rowid, so anything that may renumber or copy rows (VACUUM, a dev @@ -108,6 +160,7 @@ export const rebuildMessageSearch = Effect.fn("MessageSearchIndex.rebuild")(func SELECT 1 FROM sqlite_master WHERE name = 'orchestration_v2_message_search' `; if (index.length === 0) return; + const text = yield* indexedText(sql); // One transaction: an interrupt between clear and refill would leave an // empty index that nothing repairs, since search only checks the table exists. yield* sql.withTransaction( @@ -118,7 +171,7 @@ export const rebuildMessageSearch = Effect.fn("MessageSearchIndex.rebuild")(func `; yield* sql` INSERT INTO orchestration_v2_message_search (rowid, text) - SELECT messages.rowid, ${indexedMessageText(sql, "messages.payload_json")} + SELECT messages.rowid, ${text("messages")} FROM orchestration_v2_projection_messages AS messages WHERE ${indexedMessageCondition(sql)} AND json_extract(messages.payload_json, '$.text') IS NOT NULL diff --git a/apps/server/src/persistence/Migrations.ts b/apps/server/src/persistence/Migrations.ts index 6a9c57e18cc4..500e986aef44 100644 --- a/apps/server/src/persistence/Migrations.ts +++ b/apps/server/src/persistence/Migrations.ts @@ -83,6 +83,7 @@ import Migration0077 from "./Migrations/077_RepairBooleanSettledOverride.ts"; import Migration0078 from "./Migrations/078_DropOrchestrationEventCorrelationIndex.ts"; import Migration0079 from "./Migrations/079_DelegatedRecoveryIndexes.ts"; import Migration0080 from "./Migrations/080_ScheduledTaskThreadMode.ts"; +import Migration0081 from "./Migrations/081_ProjectionMessageSearchText.ts"; const migrationEntries = [ [1, "OrchestrationEvents", Migration0001], @@ -166,6 +167,7 @@ const migrationEntries = [ [78, "DropOrchestrationEventCorrelationIndex", Migration0078], [79, "DelegatedRecoveryIndexes", Migration0079], [80, "ScheduledTaskThreadMode", Migration0080], + [81, "ProjectionMessageSearchText", Migration0081], ] as const; export const migrationManifest = migrationEntries.map(([id, name]) => [id, name] as const); diff --git a/apps/server/src/persistence/Migrations/081_ProjectionMessageSearchText.test.ts b/apps/server/src/persistence/Migrations/081_ProjectionMessageSearchText.test.ts new file mode 100644 index 000000000000..84ca7f9de23a --- /dev/null +++ b/apps/server/src/persistence/Migrations/081_ProjectionMessageSearchText.test.ts @@ -0,0 +1,96 @@ +import { assert, it } from "@effect/vitest"; +import { EnvironmentId, MessageId, ThreadId } from "@t3tools/contracts"; +import { serializeAssistantCitation } from "@t3tools/shared/assistantCitations"; +import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient"; +import * as Effect from "effect/Effect"; +import * as SqlClient from "effect/sql/SqlClient"; + +import { runMigrations } from "../Migrations.ts"; + +const createdAt = "2026-10-01T00:00:00.000Z"; +const quote = serializeAssistantCitation({ + version: 1, + environmentId: EnvironmentId.make("environment:local"), + threadId: ThreadId.make("thread:quoted"), + messageId: MessageId.make("quoted-answer"), + text: "A lighthouse guides ships safely through the darkness.", + comment: "what are you talking about", + start: 0, + end: 54, + prefix: "beacon01", + suffix: "harbor", +}); +const readable = + "A lighthouse guides ships safely through the darkness.\nComment: what are you talking about"; +const indexed = (sql: SqlClient.SqlClient, phrase: string) => + sql<{ readonly message_id: string }>` + SELECT messages.message_id + FROM orchestration_v2_message_search AS search + INNER JOIN orchestration_v2_projection_messages AS messages + ON messages.rowid = search.rowid + WHERE orchestration_v2_message_search MATCH ${phrase} + `.pipe(Effect.map((rows) => rows.map((row) => row.message_id))); + +it.layer(NodeSqliteClient.layer({ filename: ":memory:" }))( + "081_ProjectionMessageSearchText", + (it) => { + it.effect("backfills readable text only for messages whose citations read differently", () => + Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + yield* runMigrations({ toMigrationInclusive: 80 }); + const insertMessage = (messageId: string, text: unknown) => sql` + INSERT INTO orchestration_v2_projection_messages ( + message_id, thread_id, role, streaming, created_at, updated_at, payload_json + ) VALUES ( + ${messageId}, 'thread:quote', 'user', 0, ${createdAt}, ${createdAt}, + ${JSON.stringify({ text })} + ) + `; + yield* insertMessage("quoted", `${quote} quiet silver lantern`); + yield* insertMessage("plain", "quiet silver lantern"); + yield* insertMessage("invalid", "[Assistant quote](t3-citation://v1/not-a-citation)"); + yield* insertMessage("unreadable", 20261001); + yield* insertMessage( + "accented", + serializeAssistantCitation({ + version: 1, + environmentId: EnvironmentId.make("environment:local"), + threadId: ThreadId.make("thread:quoted"), + messageId: MessageId.make("quoted-answer"), + text: "The kittiwake's résumé", + start: 0, + end: 22, + prefix: "", + suffix: "", + }), + ); + yield* Effect.forEach( + Array.from({ length: 501 }, (_, index) => `page-${String(index).padStart(3, "0")}`), + (messageId) => insertMessage(messageId, quote), + { discard: true }, + ); + + assert.deepEqual(yield* runMigrations({ toMigrationInclusive: 81 }), [ + [81, "ProjectionMessageSearchText"], + ]); + const rows = yield* sql<{ + readonly message_id: string; + readonly search_text: string | null; + }>`SELECT message_id, search_text FROM orchestration_v2_projection_messages`; + const searchText = new Map(rows.map((row) => [row.message_id, row.search_text])); + assert.equal(searchText.get("quoted"), `${readable} quiet silver lantern`); + assert.isNull(searchText.get("plain")); + assert.isNull(searchText.get("invalid")); + assert.isNull(searchText.get("unreadable")); + assert.equal(searchText.get("page-000"), readable); + assert.equal(searchText.get("page-500"), readable); + + // Messages indexed as stored are indexed again as they read. + assert.deepEqual(yield* indexed(sql, '"résumé"'), ["accented"]); + assert.deepEqual(yield* indexed(sql, `"kittiwake's"`), ["accented"]); + assert.deepEqual(yield* indexed(sql, '"beacon01"'), []); + assert.lengthOf(yield* indexed(sql, '"what are you talking about"'), 502); + }), + ); + }, +); diff --git a/apps/server/src/persistence/Migrations/081_ProjectionMessageSearchText.ts b/apps/server/src/persistence/Migrations/081_ProjectionMessageSearchText.ts new file mode 100644 index 000000000000..4bbcec4d6182 --- /dev/null +++ b/apps/server/src/persistence/Migrations/081_ProjectionMessageSearchText.ts @@ -0,0 +1,44 @@ +import * as Effect from "effect/Effect"; +import * as SqlClient from "effect/sql/SqlClient"; + +import { + readableSearchText, + recreateMessageSearchTriggers, +} from "../../orchestration-v2/messageSearchIndex.ts"; + +export default Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + yield* sql`ALTER TABLE orchestration_v2_projection_messages ADD COLUMN search_text TEXT`; + + // Migration 74's triggers index the stored text, where a quote's words are + // percent-encoded. Recreated now that search_text exists, they index it, and + // the backfill below re-indexes every message that reads differently. + yield* recreateMessageSearchTriggers(sql); + + // Only messages holding a citation link read differently. The raw check + // first skips parsing the JSON of every other message. + const pageSize = 500; + let afterMessageId = ""; + while (true) { + const rows = yield* sql<{ readonly message_id: string; readonly text: string }>` + SELECT message_id, json_extract(payload_json, '$.text') AS text + FROM orchestration_v2_projection_messages + WHERE message_id > ${afterMessageId} + AND payload_json LIKE '%t3-citation://%' + AND json_type(payload_json, '$.text') = 'text' + ORDER BY message_id + LIMIT ${pageSize} + `; + for (const row of rows) { + const searchText = readableSearchText(row.text); + if (searchText === null) continue; + yield* sql` + UPDATE orchestration_v2_projection_messages + SET search_text = ${searchText} + WHERE message_id = ${row.message_id} + `; + } + if (rows.length < pageSize) break; + afterMessageId = rows.at(-1)!.message_id; + } +}); diff --git a/apps/server/src/persistence/reconcileV2PreviewMigration.test.ts b/apps/server/src/persistence/reconcileV2PreviewMigration.test.ts index 84fa4ddb44c7..8acfd0cb6b9c 100644 --- a/apps/server/src/persistence/reconcileV2PreviewMigration.test.ts +++ b/apps/server/src/persistence/reconcileV2PreviewMigration.test.ts @@ -60,6 +60,7 @@ describe("V2 preview upgrade", () => { [78, "DropOrchestrationEventCorrelationIndex"], [79, "DelegatedRecoveryIndexes"], [80, "ScheduledTaskThreadMode"], + [81, "ProjectionMessageSearchText"], ]); assert.deepStrictEqual(yield* runMigrations(), []); assert.deepStrictEqual(yield* sql`SELECT * FROM orchestration_v2_legacy_imports`, imports); @@ -163,6 +164,7 @@ describe("V2 preview upgrade", () => { [78, "DropOrchestrationEventCorrelationIndex"], [79, "DelegatedRecoveryIndexes"], [80, "ScheduledTaskThreadMode"], + [81, "ProjectionMessageSearchText"], ]); }).pipe(Effect.provide(NodeSqliteClient.layer({ filename: ":memory:" }))), );