grist-kit
v0.1.2
Published
Typesafe Grist JS library and CLI.
Downloads
484
Maintainers
Readme
grist-kit
Typesafe JavaScript library and CLI for Grist.
Status
Pre-release. API unstable.
Install
npm install grist-kitTutorial
In this tutorial you'll connect to a real Grist doc, generate typesafe bindings, and build a script that finds low-stock products and places an incoming order to refill them.
We'll use the Inventory Manager template. It tracks products, incoming orders (refills from suppliers), and outgoing orders (sales). Our goal: a script that automates restocking by detecting which products are running low and placing a single incoming order to refill them.
Prerequisites: Node.js 24+ (for native TypeScript support) and pnpm.
Part 1 — Connect to the public template
Create a project:
mkdir grist-inventory && cd grist-inventory
pnpm init --init-type module
pnpm add grist-kitOpen the Inventory Manager template doc. In the left sidebar, click Settings. Scroll to the API section, expand Document ID, and click the Base doc URL to copy it.
Create a .env file:
GRIST_DOC_URL=https://templates.getgrist.com/api/docs/sXsBGDTKau1F3fvxkCyoaJThe template is public, so no API key is needed yet. Confirm the connection:
pnpm exec grist-kit tablesYou should see tables including All_Products, Incoming_Orders, and Incoming_Order_Line_Items.
Part 2 — Generate types
Generate a schema file from the live doc:
pnpm exec grist-kit generate --out grist-schema.tsThis generates a grist-schema.ts file.
[!TIP] You'll re-run this whenever the doc's columns change. Save it as a
package.jsonscript so it's easy to remember:pnpm pkg set scripts.update-grist-schema="grist-kit generate --out grist-schema.ts"Afterwards you can run
pnpm run update-grist-schemato regenerate the types.
Part 3 — Set up TypeScript
To get the type errors we promised, install TypeScript and the Node 24 base config:
pnpm add -D typescript @types/node @tsconfig/node24Create tsconfig.json:
{
"extends": "@tsconfig/node24/tsconfig.json"
}You can typecheck with pnpm exec tsc --noEmit at any point.
Part 4 — Write the refill script
The goal: a script that finds products that are low or out of stock and places a single incoming order to refill them. We'll add a --dry-run flag so we can see what it would do before actually writing.
Create refill.ts:
import { parseArgs } from "node:util";
import { gristDoc } from "grist-kit";
import type { GristSchema } from "./grist-schema.ts";
const { values } = parseArgs({
options: { "dry-run": { type: "boolean", default: false } },
});
const doc = gristDoc<GristSchema>({
baseDocUrl: process.env.GRIST_DOC_URL!,
apiKey: process.env.GRIST_API_KEY,
});
const needsRefill = await doc.table("All_Products").list({
filter: { Stock_Alert: ["Low Stock", "OUT OF STOCK"] },
});
console.log(`${needsRefill.length} products need a refill:\n`);
for (const p of needsRefill) {
console.log(` [${p.Stock_Alert}] ${p.SKU} — ${p.Product} (in stock: ${p.In_Stock})`);
}
if (needsRefill.length === 0 || values["dry-run"]) {
if (values["dry-run"]) console.log("\n(dry run — no order created)");
process.exit(0);
}
const [orderId] = await doc
.table("Incoming_Orders")
.insert([{ Order_Date: Math.floor(Date.now() / 1000), Status: "Order Placed" }]);
await doc.table("Incoming_Order_Line_Items").insert(
needsRefill.map((p) => ({
Order_Number: orderId,
SKU: p.id,
Qty: 10,
})),
);
console.log(`\nCreated order ${orderId} with ${needsRefill.length} line items.`);A few things worth noticing:
- The
filterargument is fully typed against the schema. Try misspelling"OUT OF STOCK"— TypeScript will reject it, becauseStock_Alert's allowed values are baked into the generated schema. In_StockandStock_Alertare formula columns. The schema marks them as such, so they don't appear in theinsert()payload type — you can't accidentally try to write them.Order_Dateis a GristDate, which is encoded as epoch seconds.- Every Grist row has a numeric
id(the row id).Order_Numberon the line items is aReftoIncoming_Orders, so it expects thatid—orderIdfrom the previous insert, orp.idfor the product reference onSKU.
Run it in dry-run mode against the public template:
node --env-file=.env refill.ts --dry-runYou'll see the list of low-stock items. Without an API key, the script can read but cannot write — which is fine for the dry run.
Part 5 — Make your own copy and place the order
To actually create an order, you need a doc you own.
- In the template doc, click Save copy (top-right). This creates a new document under your account.
- In the new doc, open Settings → API → Document ID and copy the new Base doc URL.
- Click your profile picture (top-right) → Profile Settings → scroll to API → create an API key. (See Grist's authentication docs for the canonical walkthrough.)
Update .env:
GRIST_DOC_URL=https://<your-host>/api/docs/<your-new-doc-id>
GRIST_API_KEY=<your-api-key>Re-generate the schema against your copy (it will be identical, but it's good practice):
pnpm exec grist-kit generate --out grist-schema.tsNow run the script for real:
node --env-file=.env refill.tsOpen your doc — there's a new row in Incoming Orders with line items for each low-stock product. Once you flip its Status to "Received" in the Grist UI, the In_Stock column updates automatically and Stock_Alert clears.
That's the full loop: generate types from a live doc, read with typesafe filters, write with typesafe inserts.
Library usage
import { gristDoc } from "grist-kit";
import type { GristSchema } from "./grist-schema.ts";
const doc = gristDoc<GristSchema>({
baseDocUrl: process.env.GRIST_DOC_URL!,
apiKey: process.env.GRIST_API_KEY!,
});
const customers = await doc.table("Customers").list({ limit: 10 });CLI
# Generate types from a live doc
grist-kit generate --out ./grist-schema.ts
# Inspect
grist-kit tables
grist-kit records Customers --limit 5
grist-kit sql "SELECT * FROM Customers WHERE Tier = 'pro'"CLI config via env (.env auto-loaded):
GRIST_DOC_URL— e.g.https://grist.example.com/api/docs/xxxxGRIST_API_KEY
Flags --doc-url, --api-key, --env-file override env.
