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

@hypequery/serve

v0.1.1

Published

Declarative HTTP server for exposing hypequery analytics endpoints

Readme

@hypequery/serve

Declarative HTTP server for exposing hypequery analytics endpoints. Build type-safe REST APIs with automatic OpenAPI documentation, authentication, middleware, and multi-platform deployment support.

Installation

npm install @hypequery/serve zod

Peer dependencies: zod@^3, tsx@^4 (optional, for dev server)

Quick Start

// analytics/server.ts
import { initServe } from '@hypequery/serve';
import { z } from 'zod';
import { db } from './client';

const { define, queries, query } = initServe({
  context: () => ({ db }),
});

export const api = define({
  queries: queries({
    weeklyRevenue: query
      .describe('Calculate weekly revenue')
      .input(z.object({ startDate: z.string() }))
      .query(({ ctx, input }) =>
        ctx.db
          .table('sales')
          .select(['total_amount'])
          .where('date', 'gte', input.startDate)
          .sum('total_amount', 'total')
          .execute()
      ),
  }),
});

// Expose as HTTP endpoint
api.route('/weeklyRevenue', api.queries.weeklyRevenue);

Your API is now running with:

  • Endpoint: POST http://localhost:4000/weeklyRevenue
  • Docs: http://localhost:4000/docs (interactive Swagger UI)
  • OpenAPI: http://localhost:4000/openapi.json (machine-readable schema)

Core Concepts

1. Server Builders

initServe<TContext, TAuth>(options)

Main entry point for creating a hypequery server. Returns an initializer with type-safe query builders and context inference.

Parameters:

interface ServeInitializerOptions<TContext, TAuth> {
  // Context factory (runs per-request to inject dependencies)
  context: TContext | ((opts: { request: ServeRequest; auth: TAuth | null }) => TContext | Promise<TContext>);

  // Base path for all routes
  basePath?: string;

  // Authentication strategy
  auth?: AuthStrategy<TAuth> | AuthStrategy<TAuth>[];

  // Global middlewares (run before every endpoint)
  middlewares?: ServeMiddleware<any, any, TContext, TAuth>[];

  // Multi-tenancy configuration
  tenant?: TenantConfig<TAuth>;

  // Lifecycle hooks
  hooks?: ServeLifecycleHooks<TAuth>;

  // OpenAPI configuration
  openapi?: OpenApiOptions;

  // Docs UI configuration
  docs?: DocsOptions;
}

Returns:

interface ServeInitializer<TContext, TAuth> {
  // Query builder (chainable query configuration)
  query: QueryProcedureBuilder<TContext, TAuth>;

  // Helper to group multiple queries
  queries<TQueries>(definitions: TQueries): TQueries;

  // Define server with queries
  define<TQueries>(config: { queries: TQueries }): ServeBuilder<TQueries, TContext, TAuth>;
}

Example:

// Define context once
const { define, queries, query } = initServe({
  basePath: '/api',
  context: async ({ auth }) => ({
    db: createDatabase(),
    userId: auth?.userId,
  }),
  auth: createBearerTokenStrategy({
    validate: async (token) => {
      const user = await verifyJWT(token);
      return user ? { userId: user.id, role: user.role } : null;
    },
  }),
});

// Create queries with inferred types
export const api = define({
  queries: queries({
    getUser: query
      .input(z.object({ id: z.string() }))
      .query(async ({ ctx, input }) => {
        return ctx.db.query.users.findFirst({
          where: eq(users.id, input.id),
        });
      }),

    weeklyRevenue: query
      .describe('Calculate weekly revenue totals')
      .input(z.object({ startDate: z.string() }))
      .query(async ({ ctx, input }) => {
        return ctx.db
          .table('sales')
          .where('date', 'gte', input.startDate)
          .sum('amount', 'total')
          .execute();
      }),
  }),
});

// Expose as HTTP endpoints
api.route('/users/:id', api.queries.getUser, { method: 'GET' });
api.route('/weeklyRevenue', api.queries.weeklyRevenue);

2. HTTP Adapters

Adapters convert the framework-agnostic ServeHandler into platform-specific handlers.

createNodeHandler(handler)

Creates a Node.js HTTP handler for use with http.createServer() or Express.

import { createNodeHandler } from '@hypequery/serve';
import { createServer } from 'http';

const nodeHandler = createNodeHandler(api.handler);
const server = createServer(nodeHandler);
server.listen(3000);

createFetchHandler(handler)

Creates a Web Fetch API handler for modern runtimes (Cloudflare Workers, Deno, Bun).

import { createFetchHandler } from '@hypequery/serve';

const fetchHandler = createFetchHandler(api.handler);

// Cloudflare Workers
export default {
  fetch: fetchHandler,
};

// Deno
Deno.serve(fetchHandler);

// Bun
Bun.serve({
  fetch: fetchHandler,
  port: 3000,
});

createVercelEdgeHandler(handler)

Creates a Vercel Edge Runtime handler (uses Fetch API).

// pages/api/analytics.ts
import { createVercelEdgeHandler } from '@hypequery/serve';
import { api } from '@/analytics/server';

export const config = { runtime: 'edge' };
export default createVercelEdgeHandler(api.handler);

createVercelNodeHandler(handler)

Creates a Vercel Node.js handler (uses Node HTTP).

// pages/api/analytics.ts
import { createVercelNodeHandler } from '@hypequery/serve';
import { api } from '@/analytics/server';

export default createVercelNodeHandler(api.handler);

3. Authentication

createApiKeyStrategy<TAuth>(options)

Creates an authentication strategy that validates API keys from headers or query parameters.

Parameters:

interface ApiKeyStrategyOptions<TAuth> {
  header?: string;        // Header name (default: "authorization")
  queryParam?: string;    // Query param name (optional)
  validate: (key: string, request: ServeRequest) => Promise<TAuth | null> | TAuth | null;
}

Example:

import { createApiKeyStrategy } from '@hypequery/serve';

const apiKeyAuth = createApiKeyStrategy({
  header: 'x-api-key',
  queryParam: 'apiKey',  // Allow ?apiKey=xxx for development
  validate: async (key, request) => {
    const user = await db.query.apiKeys.findFirst({
      where: eq(apiKeys.key, key),
    });

    if (!user || user.revoked) return null;

    return {
      userId: user.userId,
      scopes: user.scopes,
    };
  },
});

const api = defineServe({
  auth: apiKeyAuth,
  queries: { /* ... */ },
});

Usage:

# Header (preferred)
curl -H "x-api-key: sk_live_abc123" http://localhost:3000/revenue

# Query param (development only)
curl http://localhost:3000/revenue?apiKey=sk_live_abc123

createBearerTokenStrategy<TAuth>(options)

Creates an authentication strategy that validates Bearer tokens (JWT, OAuth).

Parameters:

interface BearerTokenStrategyOptions<TAuth> {
  header?: string;   // Header name (default: "authorization")
  prefix?: string;   // Token prefix (default: "Bearer ")
  validate: (token: string, request: ServeRequest) => Promise<TAuth | null> | TAuth | null;
}

Example:

import { createBearerTokenStrategy } from '@hypequery/serve';
import jwt from 'jsonwebtoken';

const jwtAuth = createBearerTokenStrategy({
  validate: async (token) => {
    try {
      const payload = jwt.verify(token, process.env.JWT_SECRET) as {
        sub: string;
        role: string;
      };

      return {
        userId: payload.sub,
        role: payload.role,
      };
    } catch {
      return null;  // Invalid token
    }
  },
});

const api = defineServe({
  auth: jwtAuth,
  queries: { /* ... */ },
});

Usage:

curl -H "Authorization: Bearer eyJhbGc..." http://localhost:3000/revenue

4. Query Builder (Advanced Query Configuration)

The query builder provides a chainable API for configuring queries with full type inference.

Available Methods:

interface QueryProcedureBuilder<TContext, TAuth> {
  // Description (OpenAPI documentation, supports Markdown)
  describe(description: string): QueryProcedureBuilder;

  // Input schema (Zod)
  input<TSchema extends ZodTypeAny>(schema: TSchema): QueryProcedureBuilder;

  // HTTP method (GET, POST, PUT, DELETE, etc.)
  method(method: HttpMethod): QueryProcedureBuilder;

  // Add single tag (for OpenAPI grouping)
  tag(tag: string): QueryProcedureBuilder;

  // Add multiple tags
  tags(tags: string[]): QueryProcedureBuilder;

  // Cache TTL in milliseconds (sets Cache-Control header)
  cache(ttlMs: number | null): QueryProcedureBuilder;

  // Authentication strategy (overrides global auth)
  auth(strategy: AuthStrategy<TAuth>): QueryProcedureBuilder;

  // Multi-tenancy configuration
  tenant(config: Partial<TenantConfig<TAuth>>): QueryProcedureBuilder;
  tenantOptional(config?: Partial<TenantConfig<TAuth>>): QueryProcedureBuilder;
  require(): QueryProcedureBuilder;

  // Custom metadata (for extensions)
  custom(metadata: Record<string, unknown>): QueryProcedureBuilder;

  // Add middleware (runs before query handler)
  use(...middlewares: ServeMiddleware[]): QueryProcedureBuilder;

  // Define query handler (terminal operation)
  query<TExecutable extends ExecutableQuery>(
    executable: TExecutable
  ): ServeQueryConfig;
}

Example:

const { query } = initServe({
  context: async () => ({ db: createDatabase() }),
});

const getAnalytics = query
  .describe(`
    Returns aggregated analytics for the specified metric and date range.
    Supports revenue, user count, and session metrics.
  `)
  .input(z.object({
    startDate: z.string(),
    endDate: z.string(),
    metric: z.enum(['revenue', 'users', 'sessions']),
  }))
  .tag('Analytics')
  .cache(300000)  // Cache for 5 minutes
  .query(async ({ ctx, input }) => {
    const result = await ctx.db
      .table('analytics')
      .where('date', 'gte', input.startDate)
      .where('date', 'lte', input.endDate)
      .sum(input.metric, 'total')
      .execute();

    return result[0];
  });

5. Multi-Tenancy

Hypequery supports multi-tenant applications with automatic tenant isolation.

Configuration:

interface TenantConfig<TAuth> {
  // Extract tenant ID from auth context
  extract: (auth: TAuth) => string | null;

  // Tenant isolation mode
  mode?: 'manual' | 'auto-inject';  // Default: 'manual'

  // Column name for tenant filtering (required for auto-inject mode)
  column?: string;

  // Is tenant required? (default: true)
  required?: boolean;

  // Custom error message when tenant is missing
  errorMessage?: string;
}

Example (Manual Mode):

const { define, queries, query } = initServe({
  tenant: {
    extract: (auth) => auth?.tenantId ?? null,
    mode: 'manual',  // You manually filter by tenantId
    required: true,
  },
  context: async ({ auth }) => ({
    db: createDatabase(),
    tenantId: auth?.tenantId,
  }),
});

export const api = define({
  queries: queries({
    getUsers: query
      .query(async ({ ctx }) => {
        // Manually filter by tenant
        return ctx.db
          .table('users')
          .where('tenant_id', 'eq', ctx.tenantId)
          .execute();
      }),
  }),
});

Example (Auto-Inject Mode):

const { define, queries, query } = initServe({
  tenant: {
    extract: (auth) => auth?.organizationId ?? null,
    mode: 'auto-inject',
    column: 'organization_id',  // Column to filter on
  },
  context: async () => ({
    db: createDatabase(),
  }),
});

export const api = define({
  queries: queries({
    getUsers: query
      .query(async ({ ctx }) => {
        // Tenant filter is automatically injected
        return ctx.db
          .table('users')
          .select(['id', 'name'])
          .execute();
        // Equivalent to: SELECT id, name FROM users WHERE organization_id = <tenant_id>
      }),
  }),
});

Per-query override (optional tenant, no auto-inject):

export const api = define({
  queries: queries({
    adminStats: query
      .tenantOptional({ mode: 'manual' })
      .query(async ({ ctx }) => {
        if (ctx.tenantId) {
          return ctx.db.table('stats').where('tenant_id', 'eq', ctx.tenantId).execute();
        }
        return ctx.db.table('stats').execute();
      }),
  }),
});

6. Client Configuration

extractClientConfig(api)

Extracts serializable client configuration from a ServeBuilder. Returns HTTP method information for each query, used by @hypequery/react to configure hooks.

Example:

// Server-side API route
import { api } from '@/analytics/server';
import { extractClientConfig } from '@hypequery/serve';

export async function GET() {
  return Response.json(extractClientConfig(api));
}

// Returns:
// {
//   "weeklyRevenue": { "method": "GET" },
//   "createSale": { "method": "POST" }
// }

Client-side usage:

// lib/analytics.ts
import { createHooks } from '@hypequery/react';
import type { Api } from '@/analytics/server';

const config = await fetch('/api/config').then(r => r.json());

export const { useQuery, useMutation } = createHooks<Api>({
  baseUrl: '/api/analytics',
  config,  // Auto-configures HTTP methods
});

defineClientConfig(config)

Type-safe helper to manually define client configuration when you can't access the API object.

Example:

import { defineClientConfig } from '@hypequery/serve';

const config = defineClientConfig({
  weeklyRevenue: { method: 'GET' },
  createSale: { method: 'POST' },
  updateProduct: { method: 'PUT' },
  deleteOrder: { method: 'DELETE' },
});

export const { useQuery, useMutation } = createHooks<Api>({
  baseUrl: '/api',
  config,
});

7. Development Server

serveDev(api, options?)

Starts a development server with enhanced logging and automatic documentation.

Parameters:

interface ServeDevOptions {
  port?: number;          // Default: 4000 or process.env.PORT
  hostname?: string;      // Default: 'localhost'
  quiet?: boolean;        // Suppress logs (default: false)
  signal?: AbortSignal;   // Graceful shutdown signal
  logger?: (message: string) => void;  // Custom logger
}

Example:

import { serveDev } from '@hypequery/serve';
import { api } from './analytics/server';

await serveDev(api, {
  port: 4000,
  logger: (msg) => console.log(`[API] ${msg}`),
});

// Output:
// [API] hypequery dev server running at http://localhost:4000
// [API] Docs available at http://localhost:4000/docs

Advanced Features

Middleware

Middlewares run before query handlers and can modify context, validate permissions, or log requests.

Signature:

type ServeMiddleware<TInput, TOutput, TContext, TAuth> = (
  ctx: EndpointContext<TInput, TContext, TAuth>,
  next: () => Promise<TOutput>
) => Promise<TOutput>;

Example:

// Logging middleware
const logMiddleware: ServeMiddleware<any, any, any, any> = async (ctx, next) => {
  console.log(`[${ctx.request.method}] ${ctx.request.path}`);
  const start = Date.now();
  const result = await next();
  console.log(`Completed in ${Date.now() - start}ms`);
  return result;
};

// Permission middleware
const requireAdmin: ServeMiddleware<any, any, any, { role: string }> = async (ctx, next) => {
  if (ctx.auth?.role !== 'admin') {
    throw new Error('Admin access required');
  }
  return next();
};

// Apply globally
const api = defineServe({
  middlewares: [logMiddleware],
  queries: { /* ... */ },
});

// Apply per-query
const deleteUser = t.procedure
  .use(requireAdmin)
  .query(async ({ input, ctx }) => {
    // Only admins can reach here
  });

Lifecycle Hooks

Hooks provide observability into the request lifecycle.

Available Hooks:

interface ServeLifecycleHooks<TAuth> {
  // Before request processing
  onRequestStart?: (event: {
    requestId: string;
    queryKey: string;
    metadata: EndpointMetadata;
    request: ServeRequest;
    auth: TAuth | null;
  }) => void | Promise<void>;

  // After successful request
  onRequestEnd?: (event: {
    requestId: string;
    queryKey: string;
    metadata: EndpointMetadata;
    request: ServeRequest;
    auth: TAuth | null;
    durationMs: number;
    result: unknown;
  }) => void | Promise<void>;

  // On authentication failure
  onAuthFailure?: (event: {
    requestId: string;
    queryKey: string;
    metadata: EndpointMetadata;
    request: ServeRequest;
    auth: TAuth | null;
    reason: 'MISSING' | 'INVALID';
  }) => void | Promise<void>;

  // On any error
  onError?: (event: {
    requestId: string;
    queryKey: string;
    metadata: EndpointMetadata;
    request: ServeRequest;
    auth: TAuth | null;
    durationMs: number;
    error: unknown;
  }) => void | Promise<void>;
}

Example:

const api = defineServe({
  hooks: {
    onRequestStart: async (event) => {
      await analytics.track({
        event: 'api_request_start',
        queryKey: event.queryKey,
        userId: event.auth?.userId,
      });
    },

    onError: async (event) => {
      await errorReporting.captureException(event.error, {
        queryKey: event.queryKey,
        requestId: event.requestId,
      });
    },
  },
  queries: { /* ... */ },
});

OpenAPI Configuration

Options:

interface OpenApiOptions {
  enabled?: boolean;       // Enable OpenAPI endpoint (default: true)
  path?: string;           // OpenAPI JSON path (default: '/openapi.json')
  info?: {
    title?: string;        // API title (default: 'Hypequery API')
    version?: string;      // API version (default: '1.0.0')
    description?: string;  // API description
  };
  servers?: Array<{
    url: string;
    description?: string;
  }>;
}

Example:

const api = defineServe({
  openapi: {
    path: '/api-schema.json',
    info: {
      title: 'Analytics API',
      version: '2.0.0',
      description: 'Real-time analytics and reporting API',
    },
    servers: [
      { url: 'https://api.example.com', description: 'Production' },
      { url: 'http://localhost:4000', description: 'Development' },
    ],
  },
  queries: { /* ... */ },
});

Documentation UI

Options:

interface DocsOptions {
  enabled?: boolean;  // Enable docs UI (default: true)
  path?: string;      // Docs UI path (default: '/docs')
  title?: string;     // Page title (default: 'API Documentation')
}

Example:

const api = defineServe({
  docs: {
    path: '/api-docs',
    title: 'Analytics API Reference',
  },
  queries: { /* ... */ },
});

Deployment Examples

Vercel (Edge Runtime)

// pages/api/analytics/[...path].ts
import { createVercelEdgeHandler } from '@hypequery/serve';
import { api } from '@/analytics/server';

export const config = { runtime: 'edge' };
export default createVercelEdgeHandler(api.handler);

Cloudflare Workers

// src/index.ts
import { createFetchHandler } from '@hypequery/serve';
import { api } from './analytics/server';

const handler = createFetchHandler(api.handler);

export default {
  fetch: handler,
};

Express.js

// server.ts
import express from 'express';
import { createNodeHandler } from '@hypequery/serve';
import { api } from './analytics/server';

const app = express();
const analyticsHandler = createNodeHandler(api.handler);

app.use('/api/analytics', analyticsHandler);
app.listen(3000);

Next.js App Router

// app/api/analytics/[...path]/route.ts
import { createFetchHandler } from '@hypequery/serve';
import { api } from '@/analytics/server';

const handler = createFetchHandler(api.handler);

export { handler as GET, handler as POST, handler as PUT, handler as DELETE };

TypeScript

All functions are fully typed with automatic inference:

import { initServe } from '@hypequery/serve';
import { z } from 'zod';

const { define, queries, query } = initServe({
  context: async ({ auth }) => ({
    db: createDatabase(),
    userId: auth?.userId,
  }),
});

export const api = define({
  queries: queries({
    getUser: query
      .input(z.object({ id: z.string() }))
      .query(async ({ ctx, input }) => {
        // input: { id: string }
        // ctx: { db: Database; userId: string | undefined }
        return ctx.db
          .table('users')
          .where('id', 'eq', input.id)
          .select(['name', 'email'])
          .limit(1)
          .execute();
      }),
  }),
});

// Execute with type safety (aliases: api.execute, api.client)
const result = await api.run('getUser', { id: '123' });
const user = result[0];
// user: { name: string; email: string }

Error Handling

All errors follow a consistent format:

interface ErrorEnvelope {
  error: {
    type: 'VALIDATION_ERROR' | 'UNAUTHORIZED' | 'NOT_FOUND' | 'INTERNAL_SERVER_ERROR';
    message: string;
    details?: Record<string, unknown>;
  };
}

Example Error Response:

{
  "error": {
    "type": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": {
      "issues": [
        {
          "code": "invalid_type",
          "expected": "string",
          "received": "number",
          "path": ["startDate"],
          "message": "Expected string, received number"
        }
      ]
    }
  }
}

License

Apache-2.0