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

@woutersmedia/cimi-sdk

v1.1.10

Published

Type-safe SDK for fetching CIMI content

Downloads

1,033

Readme

@woutersmedia/cimi-sdk

npm version License: MIT

Type-safe SDK for fetching CIMI content and products. This SDK provides a fully type-safe interface for interacting with the CIMI API, with no any, unknown, or never types.

Features

  • 100% Type-Safe - All types are strictly defined, no any, unknown, or never
  • 🚀 Simple API - Clean, intuitive methods for fetching content
  • 📦 Lightweight - Zero runtime dependencies
  • 🧪 Well Tested - Comprehensive test coverage
  • 📝 Full TypeScript Support - Built with TypeScript from the ground up
  • 🌐 Framework Agnostic - Works with Next.js, React, Vue, or vanilla TypeScript
  • 📚 Comprehensive Docs - Detailed guides and examples

Installation

Install via npm:

npm install @woutersmedia/cimi-sdk

Or using yarn:

yarn add @woutersmedia/cimi-sdk

Or using pnpm:

pnpm add @woutersmedia/cimi-sdk

Note: This package requires Node.js 18 or higher.

Quick Start

import { CimiClient } from '@woutersmedia/cimi-sdk';

// Create a client instance
const client = new CimiClient({
  apiToken: 'cimi_your_token_here',
});

// Or with a custom base URL
const clientCustom = new CimiClient({
  baseUrl: 'https://your-cimi-instance.vercel.app',
  apiToken: 'cimi_your_token_here',
});

// Fetch apps
const appsResponse = await client.getApps();
if (appsResponse.data) {
  console.log('Apps:', appsResponse.data.apps);
}

// Fetch content
const contentResponse = await client.getContent('my-app', { type: 'hero' });
if (contentResponse.data) {
  console.log('Content:', contentResponse.data.items);
}

// Fetch products
const productsResponse = await client.getProducts('my-app', { category: 'CLOTHES' });
if (productsResponse.data) {
  console.log('Products:', productsResponse.data.products);
}

API Reference

CimiClient

Constructor

new CimiClient(config: CimiClientConfig)

Config Options:

  • baseUrl?: string - Base URL of your CIMI instance (defaults to https://cimi.woutersmedia.nl)
  • apiToken: string - API token for authentication
  • fetch?: typeof fetch - Optional custom fetch implementation (defaults to global fetch)

Methods

getApps()

Fetches all apps accessible with the current API token.

const response = await client.getApps();

Returns: ApiResponse<AppsResponse>

Example:

const response = await client.getApps();
if (response.data) {
  response.data.apps.forEach((app) => {
    console.log(`App: ${app.name} (${app.slug})`);
  });
} else {
  console.error('Error:', response.error.error);
}

getApp(appId)

Fetches a specific app by ID or slug.

const response = await client.getApp('my-app-slug');

Parameters:

  • appId: string - App UUID or slug

Returns: ApiResponse<App>

getContent(appId, params?)

Fetches content items for a specific app.

const response = await client.getContent('my-app', {
  type: 'hero',
  pageId: 'page-uuid',
});

Parameters:

  • appId: string - App UUID or slug
  • params?: ContentQueryParams - Optional filters
    • type?: string - Filter by content type
    • pageId?: string | null - Filter by page ID

Returns: ApiResponse<ContentResponse>

Examples:

// Get all content
const all = await client.getContent('my-app');

// Get content by type
const heroes = await client.getContent('my-app', { type: 'hero' });

// Get content by page
const pageContent = await client.getContent('my-app', { pageId: 'page-uuid' });

// Get content not linked to any page
const unlinked = await client.getContent('my-app', { pageId: null });

getContentItem(appId, contentId)

Fetches a single content item by ID.

const response = await client.getContentItem('my-app', 'content-uuid');

Parameters:

  • appId: string - App UUID or slug
  • contentId: string - Content item UUID

Returns: ApiResponse<ContentItem>

getProducts(appId, params?)

Fetches products for a specific app.

const response = await client.getProducts('my-app', {
  category: 'CLOTHES',
  status: 'IN_STOCK',
});

Parameters:

  • appId: string - App UUID or slug
  • params?: ProductQueryParams - Optional filters
    • category?: ProductCategory - Filter by product category
    • status?: ProductStatus - Filter by product status

Returns: ApiResponse<ProductsResponse>

Examples:

// Get all products
const all = await client.getProducts('my-app');

// Get products by category
const clothes = await client.getProducts('my-app', { category: 'CLOTHES' });

// Get products by status
const inStock = await client.getProducts('my-app', { status: 'IN_STOCK' });

getProduct(appId, productId)

Fetches a single product by ID.

const response = await client.getProduct('my-app', 'product-uuid');

Parameters:

  • appId: string - App UUID or slug
  • productId: string - Product UUID

Returns: ApiResponse<Product>

getPages(appId)

Fetches all pages for a specific app.

const response = await client.getPages('my-app');

Parameters:

  • appId: string - App UUID or slug

Returns: ApiResponse<PagesResponse>

getPage(appId, pageId, params?)

Fetches a single page by ID.

// Get page without content
const response = await client.getPage('my-app', 'page-uuid');

// Get page with content items included
const responseWithContent = await client.getPage('my-app', 'page-uuid', {
  includeContent: true,
});

Parameters:

  • appId: string - App UUID or slug
  • pageId: string - Page UUID
  • params?: PageQueryParams - Optional parameters
    • includeContent?: boolean - Include content items for this page (default: false)

Returns: ApiResponse<PageResponse>

Response includes:

  • page: ContentPage - The page object (blocks, seo, merged data without duplicating blocks in data)
  • content?: ContentItem[] - Content items (only if includeContent is true)

Type Definitions

Core Types

// API Response wrapper - always returns data or error, never both
type ApiResponse<T> = ApiSuccess<T> | ApiFailure;

type ApiSuccess<T> = {
  data: T;
  error: null;
};

type ApiFailure = {
  data: null;
  error: ApiError;
};

type ApiError = {
  error: string;
  statusCode: number;
};

Data Types

// App
type App = {
  id: string;
  slug: string;
  name: string;
  description: string | null;
  defaultLocale: string;
  locales: string[];
  enabledModules?: string[]; // Available modules for this app
};

// Content Item
type ContentItem = {
  id: string;
  type: string;
  title: string;
  slug: string | null;
  body: string | null;
  richTextBlocks: RichTextBlock[];
  data: Record<string, string | number | boolean | null> | null;
  pageId: string | null;
  isActive: boolean;
  sortOrder: number;
};

// Product
type Product = {
  id: string;
  name: string;
  sku: string;
  category: ProductCategory;
  price: string;
  stock: number;
  reserved: number;
  status: ProductStatus;
  badge?: ProductBadge | null;
  imageUrl?: string | null;
  imageUrls?: string[] | null;
  heightCm?: number | null;
  widthCm?: number | null;
  adminOnly?: boolean;
  requiresShipping?: boolean;
};

// Flexible block (library) vs synthesized CMS row
type PageBlock = FlexiblePageBlock | SynthesizedContentItemBlock;

type FlexiblePageBlock = {
  blockId: string;
  blockKey?: string;
  label?: string;
  data?: Record<string, unknown>;
};

type SynthesizedContentItemBlock = {
  source: 'contentItem';
  contentItemId: string;
  type: string;
  title: string;
  slug: string | null;
  data: unknown;
};

type PageSeo = {
  title: string | null;
  description: string | null;
  image: string | null;
  noIndex: boolean;
};

// Content Page
type ContentPage = {
  id: string;
  parentId: string | null;
  title: string;
  slug: string;
  path: string;
  description: string | null;
  metaTitle: string | null;
  metaDescription: string | null;
  metaKeywords: string | null;
  ogTitle: string | null;
  ogDescription: string | null;
  ogImage: string | null;
  isFolder: boolean;
  isRoot: boolean;
  isActive: boolean;
  sortOrder: number;
  data: Record<string, unknown> | null;
  blocks?: PageBlock[];
  seo?: PageSeo;
};

Enum Types

type ProductCategory = 'CLOTHES' | 'ACCESSOIRES' | 'BADGES' | 'OTHER';

type ProductStatus =
  | 'IN_STOCK'
  | 'OUT_OF_STOCK'
  | 'SOON_AVAILABLE'
  | 'SOON_AGAIN_AVAILABLE'
  | 'NEVER_IN_RESTOCK';

type ProductBadge = 'EXCLUSIVE' | 'NEW' | 'HIGHLIGHTED' | 'FINAL_STOCK' | 'COLLABORATION';

Error Handling

All API methods return an ApiResponse<T> type that contains either data or error, never both.

const response = await client.getProducts('my-app');

if (response.error) {
  // Handle error
  console.error(`Error ${response.error.statusCode}: ${response.error.error}`);
} else {
  // Use data
  console.log('Products:', response.data.products);
}

Usage with Next.js

Server Components

import { CimiClient } from '@woutersmedia/cimi-sdk';

export default async function ProductsPage() {
  const client = new CimiClient({
    apiToken: process.env.CIMI_API_TOKEN!
  });

  const response = await client.getProducts('my-app');

  if (response.error) {
    return <div>Error: {response.error.error}</div>;
  }

  return (
    <div>
      {response.data.products.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  );
}

Client Components

'use client';

import { CimiClient } from '@woutersmedia/cimi-sdk';
import { useEffect, useState } from 'react';

export function ProductList() {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const client = new CimiClient({
      apiToken: process.env.NEXT_PUBLIC_CIMI_API_TOKEN!
    });

    client.getProducts('my-app').then(response => {
      if (response.data) {
        setProducts(response.data.products);
      }
    });
  }, []);

  return (
    <div>
      {products.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  );
}

Type Safety

This SDK is designed with strict type safety in mind:

  • ✅ No any types
  • ✅ No unknown types
  • ✅ No never types
  • ✅ All properties are explicitly typed
  • ✅ Union types for enums (e.g., ProductCategory)
  • ✅ Proper null handling with | null types

The SDK passes TypeScript's strict mode checks and enforces type safety throughout.

Testing

The SDK includes comprehensive tests. To run them:

npm test

To run tests with coverage:

npm run test:coverage

Using in External Projects

This package is published to npm and can be used in any JavaScript/TypeScript project.

Getting Your API Token

  1. Log in to your CIMI instance
  2. Navigate to Profile → API Tokens
  3. Create a new token with appropriate permissions
  4. Use the token in your SDK configuration

Environment Variables

For production use, store credentials in environment variables:

# .env
CIMI_API_TOKEN=cimi_your_token_here (configurable via CIMI app)
# Optional: CIMI_BASE_URL=https://your-custom-instance.com/

Then use them in your code:

const client = new CimiClient({
  apiToken: process.env.CIMI_API_TOKEN!,
});

// Or with a custom URL
const customClient = new CimiClient({
  baseUrl: process.env.CIMI_BASE_URL,
  apiToken: process.env.CIMI_API_TOKEN!,
});

Framework Support

The SDK works with any JavaScript framework or runtime:

  • ✅ Next.js (App Router & Pages Router)
  • ✅ React (Create React App, Vite, etc.)
  • ✅ Vue.js
  • ✅ Svelte
  • ✅ Node.js
  • ✅ Deno
  • ✅ Bun

See USAGE_EXAMPLES.md for framework-specific examples.

Publishing

If you're a maintainer and want to publish a new version, see PUBLISHING.md for detailed instructions.

Contributing

This package is part of the CIMI monorepo. Contributions are welcome!

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Run tests: npm test
  5. Submit a pull request

License

MIT License - see LICENSE file for details

Support

For support, please contact [email protected]

Links