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

@unchainedshop/cockpit-api

v2.4.0

Published

A package to interact with the Cockpit CMS API, including functionalities to handle GraphQL requests and various CMS content manipulations.

Readme

Cockpit API

npm version CI License: MIT Node.js

A TypeScript client for interacting with Cockpit CMS, including GraphQL requests, content management, and schema stitching support.

Installation

npm install --save @unchainedshop/cockpit-api

Package Exports

This package provides three entry points:

| Export | Description | |--------|-------------| | @unchainedshop/cockpit-api | Full-featured async API client with caching and response transformation | | @unchainedshop/cockpit-api/schema | GraphQL schema stitching utilities | | @unchainedshop/cockpit-api/fetch | Lightweight client for edge/RSC environments |

Quick Start

Main Client

import { CockpitAPI } from '@unchainedshop/cockpit-api';

// With explicit endpoint
const cockpit = await CockpitAPI({
  endpoint: 'https://your-cockpit-instance.com/api/graphql',
});

// Or using environment variables
const cockpit = await CockpitAPI();  // Uses COCKPIT_GRAPHQL_ENDPOINT

Lightweight Fetch Client (Edge/RSC)

import { createFetchClient } from '@unchainedshop/cockpit-api/fetch';

// Synchronous initialization - no await needed
const cockpit = createFetchClient({
  endpoint: process.env.NEXT_PUBLIC_COCKPIT_ENDPOINT,
  tenant: 'mytenant',
  cache: 'force-cache',  // Uses platform caching
});

const page = await cockpit.pageByRoute('/about', { locale: 'en' });

GraphQL Schema Stitching

import { makeCockpitGraphQLSchema } from '@unchainedshop/cockpit-api/schema';
import { stitchSchemas } from '@graphql-tools/stitch';

const cockpitSchema = await makeCockpitGraphQLSchema({
  tenantHeader: 'x-cockpit-space',
  filterMutations: true,
});

const gatewaySchema = stitchSchemas({
  subschemas: [{ schema: cockpitSchema }],
});

Main Client API

GraphQL Requests

import { gql } from 'graphql-tag';

const query = gql`
  query {
    allPosts {
      title
      content
    }
  }
`;

const result = await cockpit.graphQL(query, {});

Content Operations

// Get a single content item
const post = await cockpit.getContentItem({ model: 'posts', id: '123' });

// With locale and field selection
const localizedPost = await cockpit.getContentItem({
  model: 'posts',
  id: '123',
  locale: 'en',
  queryParams: { fields: { title: 1, content: 1 } }
});

// Get multiple content items - always returns { data, meta? }
const response = await cockpit.getContentItems('posts', {
  limit: 10,
  sort: { _created: -1 },
  filter: { published: true }
});
// response: { data: Post[], meta?: { total: number } } | null

// Access items and metadata
const items = response?.data || [];
const total = response?.meta?.total;

// Get tree structure
const tree = await cockpit.getContentTree('categories', {
  parent: 'root-id',
  populate: 2
});

// Aggregation pipeline
const stats = await cockpit.getAggregateModel({
  model: 'orders',
  pipeline: [{ $group: { _id: '$status', count: { $sum: 1 } } }]
});

// Create content item
const newPost = await cockpit.postContentItem('posts', { title: 'New Post' });

// Delete content item
await cockpit.deleteContentItem('posts', '123');

Pages

// List pages - always returns { data, meta? }
const response = await cockpit.pages({ locale: 'en', limit: 50 });
const allPages = response?.data || [];
const total = response?.meta?.total;

// Get page by ID
const page = await cockpit.pageById({ page: 'blog', id: '123', locale: 'en' });

// Get page by route
const aboutPage = await cockpit.pageByRoute('/about', { locale: 'en', populate: 2 });

Menus

// Get all menus
const menus = await cockpit.pagesMenus({ locale: 'en' });

// Get specific menu
const mainMenu = await cockpit.pagesMenu('main-navigation', { locale: 'en' });

Routes & Sitemap

const routes = await cockpit.pagesRoutes('en');
const sitemap = await cockpit.pagesSitemap();
const settings = await cockpit.pagesSetting('en');
const fullRoute = await cockpit.getFullRouteForSlug('my-slug');

Search (Detektivo addon)

const results = await cockpit.search({
  index: 'products',
  q: 'search term',
  limit: 10,
  offset: 0
});

Localization (Lokalize addon)

const translations = await cockpit.localize('my-project', {
  locale: 'en',
  nested: true
});

Assets

import { ImageSizeMode, MimeType } from '@unchainedshop/cockpit-api';

// Get asset metadata
const asset = await cockpit.assetById('asset-id');

// Get transformed image
const image = await cockpit.imageAssetById('asset-id', {
  m: ImageSizeMode.BestFit,
  w: 800,
  h: 600,
  q: 80,
  mime: MimeType.WEBP
});

System

// Health check
const health = await cockpit.healthCheck();

// Clear cache (async in v2.2.0+)
await cockpit.clearCache();  // Clear all
await cockpit.clearCache('pages');  // Clear by pattern

Lightweight Fetch Client API

The fetch client is designed for edge/RSC environments with minimal overhead:

import { createFetchClient } from '@unchainedshop/cockpit-api/fetch';

const cockpit = createFetchClient({
  endpoint: process.env.NEXT_PUBLIC_COCKPIT_ENDPOINT,
  tenant: 'mytenant',
  cache: 'force-cache',
  apiKey: 'your-api-key',
  headers: { 'X-Custom-Header': 'value' }
});

// Available methods
const page = await cockpit.pageByRoute('/about', { locale: 'en' });

// List methods return { data, meta? }
const pagesResponse = await cockpit.pages({ locale: 'en' });
const pages = pagesResponse?.data || [];

const pageById = await cockpit.pageById('blog', '123', { locale: 'en' });

const itemsResponse = await cockpit.getContentItems('news', { locale: 'en', limit: 10 });
const items = itemsResponse?.data || [];

const item = await cockpit.getContentItem('news', '123', { locale: 'en' });
const custom = await cockpit.fetchRaw('/custom/endpoint', { param: 'value' });

Schema Stitching API

For building GraphQL gateways with Cockpit:

import { makeCockpitGraphQLSchema, createRemoteExecutor } from '@unchainedshop/cockpit-api/schema';

// Create schema for stitching
const schema = await makeCockpitGraphQLSchema({
  tenantHeader: 'x-cockpit-space',
  filterMutations: true,
  transforms: [],  // Additional GraphQL transforms
  extractTenant: (ctx) => ctx.req?.headers['x-tenant'],
  cockpitOptions: {
    endpoint: 'https://cms.example.com/api/graphql',
    apiKey: 'your-api-key',
    useAdminAccess: true
  }
});

// Or use the executor directly for custom implementations
const executor = createRemoteExecutor({
  tenantHeader: 'x-cockpit-space',
  cockpitOptions: { endpoint: '...' }
});

Configuration Options

const cockpit = await CockpitAPI({
  endpoint: 'https://...',      // Falls back to COCKPIT_GRAPHQL_ENDPOINT
  tenant: 'mytenant',           // Optional: for multi-tenant setups
  apiKey: 'your-api-key',       // Falls back to COCKPIT_SECRET env var
  useAdminAccess: true,         // Optional: inject api-Key header
  defaultLanguage: 'de',        // Language that maps to Cockpit's "default" locale (default: "de")
  preloadRoutes: true,          // Optional: preload route replacements
  cache: {
    max: 100,                   // Falls back to COCKPIT_CACHE_MAX (default: 100)
    ttl: 100000,                // Falls back to COCKPIT_CACHE_TTL (default: 100000)
    store: customStore,         // Optional: custom async cache store (Redis, Keyv, etc.)
  },
  // Or disable caching entirely
  // cache: false,
});

Environment Variables

COCKPIT_GRAPHQL_ENDPOINT=https://your-cockpit-instance.com/api/graphql
COCKPIT_SECRET=your-api-key                # Default API key
COCKPIT_SECRET_MYTENANT=tenant-api-key     # Tenant-specific API key
COCKPIT_CACHE_MAX=100                      # Max cache entries (default: 100)
COCKPIT_CACHE_TTL=100000                   # Cache TTL in ms (default: 100000)

Multi-Tenant Support

// Tenant-specific client
const cockpit = await CockpitAPI({
  endpoint: 'https://cms.example.com/api/graphql',
  tenant: 'mytenant',  // Requests use /:mytenant/api/... path
});

// Resolve tenant from URL
import { resolveTenantFromUrl, getTenantIds } from '@unchainedshop/cockpit-api';

const { tenant, slug } = resolveTenantFromUrl('https://mytenant.example.com/page');
const allTenants = getTenantIds();  // From COCKPIT_SECRET_* env vars

Custom Cache Stores

v2.2.0+ supports pluggable async cache stores for Redis, Keyv, or custom implementations:

import { createClient } from 'redis';
import type { AsyncCacheStore } from '@unchainedshop/cockpit-api';

// Redis example
const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

const redisStore: AsyncCacheStore = {
  async get(key: string) {
    const value = await redisClient.get(key);
    return value ? JSON.parse(value) : undefined;
  },
  async set(key: string, value: unknown) {
    await redisClient.set(key, JSON.stringify(value), { EX: 100 });
  },
  async clear(pattern?: string) {
    if (pattern) {
      const keys = await redisClient.keys(`${pattern}*`);
      if (keys.length > 0) await redisClient.del(keys);
    } else {
      await redisClient.flushDb();
    }
  }
};

const cockpit = await CockpitAPI({
  endpoint: 'https://cms.example.com/api/graphql',
  cache: { store: redisStore }
});

Response Format (v3.0.0+)

All list methods return a consistent response format regardless of parameters:

interface CockpitListResponse<T> {
  data: T[];
  meta?: CockpitListMeta;  // Present when using pagination (skip parameter)
}

Methods with Consistent Response Format

  • getContentItems() - Always returns CockpitListResponse<T> | null
  • pages() - Always returns CockpitListResponse<T> | null
  • Fetch client methods - Always return CockpitListResponse<T> | null

Usage Example

import type { CockpitListResponse } from '@unchainedshop/cockpit-api';

// Always get { data, meta? } format
const response = await cockpit.getContentItems('posts', { limit: 10, skip: 0 });

// Access items
const items = response?.data || [];

// Access metadata (available when using skip parameter)
const total = response?.meta?.total;

Benefits:

  • No need to check if response is array or object
  • Predictable type signatures
  • Easier to work with pagination

TypeScript Support

import type {
  // Client
  CockpitAPIClient,
  CockpitAPIOptions,
  CacheManager,
  CacheOptions,
  AsyncCacheStore,

  // Query Options
  ContentItemQueryOptions,
  ContentListQueryOptions,
  TreeQueryOptions,
  PageQueryOptions,
  SearchQueryOptions,
  ImageAssetQueryParams,

  // Response Types
  CockpitPage,
  CockpitAsset,
  CockpitMenu,
  CockpitRoute,
  CockpitSearchResult,
  CockpitContentItem,
  CockpitListResponse,   // New: for paginated content responses
  CockpitListMeta,       // New: metadata in paginated responses

  // Schema Types
  MakeCockpitSchemaOptions,
  CockpitExecutorContext,

  // Fetch Types
  FetchClientOptions,
  FetchCacheMode,
} from '@unchainedshop/cockpit-api';

import { ImageSizeMode, MimeType } from '@unchainedshop/cockpit-api';

Breaking Changes

v2.0.0

  • lokalize() renamed to localize()
  • Methods use options objects instead of positional parameters
  • HTTP errors now throw instead of returning null (404 still returns null)
  • Each client instance has its own cache (no shared state)

v2.1.0 (New Features)

  • /schema subpackage for GraphQL schema stitching
  • /fetch subpackage for lightweight edge/RSC environments
  • preloadRoutes option for preloading route replacements
  • defaultLanguage option to configure which language maps to Cockpit's "default" locale
  • Expanded tenant utilities: resolveTenantFromUrl(), resolveTenantFromSubdomain()

v2.2.0 (Breaking Changes)

Async Cache Operations:

  • All cache operations are now async and return Promises
  • await cockpit.clearCache() is now required (was synchronous in v2.1.x)
  • Custom cache stores can be provided via cache.store option
  • Cache can be explicitly disabled with cache: false

Migration:

// Before (v2.1.x)
cockpit.clearCache();
cockpit.clearCache('ROUTE');

// After (v2.2.0)
await cockpit.clearCache();
await cockpit.clearCache('ROUTE');

v3.0.0 (Breaking Changes)

Consistent List Response Format:

All list methods now return CockpitListResponse<T> | null instead of varying between arrays and wrapped responses:

Changed Methods:

  • getContentItems() - Now always returns { data: T[], meta?: {...} } | null
  • pages() - Now always returns { data: T[], meta?: {...} } | null
  • Fetch client getContentItems() and pages() - Now always return { data: T[], meta?: {...} } | null

Migration:

// Before (v2.x)
const items = await cockpit.getContentItems('posts', { limit: 10 });
// items could be Post[] or null

const pages = await cockpit.pages({ limit: 10 });
// pages could be Page[] or null

// After (v3.0.0)
const itemsResponse = await cockpit.getContentItems('posts', { limit: 10 });
const items = itemsResponse?.data || [];
const total = itemsResponse?.meta?.total;

const pagesResponse = await cockpit.pages({ limit: 10 });
const pages = pagesResponse?.data || [];
const total = pagesResponse?.meta?.total;

Benefits:

  • Single, predictable return type for all list methods
  • No need to check Array.isArray() or normalize responses
  • Cleaner TypeScript types
  • Metadata always accessible via .meta property

TreeQueryOptions Type Correction:

TreeQueryOptions no longer incorrectly includes limit and skip parameters (which were always ignored). Tree structures use parent, populate, filter, and fields instead.

// Before (v2.x) - allowed but ignored
await cockpit.getContentTree('categories', { limit: 10 });  // ❌ TypeScript allowed this

// After (v3.0.0) - TypeScript prevents invalid usage
await cockpit.getContentTree('categories', {
  parent: 'root-id',  // ✅ Correct
  populate: 2,        // ✅ Correct
  filter: { active: true }  // ✅ Correct
});

Peer Dependencies

  • graphql (optional) - Required for the graphQL() method
  • @graphql-tools/wrap (optional) - Required for the /schema subpackage

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

License

This project is licensed under the MIT License. See the LICENSE file for details.