npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jamx-framework/graphql

v2.0.0

Published

JAMX Framework — GraphQL adapter

Readme

@jamx-framework/graphql

Descripción

Módulo de GraphQL para JAMX Framework. Proporciona una API completa para construir, ejecutar y gestionar esquemas GraphQL, incluyendo soporte para DataLoader (carga por lotes), paginación, y registro de resolvers. Permite crear servidores GraphQL type-safe que se integran perfectamente con el ecosistema JAMX.

Cómo funciona

El módulo implementa las capacidades principales de GraphQL:

  1. Schema Builder: Construye esquemas GraphQL a partir de clases y decoradores de JAMX
  2. Resolver Registry: Registra y gestiona resolvers para campos y tipos
  3. Executor: Ejecuta queries y mutations contra el schema
  4. DataLoader: Carga por lotes para evitar el problema N+1
  5. Pagination: Utilidades para paginación cursor-based y offset-based

Componentes principales

  • src/schema-builder.ts: Clase SchemaBuilder que construye esquemas GraphQL desde tipos TypeScript
  • src/executor.ts: Clase Executor que ejecuta queries y mutations
  • src/resolver-registry.ts: ResolverRegistry que gestiona resolvers de campos
  • src/dataloader.ts: Implementación de DataLoader para batch loading
  • src/pagination.ts: Utilidades de paginación (Connection, Edge, PageInfo)
  • src/types.ts: Tipos compartidos (GraphQLType, Resolver, etc.)
  • src/index.ts: Punto de exportación

Uso básico

import { SchemaBuilder, Executor, ResolverRegistry } from '@jamx-framework/graphql';

// 1. Definir tipos GraphQL
const builder = new SchemaBuilder();

const UserType = builder.objectType('User', {
  fields: (t) => ({
    id: t.field({ type: 'ID' }),
    name: t.field({ type: 'String' }),
    email: t.field({ type: 'String' }),
  }),
});

const QueryType = builder.objectType('Query', {
  fields: (t) => ({
    user: t.field({
      type: UserType,
      args: { id: t.arg({ type: 'ID' }) },
      resolve: async (parent, args, ctx) => {
        return await ctx.db.users.findById(args.id);
      },
    }),
  }),
});

// 2. Construir schema
const schema = builder.build();

// 3. Registrar resolvers
const registry = new ResolverRegistry();
registry.register('Query.user', async (parent, args, ctx) => {
  return await ctx.db.users.findById(args.id);
});

// 4. Ejecutar query
const executor = new Executor(schema, registry);
const result = await executor.execute(`
  query GetUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`, { id: '123' }, { db });

console.log('Resultado GraphQL:', result);

Ejemplos

Schema completo con mutations

import { SchemaBuilder } from '@jamx-framework/graphql';

const builder = new SchemaBuilder();

// Input types
const CreateUserInput = builder.input('CreateUserInput', {
  fields: (t) => ({
    name: t.field({ type: 'String' }),
    email: t.field({ type: 'String' }),
    password: t.field({ type: 'String' }),
  }),
});

// User type
const UserType = builder.objectType('User', {
  fields: (t) => ({
    id: t.field({ type: 'ID' }),
    name: t.field({ type: 'String' }),
    email: t.field({ type: 'String' }),
    createdAt: t.field({ type: 'String' }),
  }),
});

// Query
builder.queryType({
  fields: (t) => ({
    users: t.field({
      type: ['User'],
      resolve: async (_, __, ctx) => await ctx.db.users.findAll(),
    }),
    user: t.field({
      type: UserType,
      args: { id: t.arg({ type: 'ID' }) },
      resolve: async (_, args, ctx) => await ctx.db.users.findById(args.id),
    }),
  }),
});

// Mutation
builder.mutationType({
  fields: (t) => ({
    createUser: t.field({
      type: UserType,
      args: { input: t.arg({ type: CreateUserInput }) },
      resolve: async (_, { input }, ctx) => {
        const user = await ctx.db.users.insert(input);
        return user;
      },
    }),
    deleteUser: t.field({
      type: 'Boolean',
      args: { id: t.arg({ type: 'ID' }) },
      resolve: async (_, { id }, ctx) => {
        await ctx.db.users.delete(id);
        return true;
      },
    }),
  }),
});

const schema = builder.build();

DataLoader para evitar N+1

import { DataLoader } from '@jamx-framework/graphql';

// Crear DataLoader para cargar usuarios por IDs en batch
const userLoader = new DataLoader<string, User>(async (ids) => {
  // Esta función se llama una vez con todos los IDs necesarios
  const users = await db.users.findAll({ where: { id: ids } });
  const userMap = new Map(users.map(u => [u.id, u]));
  return ids.map(id => userMap.get(id) ?? null);
});

// En el resolver:
const userType = builder.objectType('User', {
  fields: (t) => ({
    id: t.field({ type: 'ID' }),
    name: t.field({ type: 'String' }),
    // Relación que usa DataLoader
    posts: t.field({
      type: ['Post'],
      resolve: async (user, _, ctx) => {
        // Esto se cachea automáticamente por usuario
        return await ctx.postLoader.load(user.id);
      },
    }),
  }),
});

Paginación con cursor

import { paginate, Connection, Edge } from '@jamx-framework/graphql';

// En el resolver:
const usersConnection: Connection<User> = await paginate(
  await db.users.findAll({ where: { active: true } }),
  { first: 10, after: cursor },
  (user) => user.id // cursor field
);

return {
  edges: usersConnection.edges.map(edge => ({
    cursor: edge.cursor,
    node: edge.node,
  })),
  pageInfo: {
    hasNextPage: usersConnection.hasNext,
    hasPrevPage: usersConnection.hasPrev,
    startCursor: usersConnection.edges[0]?.cursor,
    endCursor: usersConnection.edges[usersConnection.edges.length - 1]?.cursor,
  },
  totalCount: usersConnection.total,
};

Integración con JAMX Container

import { Container } from '@jamx-framework/core';
import { Executor } from '@jamx-framework/graphql';

// Registrar en contenedor
Container.registerSingleton('graphqlExecutor', () => {
  const schema = buildSchema(); // tu función que construye el schema
  const registry = new ResolverRegistry();
  // registrar resolvers...
  return new Executor(schema, registry);
});

// Usar en controlador
const executor = Container.resolve<Executor>('graphqlExecutor');
const result = await executor.execute(query, variables, context);
res.json(result);

Flujo interno

  1. Schema Building: SchemaBuilder procesa definiciones de tipos, queries y mutations
  2. Type Resolution: Convierte tipos TypeScript a tipos GraphQL (String, Int, ID, Object, etc.)
  3. Resolver Registration: ResolverRegistry asocia funciones a campos específicos
  4. Execution: Executor recibe una query, la parsea, resuelve campos y ejecuta resolvers
  5. DataLoader: Cachea requests de batch para evitar llamadas repetidas a BD
  6. Pagination: Utilidades para convertir resultados a formato Connection de GraphQL
  7. Error Handling: Los errores en resolvers se capturan y formatean según GraphQL spec

API Reference (Resumen)

SchemaBuilder

  • objectType(name, config): Define un tipo objeto
  • inputType(name, config): Define un tipo de input
  • interfaceType(name, config): Define una interfaz
  • enumType(name, values): Define un enum
  • unionType(name, types): Define una unión
  • queryType(config): Define el tipo Query raíz
  • mutationType(config): Define el tipo Mutation raíz
  • build(): Construye el schema final

Executor

  • constructor(schema, registry, context?)
  • execute(query, variables?, context?): Ejecuta una query
  • executeSubscription(...): Ejecuta una suscripción (si está soportado)

ResolverRegistry

  • register(fieldPath, resolver): Registra un resolver para un campo
  • get(fieldPath): Obtiene el resolver para un campo
  • clear(): Limpia todos los resolvers

DataLoader

  • load(key): Carga un dato por clave (batch automático)
  • loadMany(keys): Carga múltiples datos
  • clear(key): Limpia cache para una clave
  • clearAll(): Limpia todo el cache

Pagination

  • paginate<T>(data, options, cursorField): Pagina resultados
  • Connection<T>, Edge<T>, PageInfo: Tipos de paginación

Performance Considerations

  • DataLoader: Reduce queries N+1 a 1 query batch
  • Resolver caching: Los resultados de DataLoader se cachean por request
  • Schema validation: El schema se valida una vez al construir, no en cada query
  • Lazy loading: Los campos se resuelven solo cuando se solicitan
  • Batching: DataLoader agrupa requests en un solo batch por tick de event loop

Configuration Options

const executor = new Executor(schema, registry, {
  // Contexto global para todos los resolvers
  db,
  cache,
  auth,
  
  // Límites
  maxDepth: 10,        // profundidad máxima de query
  maxComplexity: 1000, // complejidad máxima
  fieldLimit: 100,     // máximo campos por query
  
  // Formato de errores
  formatError: (err) => ({
    message: err.message,
    locations: err.locations,
    path: err.path,
  }),
});

Testing

Tests en packages/graphql/tests/unit/:

pnpm test

Cubre:

  • Construcción de schemas
  • Resolución de campos
  • DataLoader batch loading
  • Paginación
  • Manejo de errores
  • Variables y fragments

Compatibility

  • Compatible con GraphQL SDL (Specification)
  • Funciona con Node.js 18+
  • Integra con cualquier base de datos a través de JAMX db
  • Soporta subscriptions (parcialmente, requiere WebSocket)

CLI Integration

  • jamx graphql:playground: Abre GraphQL Playground en dev
  • jamx graphql:introspect: Genera schema SDL
  • jamx graphql:validate <query>: Valida una query contra el schema

Best Practices

  1. Usar DataLoader para relaciones y queries N+1
  2. Limitar profundidad de queries para evitar DoS
  3. Validar queries en producción (no ejecutar queries arbitrarias)
  4. Cachear resultados de queries costosas
  5. Documentar schema con descripciones en campos
  6. Usar paginación para listas grandes
  7. Manejar errores consistentemente en resolvers

This GraphQL module provides a powerful, type-safe way to build GraphQL APIs in JAMX applications, with built-in support for batching, pagination, and seamless integration with the rest of the framework.