Help us shape the future of Go! Take the 2026 Go Developer Survey

sheets

package
v0.1.0-rc.1 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 30 Imported by: 0

Documentation

Overview

Package sheets is the built-in Google Sheets store: a Backend and EventLog over one spreadsheet, one tab per table, and a run lease held as a file in a Cloud Storage bucket.

  • Every Commit is one spreadsheets.batchUpdate, which Sheets applies all-or-nothing. Rows are changed in place (rewritten tabs keep their sheet ids, so protection, filters and team formulas survive); growing tables are appended to, never rewritten.
  • A tab is created by the first write that names it, at exactly its column count with a frozen header row, because Sheets counts every grid cell toward its 10-million-cell cap. Health is the one exception: its cell H1 holds the staleness formula, rewritten by every commit that writes Health.
  • Tool tabs are written as raw text and read unformatted, so a value that starts with "=" stays text; people-owned tabs (Overrides, Companies, Leads) are read formatted.
  • Raw receiver events go to one tab per UTC month of their received time ("Events 2026-10"). An event's sequence is its (tab, row); a cursor holds a position for every tab.

With `base_url` set in its block (tests), every Google client points there and skips authentication; `_http_client` supplies the HTTP client.

Index

Constants

View Source
const (
	StaleMessage     = "STALE: no successful run in 3 intervals"
	NoSuccessMessage = "STALE: no successful run yet"
)

StaleMessage is what Health!H1 shows when no run succeeded in three schedule intervals; NoSuccessMessage before any run has succeeded.

View Source
const (
	TimeZone   = "Etc/GMT"
	AutoRecalc = "HOUR"
)

Spreadsheet settings setup writes and the `sheets` check expects. The staleness formula compares stored UTC times with NOW(), so the spreadsheet runs on UTC; HOUR recalculation makes NOW() move with no edits.

View Source
const HealthNote = "ok: a run succeeded within the last three schedule intervals. " +
	"STALE: none did, so leadscore may have stopped; run `leadscore doctor`. " +
	"The spreadsheet recalculates this every hour."

HealthNote explains Health!H1 to a person.

View Source
const LeaseObject = "leadscore-lease.json"

LeaseObject is the lease file in the lease bucket. Sheets has no compare-and-swap, so the lease lives in Cloud Storage: every write and delete is conditioned on the generation read just before (ifGenerationMatch, 0 when the file is absent), which Cloud Storage refuses if another run changed the file first. Two callers can never both hold it.

View Source
const MaxCellChars = 50_000

MaxCellChars is the most characters a Sheets cell holds.

Variables

View Source
var LeadsHeaders = []string{"Email", "Full name", "Title", "Company", "Company domain", "LinkedIn URL"}

LeadsHeaders are the starting columns of the template's Leads tab: the built-in fields a team most often has, in words a person reads. The tab is the team's; they add, rename or remove columns freely.

View Source
var MaxCommitBytes = 9 << 20 // a variable so tests can reach Sheets' own limit

MaxCommitBytes is the largest encoded batchUpdate a commit sends; a bigger one is ErrTooLarge, under Sheets' 10 MB request cap.

ReadOnlyScopes are what a reader of the team's tabs needs (sheetsource).

Scopes are the Google sign-in scopes the store and its setup use: Sheets, Drive (sharing and the sheet-access check) and Cloud Storage (the lease).

Functions

func CanEdit

func CanEdit(role string) bool

CanEdit reports a Drive role that can edit the spreadsheet.

func Create

func Create(ctx context.Context, svc *Services, t Template) (string, error)

Create makes a new spreadsheet from the template, owned by whoever svc signs in as (the person): every tab at its exact width with a bold, frozen header (notes on the Overrides and Companies headers), the machine-data tabs hidden, the people tabs green with room to type, Ranked and Health colored, hourly recalculation, UTC, editors unable to re-share, the tool tabs protected for the accounts (Events tabs for both, every other tool tab for the run account), and the staleness formula in Health!H1 with its note and a red highlight on STALE. It does not share the spreadsheet; call Share next. It returns the new spreadsheet's id.

func EventsTab

func EventsTab(t time.Time) string

EventsTab is the tab holding events received in t's UTC month.

func Formula

func Formula(header []string, rows []api.Row) string

Formula is the staleness formula for Health!H1, given the Health tab's header and its rows in sheet order (nil for a blank row). It names the cell holding the `last_success_at` result and compares it with NOW() against three `schedule` intervals; the schedule comes from the `schedule` result row (the run writes it), else the 15-minute default. Before any run has succeeded it shows NoSuccessMessage. The times compared are UTC, so setup sets the spreadsheet's time zone to UTC.

func FormulaRequest

func FormulaRequest(sheetID int64, formula string) *sheetsapi.Request

FormulaRequest writes a formula into H1 of the tab, as a person typing it would (USER_ENTERED), the one cell the store writes that way.

func QuoteTab

func QuoteTab(name string) string

QuoteTab quotes a tab name for an A1 range: 'Seen events', with any apostrophe doubled.

func Repair

func Repair(ctx context.Context, svc *Services, id string, acc Accounts) error

Repair puts an existing spreadsheet's settings back (`setup sheet --repair`): hourly recalculation, UTC, editors unable to re-share, protection on every tool tab not already fully protected, and the Health!H1 formula. It does not add or change tabs or rows.

func Share

func Share(ctx context.Context, svc *Services, id string, acc Accounts) error

Share gives each account edit access to the spreadsheet, with no email sent. A Workspace sharing policy that blocks it comes back as Drive's own error.

func SharedWith

func SharedWith(ctx context.Context, svc *Services, id string) (map[string]string, error)

SharedWith returns each account the spreadsheet is shared with, by lowercased email, and its Drive role. Listing needs the caller to be allowed to see the sharing; the caller decides what a failure means.

func UTCZone

func UTCZone(tz string) bool

UTCZone reports a spreadsheet time zone that is UTC.

func ViewConfig

func ViewConfig(store api.Config, spreadsheet string) api.Config

ViewConfig is the block that reaches a spreadsheet other than the store's (the SQLite view): the store block with `spreadsheet` set to it.

Types

type Accounts

type Accounts struct {
	Run, Receiver string
}

Accounts are the service accounts the spreadsheet is shared with: Run writes every tool tab, Receiver appends to the Events tabs. On Docker one account is both.

func AccountsFrom

func AccountsFrom(c *config.Config, view bool) (Accounts, error)

AccountsFrom works out which accounts the spreadsheet is shared with. A Google Cloud install names them in `hosting` (run_account and receiver_account; a bare name is completed with the project's service-account domain). Otherwise the one account is the service account in `store.credentials`. The view is shared with the run account only.

func (Accounts) List

func (a Accounts) List() []string

List is the distinct accounts, the run account first.

type Info

type Info struct {
	Title      string
	AutoRecalc string
	TimeZone   string
	Tabs       []TabCells // every tab, largest first
	Cells      int64      // grid cells in the whole spreadsheet
}

Info is what the doctor checks read about a spreadsheet.

func Inspect

func Inspect(ctx context.Context, svc *Services, id string) (Info, error)

Inspect reads a spreadsheet's settings and cell use. It fails when the signed-in account cannot open the spreadsheet.

type Services

type Services struct {
	Sheets  *sheetsapi.Service
	Drive   *drive.Service
	Storage *storage.Service
}

Services are the Google API clients for one install.

func Connect

func Connect(ctx context.Context, cfg api.Config, scopes ...string) (*Services, error)

Connect builds the clients from an adapter block (`store`, or a Sheet-tab source's entry), signing in with scopes (Scopes when none are given). With `base_url` set, every client points there and skips authentication; only tests set it, together with `_http_client` (RunWith adds both), so a `base_url` without a client, as a file could carry, is refused: it would send the team's data to whatever address the file names. Otherwise `_http_client`, when set, is an already-authenticated client (setup signs in as the person); else the store signs in with `credentials` (a service-account key file) or, when that is empty, Google's standard credential loading.

type Store

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

Store is a Sheets Backend, EventLog and LeaseInspector.

func New

func New(svc *Services, spreadsheet, leaseBucket string) *Store

New returns a store over an existing spreadsheet.

func Open

func Open(ctx context.Context, cfg api.Config) (*Store, error)

Open builds a store from the `store` block: `spreadsheet` is required; `lease_bucket` is needed to take the lease.

func (*Store) AppendEvents

func (s *Store) AppendEvents(ctx context.Context, events []api.RawEvent) error

AppendEvents stores a batch in one batchUpdate, so all-or-nothing, each event in the tab of its received time's month, in order, each row with a fresh id in `seq`. It creates a missing month's tab (hidden) in the same request, protected like the existing Events tabs. It retries Google's "slow down" until ctx is done; when another writer created the same tab first, it reads the spreadsheet again and appends.

func (*Store) Commit

func (s *Store) Commit(ctx context.Context, writes []api.TableWrite) error

Commit applies every write in one batchUpdate, all-or-nothing. It reads the tabs the writes touch, applies the writes to that copy in memory (so a duplicate append key or a bad write fails before anything is sent), and sends the difference as row-level requests: deleted rows, changed cells, and appended rows. It creates a missing tab and appends a missing column the first time a write names it, and returns ErrTooLarge rather than splitting.

People type into Overrides (and the other people-owned tabs) at any time, and a row delete is by position. So when a commit deletes or changes rows of such a tab, it re-reads the tab just before sending and refuses if it changed, and re-reads it after: a row that is gone but was not meant to go (the person sorted rows at that moment) is put back and the commit returns ErrCommittedWithProblems (everything was saved; a person should look).

func (*Store) DeleteProcessed

func (s *Store) DeleteProcessed(ctx context.Context, committed api.Cursor, olderThan time.Time) (api.Cursor, error)

DeleteProcessed deletes whole monthly tabs: a tab goes when every row in it is at or below committed (and its last row is the one committed read), its month ended before olderThan (so every event in it is older), and its month plus the one-hour grace has passed, so no receiver can still append to it. The newest Events tab is never deleted, so a new month's tab always has a protected sibling to copy. Each deleted month is recorded in the same batchUpdate (see deletedMetadataKey). It returns committed without the deleted tabs; the engine saves that cursor. Rows are never deleted one by one: that would move the positions cursors hold.

func (*Store) Lease

func (s *Store) Lease(ctx context.Context, owner string, ttl time.Duration) (api.RunLease, error)

Lease takes the run lease, or returns ErrLeaseHeld while another owner's lease has not expired. An expired lease is taken over; the same owner taking it again extends it.

func (*Store) LeaseBucket

func (s *Store) LeaseBucket() string

LeaseBucket is the lease's Cloud Storage bucket; empty when none is set.

func (*Store) LeaseBucketAccess

func (s *Store) LeaseBucketAccess(ctx context.Context) (exists, writable bool, err error)

LeaseBucketAccess reports, without writing anything, whether the lease bucket exists and whether this account may write, read and delete the lease file in it (the doctor's `lease` check). It asks Cloud Storage's testIamPermissions, which needs no permission of its own, so an account holding only Storage Object Admin on the bucket can still ask.

func (*Store) LeaseInfo

func (s *Store) LeaseInfo(ctx context.Context) (string, time.Time, error)

LeaseInfo shows the lease without taking it; an empty owner means none.

func (*Store) ReadEvents

func (s *Store) ReadEvents(ctx context.Context, cursor api.Cursor) ([]api.RawEvent, api.Cursor, error)

ReadEvents returns the events after cursor, tab by tab (oldest month first) and row by row, and the cursor after the last one. Each event's Seq is the cursor just after it. It returns ErrEventsShrank when a tab the cursor names is missing (unless DeleteProcessed recorded deleting it), holds fewer rows than the cursor read, or no longer has the last row read at its place (rows were sorted, inserted or deleted).

func (*Store) ReadTable

func (s *Store) ReadTable(ctx context.Context, name string) ([]api.Row, error)

ReadTable returns every row of a tab, skipping blank rows; a missing tab returns no rows and no error. Columns are the named header cells.

func (*Store) Services

func (s *Store) Services() *Services

Services returns the store's Google clients, for the doctor checks.

func (*Store) SpreadsheetID

func (s *Store) SpreadsheetID() string

SpreadsheetID returns the spreadsheet the store writes.

type TabCells

type TabCells struct {
	Name  string
	Cells int64
}

TabCells is one tab's grid size in cells.

type Template

type Template struct {
	Title    string
	View     bool     // the SQLite view: Ranked, Health and the export tabs only
	Accounts Accounts // who the tool tabs are protected for
	Now      time.Time
	// ExportLanes are the rubric's export lane ids; the view gets a tab each
	// (the store gets them from the run's first write).
	ExportLanes []string
}

Template says what Create makes.

Jump to

Keyboard shortcuts

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