strata-go

module
v0.0.0-...-747fa0c Latest Latest
Warning

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

Go to latest
Published: Jun 21, 2026 License: Apache-2.0

README

Strata Go

Strata Go is a Go SDK and embeddable Strata runtime for applications that need a consistent, layered storage model for structured state and related binary payloads.

At the API level, Strata stores append-only stories inside anthologies. Each story is a timeline of immutable chapters. A chapter contains JSON plus zero or more named artifacts such as logs, model outputs, reports, images, or other binary payloads. Small artifact bytes can be returned inline with the chapter; larger artifacts are stored in a blob layer and fetched lazily through the same artifact interface.

The same Go client can talk to a remote Strata service, run the Go daemon in-process without opening a TCP port, or start a local Strata-compatible HTTP daemon backed by local storage.

Why Strata

Strata is useful when an application needs to keep durable, ordered state while also attaching larger files to each step of that state.

  • Consistent story state: story metadata, chapter ordinals, chapter JSON, lifecycle flags, clone links, and artifact descriptors are persisted in the rowstore.
  • Blob-aware artifacts: artifact metadata includes name, content type, size, and SHA-256. Bytes are either kept inline below a configurable threshold or stored in a blobstore and downloaded only when requested.
  • Immutable chapters: appends create ordered chapters. Existing chapters are read back by ordinal, including through linked clones.
  • Same API everywhere: code written against pkg/client works against a remote service, an in-process daemon, or a local HTTP daemon.
  • Embeddable by default: tests, CLIs, desktop tools, and local workers can use Strata storage directly without deploying a separate server.

Storage Model

Strata separates the consistency layer from the byte layer:

Layer Responsibility Current local backends
Rowstore Stories, chapters, lifecycle state, clone links, artifact descriptors, and inline artifact bytes. SQLite, Pebble
Blobstore Large artifact bodies referenced by rowstore descriptors. Filesystem blobstore

The rowstore is the source of truth for committed story state. The blobstore holds payload bytes that are too large or inconvenient to keep inline. When a chapter is read, each artifact is represented by the same artifact.Artifact interface whether the bytes were included inline or need to be streamed from the blobstore-backed download endpoint.

MaxInlineArtifactBytes controls the inline cutoff. The default is 256 bytes.

Concepts

Concept Meaning
Anthology A namespace for stories.
Story A consistent append-only timeline keyed by anthology ID and story ID.
Chapter An immutable JSON payload at a specific ordinal in a story.
Artifact A named payload attached to a chapter. Artifacts can be inline or lazily downloaded.
Clone A linked story that inherits chapters through a fork ordinal and stores only post-fork appends.

Installation

go get github.com/colony-2/strata-go@latest

Choose A Runtime

Remote Service

Use a remote client when Strata is deployed as a central HTTP service.

c, err := client.NewBuilder().
    WithRemote("https://api.strata.example.com", os.Getenv("STRATA_TOKEN")).
    Build(ctx)
if err != nil {
    log.Fatal(err)
}

The simpler constructor is still available when you already have a base URL and API key:

c := client.MustNew(client.Config{
    BaseURL: "https://api.strata.example.com",
    APIKey:  os.Getenv("STRATA_TOKEN"),
})
Embedded In-Process

Use embedded mode for tests, local tools, single-process services, or edge deployments. The client routes requests directly to the daemon handler; no TCP listener is opened.

c, err := client.NewBuilder().
    WithEmbeddedConfig(daemon.Config{
        RowStoreURI:            "sqlite:///tmp/strata.db",
        BlobStoreURI:           "blobfs:///tmp/strata-blobs",
        MaxInlineArtifactBytes: 256,
    }).
    Build(ctx)
if err != nil {
    log.Fatal(err)
}
defer c.Close(ctx)

If your application already owns storage handles, pass them in with WithEmbeddedStores. Borrowed stores are not closed by Client.Close.

rows, err := sqliterowstore.Open("/tmp/strata.db")
if err != nil {
    log.Fatal(err)
}
defer rows.Close()

blobs, err := blobstore.NewFS("/tmp/strata-blobs")
if err != nil {
    log.Fatal(err)
}

c, err := client.NewBuilder().
    WithEmbeddedStores(rows, blobs).
    Build(ctx)
if err != nil {
    log.Fatal(err)
}

Supported embedded storage URI schemes:

Setting Value Notes
RowStoreURI sqlite:///absolute/path/to/strata.db SQLite rowstore; the usual default for durable local embedded use.
RowStoreURI pebble:///absolute/path/to/rows-dir Pebble rowstore.
BlobStoreURI blobfs:///absolute/path/to/blob-dir Filesystem blobstore for large artifact bytes.
Local HTTP Daemon

Use the daemon when you want a real Strata-compatible HTTP endpoint backed by local stores.

cfg := daemon.Config{
    ListenAddr:             "127.0.0.1:0",
    RowStoreURI:            "sqlite:///tmp/strata.db",
    BlobStoreURI:           "blobfs:///tmp/strata-blobs",
    MaxInlineArtifactBytes: 256,
}

srv, err := daemon.New(cfg)
if err != nil {
    log.Fatal(err)
}
defer srv.Shutdown(context.Background())

if err := srv.Start(context.Background()); err != nil {
    log.Fatal(err)
}

addr, err := srv.Addr()
if err != nil {
    log.Fatal(err)
}

c := client.MustNew(client.Config{
    BaseURL: fmt.Sprintf("http://%s", addr),
    APIKey:  "local",
})

Quick Start

package main

import (
    "context"
    "log"
    "os"
    "strings"

    "github.com/colony-2/strata-go/pkg/client"
    "github.com/colony-2/strata-go/pkg/client/story"
)

func main() {
    ctx := context.Background()

    c := client.MustNew(client.Config{
        BaseURL: "https://api.strata.example.com",
        APIKey:  os.Getenv("STRATA_TOKEN"),
    })

    key := story.Key{AnthologyID: "demo", StoryID: "pipeline"}

    handle, err := c.CreateStory(ctx, key, story.CreateOptions{
        RequestID: "50a045d3-f77a-4e42-84e2-3d4dbd16a313",
    })
    if err != nil {
        log.Fatalf("create story: %v", err)
    }

    chapter := handle.NewChapter().
        WithJSON(strings.NewReader(`{"event":"build","status":"ok"}`))

    if err := handle.AppendChapter(ctx, chapter); err != nil {
        log.Fatalf("append chapter: %v", err)
    }
}

Run the example application:

go run ./examples/quickstart

Chapters And Artifacts

Artifacts are intentionally uniform. The same artifact.Artifact value can be created from memory, a file, a reader factory, inline API data, or a remote download locator.

import (
    "context"
    "io"
    "os"
    "strings"

    "github.com/colony-2/strata-go/pkg/client/artifact"
)

summary := []byte(`{"tests":42,"failed":0}`)

logArtifact := artifact.FromReader(
    "build.log",
    "text/plain",
    logSize,
    func(ctx context.Context) (io.ReadCloser, error) {
        return os.Open(logFile)
    },
)

chapter := handle.NewChapter().
    WithJSON(strings.NewReader(`{"event":"build"}`)).
    AddArtifact(artifact.FromBytes("summary.json", "application/json", summary)).
    AddArtifact(logArtifact)

if err := handle.AppendChapter(ctx, chapter); err != nil {
    return err
}

latest, err := handle.GetLastChapter(ctx)
if err != nil {
    return err
}

for _, art := range latest.Artifacts() {
    // Bytes returns inline data directly or fetches remote artifact bytes on demand.
    // For large artifacts, prefer WriteTo or SaveToFile to avoid materializing bytes.
    data, err := art.Bytes(ctx)
    if err != nil {
        return err
    }
    _ = data
}

When you already know the story key and chapter ordinal, read a chapter directly:

chapter, err := c.Chapter(ctx, story.Key{
    AnthologyID: "demo",
    StoryID:     "pipeline",
}, 42)
if err != nil {
    return err
}

payload := chapter.Body()
_ = payload

If the story already exists and you only need to append, skip loading a story handle:

chapter := story.NewChapter().
    WithJSON(strings.NewReader(`{"event":"fast-path"}`))

if err := c.SaveChapter(ctx, key, chapter); err != nil {
    return err
}

Story Lifecycle

key := story.Key{AnthologyID: "demo", StoryID: "pipeline-123"}

initial := story.NewChapter().
    WithJSON(strings.NewReader(`{"event":"seed"}`))

handle, err := c.CreateStory(ctx, key, story.CreateOptions{
    RequestID:      "50a045d3-f77a-4e42-84e2-3d4dbd16a313",
    InitialChapter: initial,
})
if err != nil {
    log.Fatal(err)
}

clone, err := c.CloneStory(ctx, key, story.CloneOptions{
    DestinationKey: story.Key{
        AnthologyID: "demo",
        StoryID:     "pipeline-123-copy",
    },
    LastOrdinal: handle.ChapterCount() - 1,
})
if err != nil {
    log.Fatal(err)
}

if err := clone.Finalize(ctx, story.FinalizeOptions{}); err != nil {
    log.Fatal(err)
}

iter, err := c.ListStories(ctx, "demo", story.ListOptions{PageSize: 25})
if err != nil {
    log.Fatal(err)
}

for iter.HasNext() {
    summary, err := iter.Next(ctx)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("story=%s finalized=%v", summary.StoryId, summary.Finalized)
}

Pagination

List helpers return pull-based iterators so callers do not need to manage page tokens directly.

iter, err := handle.Chapters(ctx, story.ChaptersOptions{PageSize: 50})
if err != nil {
    log.Fatal(err)
}

for iter.HasNext() {
    chapter, err := iter.Next(ctx)
    if err != nil {
        if errors.Is(err, pagination.ErrNoMoreItems) {
            break
        }
        log.Fatal(err)
    }
    consume(chapter)
}

Operational Notes

WithRetryPolicy, WithUserAgent, and WithLogger work for both remote and embedded clients. WithHTTPClient is remote-only because embedded clients use an internal transport wired directly to the daemon handler.

The integration test starts the Go daemon with temporary rowstore and blobstore directories, binds to a random localhost port, and exercises the public client against that endpoint. No external Strata server binary is required.

The repository also includes an inspection CLI under cmd/strata:

go run ./cmd/strata --url http://localhost:8080 stories list --anthology demo
go run ./cmd/strata --url http://localhost:8080 chapters get --anthology demo --story pipeline --chapter 0
go run ./cmd/strata --url http://localhost:8080 artifacts get --anthology demo --story pipeline --chapter 0 --name summary.json

Development

Generated OpenAPI files live in internal/openapi. Do not edit them by hand. After changing the OpenAPI sources or generator config, regenerate with:

make generate

For validation:

make test

Maintainers should also read:

Directories

Path Synopsis
cmd
inspectdb command
strata command
examples
quickstart command
internal
openapi
Package openapi provides primitives to interact with the openapi HTTP API.
Package openapi provides primitives to interact with the openapi HTTP API.
pkg

Jump to

Keyboard shortcuts

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