sheetforge
In ProgressGoogle Sheets as a real backend. Serialized writes, idempotent retries, typed TypeScript SDKs generated live from your sheet's headers.
50 ordered
Parallel Writes
0
Row Drops
MIT
OSS Packages
60s
Self-host
Highlights
- →Per-sheet write queue with Postgres advisory-lock fencing turns Google Sheets into a serializable backend. 50 parallel POSTs land as 50 ordered rows, every time.
- →Idempotency via partial unique index on (sheet_id, idempotency_key). Network flakes never double-write.
- →Typed TypeScript SDK generated live from the sheet's header row. Literal unions inferred from sample cells. Compiler catches drift when you rename a column.
- →Upstash REST adapter written alongside the ioredis one so the same code runs on Cloudflare Workers later. Core packages ship MIT, npm-publishable.
The Problem
Google Sheets is the default backend for indie MVPs. Fast to iterate, free to host, shareable by link. Except the Sheets API has a documented concurrency bug: two parallel values.append calls can resolve to the same target row and one silently overwrites the other. Four POSTs, three rows. Every 'Sheets as a backend' wrapper on the market (SheetDB, Sheety, NoCodeAPI) forwards your request straight to the broken primitive and inherits the bug. Fine for a demo. Not fine when your signup form catches an HN spike.
What I Built
Per-Sheet Write Queue
Every Google Sheet gets its own Redis stream and Postgres advisory lock. Writes are ordered by stream position and processed serially per sheet. 50 parallel POSTs to the same sheet land as 50 ordered rows with zero drops. The lock is advisory, not a table lock, so other sheets process concurrently.
Idempotency by Design
Each write carries an idempotency key. A partial unique index on (sheet_id, idempotency_key) prevents duplicates. Network flakes, retries, and redeliveries all resolve to a single row. The ledger table tracks every attempt so you can audit what happened.
Typed SDK Generation
Reads the sheet's header row and generates a TypeScript SDK with literal unions inferred from sample cells. Rename a column and the SDK type changes at compile time. Zod schemas are generated alongside types for runtime validation. The SDK handles auth, batching, and error handling.
Crash-Safe Processing
The processor runs inside a Postgres transaction. If the handler crashes mid-write, the transaction rolls back and Redis redelivers the message. The idempotency key catches the replay and deduplicates. No messages are lost, no rows are partially written, no manual recovery needed.
Tech Stack
All projects- TypeScript
- Next.js
- Hono
- PostgreSQL
- Drizzle ORM
- Redis Streams
- Upstash
- Zod
- Turborepo
- Biome
- Google Sheets API
Related Projects
Interested in working together?