feat: add independent platform modules and multi-provider storage

This commit is contained in:
2026-09-23 14:51:14 +08:00
parent f91034477b
commit 208a9c3fbd
57 changed files with 4890 additions and 48 deletions

62
queue/queue.go Normal file
View File

@@ -0,0 +1,62 @@
// Package queue defines durable, at-least-once work delivery. Handlers must be
// idempotent: a crash after a business commit but before ACK can cause redelivery.
// This is a message queue, not a workflow engine or a distributed transaction.
package queue
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
)
var ErrFull = errors.New("queue: capacity reached")
var ErrConflict = errors.New("queue: message ID already has different content")
var ErrLeaseLost = errors.New("queue: delivery ownership changed")
type Message struct {
ID string `json:"id"` // Stable producer identity, saved before submitting.
Name string `json:"name"`
Payload json.RawMessage `json:"payload"`
}
type Receipt struct {
ID string `json:"id"`
BackendID string `json:"backendId"`
}
type Delivery struct {
Message
Attempt int64 `json:"attempt"`
}
type Handler func(context.Context, Delivery) error
type Publisher interface {
Publish(context.Context, Message) (Receipt, error)
}
type Consumer interface {
Run(context.Context, Handler) error
}
// Reject marks a known permanent failure; it is retained for operator review.
// Ordinary errors are retried up to the consumer's configured attempt limit.
type Rejection struct{ Code string }
func (e *Rejection) Error() string { return "queue: rejected: " + e.Code }
func Reject(code string) error { return &Rejection{Code: code} }
func Validate(ctx context.Context, m Message, maxBytes int) error {
if ctx == nil {
return errors.New("queue: context required")
}
if err := ctx.Err(); err != nil {
return err
}
for _, v := range []string{m.ID, m.Name} {
if v == "" || len(v) > 128 || strings.IndexFunc(v, func(r rune) bool { return r < 33 || r > 126 }) >= 0 {
return errors.New("queue: ID and name must be 1..128 printable ASCII characters")
}
}
if len(m.Payload) > maxBytes || !json.Valid(m.Payload) {
return fmt.Errorf("queue: payload must be valid JSON within %d bytes", maxBytes)
}
return nil
}