Claude-skill-registry layer-persistence

Use when creating or modifying code in nomarr/persistence/. Persistence handles database and queue access. Never stores business logic.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/layer-persistence" ~/.claude/skills/majiayu000-claude-skill-registry-layer-persistence && rm -rf "$T"
manifest: skills/data/layer-persistence/SKILL.md
source content

Persistence Layer

Purpose: Own all database and queue access. Provide a clean data access API for higher layers.

Persistence is the data access layer:

  • Database
    class owns the connection
  • *Operations
    classes own SQL for specific tables
  • External code accesses via
    db.queue.enqueue()
    ,
    db.tags.get_track_tags()

Directory Structure

persistence/
├── db.py                   # Database class (connection owner)
├── arango_client.py        # ArangoDB client wrapper
├── database/               # *_aql.py modules (AQL queries)
│   ├── file_tags_aql.py    # Tag query operations
│   ├── library_files_aql.py # Library file operations
│   ├── worker_claims_aql.py # Worker claim operations
│   └── ...
└── __init__.py

Allowed Imports

# ✅ Allowed
from nomarr.helpers.dto import FileDict, LibraryDict
from nomarr.helpers.time_helper import now_ms

Forbidden Imports

# ❌ NEVER import these in persistence
from nomarr.services import ...      # No services
from nomarr.workflows import ...     # No workflows
from nomarr.components import ...    # No components
from nomarr.interfaces import ...    # No interfaces

Database Access Pattern

External code never imports from

persistence/database/
directly:

# ✅ Correct - access via Database instance
def some_workflow(db: Database):
    files = db.library_files.get_pending_files(library_key)
    tags = db.tags.get_song_tags(file_id)

# ❌ Wrong - direct import
from nomarr.persistence.database.library_files_aql import LibraryFilesAQL

ArangoDB ID Fields

Never rename

_id
or
_key
.

These are ArangoDB-native identifiers:

  • _key
    : Document key (unique within collection)
  • _id
    : Full document ID (
    collection/_key
    )
# ✅ Correct
{"_key": "abc123", "_id": "tracks/abc123", "title": "..."}

# ❌ Wrong - renaming to generic names
{"id": "abc123", "uuid": "abc123"}  # DON'T DO THIS

Operations Class Pattern

Each

*AQL
class owns all queries for a related set of collections:

class LibraryFilesAQL:
    def __init__(self, arango: ArangoClient):
        self._arango = arango
    
    def get_pending_files(self, library_key: str, limit: int = 100) -> list[FileDict]:
        """Get pending files for processing."""
        ...
    
    def mark_discovered(self, file_key: str, discovered_at: int) -> None:
        """Mark file as discovered."""
        ...
    
    def update_status(self, file_key: str, status: str) -> None:
        """Update file processing status."""
        ...

No Business Logic

Persistence only performs data access. No business decisions:

# ✅ Correct - pure data access
def get_pending_files(self, library_key: str, limit: int = 100) -> list[FileDict]:
    return self._arango.execute_query(
        "FOR file IN library_files FILTER file.library_key == @lib AND file.status == 'pending' LIMIT @limit RETURN file",
        bind_vars={"lib": library_key, "limit": limit},
    )

# ❌ Wrong - business logic in persistence
def get_files_to_process(self, library_key: str) -> list[FileDict]:
    files = self.get_pending_files(library_key)
    # ❌ Business logic - this belongs in a workflow
    if len(files) > 10 and self._is_overloaded():
        return files[:5]
    return files

Health Data vs Business Decisions

Persistence stores and retrieves health data. It does not make liveness decisions:

# ✅ Correct - persistence reports facts
def get_last_heartbeat(self, component_id: str) -> int | None:
    """Return timestamp of last heartbeat, or None if never seen."""
    ...

# ❌ Wrong - persistence making decisions
def is_component_healthy(self, component_id: str) -> bool:
    """Check if component is healthy."""
    # This logic belongs in a service or component, not persistence
    heartbeat = self.get_last_heartbeat(component_id)
    return heartbeat is not None and (now_ms() - heartbeat) < 30000

Validation Checklist

Before committing persistence code, verify:

  • Does this file import from services, workflows, components, or interfaces? → Violation
  • Does this code make business decisions? → Move to workflow/component
  • Are
    _id
    and
    _key
    preserved as-is? → Required
  • Is external code importing from
    database/*
    directly? → Access via
    Database
  • Is health/liveness logic here instead of in services? → Move to service

Layer Scripts

This skill includes validation scripts in

.github/skills/layer-persistence/scripts/
:

lint.py

Runs all linters on the persistence layer:

python .github/skills/layer-persistence/scripts/lint.py

Executes: ruff, mypy, vulture, bandit, radon, lint-imports

check_naming.py

Validates persistence naming conventions:

python .github/skills/layer-persistence/scripts/check_naming.py

Checks:

  • Database module files must end in
    _aql.py
  • Classes must end in
    Operations
    (e.g.,
    LibraryFilesOperations
    )