- Updates CLAUDE.md tech stack to current versions (Compose 1.10.3, Kotlin 2.3.20). - Reframes kotlin-multiplatform iOS as mature; adds secp256k1-kmp 0.23.0 references. - Updates desktop-expert Main.kt references (code grew from ~270 to 1341 lines and NavigationRail moved to ui/deck/SinglePaneLayout.kt); replaces obsolete "hardcoded ctrl = true" anti-pattern note with accurate isMacOS branching. - Removes compose-desktop.md (superseded by desktop-expert/). - Adds nostr-expert references: nip19-bech32, event-factory, crypto-and-encryption, large-cache. Adds kotlin-expert/common-utilities, compose-expert/rich-text-parsing, android-expert/image-loading. - New skills: account-state (Account + LocalCache), relay-client (subscriptions, filter assemblers, preloaders), feed-patterns (FeedFilter + FeedViewModel family), auth-signers (NostrSigner across internal / NIP-46 / NIP-55).
3.4 KiB
EventFactory: Parsing JSON into Typed Events
quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt is the single dispatch point that turns a parsed (id, pubKey, createdAt, kind, tags, content, sig) tuple into the correct Event subclass.
What It Does
EventFactory is a giant when over kind that maps integer kind values to concrete event classes. If a kind isn't recognized, it falls back to the generic base Event (so unknown kinds still round-trip). Every NIP that defines a new kind registers its class here.
Typical shape:
object EventFactory {
fun create(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: TagArray,
content: String,
sig: HexKey,
): Event = when (kind) {
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
TextNoteEvent.KIND -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
// …hundreds more…
else -> Event(id, pubKey, createdAt, kind, tags, content, sig)
}
}
Callers are normally upstream of this: Event.fromJson(...) / EventMapper.fromJson(...) / the relay client's message parser. You rarely call EventFactory directly — you consume typed events it produces.
Registering a New Event Kind
Adding a NIP is roughly:
- Create the event class under
quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nipXX…/alongside the NIP package. - Subclass the right base:
Event— regular events (stored forever).BaseReplaceableEvent— kinds10000-19999,0,3.BaseAddressableEvent— kinds30000-39999(identified bykind:pubkey:d-tag).- Ephemeral events extend
Eventbut have kind20000-29999.
- Define
companion object { const val KIND = <n> }. - If the event has tag builders, define a
TagArrayBuilder<YourEvent>DSL in aTagArrayBuilderextension — seenostr-expert/references/tag-patterns.md. - Add a branch to
EventFactory.create(...)so JSON parsing produces your typed class. - If the event is addressable, ensure it exposes a stable
dTag()andaddress(). - Add tests under
quartz/src/commonTest/....
Why a Monolithic when?
- Zero overhead: compiled to a dense lookup. No reflection, no registry map.
- Exhaustive browsing: every known kind lives at one search location.
grep KIND = 1234 quartz/...finds everything. - Obvious migration path: adding a kind means adding a case; removing a kind is a grep-and-delete.
The tradeoff is the file is large and every new kind edits the same file — expect merge conflicts in PRs that touch it, and resolve by keeping both branches.
Supporting Utilities
EventAssembler.kt(crypto/) — higher-level helper that takes a signer and akind + tags + contentand produces a fully signed event (id + sig populated).EventTemplate.kt(signers/) — unsigned-event holder, useful in signer flows.Event.fromJson(...)/Event.toJson()— JSON round-trip usingOptimizedJsonMapper(Jackson on jvmAndroid).
Related References
event-hierarchy.md— class hierarchy, Kind rangesnip-catalog.md— which kind maps to which NIPtag-patterns.md—TagArrayBuilderDSL for writing tags cleanly