# 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!
}
```
