UNPKG

longcelot-sheet-db

Version:

Google Sheets-backed staging database adapter for Node.js with schema-first design

220 lines (166 loc) 9.34 kB
--- name: migrations description: Manage schema versions and migrate data out of longcelot-sheet-db. Use when exporting schemas to Prisma or SQL DDL, generating a data migration script, pushing schema changes to all registered user sheets with sync --all-users, dropping or renaming a table/column safely (schema file + live sheet + schema_versions together), configuring onSchemaMismatch detection, understanding the schema_versions admin table, or planning a migration from Google Sheets to a production database. license: MIT metadata: package: longcelot-sheet-db version: "0.1.29" --- # longcelot-sheet-db — Schema Versioning & Migrations --- ## 1. Schema Version Tracking (Runtime) `longcelot-sheet-db` hashes every registered schema (SHA-256) and stores the hash in the built-in `schema_versions` admin table. On every `withContext()` call for a non-admin user, the current hash is compared against the stored hash for that user's sheet. ### schema_versions table (built-in) Scaffolded automatically by `lsdb init`. Do not modify this table's schema manually. | Column | Type | Description | |---|---|---| | `schema_version_id` | string PK | auto-generated | | `actor_sheet_id` | string | the user's sheet ID | | `table_name` | string | the table being tracked | | `schema_hash` | string | SHA-256 of the schema definition | | `synced_at` | string | ISO timestamp of last successful sync | | `column_count` | number | number of columns at sync time | ### Configure mismatch behavior ```typescript const adapter = createSheetAdapter({ // ... onSchemaMismatch: 'warn', // log to stderr, continue (default) // onSchemaMismatch: 'error', // throw SchemaMismatchError // onSchemaMismatch: 'auto-sync', // silently sync the user sheet before proceeding }); ``` | Mode | When to use | |---|---| | `'warn'` | Safe default alerts without blocking. Good for gradual rollouts. | | `'error'` | Hard-fail stale clients in staging/test. Catch issues early. | | `'auto-sync'` | Seamless self-healing. Adds latency on mismatch but users never see errors. | ### computeSchemaHash() utility ```typescript import { computeSchemaHash } from 'longcelot-sheet-db'; const hash = computeSchemaHash(bookingsSchema); // Deterministic SHA-256 string same schema definition always produces the same hash ``` --- ## 2. sync --all-users (Bulk Schema Push) After schema changes, push updates to every registered user sheet: ```bash npx lsdb sync --all-users # apply to all users npx lsdb sync --all-users --dry-run # preview without writing ``` **What it does:** 1. Reads all rows from the admin `users` table 2. For each user, fetches their `actor_sheet_id` 3. Calls `syncSchema()` for every table schema (adds missing columns/tabs never deletes) 4. Updates `schema_versions` for each successfully synced table 5. Uses **exponential backoff** (1 s 32 s) to handle Google Sheets API rate limits **`--dry-run`** prints which user sheets are outdated without applying any changes. --- ## 3. lsdb migrate (Schema Export) Export all defined schemas to a target format for migration: ```bash npx lsdb migrate --prisma --output ./prisma # generates schema.prisma npx lsdb migrate --sql --output ./migrations # generates CREATE TABLE DDL ``` > Deprecated alias: `lsdb export` still works but emits a deprecation warning. ### Prisma output example ```prisma model bookings { booking_id String @id @default(cuid()) service String date String status String @default("pending") price Float? user_id String users users @relation(fields: [user_id], references: [id]) created_at DateTime @default(now()) updated_at DateTime @updatedAt @@map("bookings") } ``` The exporter emits `@id` for `primary()` columns and `@relation` for `ref()` columns. Review and adjust the output treat it as a starting point, not final code. ### SQL DDL output example ```sql CREATE TABLE bookings ( booking_id VARCHAR(255) PRIMARY KEY, service VARCHAR(255) NOT NULL, date VARCHAR(255) NOT NULL, status VARCHAR(255) DEFAULT 'pending', price FLOAT, user_id VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); ``` --- ## 4. lsdb migrate-data (Data Migration Script Generator) ```bash npx lsdb migrate-data # generate for all tables npx lsdb migrate-data --table bookings # single table only npx lsdb migrate-data --all-users # also include every registered user sheet npx lsdb migrate-data --output ./scripts # custom output directory npx lsdb migrate-data --dry-run # preview plan without writing ``` > Deprecated alias: `lsdb export-data` still works but emits a deprecation warning. Generates a `migrate-data.js` script with `insertRow()` stubs for each table. The developer replaces the stubs with their production DB client: ```javascript // Generated migrate-data.js (excerpt) const { createSheetAdapter } = require('longcelot-sheet-db'); async function migrateBookings(adapter) { const ctx = adapter.withContext({ userId: 'migrate-data', actor: 'admin', actorSheetId: '...' }); const rows = await ctx.table('bookings').findMany(); for (const row of rows) { await insertRow('bookings', { booking_id: row.booking_id, service: row.service, // ... map all columns }); } } // Replace this stub with your DB client async function insertRow(table, data) { // e.g. await prisma.bookings.create({ data }) // or await knex(table).insert(data) console.log(`[dry-run] INSERT INTO ${table}`, data); } ``` --- ## 5. Full Migration Path to Production Database ``` longcelot-sheet-db (staging/MVP) 1. npx lsdb migrate --prisma --output ./prisma 2. Review schema.prisma adjust types, add indexes, remove lsdb-specific columns 3. npx prisma migrate dev (or equivalent for your ORM) 4. Write migration script: npx lsdb migrate-data --output ./scripts 5. Edit migrate-data.js replace insertRow() stub with your DB client 6. Run node scripts/migrate-data.js against staging DB 7. Validate data integrity 8. Replace createSheetAdapter() with your production DB client in application code ``` --- ## 6. Dropping & Renaming Schema Elements Unlike `sync` (additive-only never deletes or renames), these commands update the schema file, the live Google Sheet, and the relevant `schema_versions` record together: ```bash npx lsdb drop-table bookings # schema file + Google Sheet tab npx lsdb drop-column bookings notes # schema file + Google Sheet column npx lsdb rename-column bookings notes remarks # in-place header rename — data preserved ``` All three accept `--all-users` to also apply to every registered user's personal sheet, and print a plan with a confirmation prompt (`--yes` to skip, `--dry-run` to preview). Full flag reference: `skills/cli/SKILL.md`. `rename-column` matters most here because of what section 1 above describes: a schema-file-only rename (hand-editing the column key with no corresponding sheet change) desyncs every existing row from the schema's expectations, and the next `onSchemaMismatch: 'auto-sync'` trigger or `sync --all-users` run has no way to recover data under the old header name `sync` only ever appends missing columns, it never renames or backfills. `rename-column` avoids that entirely by editing the sheet's header cell directly instead of dropping and re-adding the column. ### What persists after migration | Field | Persists? | Notes | |---|---|---| | `user_id` | Yes | Your logical user identity becomes PK in SQL | | `_id` | Optional | Can become PK if you prefer nanoid strings | | `actor_sheet_id` | No | Google Drive path has no meaning in SQL | | `_created_at`, `_updated_at` | Yes | Map to `created_at`, `updated_at` in SQL | | `_deleted_at` | Optional | Map to `deleted_at` if keeping soft-delete | --- ## Common Mistakes - **Modifying `schema_versions` manually** This table is managed by the adapter. Manual edits cause false mismatch detections. - **Running `sync --all-users` without `--dry-run` first** Always preview with `--dry-run` before applying bulk changes to user sheets in production. - **Using `'auto-sync'` in production without testing** Each mismatch triggers a Sheets API call before the user's request completes. Monitor latency impact. - **Not reviewing exported Prisma/SQL** The export is a starting point. Check data types (all strings/numbers are broad; tighten them), add proper indexes, and remove sheet-internal columns (`_id`, `schema_version_id`, etc.) that your SQL schema doesn't need. - **Forgetting `_id` in `migrate-data.js`** Existing `_id` values are valid nanoid strings and can be carried into SQL as the primary key, preserving existing references in your data. - **Hand-editing a schema file to remove or rename a column instead of using `drop-column`/`rename-column`** `sync` never deletes or renames anything, so a manual edit leaves the live sheet out of sync with no automatic recovery path. Use the dedicated commands so the schema file and sheet change together.