This document is a developer-facing overview of Lode’s public API. It describes the entrypoints, defaults, and configuration shape used at the callsite.
Lode focuses on persistence structure: datasets, snapshots, manifests, metadata, and safe write semantics.
Execution, scheduling, and query planning are out of scope.
NewDataset(id, storeFactory, opts...) creates a dataset with a documented
default configuration. Options override parts of that default bundle.
Default bundle:
- Layout: DefaultLayout
- Partitioner: NoOp (via layout)
- Compressor: NoOp
- Codec: none (raw blob storage)
- Checksum: none (opt-in)
Example:
import (
"github.com/pithecene-io/lode/lode"
)
ds, _ := lode.NewDataset(
"mydata",
lode.NewFSFactory("/data"),
lode.WithCodec(lode.NewJSONLCodec()), // Optional: for structured records
)NewDatasetReader(storeFactory, opts...) creates a read facade.
Default behavior:
- Layout: DefaultLayout
Example:
reader, _ := lode.NewDatasetReader(
lode.NewFSFactory("/data"),
)
// Or with Hive layout (preferred for partitioned data):
reader, _ := lode.NewDatasetReader(
lode.NewFSFactory("/data"),
lode.WithHiveLayout("day"),
)NewVolume(id, storeFactory, totalLength, opts...) creates a sparse, range-addressable
byte space with manifest-driven commit semantics.
Volume is a coequal persistence paradigm alongside Dataset. Dataset builds complete objects before committing; Volume commits truth incrementally via sparse byte ranges.
vol, _ := lode.NewVolume(
"archive",
lode.NewFSFactory("/data"),
10<<30, // 10 GB total length
)
// Stage a byte range
blk, _ := vol.StageWriteAt(ctx, 0, bytes.NewReader(chunk))
// Commit staged blocks into an immutable snapshot
snapshot, _ := vol.Commit(ctx, []lode.BlockRef{blk}, lode.Metadata{"source": "peer-1"})
// Read committed range
data, _ := vol.ReadAt(ctx, snapshot.ID, 0, int64(len(chunk)))
// Get latest snapshot (for resume)
latest, _ := vol.Latest(ctx)Volume uses a fixed internal storage layout (volumes/<id>/...). The
Layout abstraction is Dataset-specific and does not apply to Volume.
Volume options:
WithChecksum(c)— opt-in integrity checksums on staged blocksWithRetryCount(n)— opt-in automatic CAS retry on commit conflict
Contract reference: CONTRACT_VOLUME.md
Options are opt-in and composable. They override default components when provided.
Options are validated at construction time. Passing an option that does not apply to the target (dataset vs reader) returns an error.
| Option | Dataset | DatasetReader | Volume | Notes |
|---|---|---|---|---|
WithHiveLayout(keys...) |
✅ | ✅ | ❌ | Preferred for Hive layout |
WithLayout(layout) |
✅ | ✅ | ❌ | For any layout |
WithCompressor(c) |
✅ | ❌ | ❌ | Write-time compression |
WithCodec(c) |
✅ | ❌ | ❌ | Record encoding |
WithChecksum(c) |
✅ | ❌ | ✅ | File / block checksums |
WithRetryCount(n) |
✅ | ❌ | ✅ | Bounded CAS retry on conflict |
WithRetryBaseDelay(d) |
✅ | ❌ | ✅ | Backoff base delay (default: 10ms) |
WithRetryMaxDelay(d) |
✅ | ❌ | ✅ | Backoff cap (default: 2s) |
WithRetryJitter(j) |
✅ | ❌ | ✅ | Jitter factor 0.0–1.0 (default: 1.0) |
Passing an inapplicable option to NewDatasetReader or NewVolume returns
an error at construction time.
Contract reference: CONTRACT_WRITE_API.md, CONTRACT_READ_API.md
Each configurable component has a public constructor. The public surface includes a curated set of components:
Storage adapters:
NewFSFactory(root)- Filesystem storageNewMemoryFactory()- In-memory storages3.New(client, config)- S3-compatible storage (see below)
Layouts:
NewDefaultLayout()- Default novice-friendly layout (used automatically)NewHiveLayout(keys...) (layout, error)- Partition-first layout (preferWithHiveLayoutfor fluent API)NewFlatLayout()- Minimal flat layout
Compressors:
NewNoOpCompressor()- No compression (default)NewGzipCompressor()- Gzip compressionNewZstdCompressor()- Zstd compression (higher ratio, faster decompression)
Codecs:
NewJSONLCodec()- JSON Lines format (streaming-capable)NewParquetCodec(schema, opts...) (Codec, error)- Apache Parquet columnar format (non-streaming)
Checksums:
NewMD5Checksum()- MD5 file checksums (opt-in)
Constructed components are intended to be passed into dataset or reader construction.
Record helpers:
D- Shorthand alias formap[string]anyin callsites and examplesR(...)- Convenience helper to build[]anyfrom record literals
Interfaces:
Timestamped- Optional interface for records with timestamps (see below)StatisticalCodec- Optional codec interface for per-file column statisticsStatisticalStreamEncoder- Optional stream encoder interface for per-file column statistics
Types (per-file statistics):
FileStats- Per-file row count and column statisticsColumnStats- Per-column min, max, null count, and distinct count
Range read support:
Store.ReadRange(ctx, path, offset, length)- Read byte range from objectStore.ReaderAt(ctx, path)- Getio.ReaderAtfor random accessDatasetReader.ReaderAt(ctx, obj)- Getio.ReaderAtfor data object
Range reads enable efficient access to columnar formats (Parquet footers), block-indexed logs, and partial artifact previews.
Records can optionally implement the Timestamped interface to enable
automatic min/max timestamp tracking in manifests:
type Timestamped interface {
Timestamp() time.Time
}When records implement this interface, Dataset.Write computes
MinTimestamp and MaxTimestamp from the data and populates the manifest.
This enables time-range pruning by query engines.
Example:
type Event struct {
ID string
EventTime time.Time
Data lode.D
}
func (e Event) Timestamp() time.Time { return e.EventTime }
// Write computes min/max from Event.Timestamp()
ds.Write(ctx, []any{Event{...}, Event{...}}, lode.Metadata{})Records that do not implement Timestamped (including lode.D)
result in nil timestamp fields in the manifest—this is valid and indicates
timestamps are not applicable for that snapshot.
The Parquet codec provides columnar storage with explicit schema definition.
schema := lode.ParquetSchema{
Fields: []lode.ParquetField{
{Name: "id", Type: lode.ParquetInt64},
{Name: "name", Type: lode.ParquetString},
{Name: "score", Type: lode.ParquetFloat64, Nullable: true},
},
}
codec, err := lode.NewParquetCodec(schema)
if err != nil {
// Handle schema validation error (bad types, empty names, duplicates)
}
ds, _ := lode.NewDataset("analytics", factory,
lode.WithCodec(codec),
lode.WithCompressor(lode.NewNoOpCompressor()), // Important: avoid double compression
)| Type | Go Input Types | Decoded Type |
|---|---|---|
ParquetInt32 |
int, int32, int64, float64 |
int32 |
ParquetInt64 |
int, int32, int64, float64 |
int64 |
ParquetFloat32 |
float32, float64 |
float32 |
ParquetFloat64 |
float32, float64 |
float64 |
ParquetString |
string |
string |
ParquetBool |
bool |
bool |
ParquetBytes |
[]byte, string |
[]byte |
ParquetTimestamp |
time.Time, string (RFC3339) |
time.Time |
Parquet codec does NOT support streaming.
Parquet files require a footer that references all row groups, making true streaming
impossible. StreamWriteRecords with a Parquet codec returns ErrCodecNotStreamable.
Use Dataset.Write for Parquet encoding:
// Correct: use Write for Parquet
snapshot, err := ds.Write(ctx, records, metadata)
// Wrong: StreamWriteRecords returns ErrCodecNotStreamable
_, err := ds.StreamWriteRecords(ctx, iter, metadata)
// err is ErrCodecNotStreamableImportant: Parquet has internal compression. Do not double-compress.
| Configuration | Result |
|---|---|
Parquet + NewNoOpCompressor() |
✅ Correct: Parquet handles compression internally |
Parquet + NewGzipCompressor() |
❌ Wasteful: double compression, minimal benefit |
Parquet + WithParquetCompression(lode.ParquetCompressionSnappy) |
✅ Correct: uses Parquet's internal Snappy |
| Error | Cause |
|---|---|
ErrSchemaViolation |
Missing required field, type mismatch, invalid schema |
ErrInvalidFormat |
Malformed Parquet file, corrupted data |
ErrCodecNotStreamable |
StreamWriteRecords called with Parquet codec |
Contract reference: CONTRACT_PARQUET.md
The Parquet codec implements StatisticalCodec and automatically populates
per-file column statistics on each FileRef during Write:
snap, _ := ds.Write(ctx, records, metadata)
for _, f := range snap.Manifest.Files {
if f.Stats != nil {
fmt.Println("rows:", f.Stats.RowCount)
for _, col := range f.Stats.Columns {
fmt.Printf(" %s: min=%v max=%v nulls=%d\n",
col.Name, col.Min, col.Max, col.NullCount)
}
}
}Statistics are computed from Go record values during encoding. Orderable types (int32, int64, float32, float64, string, timestamp) get min/max. All columns get null count. Boolean and bytes columns have no min/max.
Codecs that do not implement StatisticalCodec (e.g., JSONL) produce no stats —
FileRef.Stats is nil and omitted from the manifest JSON.
Writes always take explicit caller-supplied metadata. Empty metadata is
valid; nil metadata is coalesced to Metadata{}.
Dataset.Write(ctx, data, metadata) creates a snapshot from in-memory data.
Dataset.StreamWrite(ctx, metadata) returns a StreamWriter for single-pass
streaming writes of a single binary payload. StreamWriter.Write streams bytes,
Commit finalizes and returns a snapshot, and Abort discards the write.
If Close is called before Commit, the stream is aborted and no snapshot is created.
Streamed writes are raw-blob only: codecs are not applied and row count is 1.
Dataset.StreamWriteRecords(ctx, records, metadata) consumes a pull-based iterator
of records and streams them through a streaming-capable codec. If the configured
codec does not support streaming, StreamWriteRecords returns an error.
| Constraint | StreamWrite | StreamWriteRecords |
|---|---|---|
| Codec allowed | ❌ Returns ErrCodecConfigured |
✅ Required (streaming-capable) |
| Partitioning | ❌ Not supported | ❌ Returns ErrPartitioningNotSupported |
| Row count | Always 1 |
Equals records consumed |
| Nil iterator | N/A | Returns ErrNilIterator |
Why no partitioning? Single-pass streaming cannot buffer records to partition
by key. Use Write for partitioned data.
Contract reference: CONTRACT_WRITE_API.md §StreamWrite Semantics, §StreamWriteRecords Semantics
Common errors when using streaming APIs:
| Error | API | Cause | Resolution |
|---|---|---|---|
ErrCodecConfigured |
StreamWrite |
Codec is configured | Remove codec or use Write |
ErrCodecNotStreamable |
StreamWriteRecords |
Codec doesn't support streaming | Use streaming codec (JSONL) or Write |
ErrNilIterator |
StreamWriteRecords |
Iterator is nil | Provide valid RecordIterator |
ErrPartitioningNotSupported |
StreamWriteRecords |
Partitioning configured | Use Write for partitioned data |
| On failure: |
- No manifest is written (snapshot does not exist)
- Partial data may remain in storage (best-effort cleanup)
- Use
errors.Is()to check sentinel errors (ErrCodec*,ErrNil*,ErrPartitioning*)
Contract reference: CONTRACT_ERRORS.md
nilmetadata is coalesced toMetadata{}(passnilorMetadata{}for empty).- Raw blob mode (no codec) requires exactly one
[]byteelement inWrite. - Raw blob mode cannot use partitioning (no record fields to extract keys).
WithHiveLayoutrequires at least one partition key (validated on apply).ListDatasetsreturnsErrNoManifestswhen storage has objects but no manifests.- Layouts that do not model datasets (e.g., flat) return
ErrDatasetsNotModeled. ReaderAtmay return anio.ReaderAtthat also implementsio.Closer; close it when done.- Checksums are computed and recorded in manifests only when configured.
- Concurrent
Write/StreamWritecalls to the same dataset are safe only with a CAS-capable store (ConditionalWriter). Without CAS, serialize writes externally. SeeCONTRACT_WRITE_API.md§Concurrent Blob Write Safety. StreamWriteis only valid when no codec is configured; otherwise it returns an error.StreamWriteRecordsrequires a streaming-capable codec; otherwise it returns an error.StreamWriteRecordsdoes not support partitioning (single-pass streaming cannot partition).- Aborted streams leave no snapshot; partial objects may remain (see "Safety Guarantees").
Lode's manifest tracks only files written through Lode's write path.
Files written via Store.Put() directly — bypassing Dataset.Write,
StreamWrite, or StreamWriteRecords — are not recorded in the manifest
and are not discoverable via Snapshots(), Read(), or any Dataset API.
This is by design. The manifest is authoritative because Lode produced the files it describes: it knows their size, computed their checksums (when configured), and applied the configured codec and compressor. Files Lode didn't write cannot carry these guarantees.
| Pattern | When to Use | Completeness |
|---|---|---|
StreamWrite per file |
Each sidecar is its own snapshot; manifest tracks it | Full — Snapshots() enumerates all committed blobs |
Write with []byte data |
Small blobs that fit in memory | Full — manifest tracks the file |
File inventory in Metadata |
Caller manages storage; Lode tracks the inventory | Caller-verified — paths listed in metadata, existence checked by caller |
// Each sidecar file gets its own snapshot via StreamWrite
sw, _ := ds.StreamWrite(ctx, lode.Metadata{"filename": "model.bin", "run_id": "r-123"})
io.Copy(sw, file)
snapshot, _ := sw.Commit(ctx)
// snapshot.Manifest.Files[0] tracks the blob with size and checksum// Caller writes files outside Lode and records paths in metadata
store.Put(ctx, "custom/path/model.bin", modelReader)
store.Put(ctx, "custom/path/index.bin", indexReader)
snapshot, _ := ds.Write(ctx, records, lode.Metadata{
"sidecar_files": []string{"custom/path/model.bin", "custom/path/index.bin"},
})
// Caller can later read snapshot.Manifest.Metadata["sidecar_files"]
// to enumerate files, but must verify existence independentlyAnti-pattern: Writing files via Store.Put() without tracking them in
metadata or using StreamWrite, then expecting the manifest to enumerate them.
- Use
Writefor in-memory data, partitioned data, or codecs that do not support streaming. - Use
StreamWritefor large binary payloads that should be streamed once (no codec). - Use
StreamWriteRecordsfor large record streams with streaming-capable codecs (no partitioning).
| Compressor | Use When | Trade-offs |
|---|---|---|
NewNoOpCompressor() |
Data is already compressed, or compression overhead not justified | No CPU cost; no size reduction |
NewGzipCompressor() |
Broad compatibility required (gzip is universal) | Good ratio; moderate speed |
NewZstdCompressor() |
Best compression ratio or fast decompression needed | Better ratio than gzip; faster decompression |
Notes:
- Compressor choice is recorded in manifests; readers must support the compressor used
- Compression is applied after codec encoding (if any)
- Streaming writes (
StreamWrite,StreamWriteRecords) apply compression on-the-fly
Contract reference: CONTRACT_LAYOUT.md §Compressor
Ensure storage exists before constructing a dataset, volume, or reader.
Lode does not create storage infrastructure (directories, buckets). This is intentional:
- No hidden side effects: Constructors do not modify external state
- Explicit provisioning: Callers control when and how storage is created
- Backend symmetry: Same pattern for filesystem, S3, and other adapters
| Backend | Prerequisite | Example |
|---|---|---|
| Filesystem | Directory must exist | mkdir -p /data/lode |
| S3 | Bucket must exist | aws s3 mb s3://my-bucket |
| Memory | None (in-process) | — |
If provisioning helpers are needed, implement them outside Lode's core APIs:
// Wrapper that ensures storage exists before constructing dataset
func EnsureFSDataset(id, root string, opts ...lode.Option) (lode.Dataset, error) {
if err := os.MkdirAll(root, 0755); err != nil {
return nil, fmt.Errorf("create storage root: %w", err)
}
return lode.NewDataset(id, lode.NewFSFactory(root), opts...)
}This keeps Lode's constructors pure and predictable.
Contract reference: CONTRACT_STORAGE.md
Manifest presence is the commit signal.
A snapshot becomes visible only after its manifest is successfully written to storage. Data files may exist before the manifest, but they are not discoverable until the manifest references them.
StreamWriter.Commit()writes the manifest; the snapshot is invisible until then.StreamWriter.Abort()orClose()withoutCommit()ensures no manifest is written.- On error during streaming, no manifest is written (the write never "happened").
Contract reference: CONTRACT_WRITE_API.md §Read-after-write Visibility, CONTRACT_STORAGE.md §Commit Semantics
Lode uses optimistic concurrency (CAS) to detect conflicting writes. Whether CAS is available depends on which storage adapter you use:
| Adapter | CAS | Concurrent writers | What happens on conflict |
|---|---|---|---|
| Memory | ✅ | Safe without coordination | ErrSnapshotConflict — retry with Latest() |
| S3 | ✅ | Safe without coordination | ErrSnapshotConflict — retry with Latest() |
| FS (Unix) | ✅ | Safe without coordination | ErrSnapshotConflict — retry with Latest() |
| FS (non-Unix) | ❌ | Single-writer only | Undefined — serialize writes externally |
| Custom adapter | Depends | Implement ConditionalWriter for CAS |
Falls back to Delete+Put without it |
CAS is always-on when the adapter supports it. No configuration required.
Multiple separate process instances (or separate Dataset/Volume values)
MAY safely write to the same dataset or volume without external coordination.
A single Dataset or Volume value is NOT safe for concurrent use by multiple
goroutines.
On conflict (manual retry):
- Catch
ErrSnapshotConflict - Re-read state via
Latest() - Merge or rebuild, then re-commit
On conflict (automatic retry):
Configure WithRetryCount(n) at construction time. The commit path
automatically refreshes state and re-parents the manifest on conflict,
with jittered exponential backoff. Data files are written once — only
the manifest and pointer CAS are retried. Customize backoff with
WithRetryBaseDelay, WithRetryMaxDelay, and WithRetryJitter.
Data files are immutable and already persisted — retry cost is one manifest write plus one pointer swap.
Callers MUST ensure at most one writer is active per dataset or volume at any time. External coordination (locks, queues, leader election) is the caller's responsibility. Without this, concurrent writers may corrupt snapshot history.
Contract reference: CONTRACT_WRITE_API.md §Concurrency, CONTRACT_VOLUME.md §Concurrency, CONTRACT_STORAGE.md §ConditionalWriter Capability
CAS replaces Delete+Put (2 store calls) with a single CompareAndSwap,
reducing both latency and allocation count. Stores without ConditionalWriter
retain the existing Delete+Put path with no change.
Benchmarks on the in-memory adapter (20 sequential writes per iteration, AMD Ryzen 9 5900XT):
| Metric | Delete+Put | CAS (ConditionalWriter) | Delta |
|---|---|---|---|
| ns/op | 161,603 | 136,200 | −16% |
| B/op | 110,587 | 98,710 | −11% |
| allocs | 740 | 720 | −3% |
For uploads exceeding the storage adapter's atomic threshold (e.g., 5GB for S3),
the S3 adapter uses conditional completion (If-None-Match on CompleteMultipartUpload)
to provide the same atomic no-overwrite guarantee as small uploads.
Both upload paths now provide atomic no-overwrite guarantees:
- Small uploads (≤ 5GB): Atomic via
PutObjectwithIf-None-Match - Large uploads (> 5GB): Atomic via
CompleteMultipartUploadwithIf-None-Match
Preflight existence checks are retained as a fail-fast optimization to avoid uploading parts for objects that already exist.
Backend Compatibility Caveat:
The atomic guarantee for large uploads (>5GB) depends on backend support for
If-None-Match on CompleteMultipartUpload:
| Backend | Conditional Multipart | Status |
|---|---|---|
| Cloudflare R2 | Supported | ✅ Production verified |
| AWS S3 | Supported | ✅ Expected compatible |
| MinIO | Unknown | |
| LocalStack | Unknown |
For backends marked
Contract reference: CONTRACT_STORAGE.md §Put Upload Paths, CONTRACT_WRITE_API.md §Storage-Level Concurrency
Cleanup of partial objects is best-effort, not guaranteed.
On abort or error before commit:
- No manifest is written (the snapshot does not exist).
- Partial data objects may remain in storage.
- Callers should not rely on automatic cleanup of partial objects.
- Cleanup uses an independent context to maximize success even if the caller's context was canceled.
Failure to delete a partial object does not create a snapshot.
Contract reference: CONTRACT_ERRORS.md §Streaming API Errors, CONTRACT_STORAGE.md §Streaming Write Atomicity
Errors are returned for invalid configuration, storage failures, or missing objects. Error semantics are stable and documented.
Use errors.Is() to check for sentinel errors:
snap, err := ds.Latest(ctx)
if errors.Is(err, lode.ErrNoSnapshots) {
// Dataset exists but has no committed snapshots
}| Sentinel | Meaning | Typical Source |
|---|---|---|
ErrNotFound |
Object/path does not exist | Storage, DatasetReader |
ErrNoSnapshots |
Dataset or Volume has no committed snapshots | Dataset, Volume |
ErrNoManifests |
Storage has objects but no valid manifests | DatasetReader |
ErrPathExists |
Write to existing path (immutability violation) | Storage |
ErrInvalidPath |
Path escapes root or has invalid parameters | Storage |
ErrDatasetsNotModeled |
Layout doesn't support dataset enumeration | DatasetReader |
ErrManifestInvalid |
Manifest fails validation | DatasetReader |
ErrCodecConfigured |
StreamWrite called with codec configured | Dataset |
ErrCodecNotStreamable |
StreamWriteRecords with non-streaming codec | Dataset |
ErrNilIterator |
Nil iterator passed to StreamWriteRecords | Dataset |
ErrOptionNotValidForDataset |
Option not applicable to Dataset | Dataset |
ErrOptionNotValidForDatasetReader |
Option not applicable to DatasetReader | DatasetReader |
ErrOptionNotValidForVolume |
Option not applicable to Volume | Volume |
ErrPartitioningNotSupported |
StreamWriteRecords with partitioning | Dataset |
ErrRangeReadNotSupported |
Store doesn't support range reads | Storage |
ErrRangeMissing |
Volume ReadAt range not fully committed | Volume |
ErrOverlappingBlocks |
Committed blocks overlap in cumulative manifest | Volume |
ErrSnapshotConflict |
Another writer committed since parent was resolved (CAS) | Dataset, Volume |
ErrSchemaViolation |
Record doesn't conform to Parquet schema | Parquet Codec |
ErrInvalidFormat |
Malformed or corrupted Parquet file | Parquet Codec |
Retry-safe:
- Storage I/O errors (network, timeout) — may retry
ErrNotFoundduring race — may retry if expecting eventual consistencyErrSnapshotConflict— re-readLatest(), merge state, re-commit (or useWithRetryCountfor automatic retry)
Non-retry (configuration/logic error):
ErrDatasetsNotModeled— reconfigure with different layoutErrManifestInvalid— data corruption, investigate sourceErrPathExists— logic error (double-write attempt)- Component mismatch — reconfigure dataset or use matching snapshot
Contract reference: CONTRACT_ERRORS.md
The lode/s3 package provides an S3-compatible storage adapter.
Public API:
s3.New(client, config)- Create store from AWS SDK clients3.Config{Bucket, Prefix}- Store configuration
Client construction uses the AWS SDK directly. This keeps Lode's API surface minimal while giving you full control over credentials, endpoints, and options.
import (
"github.com/aws/aws-sdk-go-v2/config"
awss3 "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/pithecene-io/lode/lode/s3"
)
cfg, _ := config.LoadDefaultConfig(ctx)
client := awss3.NewFromConfig(cfg)
store, _ := s3.New(client, s3.Config{
Bucket: "my-bucket",
Prefix: "lode-data/",
})import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
awss3 "github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/pithecene-io/lode/lode/s3"
)
cfg, _ := config.LoadDefaultConfig(ctx,
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider("test", "test", ""),
),
)
client := awss3.NewFromConfig(cfg, func(o *awss3.Options) {
o.BaseEndpoint = aws.String("http://localhost:4566")
o.UsePathStyle = true
})
store, _ := s3.New(client, s3.Config{Bucket: "my-bucket"})cfg, _ := config.LoadDefaultConfig(ctx,
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider("minioadmin", "minioadmin", ""),
),
)
client := awss3.NewFromConfig(cfg, func(o *awss3.Options) {
o.BaseEndpoint = aws.String("http://localhost:9000")
o.UsePathStyle = true
})
store, _ := s3.New(client, s3.Config{Bucket: "my-bucket"})cfg, _ := config.LoadDefaultConfig(ctx,
config.WithRegion("auto"),
config.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""),
),
)
client := awss3.NewFromConfig(cfg, func(o *awss3.Options) {
o.BaseEndpoint = aws.String("https://" + accountID + ".r2.cloudflarestorage.com")
})
store, _ := s3.New(client, s3.Config{Bucket: "my-bucket"})Bucket requirement: The bucket must exist before use. Lode does not create buckets.
Consistency: AWS S3 provides strong read-after-write consistency. Other backends may differ — consult their documentation.
Examples under examples/ use the public API only and demonstrate the
default bundle, explicit configuration, and required metadata.