feat(search): load/save persisted guild search index + searchIndexMeta (v0.8.87)

Integrate GuildSearchIndexStore on platform startup and debounced persist
after message ingest. Expose getGuildSearchIndexMeta and view.searchIndexMeta
for smokes and UI diagnostics.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-22 08:09:48 -04:00
co-authored by Cursor
parent 1d8e700bd1
commit 159e8c63e9
+49 -2
View File
@@ -150,6 +150,8 @@ const {
const { forumPostHaystack } = require('pearcord-forum-search') const { forumPostHaystack } = require('pearcord-forum-search')
const { const {
GuildSearchIndex, GuildSearchIndex,
GuildSearchIndexStore,
indexFromSerialized,
buildIndexRow, buildIndexRow,
mergeSearchHits, mergeSearchHits,
compactMeshSearchHit, compactMeshSearchHit,
@@ -311,6 +313,8 @@ class PearcordPlatform extends EventEmitter {
this._searchIncludeMesh = true this._searchIncludeMesh = true
this._lastSearchMeshMeta = null this._lastSearchMeshMeta = null
this._guildSearchIndexes = new Map() this._guildSearchIndexes = new Map()
this._searchIndexStore = null
this._searchIndexPersistTimers = new Map()
this._searchMeshWaiters = new Map() this._searchMeshWaiters = new Map()
this._threadsPanelFilter = 'active' this._threadsPanelFilter = 'active'
this._readOnly = opts.readOnly === true this._readOnly = opts.readOnly === true
@@ -1105,11 +1109,37 @@ class PearcordPlatform extends EventEmitter {
return hits.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)).slice(0, limit) return hits.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)).slice(0, limit)
} }
_getSearchIndexStore () {
if (!this._searchIndexStore) {
this._searchIndexStore = new GuildSearchIndexStore(this.storagePath)
}
return this._searchIndexStore
}
_scheduleSearchIndexPersist (guildId) {
if (!guildId) return
const prev = this._searchIndexPersistTimers.get(guildId)
if (prev) clearTimeout(prev)
const timer = setTimeout(() => {
this._searchIndexPersistTimers.delete(guildId)
const idx = this._guildSearchIndexes.get(guildId)
if (idx) this._getSearchIndexStore().save(guildId, idx)
}, 400)
this._searchIndexPersistTimers.set(guildId, timer)
}
async _ensureGuildSearchIndex (guildId) { async _ensureGuildSearchIndex (guildId) {
let idx = this._guildSearchIndexes.get(guildId) let idx = this._guildSearchIndexes.get(guildId)
if (idx) return idx if (idx) return idx
const blob = this._getSearchIndexStore().load(guildId)
if (blob?.rows?.length) {
idx = indexFromSerialized(blob)
this._guildSearchIndexes.set(guildId, idx)
return idx
}
idx = await this.rebuildGuildSearchIndex(guildId) idx = await this.rebuildGuildSearchIndex(guildId)
this._guildSearchIndexes.set(guildId, idx) this._guildSearchIndexes.set(guildId, idx)
this._getSearchIndexStore().save(guildId, idx)
return idx return idx
} }
@@ -1143,14 +1173,15 @@ class PearcordPlatform extends EventEmitter {
}) })
) )
} }
this._getSearchIndexStore().save(guildId, idx)
return idx return idx
} }
_indexMessageForSearch (message, channel) { async _indexMessageForSearch (message, channel) {
if (!message?.id || !this.guild?.guild?.id) return if (!message?.id || !this.guild?.guild?.id) return
const guildId = this.guild.guild.id const guildId = this.guild.guild.id
let idx = this._guildSearchIndexes.get(guildId) let idx = this._guildSearchIndexes.get(guildId)
if (!idx) return if (!idx) idx = await this._ensureGuildSearchIndex(guildId)
const enriched = enrichMessageRow(message) const enriched = enrichMessageRow(message)
idx.upsert( idx.upsert(
buildIndexRow(enriched, channel, { buildIndexRow(enriched, channel, {
@@ -1159,6 +1190,7 @@ class PearcordPlatform extends EventEmitter {
stickerNames: enriched.stickerNames stickerNames: enriched.stickerNames
}) })
) )
this._scheduleSearchIndexPersist(guildId)
} }
async searchGuildMessages (guildId, query, opts = {}) { async searchGuildMessages (guildId, query, opts = {}) {
@@ -1212,6 +1244,18 @@ class PearcordPlatform extends EventEmitter {
})) }))
} }
getGuildSearchIndexMeta (guildId) {
const id = guildId || this.guild?.guild?.id
if (!id) return { persisted: false, rowCount: 0, savedAt: null }
const blob = this._getSearchIndexStore().load(id)
const mem = this._guildSearchIndexes.get(id)
return {
persisted: !!(blob?.rows?.length),
savedAt: blob?.savedAt || null,
rowCount: blob?.rows?.length || mem?._rows?.length || 0
}
}
async searchGuildMessagesWithMesh (guildId, query, opts = {}) { async searchGuildMessagesWithMesh (guildId, query, opts = {}) {
const local = await this.searchGuildMessages(guildId, query, opts) const local = await this.searchGuildMessages(guildId, query, opts)
if (opts.mesh === false || !this.guild?.guild) { if (opts.mesh === false || !this.guild?.guild) {
@@ -8641,6 +8685,9 @@ class PearcordPlatform extends EventEmitter {
forumIncludeArchived: this._forumIncludeArchived, forumIncludeArchived: this._forumIncludeArchived,
searchResults: await this._resolveSearchResults(), searchResults: await this._resolveSearchResults(),
searchMeshMeta: this._lastSearchMeshMeta, searchMeshMeta: this._lastSearchMeshMeta,
searchIndexMeta: this.guild?.guild
? this.getGuildSearchIndexMeta(this.guild.guild.id)
: { persisted: false, rowCount: 0, savedAt: null },
searchIncludeMesh: this._searchIncludeMesh, searchIncludeMesh: this._searchIncludeMesh,
companionMode: this._companionMode, companionMode: this._companionMode,
readOnly: this._readOnly, readOnly: this._readOnly,