blob

package
v0.229.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package blob defines a read-write, path-addressed object store with conditional writes -- the seam the dynamic tier uses to hold BIBFRAME grains in S3-compatible storage. It is the fuller sibling of storage.Sink: Sink stays write-only for build pipelines; Store adds Get/List/Delete and ETag-based optimistic concurrency for read-modify-write editorial publishing. Implementations here are stdlib-only (local directory, memory); cloud stores (S3/R2/MinIO) implement the same interface in their own packages so their SDKs never reach the core.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("blob: not found")

ErrNotFound reports that no object exists at the requested path.

View Source
var ErrPreconditionFailed = errors.New("blob: precondition failed")

ErrPreconditionFailed reports that a conditional Put lost its race: the object's current state does not satisfy the request's IfMatch/IfNoneMatch. Callers recover by re-reading the object and retrying.

View Source
var ErrReadOnly = errors.New("blob: store is read-only")

ErrReadOnly reports a write attempted against a read-only store.

Functions

func PutStream

func PutStream(ctx context.Context, s Store, path string, r io.Reader, opts PutOptions) (string, error)

PutStream writes r to path through the store's streaming capability when present, and falls back to buffering the reader into a plain Put (memory stores hold the payload either way; wrappers without the capability keep their Put semantics, e.g. read-only rejection).

func ReaderAt added in v0.27.0

func ReaderAt(ctx context.Context, s Store, path string) (io.ReaderAt, int64, string, error)

ReaderAt adapts one object to io.ReaderAt: ranged reads through the store's RangeReader capability when present, else one whole-object Get held in memory. The returned size is the object's current size; callers that cache derived state should key it by the returned ETag.

func SinkOf

func SinkOf(s Store) storage.Sink

SinkOf adapts a Store to the write-only storage.Sink so existing build-side call sites (grain and artifact writers) can target any Store unchanged. Writes are buffered and stored unconditionally on Close.

func ValidatePath

func ValidatePath(path string) error

ValidatePath rejects paths that are empty, absolute, or contain empty, ".", or ".." segments, so no Store implementation can be walked outside its root.

Types

type DirStore

type DirStore struct {
	// contains filtered or unexported fields
}

DirStore is a Store backed by a local directory tree -- the dev/test and self-host default. ETags are the sha256 of the object's content. Conditional writes are enforced under a process-local mutex, which makes them exact within one process and best-effort against concurrent external writers; production multi-writer deployments use an object store with native conditional PUTs.

Computed ETags are cached against each file's (mtime, size), so List stats rather than reads files whose cache entry is current -- the same best-effort stance as the CAS: an external writer that preserves both mtime and size can serve a stale tag until the next content read.

func NewDir

func NewDir(dir string) *DirStore

NewDir returns a DirStore rooted at dir.

func (*DirStore) Delete

func (d *DirStore) Delete(ctx context.Context, path string) error

Delete removes the object, or returns ErrNotFound.

func (*DirStore) Get

func (d *DirStore) Get(ctx context.Context, path string) ([]byte, string, error)

Get returns the object's content and ETag, or ErrNotFound.

func (*DirStore) GetRange added in v0.27.0

func (d *DirStore) GetRange(ctx context.Context, path string, offset, length int64) ([]byte, error)

GetRange implements the RangeReader capability: [offset, offset+length) of the file, clamped to its end.

func (*DirStore) List

func (d *DirStore) List(ctx context.Context, prefix string) iter.Seq2[Entry, error]

List yields entries under prefix in lexicographic path order. Prefixes are plain string prefixes over slash-separated paths (as in object stores), not directory boundaries. The walk is scoped to the deepest directory the prefix names, files whose cached ETag is current are stat'd rather than read, and a file deleted mid-walk is skipped instead of truncating the listing.

func (*DirStore) Put

func (d *DirStore) Put(ctx context.Context, path string, data []byte, opts PutOptions) (string, error)

Put writes the object subject to opts' preconditions, creating parent directories as needed. The write is a temp-file rename, so readers never observe a partial object.

func (*DirStore) PutStream

func (d *DirStore) PutStream(ctx context.Context, path string, r io.Reader, opts PutOptions) (string, error)

PutStream implements blob.StreamPutter: the payload streams straight into the temp file (hashed as it copies), so only the copy buffer is held in memory. The copy runs outside the store lock; preconditions are checked and the rename performed under it, same as Put.

type Entry

type Entry struct {
	Path string
	ETag string
	Size int64
}

Entry describes one stored object in a List result.

type MemStore

type MemStore struct {
	// contains filtered or unexported fields
}

MemStore is an in-memory Store with exact conditional-write semantics -- the reference implementation for tests and local development.

func NewMem

func NewMem() *MemStore

NewMem returns an empty MemStore.

func (*MemStore) Delete

func (m *MemStore) Delete(ctx context.Context, path string) error

Delete removes the object, or returns ErrNotFound.

func (*MemStore) Get

func (m *MemStore) Get(ctx context.Context, path string) ([]byte, string, error)

Get returns the object's content and ETag, or ErrNotFound.

func (*MemStore) GetRange added in v0.27.0

func (m *MemStore) GetRange(ctx context.Context, path string, offset, length int64) ([]byte, error)

GetRange implements the RangeReader capability: [offset, offset+length) of the object, clamped to its end.

func (*MemStore) List

func (m *MemStore) List(ctx context.Context, prefix string) iter.Seq2[Entry, error]

List yields entries under prefix in lexicographic path order.

func (*MemStore) Put

func (m *MemStore) Put(ctx context.Context, path string, data []byte, opts PutOptions) (string, error)

Put stores the object subject to opts' preconditions.

type PutOptions

type PutOptions struct {
	IfMatch         string
	IfNoneMatch     bool
	ContentType     string
	ContentEncoding string
}

PutOptions carries the preconditions and metadata for a Put. IfMatch and IfNoneMatch are mutually exclusive: IfMatch (non-empty) requires the object to exist with exactly that ETag (a missing object fails the precondition); IfNoneMatch requires that no object exists at the path (create-only). ContentType is advisory; stores without metadata ignore it.

ContentEncoding is advisory in the same way, and describes the bytes actually stored: "gzip" means the object holds a gzip stream that a client is expected to decompress transparently, with ContentType naming what it decompresses to. It matters because a presigned URL hands the object straight to a browser (see export.Service.DownloadURL), so the metadata, not the serving code, is what tells that browser how to read it.

type RangeReader added in v0.27.0

type RangeReader interface {
	GetRange(ctx context.Context, path string, offset, length int64) ([]byte, error)
}

RangeReader is an optional Store capability: read [offset, offset+length) of an object without fetching the whole blob (range-served vocabulary index artifacts). Implementations return exactly length bytes unless the range runs past the object's end, where they return the available prefix (io.ReaderAt semantics come from the ReaderAt adapter, not from GetRange itself). Reads of a missing object return ErrNotFound.

type Signer

type Signer interface {
	SignedGetURL(ctx context.Context, path string, ttl time.Duration) (string, error)
}

Signer is an optional Store capability: a time-limited, unauthenticated download URL for an object (S3 presigned GETs). Stores without a native equivalent simply do not implement it; callers must type-assert and fall back to serving the bytes themselves.

type Store

type Store interface {
	// Get returns the object's content and current ETag, or ErrNotFound.
	Get(ctx context.Context, path string) (data []byte, etag string, err error)
	// Put writes the object subject to opts' preconditions and returns the
	// new ETag. Violated preconditions return ErrPreconditionFailed.
	Put(ctx context.Context, path string, data []byte, opts PutOptions) (etag string, err error)
	// List yields entries whose path starts with prefix, in lexicographic
	// path order.
	List(ctx context.Context, prefix string) iter.Seq2[Entry, error]
	// Delete removes the object, or returns ErrNotFound.
	Delete(ctx context.Context, path string) error
}

Store is a path-addressed object store. Paths are relative, slash-separated, and must not contain empty, ".", or ".." segments. ETags are opaque strings that change whenever an object's content changes; equal content need not yield equal ETags across different Store implementations.

func ReadOnly

func ReadOnly(s Store) Store

ReadOnly returns a read-only view of s: Get and List are served from s; Put and Delete return ErrReadOnly.

type StreamPutter

type StreamPutter interface {
	PutStream(ctx context.Context, path string, r io.Reader, opts PutOptions) (etag string, err error)
}

StreamPutter is an optional Store capability: write an object from a reader without holding the whole payload in memory ( full-corpus exports and vocabulary snapshot installs are output-sized). DirStore streams into its temp file; the S3 store spools to a local temp file for a seekable upload body. Use the PutStream helper rather than type-asserting directly.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL