Skip to content

feat: Make GraphQL protocol more idiomatic #724

@coodos

Description

@coodos

GraphQL API is not idiomatically designed (GraphQL-shaped REST)

Summary

The current GraphQL API works functionally, but follows REST-style patterns and leaks internal storage abstractions. This limits GraphQL’s strengths (typed queries, evolvability, pagination, introspection) and will create long-term client and maintenance issues.

Problems

1. REST-style query naming

Current queries use verbs and “by X” patterns:

  • `getMetaEnvelopeById`
  • `findMetaEnvelopesByOntology`
  • `searchMetaEnvelopes`

GraphQL convention prefers noun-based entry points with arguments.

Suggested direction

metaEnvelope(id: ID!): MetaEnvelope
metaEnvelopes(
  ontology: ID
  search: MetaEnvelopeSearchInput
  first: Int
  after: String
): MetaEnvelopeConnection

2. No pagination / connection model

List-returning queries return unpaginated arrays.

Impact

  • Unbounded result sets
  • No stable ordering
  • Poor cache behavior

Suggested direction
Use cursor-based connections for all list fields.

3. Underspecified search API

`searchMetaEnvelopes(term: String)` is:

  • case-sensitive
  • string-only
  • not extensible

Suggested direction

input MetaEnvelopeSearchInput {
  term: String
  caseSensitive: Boolean = false
  fields: [String!]
  mode: SearchMode = CONTAINS
}

4. Internal storage model leaks into the schema

The `Envelope` model overloads `ontology`:

  • sometimes an ontology UUID
  • sometimes a payload field name (`content`, `authorId`, etc.)

All values are flattened into `{ value, valueType }`.

Impact

  • Ambiguous semantics
  • Loss of type safety
  • GraphQL introspection becomes low-value

Suggested direction
Separate concerns explicitly:

  • `MetaEnvelope.ontologyId: ID!`
  • `Envelope.fieldKey: String!`
  • `Envelope.value: JSON!` (or a union)

5. `parsed` field undermines GraphQL’s purpose

`parsed` exposes an opaque JSON representation of the payload.

Impact

  • Encourages blob-based access
  • Makes schema evolution risky

Suggested direction
Expose typed fields or explicitly named JSON blobs (`payloadJson`).

6. Mutation payloads expose internals

Mutations return internal storage structures (e.g. `envelopes`) alongside domain objects.

Impact

  • Clients couple to implementation details
  • Harder to evolve internals safely

Suggested direction
Return structured mutation payloads:

type CreateMetaEnvelopePayload {
  metaEnvelope: MetaEnvelope
  errors: [UserError!]
}

7. Headers as part of the API contract

Operations require custom headers like `X-ENAME`.

Impact

  • Unusual for GraphQL APIs
  • Context should be derived from auth/session

Suggested direction
Derive tenant/identity context from JWT or a single consistent header.

8. “Global IDs” without Node semantics

The API refers to “global IDs” but does not expose `Node` or `node(id: ID!)`.

Impact

  • Misleading ID contract

Suggested direction
Either fully implement Relay-style global IDs or treat IDs as UUIDs.

9. Delete mutation returns no structured result

`deleteMetaEnvelope` returns no payload.

Impact

  • Harder for clients to update caches or confirm results

Suggested direction

type DeleteMetaEnvelopePayload {
  deletedId: ID!
  success: Boolean!
}

Goal

Refactor the GraphQL API to be:

  • noun-based
  • paginated
  • type-safe
  • evolvable
  • decoupled from internal storage models`;

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions