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 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 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`.
|
**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).
|
* Ordered PearcordPlatform prototype mixin registration (Phase 730).
|
||||||
* Preserves assign order from index.js / apply-platform-mixins (Phase 727).
|
* 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 = [
|
const PLATFORM_MIXIN_ASSIGNMENTS = [
|
||||||
{ module: './polls-scheduling', export: 'pollSchedulingMixin' },
|
{ module: './polls-scheduling', export: 'pollSchedulingMixin' },
|
||||||
@@ -101,6 +101,9 @@ const PLATFORM_MIXIN_ASSIGNMENTS = [
|
|||||||
{ module: './platform-search-index-rebuild-mixin', export: 'platformSearchIndexRebuildMixin' },
|
{ module: './platform-search-index-rebuild-mixin', export: 'platformSearchIndexRebuildMixin' },
|
||||||
{ module: './platform-search-mesh-gossip-mixin', export: 'platformSearchMeshGossipMixin' },
|
{ module: './platform-search-mesh-gossip-mixin', export: 'platformSearchMeshGossipMixin' },
|
||||||
{ module: './platform-discovery-mesh-refresh-mixin', export: 'platformDiscoveryMeshRefreshMixin' },
|
{ 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' }
|
{ 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'
|
'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 = [
|
const PLATFORM_RUNTIME_MIXIN_MODULES = [
|
||||||
'./platform-hyperswarm-runtime-mixin',
|
'./platform-hyperswarm-runtime-mixin',
|
||||||
'./platform-session-startup-mixin',
|
'./platform-session-startup-mixin',
|
||||||
@@ -36,7 +36,10 @@ const PLATFORM_RUNTIME_MIXIN_MODULES = [
|
|||||||
'./platform-search-index-store-mixin',
|
'./platform-search-index-store-mixin',
|
||||||
'./platform-search-index-rebuild-mixin',
|
'./platform-search-index-rebuild-mixin',
|
||||||
'./platform-search-mesh-gossip-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
|
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