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.mdsource 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:
class owns the connectionDatabase
classes own SQL for specific tables*Operations- 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
or _id
._key
These are ArangoDB-native identifiers:
: Document key (unique within collection)_key
: Full document ID (_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
and_id
preserved as-is? → Required_key - Is external code importing from
directly? → Access viadatabase/*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
lint.pyRuns 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
check_naming.pyValidates 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
(e.g.,Operations
)LibraryFilesOperations