feat(channels): deleteChannel and CHANNEL_DELETE gossip (v0.8.421)

Ingest channel delete on mesh; block deleting non-empty categories or
channels that still have threads.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-24 01:04:43 -04:00
co-authored by Cursor
parent 69b659121d
commit 1e7a449362
2 changed files with 45 additions and 1 deletions
+3 -1
View File
@@ -31,7 +31,9 @@ Create and load guilds in local storage, join peers on `pearcord:guild:{guildId}
| `PearcordGuild#listChannels()` / `#listMembers()` | DB queries with thread meta attached |
| `PearcordGuild#createChannel`, `#createCategory`, `#createThread`, `#createForumThread` | Channel tree |
**v0.8.420 (Phase 457):** Platform wraps `createChannel` / `createCategory` / `renameChannel` with `channel.create`, `category.create`, and `channel.update` spans (see [CHANNELS.md](../../docs/CHANNELS.md)). Channel delete API planned for Phase 458.
**v0.8.421 (Phase 458):** `deleteChannel` + `gossipChannelDelete` (`RPC.CHANNEL_DELETE`); inbound `_ingestChannelDelete` on mesh.
**v0.8.420 (Phase 457):** Platform wraps `createChannel` / `createCategory` / `renameChannel` with `channel.create`, `category.create`, and `channel.update` spans (see [CHANNELS.md](../../docs/CHANNELS.md)).
| `PearcordGuild#reorderChannelsInParent` | Reassign `position` for siblings under one category/root |
| `PearcordGuild#moveChannelToParent` | Move channel into category (v0.8.168) |
| `PearcordGuild#reorderCategories` | Reassign `position` for category rows |
+42
View File
@@ -285,6 +285,9 @@ class PearcordGuild extends EventEmitter {
this.emit('channel', ch)
}).catch(() => {})
}
if (method === RPC.CHANNEL_DELETE) {
this._ingestChannelDelete(payload).catch(() => {})
}
if (method === RPC.MESSAGE_PIN) {
this.db.insert(COLLECTIONS.PINS, payload).then(() => {
this.emit('pin', payload)
@@ -504,6 +507,10 @@ class PearcordGuild extends EventEmitter {
await this.ingestChannel(payload).catch(() => {})
return
}
if (method === RPC.CHANNEL_DELETE) {
await this._ingestChannelDelete(payload).catch(() => {})
return
}
if (method === RPC.EMOJI_UPSERT) {
this.emit('guild-emoji', payload)
return
@@ -635,6 +642,10 @@ class PearcordGuild extends EventEmitter {
broadcastGossip(this, RPC.CHANNEL_CREATE, channel)
}
gossipChannelDelete (payload) {
broadcastGossip(this, RPC.CHANNEL_DELETE, payload)
}
gossipPin (payload) {
broadcastGossip(this, RPC.MESSAGE_PIN, payload)
}
@@ -1147,6 +1158,37 @@ class PearcordGuild extends EventEmitter {
return merged
}
async _ingestChannelDelete (payload) {
const guildId = payload?.guildId
const channelId = payload?.channelId || payload?.id
if (!guildId || !channelId) return
await this.db.delete(COLLECTIONS.CHANNELS, { guildId, id: channelId })
await this.db.delete(COLLECTIONS.CHANNEL_SETTINGS, { guildId, channelId }).catch(() => {})
this.emit('channel-delete', { guildId, channelId, type: payload?.type || null })
}
async deleteChannel (channelId) {
if (!this.guild) throw new Error('no active guild')
if (!channelId) throw new Error('channelId required')
const channels = await this.listChannels()
const ch = channels.find((c) => c.id === channelId)
if (!ch) throw new Error('channel not found')
if (ch.type === CHANNEL_TYPES.CATEGORY) {
const children = channels.filter(
(c) => c.type !== CHANNEL_TYPES.CATEGORY && (c.parentId || null) === channelId
)
if (children.length) throw new Error('category not empty')
} else if (ch.type !== CHANNEL_TYPES.THREAD) {
const threads = channels.filter(
(c) => c.type === CHANNEL_TYPES.THREAD && c.parentId === channelId
)
if (threads.length) throw new Error('channel has threads')
}
const payload = { guildId: this.guild.id, channelId, type: ch.type }
await this._ingestChannelDelete(payload)
return payload
}
async createChannel ({ name, type = CHANNEL_TYPES.TEXT, parentId = null } = {}) {
if (!this.guild) throw new Error('no active guild')
if (!name) throw new Error('name required')