This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
PeerDB is a collaborative database and search platform built on PostgreSQL and Elasticsearch. It provides a document-based knowledge base with a claims-based schema, versioned storage, real-time collaboration, and an adaptive search interface. The system is designed to support multiple sites from a single installation with domain-based routing.
Key features:
- Versioned document store with full change history
- Real-time collaboration with conflict detection
- Claims-based document schema supporting 12 claim types (identifiers, strings, HTML, amounts, time, links, references, etc.)
- Adaptive search UI that automatically adjusts to data and provides relevant filters
- Multi-site support with separate schemas and indices per site
make- Build the complete application with embedded frontendmake build- Build backend binary with embedded frontend dist files
npm install- Install frontend dependenciesnpm run serve- Start Vite dev server (runs on port 5173, proxied through backend on 8080)npm run build- Build frontend for production (output todist/)
npm run generate-vue-i18n- Generate TypeScript definitions for vue-i18n fromsrc/locales/en.json
Important: The file src/vue-i18n.d.ts is automatically generated and should NOT be edited manually.
To update internationalization TypeScript definitions:
- Modify
src/locales/en.jsonwith your locale changes - Run
npm run generate-vue-i18nto regenerate the TypeScript definitions - The script
generate-vue-i18n.jsuses Vue i18n Global resource schema approach for type safety
make test- Run Go tests with coveragemake test-ci- Run tests with CI output formatsnpm run test-ci- Run frontend tests with Vitest (CI mode - exits after completion)npm run test- Run frontend tests with Vitest (watch mode - never exits, do NOT use in CI)npm run coverage- Generate frontend test coverage
make lint- Run Go linter (golangci-lint) with auto-fixmake fmt- Format Go code with gofumpt and goimportsnpm run lint- Run ESLint on frontend codenpm run lint-style- Run Stylelint on CSS/Vue filesnpm run lint-vue- Run Vue TypeScript compiler checknpm run fmt- Format frontend code with Prettier
- Comments: All comments must end with dots for consistency.
- Error Handling: When error is
errors.E, useerrEas variable name and assertion should be of formrequire.NoError(t, errE, "% -+#.1v", errE). - CI Commands: For backend-only changes, run these commands to match CI validation:
make lint- Go linter (golangci-lint) with auto-fixmake fmt- Go code formatting with gofumpt and goimportsmake test- Go tests with coveragemake lint-docs- Documentation linting (affects whole repo)make audit- Go security audit with nancy
- Comments: All comments must end with dots for consistency
- Import Convention: Always use
@/alias for internal imports, never relative paths (./,../) - Import Organization: Type imports for external packages must be at the top with
import type, followed by empty line, then type imports for local packages, followed by empty line, then regular imports for external packages, followed by empty line, then regular imports for local packages; each group sorted in alphabetical order - Internationalization: All user-facing text must use vue-i18n with global scope
- useI18n: Always use
useI18n({ useScope: 'global' })instead ofuseI18n() - i18n-t components: Always include
scope="global"attribute:<i18n-t keypath="..." scope="global"> - Technical terms like "passkey" should be extracted into translatable strings but not translated across languages
- Never put HTML in translated strings - HTML formatting must always be in Vue templates, not translation files
- ❌ Wrong:
"message": "<strong>Success!</strong> Operation completed" - ✅ Correct:
"message": "{strong} Operation completed"with<i18n-t>template interpolation
- ❌ Wrong:
- useI18n: Always use
- TypeScript: Strict typing enabled with vue-i18n message schema validation
- Formatting: Always run
npm run fmtafter making changes to maintain consistent code formatting- Use double quotes (
") for strings, not single quotes (') - Multi-line Vue template attributes should break after
>and before<on closing tags - Files should end with newlines
- Consistent spacing and indentation per Prettier configuration
- Use double quotes (
- CI Commands: For frontend-only changes, run these commands to match CI validation:
npm run lint- ESLint with auto-fixnpm run lint-vue- Vue TypeScript compilation checknpm run lint-style- Stylelint with auto-fixnpm run fmt- Prettier formattingnpm run test-ci- Frontend tests with coveragemake lint-docs- Documentation linting (affects whole repo)npm audit- Security audit
- Backend serves as proxy to Vite dev server in development mode (
-Dflag) - Production builds embed frontend files into Go binary via
embed.FS - Hot module replacement works through backend proxy during development
- TypeScript strict mode enabled
- Uses Vue 3 Composition API (Options API disabled via
__VUE_OPTIONS_API__: false)
- Both PostgreSQL and Elasticsearch must be running for integration tests
- Use
make test-cifor coverage reports - Frontend tests use Vitest with v8 coverage provider
- Go 1.25+ required
- Node.js 24+ required
- TLS certificates needed (recommend mkcert for local development)
- CompileDaemon for backend auto-reload during development
Backend (Go HTTP API)
├── Store (PostgreSQL) - Versioned key-value store with changesets
├── Coordinator - Real-time collaboration session management
├── Storage - Chunked file upload management
└── ES Bridge - Syncs Store changesets to Elasticsearch
Frontend (Vue 3 + TypeScript SPA)
└── API Client → Backend HTTP endpoints
Generic versioned key-value store with full change history. Uses PostgreSQL as backing storage with JSONB columns.
Key concepts:
- Changesets: Atomic units of change with metadata
- Views: Named branches (main view is primary)
- Versions: Identified by changeset ID and structure
- Patches: Forward and backward changes (AddClaimChange, SetClaimChange, RemoveClaimChange)
PeerDB usage: Stores documents as json.RawMessage with DocumentMetadata.
Manages append-only logs for real-time collaboration sessions.
Lifecycle: Begin session → Append operations → End/Discard session
Features:
- Conflict detection during concurrent edits
- Pagination (max 5000 operations per page)
- PostgreSQL-backed with JSONB columns
PeerDB usage: Tracks document edit sessions with conflict detection.
Claims-based document system supporting 12 claim types:
- IdentifierClaim: External IDs (e.g., Wikidata Q-IDs)
- StringClaim: Plain text strings
- HTMLClaim: Rich text with HTML
- AmountClaim: Numeric values with precision
- AmountIntervalClaim: Numeric intervals with bounds
- TimeClaim: Timestamps with precision
- TimeIntervalClaim: Time intervals with bounds
- LinkClaim: URL/IRI links
- ReferenceClaim: Relationships to other documents
- HasClaim: Property-only claim (can hold nested claims via sub-claims)
- NoneClaim: Explicitly states no value exists
- UnknownClaim: Value exists but is unknown
Core structure:
type D struct {
CoreDocument // ID (22-char identifier), Base (base for computing ID)
Claims // *ClaimTypes (collections of claims)
}
type CoreClaim struct {
ID identifier.Identifier
Confidence Confidence // float64 in [-1, 1]
Sub *ClaimTypes // optional sub-claims
}Important patterns:
- Use the Visitor pattern to traverse/manipulate claims
- Claims reference properties (also documents) via
Prop - Each claim embeds
CoreClaimwith ID, Confidence, and optional Sub-claims - Built-in classes and properties defined in
core/(NAME, DESCRIPTION, etc.)
Elasticsearch query builder with session-based filtering.
Filter types:
RefFilter: Filter by reference claimsAmountFilter: Filter by numeric rangesTimeFilter: Filter by time ranges
Limits: Max 1000 search results per query
Chunked file upload management with begin/append/end lifecycle.
Listens to Store changesets and synchronizes to Elasticsearch using bulk indexing.
Defined in routes.go using the WAF framework (gitlab.com/tozd/waf).
Document endpoints (/d/):
POST /d/create- Create documentPOST /d/beginEdit/:id- Start editingPOST /d/saveChange/:session?change=N- Append changePOST /d/endEdit/:session- Commit sessionPOST /d/discardEdit/:session- Discard session
Search endpoints (/s/):
POST /s/create- Create search sessionGET /s/:id- Get search UIGET /s/results/:id- Get search resultsGET /s/filters/:id- Get available filters
File endpoints (/f/):
POST /f/beginUpload- Start uploadPOST /f/uploadChunk/:session?start=N- Upload chunkPOST /f/endUpload/:session- Finalize upload
Config file (YAML format):
globals:
sites:
- domain: example.com
index: example_index
schema: example_schema
title: "Example Site"
elastic:
url: http://localhost:9200CLI flags override config file. Run ./peerdb --help for all options.
Single PeerDB instance can serve multiple sites:
- Each site has separate PostgreSQL schema and Elasticsearch index
- Sites share database pool and ES client
- Routing by domain via WAF framework
- Let's Encrypt automatic TLS certificates per domain
- Framework: Vue 3 with Composition API and TypeScript
- Build Tool: Vite for development and production builds
- Styling: Tailwind CSS with custom components
- Router: Vue Router for SPA navigation
- API Layer: Custom fetch wrappers in
src/api.ts - Internationalization: Vue-i18n v11 with precompiled messages (English and Slovenian support)
src/api.ts- Backend API clientsrc/document.ts- Document model (26KB, complex)src/search.ts- Search logic (32KB)src/time.ts- Timestamp handling with extended year supportsrc/components/- Reusable Vue componentssrc/views/- Page components (Home, DocumentGet, DocumentEdit, SearchGet)
- Schemas auto-created on first run (tables, views, stored procedures)
- Multi-site support via schema prefixes
- Connection pool uses serializable isolation level
- Custom error handling with request ID and schema tracking
- Index configuration generated in internal/search package
- Auto-created on first run if missing
- Run
./peerdb populateto initialize with core PeerDB properties