@t8n/gql
v1.0.3
Published
A professional, high-performance GraphQL execution extension for TitanPL. Built to seamlessly integrate GraphQL queries with Titan's synchronous Gravity engine and native `drift` asynchronous suspender.
Readme
@t8n/gql
A professional, high-performance GraphQL execution extension for TitanPL. Built to seamlessly integrate GraphQL queries with Titan's synchronous Gravity engine and native drift asynchronous suspender.
Key Features
- Drift Suspend Integration: Integrated with the Titan PL Drift runner. Run the
gql initcommand to patch thegraphql-jsexecutor to cleanly handle__SUSPEND__signals, allowing standarddrift(...)async operations to resolve synchronously within query and field resolvers. - Zero-Import Schema Builder: Declare GraphQL types cleanly as JavaScript scope constants using
gql.builder(). Core scalars (builder.ID,builder.String,builder.Boolean,builder.Int,builder.Float) and lists (builder.list(Type)) are exposed directly on the builder instance, eliminating the need to import a separate type registry. - Zero-Annotation Resolver Autocomplete: In JS files, parameters in field resolvers default to
any. By using the standard JavaScript default parameter syntaxtodo = Todo, the type compiler automatically infers the parameter's type as the exact shape ofTodo. At runtime, the GraphQL executor passes the parent object, so the default value is never evaluated. This yields 100% type safety and IDE autocomplete with zero JSDoc annotations. - Type-Level Resolvers: Keep schema execution separate and clean. You can pass a standard type-level
resolversmap configuration (e.g.{ Todo: { user: resolver } }) rather than writing manual.map()loops and inline field methods inside your root query resolvers. - Pure Native Titan Runtime: Fully optimized for Gravity sandboxes. Action execution is completely decoupled from filesystem dependencies and Node.js shims, running seamlessly within restricted V8 action isolates.
Installation
npm i @t8n/gqlInitialize GQL Patcher
After installing the package, run the initialization command to patch the graphql executor files in your project:
npx gql initUsage Guide
Here is a complete type-safe, drift-integrated action implementation using @t8n/gql:
/* eslint-disable titanpl/drift-only-titan-async */
import { defineAction, fetch } from "@titanpl/native";
import Gql, { gql } from "@t8n/gql";
// 1. Initialize the Schema Builder
const builder = gql.builder();
// 2. Define Schema Types as JS Constants (using builder scalars)
const User = builder.type("User", {
id: builder.ID.required,
name: builder.String.required,
username: builder.String.required,
email: builder.String.required,
phone: builder.String.required
});
const Todo = builder.type("Todo", {
userId: builder.String.required,
id: builder.ID.required,
title: builder.String.required,
completed: builder.Boolean,
user: User // Reference User type directly
});
const Query = builder.type("Query", {
getTodos: builder.list(Todo), // Define lists
"getTodo(id: ID!)": Todo,
"getUserTodos(userId: String!)": builder.list(Todo)
});
// 3. Compile the GraphQL Schema SDL
const schema = builder.build();
// 4. Define Root Resolvers
const root = {
getTodos() {
// Perform synchronous-like native fetch suspended under drift
const res = drift(fetch("https://jsonplaceholder.typicode.com/todos"));
return JSON.parse(res.body);
},
getTodo({ id }) {
const res = drift(fetch(`https://jsonplaceholder.typicode.com/todos/${id}`));
return JSON.parse(res.body);
},
getUserTodos({ userId }) {
const res = drift(fetch(`https://jsonplaceholder.typicode.com/todos?userId=${userId}`));
return JSON.parse(res.body);
}
};
// 5. Define Nested Type-Level Field Resolvers
const resolvers = {
Todo: {
// Use default parameter 'todo = Todo' to gain perfect IDE autocomplete
user(todo = Todo) {
// todo.userId, todo.id, todo.title will autocomplete automatically!
const res = drift(fetch(`https://jsonplaceholder.typicode.com/users/${todo.userId}`));
return JSON.parse(res.body);
}
}
};
// 6. Instantiate the GraphQL service
const gqlService = new Gql({
schema,
rootValue: root,
resolvers
});
// 7. Define and export the Titan Action
export default defineAction((req) => {
return gqlService.execute({
query: req.body.query,
variables: req.body.variables
});
});Connecting to a React Frontend (Vite)
To connect your React frontend to your TitanPl GraphQL action, follow this standard pattern:
1. Define a POST Route in TitanPl
Since GraphQL query payloads contain JSON requests, ensure your TitanPl router (app.js) exposes the action via a POST route:
import t from "@titanpl/route";
// Maps POST /test HTTP requests directly to your graphql action 'test.js'
t.post("/test").action("test");2. Configure Vite Local Proxy
To avoid CORS issues when making local network requests between the Vite dev server (e.g. http://localhost:5173) and the TitanPl server (e.g. http://localhost:5100), configure a proxy in vite.config.js:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/test': {
target: 'http://localhost:5100', // Redirects local /test calls to Titan server
changeOrigin: true
}
}
}
})3. Make HTTP Fetch Queries in React
Submit your query and variables using a standard POST request:
const handleExecute = async () => {
const query = `
query GetTodo($todoId: ID!) {
getTodo(id: $todoId) {
id
title
completed
user {
name
}
}
}
`;
const variables = { todoId: "5" };
const res = await fetch('/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ query, variables }),
});
const response = await res.json();
console.log("GraphQL Data:", response.data);
};How It Works Under The Hood
- Isolation and Suspend Handling: Standard GraphQL engines expect async resolvers to return Promises, which breaks the Gravity Engine's synchronous isolate design. The patched execution runtime intercepts resolver errors and propagates the
__SUSPEND__signal all the way up to the top handler of the engine. - CLI-Driven Patcher: The
gql initcommand patches both CJS (Executor.js) and ESM (Executor.mjs) inside thegraphqlpackage folder on the local file system. Doing this via CLI ensures that the engine runtime remains clean, fast, and compatible with sandboxed isolates. - Contextual Auto-Suggestions: By declaring GraphQL types as JavaScript variables (
User = builder.type(...)), the IDE natively tracks type scopes and references. This allows clicking a field reference to navigate to its definition and gives you instant hover previews.
