Gsd-skill-creator portable-schema-generator
Emit Postgres.sql and SQLite.sql from a single schema spec so tools work across both drivers without duplicating DDL by hand. Use when designing a schema that needs to support both shared Postgres deployments and zero-config SQLite. Reduces two-file sync burden to a single source edit.
install
source · Clone the upstream repo
git clone https://github.com/Tibsfox/gsd-skill-creator
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Tibsfox/gsd-skill-creator "$T" && mkdir -p ~/.claude/skills && cp -r "$T/project-claude/skills/portable-schema-generator" ~/.claude/skills/tibsfox-gsd-skill-creator-portable-schema-generator-4a7cfb && rm -rf "$T"
manifest:
project-claude/skills/portable-schema-generator/SKILL.mdsource content
Portable Schema Generator
Two-driver schemas mean two migration files. Hand-keeping them in sync drifts fast. This skill captures the translation rules and the minimal tooling that keeps them aligned.
When to Use
- Your tool needs to work on both Postgres and SQLite
- You want fresh-project onboarding to be zero-config (SQLite default) while shared-infra deployments keep Postgres
- A single schema change should land in both driver files with no manual diff-copying
Core Differences
| Concern | Postgres | SQLite |
|---|---|---|
| Schemas | | Namespace-flat — prefix names instead |
| Autoincrement | | |
| Timestamps | | |
| Booleans | | (0/1) |
| Enums via CHECK | | Same — both support inline CHECK |
| Foreign keys | On by default | required at connect |
| Updated-at trigger | | |
| Cascading drops | | Same, but only enforced with |
| Reserved-word casing | Case-insensitive | Case-insensitive, but be consistent |
Translation Rules
When authoring the Postgres version first:
- Strip schema prefix for SQLite —
andCREATE SCHEMA IF NOT EXISTS foo;
are removed. Keep bare table names.SET search_path - Replace
withBIGSERIAL
.INTEGER PRIMARY KEY AUTOINCREMENT - Replace
withTIMESTAMPTZ
; default becomesTEXT
.(strftime('%Y-%m-%dT%H:%M:%fZ','now')) - Replace
withBOOLEAN
(values 0/1 at insert time — use an adapter coercion).INTEGER - Drop
on truncates (SQLite usesRESTART IDENTITY
instead).DELETE FROM sqlite_sequence WHERE name = 'table' - Rewrite function-style triggers to inline
trigger bodies.BEGIN ... END
File Layout
migrations/<feature>/ 001-init.postgres.sql 001-init.sqlite.sql 002-next.postgres.sql 002-next.sqlite.sql
Numbered pairs. The adapter picks the right one based on
cfg.db.driver.
Adapter Runtime Responsibilities
The query adapter at runtime should:
- Rewrite
placeholders →$N
for SQLite? - Strip
schema prefix for SQLiterelease_history. - Translate
→now()
,strftime(...)
casts → drop::type - Coerce JS booleans → 0/1 on param bindings
- Provide a portable
helpertruncate(table)
Reference implementation:
tools/release-history/db.mjs in this repo.
Workflow
- Author the Postgres version first (richer type system reduces ambiguity).
- Translate row-by-row using the rules above into the SQLite version.
- Test both: apply each migration to a disposable DB, run schema-check script against the adapter.
- Commit both files together; never commit one without the other.
Anti-patterns to avoid
- Single mixed-syntax file — both drivers will try to parse it and one or both will fail.
- Runtime translation of whole migration files — too many edge cases.
- Letting the Postgres file diverge "just for now" — alignment rots quickly.
Example (from this repo)
See
migrations/release-history/001-init.postgres.sql and
migrations/release-history/001-init.sqlite.sql. Six tables (release,
feature, metric, retrospective, lesson, publish_target). Same semantics,
driver-idiomatic DDL.
Related
— database credentials andenv-setup
conventions.env
— migration file naming disciplinefile-operation-patterns