# GraphQL Schema Writer The GraphQL writer converts RelSpec's internal database model into GraphQL Schema Definition Language (SDL) files. ## Features - **Table to Type mapping**: Database tables become GraphQL types - **Column to Field mapping**: Table columns become type fields - **Enum support**: Database enums are preserved - **Custom scalar declarations**: Automatically declares DateTime, JSON, Date scalars - **Implicit relationships**: Generates relationship fields from foreign keys - **Many-to-many support**: Handles junction tables intelligently - **Clean output**: Proper formatting, field ordering, and comments ## Type Mappings ### SQL to GraphQL | SQL Type | GraphQL Type | Notes | |----------|--------------|-------| | bigint, integer, serial (PK) | ID | Primary keys map to ID | | bigint, integer, int | Int | | | text, varchar, char | String | | | uuid (PK) | ID | UUID primary keys also map to ID | | uuid | String | Non-PK UUIDs map to String | | double precision, numeric, float | Float | | | boolean | Boolean | | | timestamp, timestamptz | DateTime | Custom scalar | | jsonb, json | JSON | Custom scalar | | date | Date | Custom scalar | | Enum types | Enum | Preserves enum name | | Arrays (e.g., text[]) | [Type] | Mapped to GraphQL lists | ## Relationship Handling The writer intelligently generates relationship fields based on foreign key constraints: ### Forward Relationships (FK on this table) ```sql -- Post table has authorId FK to User.id CREATE TABLE post ( id bigint PRIMARY KEY, title text NOT NULL, author_id bigint NOT NULL REFERENCES user(id) ); ``` ```graphql type Post { id: ID! title: String! author: User! # Generated from authorId FK } ``` ### Reverse Relationships (FK on other table) ```graphql type User { id: ID! email: String! posts: [Post!]! # Reverse relationship (Post has FK to User) } ``` ### Many-to-Many Relationships Junction tables (tables with only PKs and FKs) are automatically detected and hidden: ```sql CREATE TABLE post_tag ( post_id bigint NOT NULL REFERENCES post(id), tag_id bigint NOT NULL REFERENCES tag(id), PRIMARY KEY (post_id, tag_id) ); ``` ```graphql type Post { id: ID! tags: [Tag!]! # Many-to-many through PostTag junction table } type Tag { id: ID! posts: [Post!]! # Reverse many-to-many } # Note: PostTag junction table is NOT included in output ``` ## Usage ### Basic Usage ```go import ( "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/writers" "git.warky.dev/wdevs/relspecgo/pkg/writers/graphql" ) opts := &writers.WriterOptions{ OutputPath: "schema.graphql", } writer := graphql.NewWriter(opts) err := writer.WriteDatabase(db) ``` ### With Metadata Options ```go opts := &writers.WriterOptions{ OutputPath: "schema.graphql", Metadata: map[string]any{ "includeScalarDeclarations": true, // Include scalar declarations "includeComments": true, // Include field/table comments }, } writer := graphql.NewWriter(opts) err := writer.WriteDatabase(db) ``` ### Write to Stdout ```go opts := &writers.WriterOptions{ OutputPath: "", // Empty path writes to stdout } writer := graphql.NewWriter(opts) err := writer.WriteDatabase(db) ``` ## CLI Usage ```bash # Convert PostgreSQL database to GraphQL relspec convert --from pgsql \ --from-conn "postgres://user:pass@localhost:5432/mydb" \ --to graphql --to-path schema.graphql # Convert GORM models to GraphQL relspec convert --from gorm --from-path ./models \ --to graphql --to-path schema.graphql # Convert JSON to GraphQL relspec convert --from json --from-path schema.json \ --to graphql --to-path schema.graphql ``` ## Output Format The generated GraphQL schema follows this structure: 1. **Header comment** (if enabled) 2. **Custom scalar declarations** (if any custom scalars are used) 3. **Enum definitions** (alphabetically sorted) 4. **Type definitions** (with fields ordered: ID first, then scalars alphabetically, then relationships) ### Example Output ```graphql # Generated GraphQL Schema # Database: myapp scalar DateTime scalar JSON scalar Date enum Role { ADMIN USER MODERATOR } type User { id: ID! createdAt: DateTime! email: String! name: String! role: Role! posts: [Post!]! profile: Profile } type Post { id: ID! content: String published: Boolean! publishedAt: Date title: String! author: User! tags: [Tag!]! } type Tag { id: ID! name: String! posts: [Post!]! } ``` ## Metadata Options | Option | Type | Description | Default | |--------|------|-------------|---------| | `includeScalarDeclarations` | bool | Include `scalar DateTime`, etc. declarations | true | | `includeComments` | bool | Include table/field descriptions as comments | true | | `preservePKType` | bool | Use Int/String for PKs instead of ID | false | ## Field Naming Conventions - **FK columns**: Foreign key columns like `authorId` are removed from the output; instead, a relationship field `author` is generated - **Relationship pluralization**: Reverse one-to-many relationships are pluralized (e.g., `posts`, `tags`) - **CamelCase**: Field names are kept in their original casing from the database ## Junction Table Detection A table is considered a junction table if it: 1. Has exactly 2 foreign key constraints 2. All columns are either primary keys or foreign keys 3. Has a composite primary key on the FK columns Junction tables are automatically hidden from the GraphQL output, and many-to-many relationship fields are generated on the related types instead. ## Limitations - All tables in all schemas are flattened into a single GraphQL schema - No support for GraphQL-specific features like directives, interfaces, or unions - Nullable vs non-nullable is determined solely by the `NOT NULL` constraint ## Example Conversion **Input** (Database Schema): ```sql CREATE TABLE user ( id bigint PRIMARY KEY, email text NOT NULL, created_at timestamp NOT NULL ); CREATE TABLE post ( id bigint PRIMARY KEY, title text NOT NULL, author_id bigint NOT NULL REFERENCES user(id) ); ``` **Output** (GraphQL Schema): ```graphql scalar DateTime type User { id: ID! createdAt: DateTime! email: String! posts: [Post!]! } type Post { id: ID! title: String! author: User! } ```