Establish the project foundation with three packages: - core: shared types (Header, Metadata, Value, ObjectType, UUID generation) - db: SQLite migration framework, connection management (WAL, FK, busy timeout), transaction helpers (StartTX/EndTX), time conversion - config: runtime configuration (DB path, blob store, Minio, gRPC addr) Includes initial schema migration (001_initial.sql) with 13 tables covering shared infrastructure, bibliographic data, and artifact repository. Full test coverage for all packages, strict linting (.golangci.yaml), and Makefile. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
// Package core defines shared types used across both the artifact repository
|
|
// and knowledge graph pillars.
|
|
package core
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// ObjectType enumerates the kinds of persistent objects in the system.
|
|
type ObjectType string
|
|
|
|
const (
|
|
ObjectTypeArtifact ObjectType = "artifact"
|
|
ObjectTypeSnapshot ObjectType = "snapshot"
|
|
ObjectTypeCitation ObjectType = "citation"
|
|
ObjectTypePublisher ObjectType = "publisher"
|
|
ObjectTypeNode ObjectType = "node"
|
|
ObjectTypeCell ObjectType = "cell"
|
|
)
|
|
|
|
// Header is attached to every persistent object.
|
|
type Header struct {
|
|
Meta Metadata
|
|
Categories []string
|
|
Tags []string
|
|
ID string
|
|
Type ObjectType
|
|
Created int64
|
|
Modified int64
|
|
}
|
|
|
|
// NewHeader creates a Header with a new UUID and current timestamps.
|
|
func NewHeader(objType ObjectType) Header {
|
|
now := time.Now().UTC().Unix()
|
|
return Header{
|
|
ID: NewUUID(),
|
|
Type: objType,
|
|
Created: now,
|
|
Modified: now,
|
|
Meta: Metadata{},
|
|
}
|
|
}
|
|
|
|
// Touch updates the Modified timestamp to now.
|
|
func (h *Header) Touch() {
|
|
h.Modified = time.Now().UTC().Unix()
|
|
}
|
|
|
|
const (
|
|
// ValueTypeUnspecified is used when the value type hasn't been explicitly
|
|
// set. It should be interpreted as a string.
|
|
ValueTypeUnspecified = "UNSPECIFIED"
|
|
|
|
// ValueTypeString is used when the value type should be explicitly
|
|
// interpreted as a string.
|
|
ValueTypeString = "String"
|
|
|
|
// ValueTypeInt is used when the value should be interpreted as an integer.
|
|
ValueTypeInt = "Int"
|
|
)
|
|
|
|
// Value stores a typed key-value entry. Contents is always stored as a string;
|
|
// the Type field tells consumers how to interpret it.
|
|
type Value struct {
|
|
Contents string
|
|
Type string
|
|
}
|
|
|
|
// Val creates a new Value with an unspecified type.
|
|
func Val(contents string) Value {
|
|
return Value{Contents: contents, Type: ValueTypeUnspecified}
|
|
}
|
|
|
|
// Vals creates a new Value with a string type.
|
|
func Vals(contents string) Value {
|
|
return Value{Contents: contents, Type: ValueTypeString}
|
|
}
|
|
|
|
// Metadata holds additional information that isn't explicitly part of a data
|
|
// definition. Keys are arbitrary strings; values carry type information.
|
|
type Metadata map[string]Value
|
|
|
|
// NewUUID returns a new random UUID string.
|
|
func NewUUID() string {
|
|
return uuid.NewString()
|
|
}
|
|
|
|
// ErrNoID is returned when a lookup is done on a struct that has no identifier.
|
|
var ErrNoID = errors.New("missing UUID identifier")
|
|
|
|
// MapFromList converts a string slice into a map[string]bool for set-like
|
|
// membership testing.
|
|
func MapFromList(list []string) map[string]bool {
|
|
m := make(map[string]bool, len(list))
|
|
for _, s := range list {
|
|
m[s] = true
|
|
}
|
|
return m
|
|
}
|
|
|
|
// ListFromMap converts a map[string]bool (set) back to a sorted slice.
|
|
func ListFromMap(m map[string]bool) []string {
|
|
list := make([]string, 0, len(m))
|
|
for k := range m {
|
|
list = append(list, k)
|
|
}
|
|
return list
|
|
}
|