@woutersmedia/cimi-sdk
v1.1.10
Published
Type-safe SDK for fetching CIMI content
Downloads
1,033
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
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 tohttps://cimi.woutersmedia.nl)apiToken: string- API token for authenticationfetch?: 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 slugparams?: ContentQueryParams- Optional filterstype?: string- Filter by content typepageId?: 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 slugcontentId: 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 slugparams?: ProductQueryParams- Optional filterscategory?: ProductCategory- Filter by product categorystatus?: 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 slugproductId: 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 slugpageId: string- Page UUIDparams?: PageQueryParams- Optional parametersincludeContent?: boolean- Include content items for this page (default: false)
Returns: ApiResponse<PageResponse>
Response includes:
page: ContentPage- The page object (blocks,seo, mergeddatawithout duplicatingblocksindata)content?: ContentItem[]- Content items (only ifincludeContentis 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
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 (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!
- 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]
