Additions to domain/ and workspace/ chron-note packages

This commit is contained in:
2026-03-11 14:03:52 +08:00
parent 8edeb3ac99
commit 34d18e244e
16 changed files with 744 additions and 3 deletions

View File

@@ -1,2 +1,11 @@
package domain
import "errors"
var (
ErrInvalidObjectID = errors.New("invalid object id")
ErrInvalidBlobID = errors.New("invalid blob id")
ErrUnknownEventType = errors.New("unknown event type")
ErrInvalidEvent = errors.New("invalid event")
ErrDecode = errors.New("decode error")
)

View File

@@ -1 +1,137 @@
package domain
import (
"fmt"
"git.michelsen.id/phill/chron/chron-note/internal/util"
)
type EventType uint8
const (
EventObjectUpsert EventType = 1
EventObjectDelete EventType = 2
)
const encodingVersion uint8 = 1
type ObjectUpsert struct {
ObjectID ObjectID
Name string
Tags []string
HasBlob bool
Blob BlobID
}
type ObjectDelete struct {
ObjectID ObjectID
}
type Event struct {
Type EventType
Upsert *ObjectUpsert
Delete *ObjectDelete
}
func EncodeEvent(e Event) ([]byte, error) {
enc := util.NewEncoder(nil)
enc.U8(encodingVersion)
enc.U8(uint8(e.Type))
switch e.Type {
case EventObjectUpsert:
if e.Upsert == nil {
return nil, ErrInvalidEvent
}
u := e.Upsert
var flags uint8
if u.HasBlob {
flags |= 0x01
}
enc.U8(flags)
enc.BytesFixed(u.ObjectID[:])
enc.String(u.Name)
enc.StringSlice(u.Tags)
if u.HasBlob {
enc.BytesFixed(u.Blob[:])
}
case EventObjectDelete:
if e.Delete == nil {
return nil, ErrInvalidEvent
}
enc.BytesFixed(e.Delete.ObjectID[:])
default:
return nil, ErrUnknownEventType
}
if err := enc.Err(); err != nil {
return nil, err
}
return enc.Bytes(), nil
}
func DecodeEvent(b []byte) (Event, error) {
dec := util.NewDecoder(b)
ver := dec.U8()
if dec.Err() != nil {
return Event{}, ErrDecode
}
if ver != encodingVersion {
return Event{}, fmt.Errorf("%w: unsupported encoding version %d", ErrDecode, ver)
}
typ := EventType(dec.U8())
if dec.Err() != nil {
return Event{}, ErrDecode
}
switch typ {
case EventObjectUpsert:
flags := dec.U8()
var objID ObjectID
copy(objID[:], dec.BytesFixed(len(objID)))
name := dec.String()
tags := dec.StringSlice()
hasBlob := (flags & 0x01) != 0
var blob BlobID
if hasBlob {
copy(blob[:], dec.BytesFixed(len(blob)))
}
if dec.Err() != nil {
return Event{}, ErrDecode
}
return Event{
Type: typ,
Upsert: &ObjectUpsert{
ObjectID: objID,
Name: NormalizeName(name),
Tags: NormalizeTags(tags),
HasBlob: hasBlob,
Blob: blob,
},
}, nil
case EventObjectDelete:
var objID ObjectID
copy(objID[:], dec.BytesFixed(len(objID)))
if dec.Err() != nil {
return Event{}, ErrDecode
}
return Event{Type: typ, Delete: &ObjectDelete{ObjectID: objID}}, nil
default:
return Event{}, ErrUnknownEventType
}
}

View File

@@ -1 +1,74 @@
package domain
import (
"crypto/rand"
"encoding/hex"
"strings"
)
type ObjectID [16]byte
type BlobID [32]byte
func NewObjectID() (ObjectID, error) {
var id ObjectID
_, err := rand.Read(id[:])
return id, err
}
func ParseObjectID(s string) (ObjectID, error) {
s = strings.TrimSpace(s)
b, err := hex.DecodeString(s)
if err != nil || len(b) != len(ObjectID{}) {
return ObjectID{}, ErrInvalidObjectID
}
var id ObjectID
copy(id[:], b)
return id, nil
}
func (id ObjectID) String() string { return hex.EncodeToString(id[:]) }
func ParseBlobID(s string) (BlobID, error) {
s = strings.TrimSpace(s)
b, err := hex.DecodeString(s)
if err != nil || len(b) != len(BlobID{}) {
return BlobID{}, ErrInvalidBlobID
}
var id BlobID
copy(id[:], b)
return id, nil
}
func (id BlobID) String() string { return hex.EncodeToString(id[:]) }
type ObjectState struct {
ID ObjectID
Name string
Tags []string
Blob BlobID
HasBlob bool
Deleted bool
}
// Keep normalization here so all layers agree.
func NormalizeName(s string) string {
s = strings.TrimSpace(s)
return s
}
func NormalizeTags(tags []string) []string {
out := make([]string, 0, len(tags))
seen := make(map[string]struct{}, len(tags))
for _, t := range tags {
t = strings.TrimSpace(strings.ToLower(t))
if t == "" {
continue
}
if _, ok := seen[t]; ok {
continue
}
seen[t] = struct{}{}
out = append(out, t)
}
return out
}