Subscriptions Stream query changes over WebSocket or SSE with the same compiler and policy model. core reference core/subscriptions core/subscriptions.md

Subscriptions

Stream query changes over WebSocket or SSE with the same compiler and policy model.

Transport

GraphJin supports subscription clients over WebSocket and SSE. The subscription query still goes through authentication, role matching, query compilation, saved-query policy, and cursor handling.

ClientWebSocket or SSESubscription querysame GraphQL compilerencrypted cursorsPolldelta loopDBProduction auth, roles, saved query rules, and cursor encryption apply to subscriptions too.

Query shape

GraphQL
subscription {
  products(limit: 10, order_by: { id: asc }) {
    id
    name
  }
}
Verified by Example_subscription tests/subs_test.go:40

Cursor resume

Subscriptions use the same cursor machinery as queries. The client requests a cursor field, stores the cursor GraphJin returns, and sends it back as a variable on reconnect or the next poll.

GraphQL
subscription LiveChats($chats_cursor: Cursor) {
  chats(
    first: 10
    after: $chats_cursor
    order_by: { id: asc }
  ) {
    id
    message
    created_at
  }

  chats_cursor
}

Cursor fields are root-level fields such as chats_cursor; they are not nested inside the row selection.

Dynamic security prefix

Subscription cursors must use GraphJin’s dynamic security prefix. Hardcoding cursor prefixes can make clients hang because encrypted cursor recognition fails.

The prefix is generated from the active GraphJin security context, for example a timestamp-based gj-...: value. Clients must treat the whole cursor as opaque. Do not build a cursor string yourself, do not strip or hardcode gj-, and do not assume MCP’s LLM-friendly __gj-enc: cache representation is the underlying GraphQL cursor.

Do not hardcode any cursor prefix in a client or subscription resume implementation.

Verified by Example_subscriptionWithCursor tests/subs_test.go:95
Verified by TestProcessCursorsForMCP serv/mcp_cursor_test.go:20

MCP cursor IDs

MCP can replace long encrypted cursors in tool output with short numeric IDs backed by an in-memory or Redis cursor cache. The next MCP request can pass that numeric ID in a cursor variable, and GraphJin expands it back to the original encrypted cursor before execution.

JSON
{
  "variables": {
    "products_cursor": "2"
  }
}

Only cursor-shaped variable names are expanded. Non-cursor variables are left alone, and already-encrypted cursors pass through unchanged.

Verified by TestExpandCursorIDs_VariousKeyNames serv/mcp_cursor_test.go:201
Verified by TestMCP_CursorRoundtripIntegration serv/mcp_test.go:282

Operational settings

YAML
subs_poll_duration: "2s"
subs_max_clients: 10000

http:
  sse: true
  websocket: true

Use explicit order_by with cursor subscriptions. Result ordering is undefined without it, and cursor resume depends on a stable order.

Docs