Documentation
¶
Overview ¶
Package gofire is a small, typed repository layer over Google Cloud Firestore.
A Repository[T] binds one Go struct type to one Firestore collection and exposes the usual document operations: Create, Get, Set, Update, Delete, queries - returning typed Document[T] values instead of raw snapshots. Transactional variants of the same operations are available through Repository.Tx.
Errors carry both a gofire sentinel (ErrNotFound, ErrAlreadyExists, ErrEmptyID) and the underlying Firestore error, so callers can branch with errors.Is and still reach the gRPC status when they need it.
Index ¶
- Variables
- type Document
- type Repository
- func (r Repository[T]) Collection() *firestore.CollectionRef
- func (r Repository[T]) Create(ctx context.Context, model T) (Document[T], error)
- func (r Repository[T]) CreateWithID(ctx context.Context, id string, model T) (Document[T], error)
- func (r Repository[T]) Delete(ctx context.Context, id string) error
- func (r Repository[T]) Documents(ctx context.Context, query firestore.Query) ([]Document[T], error)
- func (r Repository[T]) Get(ctx context.Context, id string) (Document[T], error)
- func (r Repository[T]) GetAll(ctx context.Context) ([]Document[T], error)
- func (r Repository[T]) Query() firestore.Query
- func (r Repository[T]) RunTransaction(ctx context.Context, ...) error
- func (r Repository[T]) Set(ctx context.Context, doc Document[T]) (Document[T], error)
- func (r Repository[T]) Tx(tx *firestore.Transaction) TxRepository[T]
- func (r Repository[T]) Update(ctx context.Context, id string, updates []firestore.Update) error
- type TxRepository
- func (t TxRepository[T]) Create(model T) (Document[T], error)
- func (t TxRepository[T]) CreateWithID(id string, model T) (Document[T], error)
- func (t TxRepository[T]) Delete(id string) error
- func (t TxRepository[T]) Get(id string) (Document[T], error)
- func (t TxRepository[T]) Set(doc Document[T]) (Document[T], error)
- func (t TxRepository[T]) Update(id string, updates []firestore.Update) error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotFound reports that the requested document does not exist. ErrNotFound = errors.New("document not found") // ErrAlreadyExists reports a create targeting an ID that is taken. ErrAlreadyExists = errors.New("document already exists") // ErrEmptyID reports an operation that requires a document ID but // received an empty string. ErrEmptyID = errors.New("document id is empty") )
Sentinel errors returned by repository operations. Sentinels are wrapped together with the underlying Firestore error, so errors.Is matches the sentinel while status.Code still resolves the original gRPC status.
Functions ¶
This section is empty.
Types ¶
type Document ¶
Document pairs a decoded model with the ID of the Firestore document it was read from or written to.
type Repository ¶
type Repository[T any] struct { // contains filtered or unexported fields }
Repository is a typed wrapper around a single Firestore collection. The type parameter T is the struct that maps to the collection's documents via `firestore` struct tags. The zero value is not usable; construct one with NewRepository.
func NewRepository ¶
NewRepository binds T to the collection at collectionPath. The path may name a top-level collection ("users") or a subcollection ("users/alice/orders"); Firestore requires an odd number of segments.
Example ¶
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/firestore"
"github.com/kocieusz/gofire"
)
type user struct {
Name string `firestore:"name"`
Email string `firestore:"email"`
Plan string `firestore:"plan"`
}
func main() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "my-project")
if err != nil {
log.Fatal(err)
}
defer client.Close()
users, err := gofire.NewRepository[user](client, "users")
if err != nil {
log.Fatal(err)
}
created, err := users.Create(ctx, user{Name: "Ada", Email: "ada@example.com", Plan: "free"})
if err != nil {
log.Fatal(err)
}
loaded, err := users.Get(ctx, created.ID)
if err != nil {
log.Fatal(err)
}
fmt.Println(loaded.Model.Name)
}
Output:
func (Repository[T]) Collection ¶
func (r Repository[T]) Collection() *firestore.CollectionRef
Collection exposes the underlying collection reference for needs the repository does not cover.
func (Repository[T]) Create ¶
func (r Repository[T]) Create(ctx context.Context, model T) (Document[T], error)
Create inserts model as a new document with an auto-generated ID and returns that ID.
func (Repository[T]) CreateWithID ¶
CreateWithID inserts model under the given ID and fails with ErrAlreadyExists if a document with that ID is already present.
func (Repository[T]) Delete ¶
func (r Repository[T]) Delete(ctx context.Context, id string) error
Delete removes the document with the given ID. Deleting a document that does not exist is not an error, mirroring Firestore semantics.
func (Repository[T]) Documents ¶
Documents runs query and decodes every result into a Document[T].
Example ¶
package main
import (
"context"
"fmt"
"log"
"cloud.google.com/go/firestore"
"github.com/kocieusz/gofire"
)
type user struct {
Name string `firestore:"name"`
Email string `firestore:"email"`
Plan string `firestore:"plan"`
}
func main() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "my-project")
if err != nil {
log.Fatal(err)
}
defer client.Close()
users, err := gofire.NewRepository[user](client, "users")
if err != nil {
log.Fatal(err)
}
paying, err := users.Documents(ctx, users.Query().Where("plan", "==", "pro").Limit(10))
if err != nil {
log.Fatal(err)
}
for _, doc := range paying {
fmt.Println(doc.ID, doc.Model.Email)
}
}
Output:
func (Repository[T]) GetAll ¶
func (r Repository[T]) GetAll(ctx context.Context) ([]Document[T], error)
GetAll returns every document in the collection. Use with care on large collections; prefer Documents with a limited query.
func (Repository[T]) Query ¶
func (r Repository[T]) Query() firestore.Query
Query returns the collection's base query for callers to refine with Where, OrderBy, Limit, and friends. Decode the result with Documents.
func (Repository[T]) RunTransaction ¶
func (r Repository[T]) RunTransaction(ctx context.Context, f func(ctx context.Context, tx *firestore.Transaction) error) error
RunTransaction executes f inside a Firestore transaction on the repository's client. Use Repository.Tx inside f to perform typed operations; transactions spanning several repositories work as long as all repositories share the same client. Firestore requires every read in a transaction to happen before the first write.
Example ¶
package main
import (
"context"
"log"
"cloud.google.com/go/firestore"
"github.com/kocieusz/gofire"
)
type user struct {
Name string `firestore:"name"`
Email string `firestore:"email"`
Plan string `firestore:"plan"`
}
func main() {
ctx := context.Background()
client, err := firestore.NewClient(ctx, "my-project")
if err != nil {
log.Fatal(err)
}
defer client.Close()
users, err := gofire.NewRepository[user](client, "users")
if err != nil {
log.Fatal(err)
}
memberships, err := gofire.NewRepository[map[string]string](client, "memberships")
if err != nil {
log.Fatal(err)
}
// Create a user and their membership atomically: either both documents
// land, or neither does.
err = users.RunTransaction(ctx, func(ctx context.Context, tx *firestore.Transaction) error {
created, err := users.Tx(tx).Create(user{Name: "Grace", Email: "grace@example.com"})
if err != nil {
return err
}
_, err = memberships.Tx(tx).CreateWithID(created.ID, map[string]string{"role": "owner"})
return err
})
if err != nil {
log.Fatal(err)
}
}
Output:
func (Repository[T]) Tx ¶
func (r Repository[T]) Tx(tx *firestore.Transaction) TxRepository[T]
Tx binds the repository to a running transaction.
The firestore package buffers transactional writes: errors such as ErrAlreadyExists for a duplicate create surface from RunTransaction at commit time, not from the TxRepository call that queued the write.
type TxRepository ¶
type TxRepository[T any] struct { // contains filtered or unexported fields }
TxRepository mirrors Repository's operations inside a transaction. The firestore.Transaction API carries the transaction's context, so methods do not take one.
func (TxRepository[T]) Create ¶
func (t TxRepository[T]) Create(model T) (Document[T], error)
Create buffers an insert of model under an auto-generated ID and returns that ID immediately.
func (TxRepository[T]) CreateWithID ¶
func (t TxRepository[T]) CreateWithID(id string, model T) (Document[T], error)
CreateWithID buffers an insert of model under the given ID. A duplicate ID surfaces as ErrAlreadyExists from RunTransaction at commit time.
func (TxRepository[T]) Delete ¶
func (t TxRepository[T]) Delete(id string) error
Delete buffers removal of the document with the given ID.
func (TxRepository[T]) Get ¶
func (t TxRepository[T]) Get(id string) (Document[T], error)
Get reads the document with the given ID inside the transaction.