refactor(platform): Phase 751 guild search query & mesh resolve mixins (v0.8.727)
Extract searchGuildMessages, getGuildSearchIndexMeta, _applySearchQueryFilters, and searchGuildMessagesWithMesh into three prototype mixins (manifest 98 rows, runtime registry 38). Non-breaking: identical local/mesh search, structured filter matching, and index meta reporting; platform class ~31874 lines (~127 net drop). Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -54,6 +54,8 @@ Application facade: one `PearcordPlatform` class that wires identity, database,
|
||||
|
||||
**Phase 723 (v0.8.699):** Dedicated slash registry & invoke audit export JSON — `slash-registry-json-mixin.js`, `slash-invoke-audit-json-mixin.js`, spans `slash-registry.json` / `slash-invoke-audit.json`, `getSlashInvokeAuditExportJsonExport`, `clearSlashInvokeAuditExportJsonExports`, deep links `openSlashRegistryJson` / `openSlashInvokeAuditJson`. Bundle: `npm run test:ci-phase723`.
|
||||
|
||||
**Phase 751 (v0.8.727):** `platform-search-guild-messages-mixin.js`, `platform-search-query-filters-mixin.js`, `platform-search-mesh-resolve-mixin.js` (98 mixin rows, 38 runtime mixins); guild message search, structured query filters, index meta, mesh hit merge. Bundle: `npm run test:ci-phase751`.
|
||||
|
||||
**Phase 750 (v0.8.726):** `platform-search-index-store-mixin.js`, `platform-search-index-rebuild-mixin.js`, `platform-search-mesh-gossip-mixin.js`, `platform-discovery-mesh-refresh-mixin.js` (95 mixin rows, 35 runtime mixins); guild search index store/persist/rebuild, mesh search gossip, discovery listing refresh after mesh. Bundle: `npm run test:ci-phase750`.
|
||||
|
||||
**Phase 749 (v0.8.725):** `platform-view-list-cache-mixin.js`, `platform-search-cache-mixin.js`, `platform-discovery-prefs-baseline-mixin.js` (91 mixin rows, 31 runtime mixins); reaction/pin cache invalidation, search resolve cache, discovery explore baseline from prefs. Bundle: `npm run test:ci-phase749`.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* Ordered PearcordPlatform prototype mixin registration (Phase 730).
|
||||
* Preserves assign order from index.js / apply-platform-mixins (Phase 727).
|
||||
*/
|
||||
const PLATFORM_MIXIN_ASSIGNMENT_COUNT = 95
|
||||
const PLATFORM_MIXIN_ASSIGNMENT_COUNT = 98
|
||||
|
||||
const PLATFORM_MIXIN_ASSIGNMENTS = [
|
||||
{ module: './polls-scheduling', export: 'pollSchedulingMixin' },
|
||||
@@ -101,6 +101,9 @@ const PLATFORM_MIXIN_ASSIGNMENTS = [
|
||||
{ module: './platform-search-index-rebuild-mixin', export: 'platformSearchIndexRebuildMixin' },
|
||||
{ module: './platform-search-mesh-gossip-mixin', export: 'platformSearchMeshGossipMixin' },
|
||||
{ module: './platform-discovery-mesh-refresh-mixin', export: 'platformDiscoveryMeshRefreshMixin' },
|
||||
{ module: './platform-search-guild-messages-mixin', export: 'platformSearchGuildMessagesMixin' },
|
||||
{ module: './platform-search-query-filters-mixin', export: 'platformSearchQueryFiltersMixin' },
|
||||
{ module: './platform-search-mesh-resolve-mixin', export: 'platformSearchMeshResolveMixin' },
|
||||
{ module: './platform-diagnostics-mixin', export: 'platformDiagnosticsMixin' }
|
||||
]
|
||||
|
||||
|
||||
@@ -4732,140 +4732,13 @@ class PearcordPlatform extends EventEmitter {
|
||||
|
||||
|
||||
|
||||
async searchGuildMessages (guildId, query, opts = {}) {
|
||||
const q = String(query || '').trim().toLowerCase()
|
||||
if (!q || !guildId) return []
|
||||
const limit = opts.limit || 50
|
||||
const idx = await this._ensureGuildSearchIndex(guildId)
|
||||
const indexed = idx.search(q, { limit })
|
||||
if (indexed.length) {
|
||||
return indexed.map((m) => enrichmentScope.enrichMessageRow(m))
|
||||
}
|
||||
if (opts.indexOnly) return []
|
||||
const channels = await this.db.find(sharedScope.COLLECTIONS.CHANNELS, { guildId })
|
||||
const channelById = new Map(channels.map((c) => [c.id, c]))
|
||||
const searchable = new Set(
|
||||
channels
|
||||
.filter((c) => c.type === 'text' || c.type === 'thread' || c.type === 'announcement')
|
||||
.map((c) => c.id)
|
||||
)
|
||||
let rows = []
|
||||
if (this.db.getEngine?.() === 'hyperdb') {
|
||||
for (const chId of searchable) {
|
||||
const chRows = await this.db.find(sharedScope.COLLECTIONS.MESSAGES, { channelId: chId })
|
||||
rows.push(...chRows)
|
||||
}
|
||||
} else {
|
||||
rows = await this.db.find(sharedScope.COLLECTIONS.MESSAGES, { guildId })
|
||||
}
|
||||
const seen = new Set()
|
||||
const hits = rows
|
||||
.filter((m) => {
|
||||
if (!m?.id || seen.has(m.id)) return false
|
||||
seen.add(m.id)
|
||||
const enriched = enrichmentScope.enrichMessageRow(m)
|
||||
const ch = channelById.get(m.channelId)
|
||||
const hay = [
|
||||
ch?.name || '',
|
||||
String(enriched.content || ''),
|
||||
enriched.hubMirror ? formatHubMirrorFallback(enriched.hubMirror) : '',
|
||||
...(enriched.stickerNames || []).map((n) => `:${n}:`),
|
||||
ch?.archived ? 'archived' : ''
|
||||
]
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
return searchable.has(m.channelId) && hay.includes(q)
|
||||
})
|
||||
.slice(0, limit * 2)
|
||||
const { rankAndEnrichSearchHits } = require('pearcord-search')
|
||||
const mapped = hits.map((m) => ({
|
||||
...enrichmentScope.enrichMessageRow(m),
|
||||
channelName: channelById.get(m.channelId)?.name || m.channelId.slice(0, 8)
|
||||
}))
|
||||
return rankAndEnrichSearchHits(mapped, q, { limit })
|
||||
}
|
||||
|
||||
async _applySearchQueryFilters (hits, rawQuery, guildId) {
|
||||
const {
|
||||
parseSearchQuery,
|
||||
messageMatchesSearchFilters,
|
||||
hasStructuredSearchFilters
|
||||
} = require('pearcord-search')
|
||||
const filters = parseSearchQuery(rawQuery)
|
||||
if (!hasStructuredSearchFilters(filters)) return hits
|
||||
const gId = guildId || this.guild?.guild?.id
|
||||
const members = this.guild
|
||||
? await this.guild.listMembers()
|
||||
: gId
|
||||
? await this.db.find(sharedScope.COLLECTIONS.MEMBERS, { guildId: gId })
|
||||
: []
|
||||
const usersById = await this._usersById(members)
|
||||
const channels = gId ? await this.db.find(sharedScope.COLLECTIONS.CHANNELS, { guildId: gId }) : []
|
||||
const channelById = new Map(channels.map((c) => [c.id, c]))
|
||||
let pinnedKeys = null
|
||||
if (filters.has === 'pin' && gId) {
|
||||
const pins = await this.db.find(sharedScope.COLLECTIONS.PINS, { guildId: gId })
|
||||
pinnedKeys = new Set(pins.map((p) => `${p.channelId}:${p.messageId}`))
|
||||
}
|
||||
const out = []
|
||||
for (const hit of hits || []) {
|
||||
const mem = members.find((m) => m.userId === hit.authorId)
|
||||
const u = usersById[hit.authorId] || {}
|
||||
const ch = channelById.get(hit.channelId)
|
||||
const plain = this._messagePlaintext(hit)
|
||||
if (
|
||||
!messageMatchesSearchFilters(hit, filters, {
|
||||
plaintext: plain,
|
||||
authorTokens: [mem?.nickname, u.username, u.displayName].filter(Boolean),
|
||||
authorId: hit.authorId,
|
||||
channelName: ch?.name || hit.channelName,
|
||||
isPinned: pinnedKeys ? pinnedKeys.has(`${hit.channelId}:${hit.id}`) : false,
|
||||
attachmentIds: hit.attachmentIds
|
||||
})
|
||||
) {
|
||||
continue
|
||||
}
|
||||
out.push(hit)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
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 = {}) {
|
||||
const local = await this.searchGuildMessages(guildId, query, {
|
||||
limit: opts.limit,
|
||||
indexOnly: !!opts.localIndexOnly
|
||||
})
|
||||
if (opts.mesh === false || !this.guild?.guild) {
|
||||
return { hits: local, mesh: false, meshHitCount: 0, localHitCount: local.length }
|
||||
}
|
||||
try {
|
||||
const meshHits = await this._fetchGuildSearchFromMeshOnce(guildId, query, opts)
|
||||
const merged = searchScope.mergeSearchHits(local, meshHits, {
|
||||
limit: opts.limit || 50,
|
||||
query: String(query || '').trim()
|
||||
})
|
||||
return {
|
||||
hits: merged.map((m) => enrichmentScope.enrichMessageRow(m)),
|
||||
mesh: true,
|
||||
meshHitCount: meshHits.length,
|
||||
localHitCount: local.length
|
||||
}
|
||||
} catch {
|
||||
return { hits: local, mesh: false, meshHitCount: 0, localHitCount: local.length }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
/** Prototype mixins extracted from PearcordPlatform class body (Phases 738–750). */
|
||||
/** Prototype mixins extracted from PearcordPlatform class body (Phases 738–751). */
|
||||
const PLATFORM_RUNTIME_MIXIN_MODULES = [
|
||||
'./platform-hyperswarm-runtime-mixin',
|
||||
'./platform-session-startup-mixin',
|
||||
@@ -36,7 +36,10 @@ const PLATFORM_RUNTIME_MIXIN_MODULES = [
|
||||
'./platform-search-index-store-mixin',
|
||||
'./platform-search-index-rebuild-mixin',
|
||||
'./platform-search-mesh-gossip-mixin',
|
||||
'./platform-discovery-mesh-refresh-mixin'
|
||||
'./platform-discovery-mesh-refresh-mixin',
|
||||
'./platform-search-guild-messages-mixin',
|
||||
'./platform-search-query-filters-mixin',
|
||||
'./platform-search-mesh-resolve-mixin'
|
||||
]
|
||||
|
||||
const PLATFORM_RUNTIME_MIXIN_COUNT = PLATFORM_RUNTIME_MIXIN_MODULES.length
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
'use strict'
|
||||
|
||||
const sharedScope = require('pearcord-shared')
|
||||
const enrichmentScope = require('./platform-message-enrichment')
|
||||
const { formatHubMirrorFallback } = require('pearcord-hub-cards')
|
||||
|
||||
const platformSearchGuildMessagesMixin = {
|
||||
async searchGuildMessages (guildId, query, opts = {}) {
|
||||
const q = String(query || '').trim().toLowerCase()
|
||||
if (!q || !guildId) return []
|
||||
const limit = opts.limit || 50
|
||||
const idx = await this._ensureGuildSearchIndex(guildId)
|
||||
const indexed = idx.search(q, { limit })
|
||||
if (indexed.length) {
|
||||
return indexed.map((m) => enrichmentScope.enrichMessageRow(m))
|
||||
}
|
||||
if (opts.indexOnly) return []
|
||||
const channels = await this.db.find(sharedScope.COLLECTIONS.CHANNELS, { guildId })
|
||||
const channelById = new Map(channels.map((c) => [c.id, c]))
|
||||
const searchable = new Set(
|
||||
channels
|
||||
.filter((c) => c.type === 'text' || c.type === 'thread' || c.type === 'announcement')
|
||||
.map((c) => c.id)
|
||||
)
|
||||
let rows = []
|
||||
if (this.db.getEngine?.() === 'hyperdb') {
|
||||
for (const chId of searchable) {
|
||||
const chRows = await this.db.find(sharedScope.COLLECTIONS.MESSAGES, { channelId: chId })
|
||||
rows.push(...chRows)
|
||||
}
|
||||
} else {
|
||||
rows = await this.db.find(sharedScope.COLLECTIONS.MESSAGES, { guildId })
|
||||
}
|
||||
const seen = new Set()
|
||||
const hits = rows
|
||||
.filter((m) => {
|
||||
if (!m?.id || seen.has(m.id)) return false
|
||||
seen.add(m.id)
|
||||
const enriched = enrichmentScope.enrichMessageRow(m)
|
||||
const ch = channelById.get(m.channelId)
|
||||
const hay = [
|
||||
ch?.name || '',
|
||||
String(enriched.content || ''),
|
||||
enriched.hubMirror ? formatHubMirrorFallback(enriched.hubMirror) : '',
|
||||
...(enriched.stickerNames || []).map((n) => `:${n}:`),
|
||||
ch?.archived ? 'archived' : ''
|
||||
]
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
return searchable.has(m.channelId) && hay.includes(q)
|
||||
})
|
||||
.slice(0, limit * 2)
|
||||
const { rankAndEnrichSearchHits } = require('pearcord-search')
|
||||
const mapped = hits.map((m) => ({
|
||||
...enrichmentScope.enrichMessageRow(m),
|
||||
channelName: channelById.get(m.channelId)?.name || m.channelId.slice(0, 8)
|
||||
}))
|
||||
return rankAndEnrichSearchHits(mapped, q, { limit })
|
||||
},
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { platformSearchGuildMessagesMixin }
|
||||
@@ -0,0 +1,33 @@
|
||||
'use strict'
|
||||
|
||||
const searchScope = require('pearcord-search')
|
||||
const enrichmentScope = require('./platform-message-enrichment')
|
||||
|
||||
const platformSearchMeshResolveMixin = {
|
||||
async searchGuildMessagesWithMesh (guildId, query, opts = {}) {
|
||||
const local = await this.searchGuildMessages(guildId, query, {
|
||||
limit: opts.limit,
|
||||
indexOnly: !!opts.localIndexOnly
|
||||
})
|
||||
if (opts.mesh === false || !this.guild?.guild) {
|
||||
return { hits: local, mesh: false, meshHitCount: 0, localHitCount: local.length }
|
||||
}
|
||||
try {
|
||||
const meshHits = await this._fetchGuildSearchFromMeshOnce(guildId, query, opts)
|
||||
const merged = searchScope.mergeSearchHits(local, meshHits, {
|
||||
limit: opts.limit || 50,
|
||||
query: String(query || '').trim()
|
||||
})
|
||||
return {
|
||||
hits: merged.map((m) => enrichmentScope.enrichMessageRow(m)),
|
||||
mesh: true,
|
||||
meshHitCount: meshHits.length,
|
||||
localHitCount: local.length
|
||||
}
|
||||
} catch {
|
||||
return { hits: local, mesh: false, meshHitCount: 0, localHitCount: local.length }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { platformSearchMeshResolveMixin }
|
||||
@@ -0,0 +1,52 @@
|
||||
'use strict'
|
||||
|
||||
const sharedScope = require('pearcord-shared')
|
||||
|
||||
const platformSearchQueryFiltersMixin = {
|
||||
async _applySearchQueryFilters (hits, rawQuery, guildId) {
|
||||
const {
|
||||
parseSearchQuery,
|
||||
messageMatchesSearchFilters,
|
||||
hasStructuredSearchFilters
|
||||
} = require('pearcord-search')
|
||||
const filters = parseSearchQuery(rawQuery)
|
||||
if (!hasStructuredSearchFilters(filters)) return hits
|
||||
const gId = guildId || this.guild?.guild?.id
|
||||
const members = this.guild
|
||||
? await this.guild.listMembers()
|
||||
: gId
|
||||
? await this.db.find(sharedScope.COLLECTIONS.MEMBERS, { guildId: gId })
|
||||
: []
|
||||
const usersById = await this._usersById(members)
|
||||
const channels = gId ? await this.db.find(sharedScope.COLLECTIONS.CHANNELS, { guildId: gId }) : []
|
||||
const channelById = new Map(channels.map((c) => [c.id, c]))
|
||||
let pinnedKeys = null
|
||||
if (filters.has === 'pin' && gId) {
|
||||
const pins = await this.db.find(sharedScope.COLLECTIONS.PINS, { guildId: gId })
|
||||
pinnedKeys = new Set(pins.map((p) => `${p.channelId}:${p.messageId}`))
|
||||
}
|
||||
const out = []
|
||||
for (const hit of hits || []) {
|
||||
const mem = members.find((m) => m.userId === hit.authorId)
|
||||
const u = usersById[hit.authorId] || {}
|
||||
const ch = channelById.get(hit.channelId)
|
||||
const plain = this._messagePlaintext(hit)
|
||||
if (
|
||||
!messageMatchesSearchFilters(hit, filters, {
|
||||
plaintext: plain,
|
||||
authorTokens: [mem?.nickname, u.username, u.displayName].filter(Boolean),
|
||||
authorId: hit.authorId,
|
||||
channelName: ch?.name || hit.channelName,
|
||||
isPinned: pinnedKeys ? pinnedKeys.has(`${hit.channelId}:${hit.id}`) : false,
|
||||
attachmentIds: hit.attachmentIds
|
||||
})
|
||||
) {
|
||||
continue
|
||||
}
|
||||
out.push(hit)
|
||||
}
|
||||
return out
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { platformSearchQueryFiltersMixin }
|
||||
Reference in New Issue
Block a user