Merge remote-tracking branch 'origin/kmp-completeness' into kmp-completeness

This commit is contained in:
KotlinGeekDev
2026-01-16 16:11:23 +01:00
276 changed files with 2984 additions and 1190 deletions
@@ -23,8 +23,6 @@ package com.vitorpamplona.quartz.nip01Core.core
import android.os.Parcel
import android.os.Parcelable
import androidx.compose.runtime.Stable
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Stable
actual data class Address actual constructor(
@@ -35,12 +33,6 @@ actual data class Address actual constructor(
Parcelable {
actual fun toValue() = assemble(kind, pubKeyHex, dTag)
actual fun countMemory(): Int =
3 * pointerSizeInBytes +
8 + // kind
pubKeyHex.bytesUsedInMemory() +
dTag.bytesUsedInMemory()
actual override fun compareTo(other: Address): Int {
val kindComparison = kind.compareTo(other.kind)
return if (kindComparison == 0) {
@@ -5,48 +5,62 @@ This module implements an **Event Store** with nostr-native queries.
The goal was not to make the fastest database, since there could be multiple optimizations made if
consistency can be sacrificed, but a database that will never crash and never go corrupt.
## Responsibilities
## Features
- **Storage & Retrieval**:
Stores Nostr events and enables retrieval using Nostr filters
Stores Nostr events and retrieves using Nostr filters
- **Replaceable Events**:
- Forces unique constraint by kind, pubkey
- Old versions are removed when newer versions arrive.
- Old versions are blocked if newer versions exist.
- **Addressable Events**:
- Forces unique constraint by kind, pubkey, d-tag
- Old versions are removed when newer versions arrive.
- Old versions are blocked if newer versions exist.
- **Ephemeral Events**
- Ephemeral events never stored.
- **NIP-40 Expirations**
- Manages expiration timestamps and prunes expired events.
- Deletes expired events.
- Blocks expired events from being reinserted
- **NIP-09 Deletion Events**
- Deletes by event id
- Deletes by address until the `created_at`
- Blocks deleted events from being re-inserted.
- GiftWraps are deleted by p-tag
- **NIP-62 Right to Vanish**
- Supports deleting an entire user until the `created_at` for enhanced privacy
- Deletes all user events until the `created_at`
- Blocks vanished events from being re-inserted.
- GiftWraps are deleted by p-tag
- **NIP-45 Counts**:
- Counts records matching Nostr filters
- **NIP-50 Full Text Search**:
- Implements content indexing and full text search supporting rich queries over event content.
- Custom content/tag indexing
- Rich queries over event content and tags
- Indexes updated on replaceables, deletions, vanish and expirations.
- **NIP-91: AND operator for tags**:
- Allows queries matching two or more tags at the same time
- **Immutable Tables**
Triggers ensure event immutability.
- Tables cannot be updated, only inserted and deleted.
## Indexing Strategy
The store indexes events using five dedicated tables:
- `event_headers`: stores the canonical event fields.
- `event_tags`: indexes tag values for fast filtering on tag-based queries.
- `event_tags`: indexes tag values as a hash for fast filtering on tag-based queries.
- `event_fts`: for the content of full text search
- `event_expirations`: to control when expired events must be deleted.
- `event_vanish`: to control up to when vanished accounts must be blocked.
SQL triggers ensure the **immutability of stored events**, preventing accidental or intentional
modifications.
## Querying
This module supports optimized query planning, producing efficient SQL for multi-filter evaluation
@@ -63,43 +77,6 @@ store.query(
)
```
Becomes
```sql
SELECT id, pubkey, created_at, kind, tags, content, sig FROM event_headers
INNER JOIN (
SELECT event_headers.row_id AS row_id
FROM event_headers
ORDER BY created_at DESC,id ASC
LIMIT 10
UNION
SELECT event_headers.row_id AS row_id
FROM event_headers
INNER JOIN event_fts ON event_fts.event_header_row_id = event_headers.row_id
WHERE event_headers.kind IN (1, 1111)
AND event_headers.pubkey = hexkey
AND event_fts MATCH "keywords"
ORDER BY created_at DESC, id ASC
LIMIT 100
UNION
SELECT event_headers.row_id AS row_id
FROM event_headers
INNER JOIN event_fts ON event_fts.event_header_row_id = event_headers.row_id
WHERE event_headers.kind = 20
AND event_fts MATCH "cats"
ORDER BY created_at DESC,id ASC
LIMIT 30
) AS filtered ON event_headers.row_id = filtered.row_id
ORDER BY created_at DESC,id
```
The union operations support complex filter lists while avoiding redundant data fetching and
duplicated outstreams.
## How to Use
The `EventStore` class provides a high-level interface for interacting with the event database.
@@ -27,9 +27,7 @@ import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
data class RootSceneTag(
@@ -48,13 +46,6 @@ data class RootSceneTag(
this.relay = relayHint
}
fun countMemory(): Int =
5 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
8 + // kind
pubKeyHex.bytesUsedInMemory() +
dTag.bytesUsedInMemory() +
(relay?.url?.bytesUsedInMemory() ?: 0)
fun toTag() = assembleATagId(kind, pubKeyHex, dTag)
fun toTagArray() = assemble(kind, pubKeyHex, dTag, relay)
@@ -33,8 +33,6 @@ expect class Address(
fun toValue(): String
fun countMemory(): Int
companion object {
fun assemble(
kind: Int,
@@ -24,8 +24,6 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
open class Event(
@@ -44,15 +42,6 @@ open class Event(
*/
open fun isContentEncoded() = false
open fun countMemory(): Int =
7 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
12 + // createdAt + kind
id.bytesUsedInMemory() +
pubKey.bytesUsedInMemory() +
tags.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } +
content.bytesUsedInMemory() +
sig.bytesUsedInMemory()
fun toJson(): String = OptimizedJsonMapper.toJson(this)
companion object {
@@ -24,8 +24,6 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
data class EventHintBundle<T : Event>(
@@ -39,10 +37,5 @@ data class EventHintBundle<T : Event>(
this.authorHomeRelay = authorHomeRelay
}
fun countMemory(): Int =
2 * pointerSizeInBytes + // 2 fields, 4 bytes each reference (32bit)
event.countMemory() +
(relay?.url?.bytesUsedInMemory() ?: 0)
fun toNEvent(): String = NEvent.create(event.id, event.pubKey, event.kind, relay)
}
@@ -32,9 +32,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
data class ATag(
@@ -45,13 +43,6 @@ data class ATag(
) {
constructor(address: Address, relayHint: NormalizedRelayUrl? = null) : this(address.kind, address.pubKeyHex, address.dTag, relayHint)
fun countMemory(): Int =
5 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
8 + // kind
pubKeyHex.bytesUsedInMemory() +
dTag.bytesUsedInMemory() +
(relay?.url?.bytesUsedInMemory() ?: 0)
fun toTag() = AddressSerializer.assemble(kind, pubKeyHex, dTag)
fun toATagArray() = assemble(toTag(), relay)
@@ -20,14 +20,9 @@
*/
package com.vitorpamplona.quartz.nip01Core.tags.dTag
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class DTag(
val dId: String,
) {
fun countMemory(): Int = 1 * pointerSizeInBytes + dId.bytesUsedInMemory()
fun toTagArray() = assemble(dId)
companion object {
@@ -30,9 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
data class ETag(
@@ -46,12 +44,6 @@ data class ETag(
this.author = authorPubKeyHex
}
fun countMemory(): Int =
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
eventId.bytesUsedInMemory() +
(relay?.url?.bytesUsedInMemory() ?: 0) +
(author?.bytesUsedInMemory() ?: 0)
fun toNEvent(): String = NEvent.create(eventId, author, null, relay)
override fun toTagArray() = assemble(eventId, relay, author)
@@ -33,20 +33,13 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
data class PTag(
override val pubKey: HexKey,
override val relayHint: NormalizedRelayUrl? = null,
) : PubKeyReferenceTag {
fun countMemory(): Int =
2 * pointerSizeInBytes + // 2 fields, 4 bytes each reference (32bit)
pubKey.bytesUsedInMemory() +
(relayHint?.url?.bytesUsedInMemory() ?: 0)
fun toNProfile(): String = NProfile.create(pubKey, relayHint?.let { listOf(it) } ?: emptyList())
fun toNPub(): String = pubKey.hexToByteArray().toNpub()
@@ -31,9 +31,7 @@ import com.vitorpamplona.quartz.nip19Bech32.decodePublicKey
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
data class ContactTag(
@@ -51,12 +49,6 @@ data class ContactTag(
this.petname = petname
}
fun countMemory(): Int =
3 * pointerSizeInBytes +
pubKey.bytesUsedInMemory() +
(relayUri?.url?.bytesUsedInMemory() ?: 0) +
(petname?.bytesUsedInMemory() ?: 0)
fun toTagArray() = assemble(pubKey, relayUri, petname)
companion object {
@@ -22,16 +22,12 @@ package com.vitorpamplona.quartz.nip13Pow.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class PoWTag(
val nonce: String,
val commitment: Int?,
) {
fun countMemory(): Int = 2 * pointerSizeInBytes + nonce.bytesUsedInMemory() + (commitment?.bytesUsedInMemory() ?: 0)
fun toTagArray() = assemble(nonce, commitment)
companion object {
@@ -27,9 +27,7 @@ import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
data class QAddressableTag(
@@ -53,11 +51,6 @@ data class QAddressableTag(
this.relay = relayHint
}
fun countMemory(): Int =
2 * pointerSizeInBytes +
address.countMemory() +
(relay?.url?.bytesUsedInMemory() ?: 0)
override fun toTagArray() = assemble(address, relay)
companion object {
@@ -28,9 +28,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
data class QEventTag(
@@ -44,12 +42,6 @@ data class QEventTag(
this.author = authorPubKeyHex
}
fun countMemory(): Int =
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
eventId.bytesUsedInMemory() +
(relay?.url?.bytesUsedInMemory() ?: 0) +
(author?.bytesUsedInMemory() ?: 0)
override fun toTagArray() = assemble(eventId, relay, author)
companion object {
@@ -28,9 +28,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
class ChannelTag(
@@ -38,12 +36,6 @@ class ChannelTag(
val relay: NormalizedRelayUrl? = null,
val author: HexKey? = null,
) {
fun countMemory(): Int =
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
eventId.bytesUsedInMemory() +
(relay?.url?.bytesUsedInMemory() ?: 0) +
(author?.bytesUsedInMemory() ?: 0)
fun toNEvent(): String = NEvent.create(eventId, author, null, relay)
fun toTagArray() = assemble(eventId, relay, author)
@@ -21,7 +21,6 @@
package com.vitorpamplona.quartz.nip30CustomEmoji
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@@ -32,10 +31,10 @@ class CustomEmoji {
fun fastMightContainEmoji(
input: String,
allTags: ImmutableListOfLists<String>?,
allTags: Array<Array<String>>?,
): Boolean {
if (allTags == null) return false
if (allTags.lists.any { it.size > 2 && it[0] == EmojiUrlTag.TAG_NAME }) return true
if (allTags.any { it.size > 2 && it[0] == EmojiUrlTag.TAG_NAME }) return true
return input.contains(":")
}
@@ -47,7 +46,7 @@ class CustomEmoji {
return input.contains(":")
}
fun createEmojiMap(tags: ImmutableListOfLists<String>): Map<String, String> = tags.lists.filter { it.size > 2 && it[0] == EmojiUrlTag.TAG_NAME }.associate { ":${it[1]}:" to it[2] }
fun createEmojiMap(tags: Array<Array<String>>): Map<String, String> = tags.filter { it.size > 2 && it[0] == EmojiUrlTag.TAG_NAME }.associate { ":${it[1]}:" to it[2] }
fun findAllEmojis(input: String): List<String> {
val matcher = customEmojiPattern.findAll(input)
@@ -67,11 +66,11 @@ class CustomEmoji {
fun assembleAnnotatedList(
input: String,
allTags: ImmutableListOfLists<String>?,
tags: Array<Array<String>>?,
): ImmutableList<Renderable>? {
if (allTags == null || allTags.lists.isEmpty()) return null
if (tags == null || tags.isEmpty()) return null
val emojiPairs = createEmojiMap(allTags)
val emojiPairs = createEmojiMap(tags)
return assembleAnnotatedList(input, emojiPairs)
}
@@ -22,6 +22,4 @@ package com.vitorpamplona.quartz.nip46RemoteSigner
import com.vitorpamplona.quartz.nip01Core.core.OptimizedSerializable
abstract class BunkerMessage : OptimizedSerializable {
abstract fun countMemory(): Int
}
abstract class BunkerMessage : OptimizedSerializable
@@ -20,17 +20,8 @@
*/
package com.vitorpamplona.quartz.nip46RemoteSigner
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
open class BunkerRequest(
val id: String,
val method: String,
val params: Array<String> = emptyArray(),
) : BunkerMessage() {
override fun countMemory(): Int =
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
id.bytesUsedInMemory() +
method.bytesUsedInMemory() +
params.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() }
}
) : BunkerMessage()
@@ -20,17 +20,8 @@
*/
package com.vitorpamplona.quartz.nip46RemoteSigner
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
open class BunkerResponse(
val id: String,
val result: String?,
val error: String?,
) : BunkerMessage() {
override fun countMemory(): Int =
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
id.bytesUsedInMemory() +
(result?.bytesUsedInMemory() ?: 0) +
(error?.bytesUsedInMemory() ?: 0)
}
) : BunkerMessage()
@@ -21,15 +21,11 @@
package com.vitorpamplona.quartz.nip47WalletConnect
import com.vitorpamplona.quartz.nip01Core.core.OptimizedSerializable
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
// RESPONSE OBJECTS
abstract class Response(
val resultType: String,
) : OptimizedSerializable {
abstract fun countMemory(): Int
}
) : OptimizedSerializable
// PayInvoice Call
@@ -38,11 +34,7 @@ class PayInvoiceSuccessResponse(
) : Response("pay_invoice") {
class PayInvoiceResultParams(
val preimage: String? = null,
) {
fun countMemory(): Int = pointerSizeInBytes + (preimage?.bytesUsedInMemory() ?: 0)
}
override fun countMemory(): Int = pointerSizeInBytes + (result?.countMemory() ?: 0)
)
}
class PayInvoiceErrorResponse(
@@ -51,11 +43,7 @@ class PayInvoiceErrorResponse(
class PayInvoiceErrorParams(
val code: ErrorType? = null,
val message: String? = null,
) {
fun countMemory(): Int = pointerSizeInBytes + pointerSizeInBytes + (message?.bytesUsedInMemory() ?: 0)
}
override fun countMemory(): Int = pointerSizeInBytes + (error?.countMemory() ?: 0)
)
enum class ErrorType {
RATE_LIMITED,
@@ -22,17 +22,13 @@ package com.vitorpamplona.quartz.nip48ProxyTags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
data class ProxyTag(
val id: String,
val protocol: String,
) {
fun countMemory(): Int = 2 * pointerSizeInBytes + id.bytesUsedInMemory() + protocol.bytesUsedInMemory()
fun toTagArray() = assemble(id, protocol)
companion object {
@@ -27,16 +27,12 @@ import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class AddressBookmark(
val address: Address,
val relayHint: NormalizedRelayUrl? = null,
) : BookmarkIdTag {
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
override fun toTagArray() = assemble(address, relayHint)
@@ -28,9 +28,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
class EventBookmark(
@@ -38,12 +36,6 @@ class EventBookmark(
val relay: NormalizedRelayUrl? = null,
val author: HexKey? = null,
) : BookmarkIdTag {
fun countMemory(): Int =
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
eventId.bytesUsedInMemory() +
(relay?.url?.bytesUsedInMemory() ?: 0) +
(author?.bytesUsedInMemory() ?: 0)
fun toNEvent(): String = NEvent.create(eventId, author, null, relay)
override fun toTagArray() = assemble(eventId, relay, author)
@@ -30,19 +30,12 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class UserTag(
val pubKey: HexKey,
val relayHint: NormalizedRelayUrl? = null,
) : MuteTag {
fun countMemory(): Int =
2 * pointerSizeInBytes + // 2 fields, 4 bytes each reference (32bit)
pubKey.bytesUsedInMemory() +
(relayHint?.url?.bytesUsedInMemory() ?: 0)
fun toNProfile(): String = NProfile.create(pubKey, relayHint?.let { listOf(it) } ?: emptyList())
fun toNPub(): String = pubKey.hexToByteArray().toNpub()
@@ -23,18 +23,12 @@ package com.vitorpamplona.quartz.nip51Lists.muteList.tags
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
class WordTag(
val word: String,
) : MuteTag {
fun countMemory(): Int =
1 * pointerSizeInBytes + // 1 fields, 4 bytes each reference (32bit)
word.bytesUsedInMemory()
override fun toTagArray() = assemble(word)
override fun toTagIdOnly() = assemble(word)
@@ -27,16 +27,12 @@ import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class MeetingSpaceTag(
val address: Address,
val relayHint: NormalizedRelayUrl? = null,
) {
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
fun toTagArray() = assemble(address, relayHint)
@@ -27,16 +27,12 @@ import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class MeetingRoomTag(
val address: Address,
val relayHint: NormalizedRelayUrl? = null,
) {
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
fun toTagArray() = assemble(address, relayHint)
@@ -77,8 +77,9 @@ class ReportEvent(
fun build(
reportedPost: Event,
type: ReportType,
comment: String = "",
createdAt: Long = TimeUtils.now(),
) = eventTemplate(KIND, "", createdAt) {
) = eventTemplate(KIND, comment, createdAt) {
alt(ALT_PREFIX + type.code)
event(reportedPost.id, type)
user(reportedPost.pubKey, type)
@@ -91,8 +92,9 @@ class ReportEvent(
fun build(
reportedUser: HexKey,
type: ReportType,
comment: String = "",
createdAt: Long = TimeUtils.now(),
) = eventTemplate(KIND, "", createdAt) {
) = eventTemplate(KIND, comment, createdAt) {
alt(ALT_PREFIX + type.code)
user(reportedUser, type)
}
@@ -32,7 +32,6 @@ import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
class LnZapEvent(
@@ -74,11 +73,6 @@ class LnZapEvent(
}
}
override fun countMemory(): Int =
super.countMemory() +
pointerSizeInBytes + (zapRequest?.countMemory() ?: 0) + // rough calculation
pointerSizeInBytes + 36 // bigdecimal size
override fun containedPost(): LnZapRequestEvent? =
try {
description()?.ifBlank { null }?.let { fromJson(it) } as? LnZapRequestEvent
@@ -20,14 +20,9 @@
*/
package com.vitorpamplona.quartz.nip57Zaps.zapraiser
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class ZapRaiserTag(
val amountInSats: Long,
) {
fun countMemory(): Int = 1 * pointerSizeInBytes + amountInSats.bytesUsedInMemory()
fun toTagArray() = assemble(amountInSats)
companion object {
@@ -30,8 +30,6 @@ import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
class SealedRumorEvent(
@@ -46,10 +44,6 @@ class SealedRumorEvent(
@kotlin.jvm.Transient
var innerEventId: HexKey? = null
override fun countMemory(): Int =
super.countMemory() +
pointerSizeInBytes + (innerEventId?.bytesUsedInMemory() ?: 0)
fun copyNoContent(): SealedRumorEvent {
val copy =
SealedRumorEvent(
@@ -34,8 +34,6 @@ import com.vitorpamplona.quartz.nip59Giftwrap.HostStub
import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
class GiftWrapEvent(
@@ -50,10 +48,6 @@ class GiftWrapEvent(
@kotlin.jvm.Transient
var innerEventId: HexKey? = null
override fun countMemory(): Int =
super.countMemory() +
pointerSizeInBytes + (innerEventId?.bytesUsedInMemory() ?: 0)
fun copyNoContent(): GiftWrapEvent {
val copy =
GiftWrapEvent(
@@ -23,19 +23,12 @@ package com.vitorpamplona.quartz.nip71Video.tags
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
data class TextTrackTag(
val eventId: HexKey,
var relay: String? = null,
) {
fun countMemory(): Int =
2 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
eventId.bytesUsedInMemory() +
(relay?.bytesUsedInMemory() ?: 0)
fun toTagArray() = arrayOfNotNull(TAG_NAME, eventId, relay)
companion object {
@@ -28,16 +28,12 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class ApprovedAddressTag(
val address: Address,
val relayHint: NormalizedRelayUrl? = null,
) {
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
fun toTagArray() = assemble(address, relayHint)
@@ -28,16 +28,12 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
import com.vitorpamplona.quartz.utils.arrayOfNotNull
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.ensure
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
class CommunityTag(
val address: Address,
val relayHint: NormalizedRelayUrl? = null,
) {
fun countMemory(): Int = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0)
fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag)
fun toTagArray() = assemble(address, relayHint)
@@ -51,8 +51,6 @@ class AppDefinitionEvent(
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig),
PublishedAtProvider {
override fun countMemory(): Int = super.countMemory() + (cachedMetadata?.countMemory() ?: 8)
@kotlinx.serialization.Transient
@kotlin.jvm.Transient
private var cachedMetadata: AppMetadata? = null
@@ -22,8 +22,6 @@ package com.vitorpamplona.quartz.nip89AppHandlers.definition
import androidx.compose.runtime.Stable
import com.vitorpamplona.quartz.nip01Core.core.JsonMapper
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@@ -52,26 +50,6 @@ class AppMetadata {
var lud06: String? = null
var lud16: String? = null
fun countMemory(): Int =
20 * pointerSizeInBytes + // 20 fields, 4 bytes for each reference
(name?.bytesUsedInMemory() ?: 0) +
(username?.bytesUsedInMemory() ?: 0) +
(displayName?.bytesUsedInMemory() ?: 0) +
(picture?.bytesUsedInMemory() ?: 0) +
(banner?.bytesUsedInMemory() ?: 0) +
(image?.bytesUsedInMemory() ?: 0) +
(website?.bytesUsedInMemory() ?: 0) +
(about?.bytesUsedInMemory() ?: 0) +
(subscription?.bytesUsedInMemory() ?: 0) +
(acceptsNutZaps?.bytesUsedInMemory() ?: 0) +
(supportsEncryption?.bytesUsedInMemory() ?: 0) +
(personalized?.bytesUsedInMemory() ?: 0) + // A Boolean has 8 bytes of header, plus 1 byte of payload, for a total of 9 bytes of information. The JVM then rounds it up to the next multiple of 8. so the one instance of java.lang.Boolean takes up 16 bytes of memory.
(amount?.bytesUsedInMemory() ?: 0) +
(nip05?.bytesUsedInMemory() ?: 0) +
(domain?.bytesUsedInMemory() ?: 0) +
(lud06?.bytesUsedInMemory() ?: 0) +
(lud16?.bytesUsedInMemory() ?: 0)
fun anyName(): String? = displayName ?: name ?: username
fun anyNameStartsWith(prefix: String): Boolean =
@@ -28,8 +28,6 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip31Alts.AltTag
import com.vitorpamplona.quartz.utils.Log
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
class NIP90ContentDiscoveryResponseEvent(
@@ -44,10 +42,6 @@ class NIP90ContentDiscoveryResponseEvent(
@kotlin.jvm.Transient
var events: List<HexKey>? = null
override fun countMemory(): Int =
super.countMemory() +
pointerSizeInBytes + (events?.sumOf { it.bytesUsedInMemory() } ?: 0)
fun innerTags(): List<HexKey> {
if (content.isEmpty()) {
return listOf()
@@ -20,7 +20,6 @@
*/
package com.vitorpamplona.quartz.nip30CustomEmoji
import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
@@ -150,7 +149,7 @@ class Nip30Test {
"#ioメシヨソイゲーム\n" +
"https://misskey.io/play/9g3qza4jow"
val result = CustomEmoji.assembleAnnotatedList(input, ImmutableListOfLists(tags))
val result = CustomEmoji.assembleAnnotatedList(input, tags)
assertEquals(9, result!!.size)
@@ -20,9 +20,6 @@
*/
package com.vitorpamplona.quartz.nip01Core.core
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
actual data class Address actual constructor(
actual val kind: Kind,
actual val pubKeyHex: HexKey,
@@ -30,12 +27,6 @@ actual data class Address actual constructor(
) : Comparable<Address> {
actual fun toValue() = assemble(kind, pubKeyHex, dTag)
actual fun countMemory(): Int =
3 * pointerSizeInBytes +
8 + // kind
pubKeyHex.bytesUsedInMemory() +
dTag.bytesUsedInMemory()
actual override fun compareTo(other: Address): Int {
val result = kind.compareTo(other.kind)
return if (result == 0) {
@@ -20,9 +20,6 @@
*/
package com.vitorpamplona.quartz.nip01Core.core
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
actual data class Address actual constructor(
actual val kind: Kind,
actual val pubKeyHex: HexKey,
@@ -30,12 +27,6 @@ actual data class Address actual constructor(
) : Comparable<Address> {
actual fun toValue() = assemble(kind, pubKeyHex, dTag)
actual fun countMemory(): Int =
3 * pointerSizeInBytes +
8 + // kind
pubKeyHex.bytesUsedInMemory() +
dTag.bytesUsedInMemory()
actual override fun compareTo(other: Address): Int {
val result = kind.compareTo(other.kind)
return if (result == 0) {