@woutersmedia/cimi-sdk
v1.8.0
Published
Type-safe SDK for fetching CIMI content
Maintainers
Readme
@woutersmedia/cimi-sdk
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, ornever - 🚀 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-sdkOr using yarn:
yarn add @woutersmedia/cimi-sdkOr using pnpm:
pnpm add @woutersmedia/cimi-sdkNote: This package requires Node.js 18 or higher.
Quick Start
import { CimiClient } from '@woutersmedia/cimi-sdk';
// Create a client instance (`accessToken` = app UUID or app token — not the app slug)
const client = new CimiClient({
apiToken: 'cimi_your_token_here',
accessToken: '123e4567-e89b-12d3-a456-426614174000',
});
const appId = '123e4567-e89b-12d3-a456-426614174000';
// Fetch apps
const appsResponse = await client.getApps();
if (appsResponse.data) {
console.log('Apps:', appsResponse.data.apps);
}
// Fetch content
const contentResponse = await client.getContent(appId, { type: 'hero' });
if (contentResponse.data) {
console.log('Content:', contentResponse.data.content);
}
// Fetch products
const productsResponse = await client.getProducts(appId, { category: 'CLOTHES' });
if (productsResponse.data) {
console.log('Products:', productsResponse.data.products);
}API Reference
CimiClient
Constructor
new CimiClient(config: CimiClientConfig)Config Options:
apiToken: string- API token forAuthorization: Bearer(e.g.cimi_…)accessToken: string- Default app UUID or app token for/api/v1/apps/{app}/…when a method omits an explicit app id (do not use the app slug here)baseUrl?: string- Optional base URL for the CIMI API (defaults tohttps://cimi.co.nl)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.id})`);
});
} else {
console.error('Error:', response.error.error);
}getApp(appId)
Fetches a specific app.
const response = await client.getApp('123e4567-e89b-12d3-a456-426614174000');Parameters:
appId: string- App UUID or app token
Returns: ApiResponse<App>
getContent(appId, params?)
Fetches content items for a specific app. This method returns both content items (from content_items table) and pages (from content_pages table). Pages are returned with type: 'page'.
const response = await client.getContent('123e4567-e89b-12d3-a456-426614174000', {
type: 'hero',
});Parameters:
appId: string- App UUID or app tokenparams?: ContentQueryParams- Optional filterstype?: string- Filter by content-type key or'page'(case-insensitive)slug?: string | null- Exact match on the content item’s slug (not the app slug)id?: string- Filter by content item UUID
Returns: ApiResponse<ContentResponse>
Examples:
const appId = '123e4567-e89b-12d3-a456-426614174000';
// Get all content
const all = await client.getContent(appId);
// Get content by type
const heroes = await client.getContent(appId, { type: 'hero' });
// Get pages (from content_pages table)
const pages = await client.getContent(appId, { type: 'page' });
// Home (or any route): filter by content `slug`; every item includes merged blocks + SEO
const home = await client.getContent(appId, {
slug: '/',
});
// Fetch specific item by UUID
const specific = await client.getContent(appId, {
id: '123e4567-e89b-12d3-a456-426614174000',
});getContentItem(appId, contentId)
Fetches a single content item or page by ID or slug. If an ID (UUID) is provided, it searches both content_items and content_pages. Pages return with type: 'page'.
const response = await client.getContentItem(
'123e4567-e89b-12d3-a456-426614174000',
'content-uuid-or-page-uuid',
);Parameters:
appId: string- App UUID or app tokencontentId: string- Content item UUID, page UUID, or slug
Returns: ApiResponse<ContentItem>
getContentTypes(appId?)
Fetches all content types (CMS schema) for an app. The JSON body uses the same list key as GET /content: content, not items.
const response = await client.getContentTypes('123e4567-e89b-12d3-a456-426614174000');Parameters:
appId: string(optional) - App UUID or app token; defaults to the clientaccessToken
Returns: ApiResponse<ContentTypesResponse> — ContentTypesResponse is { content: ContentType[] }
Example:
const response = await client.getContentTypes('123e4567-e89b-12d3-a456-426614174000');
if (response.data) {
console.log('Content types:', response.data.content);
}getContentType(typeKey, appId?)
Fetches a single content type by its key (e.g. PAGE). Implemented via getContentTypes and a client-side lookup.
const response = await client.getContentType('PAGE');Returns: ApiResponse<ContentType>
getContentTypeFields(appId?, typeKey?)
Fetches content type fields (field definitions). The list is under content, matching the public API.
const response = await client.getContentTypeFields('123e4567-e89b-12d3-a456-426614174000', 'PAGE');Returns: ApiResponse<ContentTypeFieldsResponse> — ContentTypeFieldsResponse is { content: ContentTypeField[] }
Example:
const pageFields = await client.getContentTypeFields(
'123e4567-e89b-12d3-a456-426614174000',
'PAGE',
);
if (pageFields.data) {
console.log('Fields:', pageFields.data.content);
}getProducts(appId, params?)
Fetches products for a specific app. The path segment must be your app’s UUID (or app token)—not the app slug.
const response = await client.getProducts('123e4567-e89b-12d3-a456-426614174000', {
category: 'CLOTHES',
status: 'IN_STOCK',
});Parameters:
appId: string- App UUID or app tokenparams?: ProductQueryParams- Optional filterscategory?: ProductCategory- Filter by product categorystatus?: ProductStatus- Filter by product status
Returns: ApiResponse<ProductsResponse>
Examples:
const appId = '123e4567-e89b-12d3-a456-426614174000';
// Get all products
const all = await client.getProducts(appId);
// Get products by category
const clothes = await client.getProducts(appId, { category: 'CLOTHES' });
// Get products by status
const inStock = await client.getProducts(appId, { status: 'IN_STOCK' });getProduct(appId, productId)
Fetches a single product by ID.
const response = await client.getProduct('123e4567-e89b-12d3-a456-426614174000', 'product-uuid');Parameters:
appId: string- App UUID or app tokenproductId: string- Product UUID
Returns: ApiResponse<Product>
Rich text helpers
The SDK exports parser and renderer helpers for HTML rich-text fields:
parseHtmlToRichText(html)→ normalize HTML into typed blocks.renderRichText(blocks, renderers)→ map blocks to your own components (React/Vue/etc).renderRichTextToHtml(blocks, options)→ generate HTML with optional class names and block overrides.
import { parseHtmlToRichText, renderRichTextToHtml } from '@woutersmedia/cimi-sdk';
const blocks = parseHtmlToRichText('<p>Hello <strong>world</strong></p>');
const html = renderRichTextToHtml(blocks, {
classNames: {
paragraph: 'prose-p text-slate-700',
heading: 'font-bold',
headingByLevel: { 2: 'text-2xl' },
},
});import { parseHtmlToRichText, renderRichText } from '@woutersmedia/cimi-sdk';
import { Paragraph, Heading } from '@/components/ui/typography';
const blocks = parseHtmlToRichText('<p>Hello</p><h2>Title</h2>');
const nodes = renderRichText(blocks, {
paragraph: ({ key, content }) => <Paragraph key={key}>{content}</Paragraph>,
heading: ({ key, level, content }) => (
<Heading key={key} as={`h${level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'}>
{content}
</Heading>
),
link: ({ key, href, content }) => (
<a key={key} href={href}>
{content}
</a>
),
image: ({ key, src, alt }) => <img key={key} src={src} alt={alt} />,
linebreak: ({ key }) => <br key={key} />,
inline: {
text: ({ content }) => content,
strong: ({ content }) => <strong>{content}</strong>,
em: ({ content }) => <em>{content}</em>,
underline: ({ content }) => <u>{content}</u>,
},
});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
};
// Flexible blocks + SEO (every content item from GET /content)
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 Item — `type` is the content-type key; always `blocks` + `seo`
type ContentItem = {
id: string;
type: string;
title: string;
slug: string | null;
isActive: boolean;
sortOrder: number;
blocks: PageBlock[];
seo: PageSeo;
};
// 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;
};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('123e4567-e89b-12d3-a456-426614174000');
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!,
accessToken: process.env.CIMI_APP_TOKEN!,
});
const response = await client.getProducts();
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!,
accessToken: process.env.NEXT_PUBLIC_CIMI_APP_TOKEN!,
});
client.getProducts().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
anytypes - ✅ No
unknowntypes - ✅ No
nevertypes - ✅ All properties are explicitly typed
- ✅ Union types for enums (e.g.,
ProductCategory) - ✅ Proper null handling with
| nulltypes
The SDK passes TypeScript's strict mode checks and enforces type safety throughout.
Testing
The SDK includes comprehensive tests. To run them:
npm testTo run tests with coverage:
npm run test:coverageUsing in External Projects
This package is published to npm and can be used in any JavaScript/TypeScript project.
Getting Your API Token
- Log in to your CIMI instance
- Navigate to Profile → API Tokens
- Create a new token with appropriate permissions
- 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
# App UUID from CIMI (not the app slug)
CIMI_APP_TOKEN=123e4567-e89b-12d3-a456-426614174000Then use them in your code:
const client = new CimiClient({
apiToken: process.env.CIMI_API_TOKEN!,
accessToken: process.env.CIMI_APP_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!
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests:
npm test - Submit a pull request
License
MIT License - see LICENSE file for details
Support
For support, please contact [email protected]
