# Plan: Kinds ## Topic Summary The kinds chapter explains what event kinds are — 16-bit integers that determine how an event's content and tags should be interpreted — without going into detail on any particular kind. It introduces a `Kind` trait that domain chapters will implement to attach behavior (validation, parsing, creation) to specific kind numbers. Classification methods like `is_replaceable()` get default implementations derived from the kind number. ## Chapter Outline 1. **Introduction** — What kinds are and why they're integers not names. Draw on building-nostr philosophy: numbers are meaningless, which allows subjective naming while keeping technical meaning intact. Mention that this chapter builds infrastructure; the domain chapters fill it in. 2. **The module** — Add `pub mod kinds;` to lib.rs, set up the file. 3. **The `Kind` trait** — The core abstraction. One required method: `fn kind_number() -> u16`. This is an associated function (not `&self`) since kind number is a property of the type, not an instance. Default methods for classification derive from it. 4. **Classification methods** — Default implementations for `is_regular()`, `is_replaceable()`, `is_ephemeral()`, `is_addressable()`. Explain the ranges and the special cases (kinds 0 and 3 are replaceable despite being < 10000). Mention the building-nostr critique that coupling storage behavior to kind ranges is a design flaw, but we implement the protocol as-is. 5. **Parsing events** — The `from_event` associated function on the trait. Takes an `&Event`, checks `event.kind == Self::kind_number()`, then delegates to a required `parse` method. Returns `Result`. This gives domain types a uniform entry point: `Profile::from_event(&event)`. 6. **Creating events** — A `to_template` method that converts a domain struct back into an `EventTemplate`. Required, no default — each kind defines its own serialization. 7. **A concrete example** — A trivial `Deletion` kind (kind 5) as a proof-of-concept implementation, showing how domain chapters will use the trait. Simple enough to not steal thunder from the domain chapters but real enough to prove the pattern works. 8. **What's next** — Tease kind ranges chapter (deeper dive into the range system and its implications for relay storage and query planning). ## API Design ```rust /// Errors when parsing an event into a domain type. pub enum KindError { /// The event's kind field doesn't match the expected kind number. WrongKind { expected: u16, got: u16 }, /// The event's content or tags couldn't be parsed for this kind. InvalidContent(String), } /// The core trait for attaching behavior to a kind number. pub trait Kind: Sized { /// The kind number this type handles. fn kind_number() -> u16; /// Parse an event's content and tags into this domain type. /// Called by `from_event` after the kind number check passes. fn parse(event: &Event) -> Result; /// Convert this domain type back into an event template. fn to_template(&self) -> EventTemplate; /// Parse an event into this type, checking the kind number first. fn from_event(event: &Event) -> Result { if event.kind != Self::kind_number() { return Err(KindError::WrongKind { expected: Self::kind_number(), got: event.kind, }); } Self::parse(event) } // --- Classification defaults --- fn is_regular() -> bool { let k = Self::kind_number(); k != 0 && k != 3 && k < 10_000 } fn is_replaceable() -> bool { let k = Self::kind_number(); k == 0 || k == 3 || (10_000 <= k && k < 20_000) } fn is_ephemeral() -> bool { let k = Self::kind_number(); 20_000 <= k && k < 30_000 } fn is_addressable() -> bool { let k = Self::kind_number(); 30_000 <= k && k < 40_000 } } ``` Also provide free functions for the same classification on raw `u16` values, for code that doesn't have a `Kind` impl handy (e.g. relay logic, filters): ```rust pub fn is_regular(kind: u16) -> bool { ... } pub fn is_replaceable(kind: u16) -> bool { ... } pub fn is_ephemeral(kind: u16) -> bool { ... } pub fn is_addressable(kind: u16) -> bool { ... } ``` ## Code Organization - All code in `coracle-lib/src/kinds.rs` - Module declared in `coracle-lib/src/lib.rs` - Depends on `events` module (uses `Event`, `EventTemplate`, `EventContent`) - No dependency on any domain crate ## Dependencies No new external crates needed. Only uses std and types from earlier chapters. ## Narrative Notes - Lead with the philosophy of why kinds are numbers. The building-nostr quote about meaningless integers allowing subjective naming is compelling. - Explain the trait design choice: why a trait instead of an enum. The set of kinds is open-ended and grows with every NIP. An enum would require updating a central file for every new kind. A trait lets domain chapters add kinds independently. - The classification defaults are a nice teaching moment: "implement one method, get four for free" demonstrates Rust's default method pattern. - The `from_event`/`parse` split mirrors applesauce's factory/cast pattern but in Rust idiom: `from_event` is the public API that does the kind check, `parse` is the kind-specific logic implementors provide. - Keep the Deletion example minimal — just enough to show the trait in action. - Note the building-nostr critique of kind ranges coupling content type with storage behavior, but don't dwell on it. We implement the protocol. ## Design Decisions 1. **Trait, not enum** — The set of kinds is open and grows with each NIP. An enum would centralize all kind definitions. A trait lets each domain module add its own kinds independently. This follows the distributed pattern seen in applesauce (no centralized registry) and is idiomatic Rust. 2. **`kind_number()` as associated function, not const** — An associated const (`const KIND: u16`) would also work but associated functions compose better with default methods. Either would be fine; the chapter can use whichever reads better. Actually, a const may be cleaner since it can't vary per call. Decide during writing. 3. **Events keep `u16`** — Per user preference. The `Kind` trait lives alongside events, not inside them. Events are protocol-level; kinds are application-level. 4. **Free functions mirror trait methods** — Not all code has a `Kind` impl. Relay logic, filter building, and storage layers need classification from a raw `u16`. The free functions are the source of truth; the trait defaults call them. 5. **`from_event` with kind check as default** — Implementors only write `parse`, which can assume the kind is correct. The trait provides the boilerplate. 6. **Deletion as example** — Kind 5 is simple (content is a reason string, tags are references to deleted events) and universally understood. It won't conflict with domain chapter content since deletion is a protocol-level concept. ## Open Questions 1. **Associated const vs function** — Should it be `const KIND: u16 = 5;` or `fn kind_number() -> u16 { 5 }`? Const is more idiomatic for a fixed value. Try const during writing and see if defaults can reference it cleanly. 2. **Should `to_template` return `EventTemplate` or `EventContent`?** — EventTemplate includes the kind, which the trait already knows. Returning EventContent and having a default `to_template` that adds the kind could reduce boilerplate. Explore during writing.