Added Graphql
This commit is contained in:
272
pkg/writers/graphql/README.md
Normal file
272
pkg/writers/graphql/README.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# 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!
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user