dgraph

package
v0.29.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 20, 2025 License: Unlicense Imports: 28 Imported by: 0

README ΒΆ

Dgraph Database Implementation for ORLY

This package provides a Dgraph-based implementation of the ORLY database interface, enabling graph-based storage for Nostr events with powerful relationship querying capabilities.

Status: Step 1 Complete βœ…

Current State: Dgraph server integration is complete and functional Next Step: DQL query/mutation implementation in save-event.go and query-events.go

Architecture

Client-Server Model

The implementation uses a client-server architecture:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚            ORLY Relay Process               β”‚
β”‚                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚    Dgraph Client (pkg/dgraph)      β”‚    β”‚
β”‚  β”‚  - dgo library (gRPC)              β”‚    β”‚
β”‚  β”‚  - Schema management               │────┼───► Dgraph Server
β”‚  β”‚  - Query/Mutate methods            β”‚    β”‚    (localhost:9080)
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚    - Event graph
β”‚                                             β”‚    - Authors, tags
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚    - Relationships
β”‚  β”‚   Badger Metadata Store            β”‚    β”‚
β”‚  β”‚  - Markers (key-value)             β”‚    β”‚
β”‚  β”‚  - Serial counters                 β”‚    β”‚
β”‚  β”‚  - Relay identity                  β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Dual Storage Strategy
  1. Dgraph (Graph Database)

    • Nostr events and their content
    • Author relationships
    • Tag relationships
    • Event references and mentions
    • Optimized for graph traversals and complex queries
  2. Badger (Key-Value Store)

    • Metadata markers
    • Serial number counters
    • Relay identity keys
    • Fast key-value operations

Setup

1. Start Dgraph Server

Using Docker (recommended):

docker run -d \
  --name dgraph \
  -p 8080:8080 \
  -p 9080:9080 \
  -p 8000:8000 \
  -v ~/dgraph:/dgraph \
  dgraph/standalone:latest
2. Configure ORLY
export ORLY_DB_TYPE=dgraph
export ORLY_DGRAPH_URL=localhost:9080  # Optional, this is the default
3. Run ORLY
./orly

On startup, ORLY will:

  1. Connect to dgraph server via gRPC
  2. Apply the Nostr schema automatically
  3. Initialize badger metadata store
  4. Initialize serial number counter
  5. Start accepting events

Schema

The Nostr schema defines the following types:

Event Nodes
type Event {
  event.id          # Event ID (string, indexed)
  event.serial      # Sequential number (int, indexed)
  event.kind        # Event kind (int, indexed)
  event.created_at  # Timestamp (int, indexed)
  event.content     # Event content (string)
  event.sig         # Signature (string, indexed)
  event.pubkey      # Author pubkey (string, indexed)
  event.authored_by # -> Author (uid)
  event.references  # -> Events (uid list)
  event.mentions    # -> Events (uid list)
  event.tagged_with # -> Tags (uid list)
}
Author Nodes
type Author {
  author.pubkey     # Pubkey (string, indexed, unique)
  author.events     # -> Events (uid list, reverse)
}
Tag Nodes
type Tag {
  tag.type          # Tag type (string, indexed)
  tag.value         # Tag value (string, indexed + fulltext)
  tag.events        # -> Events (uid list, reverse)
}
Marker Nodes (Metadata)
type Marker {
  marker.key        # Key (string, indexed, unique)
  marker.value      # Value (string)
}

Configuration

Environment Variables
  • ORLY_DB_TYPE=dgraph - Enable dgraph database (default: badger)
  • ORLY_DGRAPH_URL=host:port - Dgraph gRPC endpoint (default: localhost:9080)
  • ORLY_DATA_DIR=/path - Data directory for metadata storage
Connection Details

The dgraph client uses insecure gRPC by default for local development. For production deployments:

  1. Set up TLS certificates for dgraph
  2. Modify pkg/dgraph/dgraph.go to use grpc.WithTransportCredentials() with your certs

Implementation Details

Files
  • dgraph.go - Main implementation, initialization, lifecycle
  • schema.go - Schema definition and application
  • save-event.go - Event storage (TODO: update to use Mutate)
  • query-events.go - Event queries (TODO: update to parse DQL responses)
  • fetch-event.go - Event retrieval methods
  • delete.go - Event deletion
  • markers.go - Key-value metadata storage (uses badger)
  • serial.go - Serial number generation (uses badger)
  • subscriptions.go - Subscription/payment tracking (uses markers)
  • nip43.go - NIP-43 invite system (uses markers)
  • import-export.go - Import/export operations
  • logger.go - Logging adapter
Key Methods
Initialization
d, err := dgraph.New(ctx, cancel, dataDir, logLevel)
Querying (DQL)
resp, err := d.Query(ctx, dqlQuery)
Mutations (RDF N-Quads)
mutation := &api.Mutation{SetNquads: []byte(nquads)}
resp, err := d.Mutate(ctx, mutation)

Development Status

βœ… Step 1: Dgraph Server Integration (COMPLETE)
  • dgo client library integration
  • gRPC connection to external dgraph
  • Schema definition and auto-application
  • Query() and Mutate() method stubs
  • ORLY_DGRAPH_URL configuration
  • Dual-storage architecture
  • Proper lifecycle management
πŸ“ Step 2: DQL Implementation (NEXT)

Priority tasks:

  1. save-event.go - Replace RDF string building with actual Mutate() calls
  2. query-events.go - Parse actual JSON responses from Query()
  3. fetch-event.go - Implement DQL queries for event retrieval
  4. delete.go - Implement deletion mutations
πŸ“ Step 3: Testing (FUTURE)
  • Integration testing with relay-tester
  • Performance benchmarks vs badger
  • Memory profiling
  • Production deployment testing

Troubleshooting

Connection Refused
failed to connect to dgraph at localhost:9080: connection refused

Solution: Ensure dgraph server is running:

docker ps | grep dgraph
docker logs dgraph
Schema Application Failed
failed to apply schema: ...

Solution: Check dgraph server logs and ensure no schema conflicts:

docker logs dgraph
Binary Not Finding libsecp256k1.so

This is unrelated to dgraph. Ensure:

export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:+$LD_LIBRARY_PATH:}$(pwd)/pkg/crypto/p8k"

Performance Considerations

When to Use Dgraph

Good fit:

  • Complex graph queries (follows-of-follows, social graphs)
  • Full-text search requirements
  • Advanced filtering and aggregations
  • Multi-hop relationship traversals

Not ideal for:

  • Simple key-value lookups (badger is faster)
  • Very high write throughput (badger has lower latency)
  • Single-node deployments with simple queries
Optimization Tips
  1. Indexing: Ensure frequently queried fields have appropriate indexes
  2. Pagination: Use offset/limit in DQL queries for large result sets
  3. Caching: Consider adding an LRU cache for hot events
  4. Schema Design: Use reverse edges for efficient relationship traversal

Resources

Contributing

When working on dgraph implementation:

  1. Test changes against a local dgraph instance
  2. Update schema.go if adding new node types or predicates
  3. Ensure dual-storage strategy is maintained (dgraph for events, badger for metadata)
  4. Add integration tests for new features
  5. Update DGRAPH_IMPLEMENTATION_STATUS.md with progress

Documentation ΒΆ

Overview ΒΆ

Package dgraph provides a Dgraph-based implementation of the database interface. This is a simplified implementation for testing - full dgraph integration to be completed later.

Index ΒΆ

Constants ΒΆ

View Source
const NostrSchema = `` /* 1159-byte string literal not displayed */

NostrSchema defines the Dgraph schema for Nostr events

Variables ΒΆ

This section is empty.

Functions ΒΆ

func NewLogger ΒΆ

func NewLogger(logLevel int, label string) (l *logger)

NewLogger creates a new dgraph logger.

Types ΒΆ

type Config ΒΆ

type Config struct {
	DataDir             string
	LogLevel            string
	DgraphURL           string // Dgraph gRPC endpoint (e.g., "localhost:9080")
	EnableGraphQL       bool
	EnableIntrospection bool
}

Config holds configuration options for the Dgraph database

type D ΒΆ

type D struct {
	Logger *logger
	// contains filtered or unexported fields
}

D implements the database.Database interface using Dgraph as the storage backend

func New ΒΆ

func New(
	ctx context.Context, cancel context.CancelFunc, dataDir, logLevel string,
) (
	d *D, err error,
)

New creates a new Dgraph-based database instance

func (*D) AddNIP43Member ΒΆ

func (d *D) AddNIP43Member(pubkey []byte, inviteCode string) error

AddNIP43Member adds a member using an invite code

func (*D) CacheEvents ΒΆ added in v0.29.11

func (d *D) CacheEvents(f *filter.F, events event.S)

func (*D) CacheMarshaledJSON ΒΆ

func (d *D) CacheMarshaledJSON(f *filter.F, marshaledJSON [][]byte)

func (*D) CheckForDeleted ΒΆ

func (d *D) CheckForDeleted(ev *event.E, admins [][]byte) (err error)

CheckForDeleted checks if an event has been deleted

func (*D) Close ΒΆ

func (d *D) Close() (err error)

Close closes the database

func (*D) CountEvents ΒΆ

func (d *D) CountEvents(c context.Context, f *filter.F) (
	count int, approximate bool, err error,
)

CountEvents counts events matching a filter

func (*D) DeleteEvent ΒΆ

func (d *D) DeleteEvent(c context.Context, eid []byte) error

DeleteEvent deletes an event by its ID

func (*D) DeleteEventBySerial ΒΆ

func (d *D) DeleteEventBySerial(c context.Context, ser *types.Uint40, ev *event.E) error

DeleteEventBySerial deletes an event by its serial number

func (*D) DeleteExpired ΒΆ

func (d *D) DeleteExpired()

DeleteExpired removes events that have passed their expiration time (NIP-40)

func (*D) DeleteInviteCode ΒΆ

func (d *D) DeleteInviteCode(code string) error

DeleteInviteCode removes an invite code

func (*D) DeleteMarker ΒΆ

func (d *D) DeleteMarker(key string) error

DeleteMarker removes a metadata marker

func (*D) EventIdsBySerial ΒΆ

func (d *D) EventIdsBySerial(start uint64, count int) (
	evs []uint64, err error,
)

EventIdsBySerial retrieves event IDs by serial range

func (*D) Export ΒΆ

func (d *D) Export(c context.Context, w io.Writer, pubkeys ...[]byte)

Export exports events to a writer (JSONL format)

func (*D) ExtendBlossomSubscription ΒΆ

func (d *D) ExtendBlossomSubscription(
	pubkey []byte, tier string, storageMB int64, daysExtended int,
) error

ExtendBlossomSubscription extends a Blossom storage subscription

func (*D) ExtendSubscription ΒΆ

func (d *D) ExtendSubscription(pubkey []byte, days int) error

ExtendSubscription extends a subscription by the specified number of days

func (*D) FetchEventBySerial ΒΆ

func (d *D) FetchEventBySerial(ser *types.Uint40) (ev *event.E, err error)

FetchEventBySerial retrieves an event by its serial number

func (*D) FetchEventsBySerials ΒΆ

func (d *D) FetchEventsBySerials(serials []*types.Uint40) (
	events map[uint64]*event.E, err error,
)

FetchEventsBySerials retrieves multiple events by their serial numbers

func (*D) GetAllNIP43Members ΒΆ

func (d *D) GetAllNIP43Members() ([][]byte, error)

GetAllNIP43Members retrieves all member pubkeys

func (*D) GetBlossomStorageQuota ΒΆ

func (d *D) GetBlossomStorageQuota(pubkey []byte) (quotaMB int64, err error)

GetBlossomStorageQuota retrieves the storage quota for a pubkey

func (*D) GetCachedEvents ΒΆ added in v0.29.11

func (d *D) GetCachedEvents(f *filter.F) (event.S, bool)

func (*D) GetCachedJSON ΒΆ

func (d *D) GetCachedJSON(f *filter.F) ([][]byte, bool)

func (*D) GetFullIdPubkeyBySerial ΒΆ

func (d *D) GetFullIdPubkeyBySerial(ser *types.Uint40) (
	fidpk *store.IdPkTs, err error,
)

GetFullIdPubkeyBySerial retrieves ID and pubkey for a serial number

func (*D) GetFullIdPubkeyBySerials ΒΆ

func (d *D) GetFullIdPubkeyBySerials(sers []*types.Uint40) (
	fidpks []*store.IdPkTs, err error,
)

GetFullIdPubkeyBySerials retrieves IDs and pubkeys for multiple serials

func (*D) GetMarker ΒΆ

func (d *D) GetMarker(key string) (value []byte, err error)

GetMarker retrieves a metadata marker

func (*D) GetNIP43Membership ΒΆ

func (d *D) GetNIP43Membership(pubkey []byte) (*database.NIP43Membership, error)

GetNIP43Membership retrieves membership information

func (*D) GetOrCreateRelayIdentitySecret ΒΆ

func (d *D) GetOrCreateRelayIdentitySecret() (skb []byte, err error)

GetOrCreateRelayIdentitySecret retrieves or creates the relay identity

func (*D) GetPaymentHistory ΒΆ

func (d *D) GetPaymentHistory(pubkey []byte) ([]database.Payment, error)

GetPaymentHistory retrieves payment history for a pubkey

func (*D) GetRelayIdentitySecret ΒΆ

func (d *D) GetRelayIdentitySecret() (skb []byte, err error)

GetRelayIdentitySecret retrieves the relay's identity secret key

func (*D) GetSerialById ΒΆ

func (d *D) GetSerialById(id []byte) (ser *types.Uint40, err error)

GetSerialById retrieves the serial number for an event ID

func (*D) GetSerialsByIds ΒΆ

func (d *D) GetSerialsByIds(ids *tag.T) (
	serials map[string]*types.Uint40, err error,
)

GetSerialsByIds retrieves serial numbers for multiple event IDs

func (*D) GetSerialsByIdsWithFilter ΒΆ

func (d *D) GetSerialsByIdsWithFilter(
	ids *tag.T, fn func(ev *event.E, ser *types.Uint40) bool,
) (serials map[string]*types.Uint40, err error)

GetSerialsByIdsWithFilter retrieves serials with a filter function

func (*D) GetSerialsByRange ΒΆ

func (d *D) GetSerialsByRange(idx database.Range) (
	serials types.Uint40s, err error,
)

GetSerialsByRange retrieves serials within a range

func (*D) GetSerialsFromFilter ΒΆ

func (d *D) GetSerialsFromFilter(f *filter.F) (serials types.Uint40s, err error)

GetSerialsFromFilter returns event serials matching a filter

func (*D) GetSubscription ΒΆ

func (d *D) GetSubscription(pubkey []byte) (*database.Subscription, error)

GetSubscription retrieves subscription information for a pubkey

func (*D) HasMarker ΒΆ

func (d *D) HasMarker(key string) bool

HasMarker checks if a marker exists

func (*D) Import ΒΆ

func (d *D) Import(rr io.Reader)

Import imports events from a reader (JSONL format)

func (*D) ImportEventsFromReader ΒΆ

func (d *D) ImportEventsFromReader(ctx context.Context, rr io.Reader) error

ImportEventsFromReader imports events from a reader

func (*D) ImportEventsFromStrings ΒΆ

func (d *D) ImportEventsFromStrings(
	ctx context.Context,
	eventJSONs []string,
	policyManager interface {
		CheckPolicy(action string, ev *event.E, pubkey []byte, remote string) (bool, error)
	},
) error

ImportEventsFromStrings imports events from JSON strings

func (*D) Init ΒΆ

func (d *D) Init(path string) (err error)

Init initializes the database with a given path (no-op, path set in New)

func (*D) InvalidateQueryCache ΒΆ

func (d *D) InvalidateQueryCache()

func (*D) IsFirstTimeUser ΒΆ

func (d *D) IsFirstTimeUser(pubkey []byte) (bool, error)

IsFirstTimeUser checks if a pubkey is a first-time user

func (*D) IsNIP43Member ΒΆ

func (d *D) IsNIP43Member(pubkey []byte) (isMember bool, err error)

IsNIP43Member checks if a pubkey is a member

func (*D) IsSubscriptionActive ΒΆ

func (d *D) IsSubscriptionActive(pubkey []byte) (bool, error)

IsSubscriptionActive checks if a pubkey has an active subscription

func (*D) Mutate ΒΆ

func (d *D) Mutate(ctx context.Context, mutation *api.Mutation) (*api.Response, error)

Mutate executes a mutation against dgraph

func (*D) Path ΒΆ

func (d *D) Path() string

Path returns the data directory path

func (*D) ProcessDelete ΒΆ

func (d *D) ProcessDelete(ev *event.E, admins [][]byte) (err error)

ProcessDelete processes a kind 5 deletion event

func (*D) PublishNIP43MembershipEvent ΒΆ

func (d *D) PublishNIP43MembershipEvent(kind int, pubkey []byte) error

PublishNIP43MembershipEvent publishes a membership event

func (*D) Query ΒΆ

func (d *D) Query(ctx context.Context, query string) (*api.Response, error)

Query executes a DQL query against dgraph

func (*D) QueryAllVersions ΒΆ

func (d *D) QueryAllVersions(c context.Context, f *filter.F) (evs event.S, err error)

QueryAllVersions retrieves all versions of events matching the filter

func (*D) QueryDeleteEventsByTargetId ΒΆ

func (d *D) QueryDeleteEventsByTargetId(c context.Context, targetEventId []byte) (
	evs event.S, err error,
)

QueryDeleteEventsByTargetId retrieves delete events targeting a specific event ID

func (*D) QueryEvents ΒΆ

func (d *D) QueryEvents(c context.Context, f *filter.F) (evs event.S, err error)

QueryEvents retrieves events matching the given filter

func (*D) QueryEventsWithOptions ΒΆ

func (d *D) QueryEventsWithOptions(
	c context.Context, f *filter.F, includeDeleteEvents bool, showAllVersions bool,
) (evs event.S, err error)

QueryEventsWithOptions retrieves events with specific options

func (*D) QueryForIds ΒΆ

func (d *D) QueryForIds(c context.Context, f *filter.F) (
	idPkTs []*store.IdPkTs, err error,
)

QueryForIds retrieves event IDs matching a filter

func (*D) QueryForSerials ΒΆ

func (d *D) QueryForSerials(c context.Context, f *filter.F) (
	serials types.Uint40s, err error,
)

QueryForSerials retrieves event serials matching a filter

func (*D) Ready ΒΆ

func (d *D) Ready() <-chan struct{}

Ready returns a channel that closes when the database is ready to serve requests. This allows callers to wait for database warmup to complete.

func (*D) RecordPayment ΒΆ

func (d *D) RecordPayment(
	pubkey []byte, amount int64, invoice, preimage string,
) error

RecordPayment records a payment for subscription extension

func (*D) RemoveNIP43Member ΒΆ

func (d *D) RemoveNIP43Member(pubkey []byte) error

RemoveNIP43Member removes a member

func (*D) RunMigrations ΒΆ

func (d *D) RunMigrations()

RunMigrations runs database migrations (no-op for dgraph)

func (*D) SaveEvent ΒΆ

func (d *D) SaveEvent(c context.Context, ev *event.E) (exists bool, err error)

SaveEvent stores a Nostr event in the Dgraph database. It creates event nodes and relationships for authors, tags, and references.

func (*D) SetLogLevel ΒΆ

func (d *D) SetLogLevel(level string)

SetLogLevel sets the logging level

func (*D) SetMarker ΒΆ

func (d *D) SetMarker(key string, value []byte) error

SetMarker sets a metadata marker

func (*D) SetRelayIdentitySecret ΒΆ

func (d *D) SetRelayIdentitySecret(skb []byte) error

SetRelayIdentitySecret sets the relay's identity secret key

func (*D) StoreInviteCode ΒΆ

func (d *D) StoreInviteCode(code string, expiresAt time.Time) error

StoreInviteCode stores an invite code with expiration

func (*D) Sync ΒΆ

func (d *D) Sync() (err error)

Sync flushes pending writes (DGraph handles persistence automatically)

func (*D) ValidateInviteCode ΒΆ

func (d *D) ValidateInviteCode(code string) (valid bool, err error)

ValidateInviteCode checks if an invite code is valid

func (*D) Wipe ΒΆ

func (d *D) Wipe() (err error)

Wipe removes all data

func (*D) WouldReplaceEvent ΒΆ

func (d *D) WouldReplaceEvent(ev *event.E) (bool, types.Uint40s, error)

WouldReplaceEvent checks if an event would replace existing events

Source Files ΒΆ

  • delete.go
  • dgraph.go
  • fetch-event.go
  • identity.go
  • import-export.go
  • logger.go
  • markers.go
  • nip43.go
  • query-events.go
  • save-event.go
  • schema.go
  • serial.go
  • subscriptions.go
  • utils.go

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL