Split GraphQL schemas into per-entity files
Split each API's monolithic schema.graphql into per-coredata-model
files under graphql/ subdirectories. gqlgen's follow-schema layout
with {name}.resolvers.go template generates one resolver file per
schema file. Relay uses schema + schemaExtensions to load the split
files.
Connect API: 8 files (base, session, organization, profile,
personal_api_key, saml, scim, audit_log)
Trust API: 5 files (base, trust_center, auth, nda, mailing_list)
Console API: 25 files covering all domain entities
Types extended across files (Organization, Mutation, Viewer,
TrustCenter, Identity) are defined in base.graphql as required by
Relay's schemaExtensions.
Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -1,6 +1,15 @@
|
||||
# GraphQL (Go Backend — gqlgen)
|
||||
|
||||
Schema-first GraphQL using [gqlgen](https://gqlgen.com/). The schema is hand-written; Go types and resolvers are generated.
|
||||
Schema-first GraphQL using [gqlgen](https://gqlgen.com/). The schema is hand-written and split into per-entity files under `graphql/`; Go types and resolvers are generated.
|
||||
|
||||
## Schema file organization
|
||||
|
||||
Each API's schema lives in `pkg/server/api/{api}/v1/graphql/` as multiple `.graphql` files, one per coredata model:
|
||||
|
||||
- `base.graphql` — directives, scalars, Node interface, PageInfo, OrderDirection, root Query/Mutation/Organization types
|
||||
- Entity files (e.g., `vendor.graphql`, `control.graphql`) — use `extend type Organization`, `extend type Mutation`, etc.
|
||||
|
||||
gqlgen's `follow-schema` layout generates one resolver file per schema file (e.g., `vendor.resolvers.go`). Types that get extended across files (Organization, Mutation, Viewer, TrustCenter) must be defined in `base.graphql`.
|
||||
|
||||
## Connection types and `@goModel`
|
||||
|
||||
@@ -40,13 +49,15 @@ enum VendorOrderField
|
||||
|
||||
## Schema directives
|
||||
|
||||
| Directive | Target | Purpose |
|
||||
|-----------|--------|---------|
|
||||
| `@goModel(model: "...")` | `OBJECT`, `ENUM`, `INPUT_OBJECT`, `SCALAR`, `INTERFACE`, `UNION` | Map GraphQL type to existing Go type |
|
||||
| `@goEnum(value: "...")` | `ENUM_VALUE` | Map enum value to Go constant |
|
||||
| `@goField(forceResolver: true)` | `FIELD_DEFINITION` | Force a resolver function instead of struct field |
|
||||
| `@goField(name: "...")` | `FIELD_DEFINITION`, `INPUT_FIELD_DEFINITION` | Override Go field name |
|
||||
| `@goField(omittable: true)` | `INPUT_FIELD_DEFINITION` | Use `graphql.Omittable[T]` for distinguishing null vs absent |
|
||||
|
||||
| Directive | Target | Purpose |
|
||||
| ------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| `@goModel(model: "...")` | `OBJECT`, `ENUM`, `INPUT_OBJECT`, `SCALAR`, `INTERFACE`, `UNION` | Map GraphQL type to existing Go type |
|
||||
| `@goEnum(value: "...")` | `ENUM_VALUE` | Map enum value to Go constant |
|
||||
| `@goField(forceResolver: true)` | `FIELD_DEFINITION` | Force a resolver function instead of struct field |
|
||||
| `@goField(name: "...")` | `FIELD_DEFINITION`, `INPUT_FIELD_DEFINITION` | Override Go field name |
|
||||
| `@goField(omittable: true)` | `INPUT_FIELD_DEFINITION` | Use `graphql.Omittable[T]` for distinguishing null vs absent |
|
||||
|
||||
|
||||
## Cursor pagination schema types
|
||||
|
||||
@@ -158,7 +169,8 @@ Default page size is **25** when neither `first` nor `last` is provided.
|
||||
## Adding a new paginated field — checklist
|
||||
|
||||
1. **Schema** — add `enum XxxOrderField` (with `@goModel`/`@goEnum`), `input XxxOrder`, `type XxxConnection` (with `@goModel` and `totalCount` using `@goField(forceResolver: true)`), `type XxxEdge`, and the connection field with Relay arguments on the parent type
|
||||
2. **Coredata** — add `*_order_field.go` (with `Column()`, `IsValid()`, marshaling), `CursorKey(field)` method on the entity, and the `LoadAllBy*` query using cursor SQL fragments + `page.NewPage()`
|
||||
2. **Coredata** — add `*_order_field.go` (with `Column()`, `IsValid()`, marshaling), `CursorKey(field)` method on the entity, and the `LoadAllBy`* query using cursor SQL fragments + `page.NewPage()`
|
||||
3. **API types** — add `*_connection.go` with `OrderBy` alias, connection struct, `NewXxxConnection`, `NewXxxEdge`
|
||||
4. **Resolver** — implement the resolver (authorize, build order, build cursor, call service, build connection)
|
||||
5. **Codegen** — run `go generate` for the relevant API package
|
||||
|
||||
|
||||
@@ -6,16 +6,18 @@ The console app uses [Relay](https://relay.dev/) as its GraphQL client. All Grap
|
||||
|
||||
Two Relay environments connect to two separate GraphQL APIs:
|
||||
|
||||
| Environment | Endpoint | Purpose |
|
||||
|-------------|----------|---------|
|
||||
| `coreEnvironment` | `/api/console/v1/graphql` | Main application data |
|
||||
| `iamEnvironment` | `/api/connect/v1/graphql` | Authentication / identity |
|
||||
|
||||
| Environment | Endpoint | Purpose |
|
||||
| ----------------- | ------------------------- | ------------------------- |
|
||||
| `coreEnvironment` | `/api/console/v1/graphql` | Main application data |
|
||||
| `iamEnvironment` | `/api/connect/v1/graphql` | Authentication / identity |
|
||||
|
||||
|
||||
Configured in `apps/console/src/environments.ts`. Each has its own store with 1-minute query cache expiration.
|
||||
|
||||
## Relay compiler
|
||||
|
||||
Config lives in `relay.config.json` at the repo root with three projects (`core`, `iam`, `trust`) mapped to different source directories and schemas. Generated files go into `__generated__/` directories.
|
||||
Config lives in `relay.config.json` at the repo root with three projects (`core`, `iam`, `trust`) mapped to different source directories and schemas. Each project uses `schema` pointing to `base.graphql` and `schemaExtensions` pointing to the `graphql/` directory containing the per-entity schema files. Generated files go into `__generated__/` directories.
|
||||
|
||||
```sh
|
||||
npm run relay # clean + compile (from repo root)
|
||||
@@ -381,7 +383,7 @@ return () => {
|
||||
|
||||
## File organization
|
||||
|
||||
GraphQL operations are colocated with the components that use them. See [`contrib/claude/app-arborescence.md`](app-arborescence.md) for the full folder layout.
|
||||
GraphQL operations are colocated with the components that use them. See `[contrib/claude/app-arborescence.md](app-arborescence.md)` for the full folder layout.
|
||||
|
||||
```
|
||||
pages/organizations/vendors/
|
||||
@@ -394,4 +396,4 @@ pages/organizations/vendors/
|
||||
VendorComplianceTab.tsx
|
||||
```
|
||||
|
||||
Component-specific operations (queries, fragments, mutations) are defined inline in the component file that uses them. Shared sub-components live in `_components/` next to the page (scoped to the nearest common ancestor).
|
||||
Component-specific operations (queries, fragments, mutations) are defined inline in the component file that uses them. Shared sub-components live in `_components/` next to the page (scoped to the nearest common ancestor).
|
||||
Reference in New Issue
Block a user