GSAB
Docs · v0.10.0

JavaScript / TypeScript client

gsab-js brings GSAB to the JS ecosystem — same ideas as the Python library (schemas, validation, CRUD, server-side queries, realtime-style watch), shipped as one small package that runs in the browser and Node. TypeScript types included.

npm install gsab-js

Work in progress — the core is here, the rest is coming. gsab-js (0.2.x) focuses on the main functionality first: no-auth reads and queries, watch(), Node CRUD, the reactive cache, React bindings and serverless deploy auth — enough to build and ship a real app (like the live demo, which runs entirely on it). Python-parity features are on the way next: field encryption, charts, access policies and the MCP server. watch(), createCache() and useSheet() are Experimental — the API may change. You're not building blind meanwhile: there's a bundled agent skill for your coding agent, and this page has a raw-markdown twin and lives in llms.txt for easy reference.

Read a public sheet — no auth, no API key

Reading a sheet shared as "anyone with the link" works straight from a browser tab or a Node script, with zero setup:

import { connect } from "gsab-js";

const db = connect("https://docs.google.com/spreadsheets/d/<ID>/edit").sheet();

const rows = await db.read();                    // every row, typed by header
const pro  = await db.read({ plan: "pro" });     // client-side filters
const top  = await db.query("SELECT A, D ORDER BY D DESC LIMIT 10"); // server-side (gviz)

for await (const change of db.watch({ interval: 2000 })) {
  console.log(change); // { added, updated, removed } — includes edits made in the Sheet UI
}

Reactive cache

One poller, one in-memory snapshot, granular events — many views can share it instead of each re-reading the sheet:

import { connect, createCache } from "gsab-js";

const cache = createCache(connect("<url>").sheet(), { key: "id", interval: 2000 });
cache.on("insert", (row) => console.log("added", row));
cache.on("update", (row, prev) => console.log("changed", prev, "→", row));
cache.on("delete", (row) => console.log("removed", row));
await cache.start();   // resolves once the initial snapshot loads
cache.all();           // current rows; also cache.get(key), cache.size, cache.running

React

useSheet turns a sheet into live component state:

import { connect } from "gsab-js";
import { useSheet } from "gsab-js/react";

const db = connect("<url>").sheet(); // module scope

function Users() {
  const { rows, loading, error } = useSheet(db, { key: "id" });
  if (loading) return <p>Loading…</p>;
  return <ul>{rows.map((r) => <li key={String(r.id)}>{String(r.name)}</li>)}</ul>;
}

Pass a shared createCache(...) instead of a manager and any number of components ride one poller. React ≥18 is an optional peer dependency — the core entry never loads it.

Writes (Node)

Writes need a Google sign-in. loopbackAuth() reuses the same sign-in as the Python CLI (gsab auth login) — a browser opens once, the token is cached after:

import { connect } from "gsab-js";
import { loopbackAuth } from "gsab-js/node";

const schema = {
  name: "users",
  fields: {
    id: { type: "integer", primaryKey: true },
    name: { type: "string", required: true },
    plan: { type: "string", default: "free" },
  },
};

const db = connect({ auth: await loopbackAuth() }).sheet(schema);
const id = await db.createSheet("My App DB");
await db.insert({ id: 1, name: "Ada", plan: "pro" });
await db.upsert({ id: 1, plan: "team" });
await db.update({ id: 1 }, { plan: "free" });   // writes ONLY the changed cells
await db.delete({ id: 1 });
const url = await db.share("reader");

Validation, primary keys, defaults and the GSABError hierarchy match the Python library, and update()/upsert() are cell-targeted — concurrent edits to different fields of the same row don't clobber each other.

Deploying (Vercel / serverless / CI)

A server can't open a browser, so it uses a long-lived refresh token. One command on your own machine, one env var on the host:

npx gsab-js env
# GSAB_CREDENTIALS=…   ← set this single value in your host's secret store
import { refreshTokenAuth } from "gsab-js/node";
const db = connect({ spreadsheetId, auth: refreshTokenAuth() }).sheet(schema);

Debugging a deployment? Run npx gsab-js doctor in that environment — it names anything missing and performs a real token refresh. Prefer separate variables? npx gsab-js env --split prints the GSAB_CLIENT_ID / GSAB_CLIENT_SECRET / GSAB_REFRESH_TOKEN trio, which refreshTokenAuth() also accepts.

If the credential leaks: its scope is drive.file — it can only touch sheets gsab created, never the rest of your Drive — and you can revoke it any time at myaccount.google.com → Security → Third-party access. This is exactly how the live demo runs: public no-auth reads in the visitor's browser, and refreshTokenAuth() writes inside Next.js route handlers.

Browser sign-in

Direct in-browser Google sign-in (Google Identity Services) is Planned — today the browser tier is read-only on public sheets, and writes go through your server as above.

Links