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

zucms

v0.0.9

Published

Typed TypeScript SDK for the Zu CMS API.

Readme

Zucms TypeScript SDK

Typed TypeScript SDK for the Zu CMS API with schema sync and generated request types.

Install

pnpm add zucms

What You Get

  • Schema-driven request typing from your CMS
  • Small runtime client with predictable methods
  • Automatic type inference for known model keys
  • Manual generic fallback for unknown or custom models
  • CLI workflow for config bootstrap and schema sync

Recommended Workflow

  1. Install the package.
  2. Run pnpm zucms init.
  3. Put your API token into zucms.config.ts.
  4. Run pnpm zucms sync.
  5. Import generated/zucms/types once in your app.
  6. Use createClient() without passing the token again.

CLI

pnpm zucms init

Creates a zucms.config.ts file in the current project root.

Example output:

import type { ZucmsConfig } from "zucms";

const zucmsConfig: ZucmsConfig = {
  token: process.env.ZUCMS_API_KEY,
  generatedPath: "generated/zucms/types.ts",
};

export default zucmsConfig;

Rules:

  • The command fails if zucms.config.ts already exists.
  • The config file is the source of truth for schema sync and the default client setup.

pnpm zucms sync

Loads zucms.config.ts, fetches the schema from the CMS, normalizes it, and generates:

generated/zucms/types.ts

This generated file should not be edited manually.

Before loading the config, the command also reads a local .env file if present. That makes token: process.env.ZUCMS_API_KEY work out of the box.

Optional CLI flags:

  • --config to use a custom config file path
  • --out-file to override the generated types path

Config

The config file must default-export a zucmsConfig object.

import type { ZucmsConfig } from "zucms";

const zucmsConfig: ZucmsConfig = {
  token: process.env.ZUCMS_API_KEY,
};

export default zucmsConfig;

Config Shape

type ZucmsConfig = {
  token: string | undefined;
  baseUrl?: string;
  authMode?: "bearer" | "x-api-key";
  schemaUrl?: string;
  generatedPath?: string;
};

Defaults

  • baseUrl defaults to https://api.zucms.co
  • authMode defaults to "bearer"
  • schemaUrl defaults to "/schema"
  • generatedPath defaults to "generated/zucms/types.ts"

Quick Start

Import the generated types once before using the client.

import "generated/zucms/types";
import { createClient } from "zucms";

const client = createClient();

const products = await client.model("products").list();
const product = await client.model("products").get("entry_1");

createClient()

Config-driven client

If zucms.config.ts exists, createClient() will use it automatically.

import "generated/zucms/types";
import { createClient } from "zucms";

const client = createClient();

Explicit overrides

You can still override values directly.

import { createClient } from "zucms";

const client = createClient({
  apiKey: "zw_...",
  baseUrl: "https://api.example.com",
  authMode: "x-api-key",
});

Client override type

type CreateClientOverrides = {
  apiKey?: string;
  authMode?: "bearer" | "x-api-key";
  baseUrl?: string;
  fetch?: typeof globalThis.fetch;
  headers?: HeadersInit;
};

Notes:

  • If apiKey is omitted, the client tries to read it from zucms.config.ts.
  • If both apiKey and config are missing, the client throws a helpful error.
  • fetch can be overridden for tests or custom runtimes.
  • headers are merged into every request.
  • baseUrl may include or omit a trailing slash.

Generated Typing Model

After pnpm zucms sync, the generated file contributes:

  • ZucmsModelKey
  • ZucmsModels
  • per-model interfaces like Products, Authors, Documents
  • relation helper types
  • locale union types
  • zucmsModelMeta

It also augments the SDK so that known model keys become typesafe automatically.

Typed Requests

Model-scoped API

const products = client.model("products");

const list = await products.list();
const single = await products.get("entry_1");

await products.create({
  slug: "zucms",
  website: "https://zucms.co",
});

await products.update("entry_1", {
  twitter_handle: "@zucms",
});

await products.delete("entry_1");

When the model key exists in the generated schema:

  • list() returns SdkListResponse<ZucmsModels["products"]>
  • get() returns SdkSingleResponse<ZucmsModels["products"]>
  • create() requires ZucmsModels["products"]
  • update() requires Partial<ZucmsModels["products"]>
  • query keys like fields, include, filter, sort, and locale are typed from the generated schema

Top-level API

const authors = await client.list("authors");
const author = await client.get("authors", "entry_1");

await client.create("authors", {
  first_name: "Levi",
  slug: "levi",
});

await client.update("authors", "entry_1", {
  last_name: "Hessmann",
});

await client.delete("authors", "entry_1");

Fallback API For Unknown Models

If a model key is not part of the generated schema, the SDK falls back to the generic API.

const customEntries = await client.model("custom").list();

In that case the response data is unknown unless you provide a generic.

type CustomEntry = {
  title: string;
};

const customEntries = await client.model("custom").list<CustomEntry>();

await client.model("custom").create<CustomEntry>({
  title: "Hello",
});

This is useful when:

  • you have not synced the schema yet
  • you work against dynamic model keys
  • you want to prototype before generating types

Localized Fields

Generated model types use Localized<TValue, TLocale> for localized CMS fields.

import type { Localized } from "zucms";

type Article = {
  title: Localized<string, "de" | "en">;
  description: Localized<string, "de" | "en">;
};

Relations And Files

Generated relations are represented through SDK helper types:

type ZucmsRelation<TModelKey> = {
  id: string;
  modelKey: TModelKey;
};

Files use the built-in SdkFile type:

type SdkFile = {
  id: string;
  filename: string;
  mimeType: string;
  sizeBytes: number;
  storagePath: string;
  uploadedAt: string;
  url: string;
};

Queries

List requests support query parameters for pagination, search, sorting, field selection, includes, locale handling, and filters.

const response = await client.model("products").list({
  page: 1,
  pageSize: 25,
  search: "zucms",
  sort: ["slug", "-createdAt"],
  fields: ["slug", "website"],
  include: ["documents"],
  locale: "de",
  fallback: true,
  filter: {
    slug: { contains: "zu" },
  },
});

For generated model keys, query values are constrained:

  • fields only accepts field keys of that model
  • include only accepts relation keys of that model
  • filter only accepts field keys of that model
  • sort only accepts field keys or -fieldKey
  • locale only accepts generated locales

Query shape

type SdkListQuery = {
  page?: number;
  pageSize?: number;
  search?: string | null;
  sort?: string[];
  fields?: string[];
  include?: string[];
  locale?: string | null;
  fallback?: boolean;
  filter?: Record<string, SdkFilterCondition>;
};

Supported filter operators

  • eq
  • ne
  • contains
  • startsWith
  • endsWith
  • in
  • nin
  • gt
  • gte
  • lt
  • lte
  • exists

Filter serialization

filter[slug][contains]=zu
filter[status][eq]=published

Responses

List

type SdkListResponse<TData> = {
  data: Array<{
    id: string;
    tenantId: string;
    modelKey: string;
    data: TData;
    createdAt: string;
    updatedAt: string;
  }>;
  meta: {
    tenantId: string;
    modelKey: string;
    page: number;
    pageSize: number;
    total: number;
    totalPages: number;
    sort: string[];
    filters: Record<string, unknown>;
    search: string | null;
    fields: string[];
    include: string[];
  };
};

Single

type SdkSingleResponse<TData> = {
  data: {
    id: string;
    tenantId: string;
    modelKey: string;
    data: TData;
    createdAt: string;
    updatedAt: string;
  };
};

Delete

type SdkDeleteResponse = {
  data: {
    id: string;
    deleted: boolean;
  };
};

Error Handling

All non-2xx responses are mapped to SdkClientError.

import { SdkClientError } from "zucms";

try {
  await client.model("products").get("missing");
} catch (error) {
  if (error instanceof SdkClientError) {
    console.log(error.status);
    console.log(error.code);
    console.log(error.message);
    console.log(error.details);
    console.log(error.payload);
  }
}

Auth Modes

Bearer token

const client = createClient({
  apiKey: "zw_...",
  authMode: "bearer",
});

x-api-key

const client = createClient({
  apiKey: "zw_...",
  authMode: "x-api-key",
});

Request Options

Every request method accepts optional request options as the last argument.

const controller = new AbortController();

await client.model("products").list(
  { page: 1 },
  {
    signal: controller.signal,
    headers: {
      "x-request-id": "req_123",
    },
  },
);
type SdkRequestOptions = {
  signal?: AbortSignal;
  headers?: HeadersInit;
};

Programmatic Schema Utilities

The package also exports the schema helpers used by the CLI.

  • loadConfig()
  • loadConfigSync()
  • fetchSchema()
  • normalizeSchema()
  • renderTypes()
  • syncSchema()

Example:

import { syncSchema } from "zucms";

await syncSchema();

Covered Endpoints

  • GET /api/models/{modelKey}/entries
  • POST /api/models/{modelKey}/entries
  • GET /api/models/{modelKey}/entries/{entryId}
  • PATCH /api/models/{modelKey}/entries/{entryId}
  • DELETE /api/models/{modelKey}/entries/{entryId}

Important Notes

  • Run pnpm zucms sync whenever the CMS schema changes.
  • Commit generated/zucms/types.ts if your app depends on generated compile-time types.
  • Import generated/zucms/types before using schema-aware request inference.
  • Do not edit generated files by hand.
  • Unknown model keys remain supported through the generic API.