
Searchable Events
FreeStreamline event indexing for efficient searchability.
Free · Opens the source repo
What Searchable Events does
The Searchable Events skill provides a framework for defining and implementing searchable event kinds in the Quartz system. By utilizing the SearchableEvent interface, developers can ensure that specific event classes contribute meaningful text to the search index. This skill is essential for those working with event-driven architectures where efficient searching of event data is crucial. The indexableContent() method serves as both a marker and an extractor, allowing developers to specify how their event data should be indexed for search operations.
When implementing the SearchableEvent interface, developers must adhere to specific rules to maintain consistency and reliability in the search index. This includes returning plain text without markup, ensuring that no exceptions are thrown during the indexing process, and only including public data. The skill also provides a comprehensive table of searchable kinds, detailing the exact content that each event type contributes to the search index. This allows for easy tracking of changes and ensures that any modifications to the searchable set are well-documented.
The skill is particularly useful during version updates, as it allows developers to diff the searchable set and verify if any changes have been made to the indexed content. Additionally, it aids in debugging search issues, helping to determine why certain events may not be retrievable through search queries. With the ability to extend search functionality through the SearchQuery class, developers can implement advanced search features, including filtering based on specific criteria.
Overall, the Searchable Events skill is designed for developers and designers who need to implement and manage searchable event types within the Quartz framework, ensuring that their applications can efficiently handle and retrieve event data based on user queries.
When to use it
Use this skill when implementing searchable event types in the Quartz framework, especially when you need to control how event data is indexed for search.
When not to use it
This skill may not be suitable for applications that do not require event search functionality or where event data is not intended to be indexed.
What you can build with it
Implementing a New Searchable Event
When adding a new event type that requires search functionality, use the Searchable Events skill to define how it should be indexed.
Debugging Search Issues
If certain events are not appearing in search results, utilize this skill to verify the implementation of the `indexableContent()` method.
Updating Searchable Events on Version Bump
When updating the Quartz framework, check the `searchable-kinds.md` file to see if any changes to the searchable set have occurred.
How to install Searchable Events
View source1. Install with the skills CLI
npx skills add vitorpamplona/amethyst/searchable-events --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by vitorpamplonaSearchable Events — the NIP-50 indexing surface
The contract
quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip50Search/SearchableEvent.kt:
interface SearchableEvent {
fun indexableContent(): String
}
One method; marker and extractor in one. An event kind is searchable iff its event class
implements this interface and the class is wired into EventFactory (the stores probe
searchability by kind through EventFactory.create — an unwired implementor is invisible).
Rules every implementation follows (keep them when adding one):
- Plain text out. Return the human-meaningful fields joined with
"\n"(a handful of metadata-ish kinds use" "); no markup stripping is performed — markdown/asciidoc content goes in raw, JSON-content kinds (kind 0 metadata, marketplace stalls, channel info) parse first and join the extracted fields, never the raw JSON. - Never throw, never null. There is no defensive wrapper at any call site; a throw aborts
the insert transaction. Parsed-JSON implementations use
?.let { … } ?: "". - Only public data. Encrypted content stays out (e.g. kind 30382 contact cards index only the public petname/summary/topics, never the NIP-44 payload).
- Typical shapes:
contentalone (~33 kinds);listOfNotNull(title(), content);listOfNotNull(title(), summary(), content); lists indextitle() + description().
The full kind table
references/searchable-kinds.md in this skill holds the authoritative table — every
implementor with its kind number, class, and the exact indexableContent() expression
(126 concrete classes / 129 kind values as of 2026-08). Diff that file at a version bump to
answer "did the searchable set or any kind's indexed text change?".
Notables that surprise people:
- Kind 9735 (zap receipt) indexes the embedded zap request's content
(
zapRequest?.content.orEmpty()) — receipts are searchable by the zapper's comment. - Kind 0 / 31990 index many profile fields space-joined (name, about, nip05, lud16, website, picture URL, …).
- Kind 30063 is claimed twice (
ReleaseArtifactSetEventin nip51Lists and the experimentalSoftwareReleaseEvent);EventFactoryresolves 30063 toReleaseArtifactSetEvent, sotitle()\ndescription()is what actually gets indexed —SoftwareReleaseEvent.indexableContent()is dead on the store path. - Poll kinds (1068, 6969) append each option label on its own line.
MANDATORY maintenance when you touch this surface
Adding SearchableEvent to a kind, removing it, or changing any indexableContent() body:
- Update
references/searchable-kinds.mdin the same PR (external search engines — e.g. the Vespa-backed store'sSearchExtractors— mirror this table at pin bumps; a silent change ships them stale search results). - Remember existing databases don't reindex themselves. Old rows keep their old (or
missing) FTS text until
IEventStore.reindexFullTextSearch()runs — the KDoc on that method is the contract. App-side, schedule the resumable overload after shipping such a change. - New implementors must be registered in
EventFactoryor the reindex scan and kind pre-filter (FullTextSearchModule.isSearchableKind) will never see them.
Eligibility policy: a kind becomes searchable when it carries human-authored, human-meaningful text (titles, bodies, names, descriptions). Pure-machine kinds (reactions, follow lists, zaps minus their comment, relay lists) stay out to keep the index small.
How the stores consume it
SQLite (nip01Core/store/sqlite/FullTextSearchModule.kt):
CREATE VIRTUAL TABLE event_fts USING fts5(content, content='', contentless_delete=1) —
contentless, rowid = event_headers.row_id, an AFTER DELETE trigger keeps it in sync. On
insert (when FTS is on and not deferred): if (event is SearchableEvent) → bind
event.indexableContent() — the only method ever called. Tokenization is entirely SQLite's
default FTS5 unicode61; queries are always a bound event_fts MATCH ? (never concatenated),
ordered by bm25 rank then created_at DESC. Query-side semantics (relevance ordering,
extension stripping, FTS-off behavior, deferred catch-up) are rules STORE-S01…S06 in the
event-store-semantics skill.
Filesystem store (jvmMain/.../store/fs/FsIndexer.kt + FsSearchTokenizer.kt): tokenizes
indexableContent() itself, approximating unicode61 (split on non-letter/digit, lowercase);
the same tokenizer runs on queries so drift cancels.
NIP-50 client side
SearchQuery (nip50Search/SearchQuery.kt) — typed parse of the search filter string
into terms + extensions. A whitespace token is an extension iff it looks like
lowercasekey:value (the value not starting with //, so URLs stay free text); duplicate keys
keep the last; unknown extensions are preserved (extension(key)). Typed accessors:
includeSpam, domain, language, sentiment, nsfw. stripExtensions() /
Filter.strippingSearchExtensions() is the bridge the built-in stores use — unsupported
extensions are ignored (NIP-50), so an extensions-only search collapses to an unconstrained
query, never match-nothing. A server-side store that implements its own extensions
(observer:, sort:rank, …) receives the raw string (see the IEventStore KDoc) and should
parse with SearchQuery.parse so its syntax stays compatible with what clients send.
SearchRelayListEvent — kind 10007, the user's search-relay list (NIP-51-style, public
tags + NIP-44 private tags; not a SearchableEvent itself). Client consumption:
commons/.../actions/SearchActions.kt, bootstrap defaults in
commons/.../account/AccountBootstrapEvents.kt.
Don't confuse it with commons/.../commons/search/SearchQuery.kt — an app-level local-feed
query model (authors/kinds/hashtags/or-terms), unrelated to the NIP-50 wire string.
Tests (executable spec)
commonTest/.../nip50Search/SearchQueryTest.kt— the extension grammar, token by token.commonTest/.../store/sqlite/SearchTest.kt— per-kind indexing (kind 0 profile fields, 40/41 channel JSON, 31924/30617), extension-token ignoring, reindex/resumable-reindex, FTS cleanup on replaceable rotation.commonTest/.../store/sqlite/SearchRelevanceOrderTest.kt— bm25-before-recency ordering, limit-after-score, multi-filter rank union.commonTest/.../store/sqlite/NoFullTextSearchTest.kt— FTS-off contract.jvmTest/.../store/fs/FsSearchTest.kt— tokenizer parity for the filesystem store.
Frequently asked questions about Searchable Events
Similar skills
Python PyPI Package Builder
Streamline the process of creating and publishing Python packages.
Minecraft Plugin Development
Streamline your Minecraft server plugin creation.
MCP Server Builder
Easily build .NET MCP servers with the latest standards.
CommunityToolkit.Mvvm Messenger
Decoupled communication for ViewModels in .NET applications.
MVVM Toolkit DI
Streamline ViewModel integration with Dependency Injection in .NET.
MCP Apps Builder
Essential guidelines for MCP server development.
