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

@viingx/content-hub-codegen

v0.1.0-preview.11

Published

Generates a typed Content Hub client from type definitions

Readme

@viingx/content-hub-codegen

Official code generator for the viingx Content Hub: generates a fully-typed, app-local client from your type definitions, on top of @viingx/content-hub-sdk. You get one typed accessor per content type — hub.blogPost.create({ … }) — with autocomplete and compile-time checking of every field, option, and reference. Queries are checked on both sides: the path (only indexed paths compile) and the value (q.eq("content.payload.status", "publish") is a compile error when the options are "draft" | "published" | "archived"). A dynamically-typed value (a plain string variable) against an option-list path needs the literal union — grab it from the generated XxxPathValue map, e.g. XxxPathValue["content.payload.status"].

The whole point: make writing correct Content Hub code fast. Generate once, then let tsc catch mistakes as you (or an AI agent) type. generate also maintains agent docs in your working tree (see below), so AI coding agents learn the non-guessable Hub rules automatically.

The @viingx family: @viingx/content-hub-sdk (the runtime this generates on top of) · this package · @viingx/extension-sdk (build UI extensions for the web client) · @viingx/create-extension (scaffold an extension project).

Preview release (0.1.0-preview.x). The API may change before 1.0.

Install

npm install --save-dev @viingx/content-hub-codegen
npm install @viingx/content-hub-sdk   # runtime dep — the generated client imports it

content-hub-sdk is a peer dependency: the tool itself doesn't need it, but the client it generates does. Run the CLI with npx @viingx/content-hub-codegen … or from an npm script.

The HUB_KEY that pull uses is a build-time credential — you run pull once (and when the schema changes) to snapshot your tenant's types into committed files. The generated client is import type only (erased at build) over the SDK you already use, so it adds no runtime dependency or auth: your app authenticates however it likes — API key, session token, or username/password login — and passes that connection to createHub. Prefer codegen when building against one known tenant: static payload types, autocomplete, and tsc checking are the biggest correctness lever — most of all for AI-written code, where tsc catches a wrong field/option/path as it's written. Fall back to the plain SDK (reading hub.types.list()/info() at runtime, no key) only for a generic multi-tenant client whose schema isn't known at build, when you can't get a type/* read key, or for a throwaway spike — and note codegen is additive, so you can layer it in later either way. Tenant-configured labels always come from runtime info()/localize(), with or without codegen.

Generate

The package ships a CLI (content-hub-codegen):

content-hub-codegen generate      # read type definitions → write the client
content-hub-codegen check         # exit 1 if the generated client is out of date (for CI)
content-hub-codegen pull          # mirror a live Hub's type definitions into typesDir
content-hub-codegen schema pull   # snapshot the tenant's GraphQL schema as SDL

Configure it with a content-hub.config.{js,mjs,ts} in your project root (a .ts config needs Node ≥ 22.18, which strips types natively; on older Node use .mjs/.js — it can still import { defineConfig }):

// content-hub.config.mjs
import { defineConfig } from "@viingx/content-hub-codegen";

export default defineConfig({
    typesDir: "content-hub/types", // directory of committed TypeDefinition JSON files
    output: "src/content-hub.generated.ts",
});

…or skip the config and pass flags (they override the config when both are present):

content-hub-codegen generate --types <typesDir> --out <outFile>

Generation reads the committed type-definition files and writes the client. Commit the generated file — it's deterministic (same input → identical output), reviewable in PRs, and check keeps it honest in CI.

Wire both into your project so a stale client can't slip through — an npm script pair and a CI step:

// package.json
"scripts": {
    "codegen": "content-hub-codegen generate",
    "codegen:check": "content-hub-codegen check" // exits 1 if the committed client is out of date
}
# CI: fail the build if someone changed the schema but forgot to regenerate
- run: npm run codegen:check

Pull from a live Hub

pull bootstraps (or refreshes) the committed type-definition files from a tenant — after that, generation stays offline and deterministic:

HUB_URL=https://<instance>.viingx.io HUB_KEY=<api key> npx @viingx/content-hub-codegen pull

It writes <typesDir>/<name>.json for every type (faithful, pretty-printed), plus _derivativeDefinitions.json with the tenant's derivative definitions (underscore-prefixed files aren't read as type definitions — they feed the DerivativeName union). Existing files are overwritten; files whose type no longer exists on the Hub are warned about, never deleted. Credentials come from --url/--key flags, the HUB_URL/HUB_KEY environment variables, or a gitignored .env in the working directory (HUB_URL=…/HUB_KEY=… lines — the same file the extension templates' deploy uses) — prefer the env variables or .env so keys stay out of shell history and npm scripts.

GraphQL schema snapshot

The Hub's GraphQL schema is generated per tenant, so typed GraphQL starts with a committed snapshot:

npm install -D graphql   # optional peer — needed only for `schema pull`
HUB_URL=… HUB_KEY=… npx @viingx/content-hub-codegen schema pull --out schema.graphql

Point your preferred GraphQL toolchain (@graphql-codegen, gql.tada) at the snapshot for typed queries; this package deliberately doesn't bundle one.

Agent docs (CLAUDE.md / AGENTS.md)

When run with a config file, generate also maintains a marked, managed section in your working-tree CLAUDE.md and AGENTS.md: a pointer to the generated client plus the non-guessable Content Hub rules (origin-only baseUrl, per-write version, source locale on create, server-assigned ids → upsert-by-query, index-commit write latency, workflow-document versions, …). Working-tree agent docs are auto-loaded by AI coding agents — unlike anything inside node_modules — so the rules are read every session, not just when someone thinks to look. Content outside the markers is never touched; updates are idempotent. Opt out with agentDocs: false in the config or --no-agent-docs (flag-only runs default to off).

Use

import { createHub } from "./src/content-hub.generated"; // the `output` path from your config
import { apiKeyConnection } from "@viingx/content-hub-sdk";

// origin (a full /api/v1.0 base works too); the SDK appends /api/v1.0
const hub = createHub(apiKeyConnection("https://hub.example.com", process.env.HUB_API_KEY!), { defaultLocale: "en" });

// `hub.<type>` is a typed collection. Payload, options, and references are all checked:
const id = await hub.blogPost.create({
    title: "Hello world",
    body: "<p>…</p>",
    status: "published", // only the schema's options ("draft" | "published" | "archived") are allowed
    author: { type: "person", id: "p1" }, // reference narrowed to its target type
});

import { q } from "@viingx/content-hub-sdk";

const recent = await hub.blogPost.query(
    // Property paths are checked against this type's queryable (indexed) paths — a typo is a compile error.
    { where: q.eq("content.payload.status", "published"), sort: [q.desc("content.payload.publishedAt")] },
    { paging: { type: "offset", offset: 0, limit: 20 } },
);
console.log(recent.total);

createHub returns everything the base SDK's createContentHub does, plus the typed accessors. Runtime semantics (auth, optimistic-concurrency version, errors, paging) are unchanged — see the SDK README.

What gets generated

For each type, from its definition:

  • XxxPayload — a type alias of the payload, with listString options as literal unions, reference narrowed to { type: <target>; id: string }, group/choice as nested/discriminated types, multiValue → Array<…>, and optional → ?. Each field carries JSDoc built from the property's description plus a type-specific hint (RFC 3339 format for dates/times, "whole numbers only" for integers, hex format for colors, …) and any validation constraints (value range, length, count) — so the format and limits show up on hover, not just the field's purpose.
  • XxxLocalePayload — the localizable subset (or Record<string, never>), documented the same way.
  • XxxPath — the valid query property paths for that type, each union member annotated with the field's description and format hint.
  • XxxState / XxxGate — literal unions of the workflow state names / quality-gate names (only when the type defines them). They're bound as the accessor's S/QG generics, so workflow.update only accepts defined states, suggestStates returns them, and quality-gate results are keyed by name.
  • DerivativeName — the tenant-wide union of derivative (rendition) names: the system previews (systemPreview, systemHighPreview, systemPdfPreview) plus the tenant's derivative definitions (mirrored by pull into <typesDir>/_derivativeDefinitions.json). Bound as the file APIs' derivative-name parameter, so a misspelled rendition is a compile error.
  • a typed accessor on createHub's result.

Why this is fast (and AI-friendly)

  • tsc is the oracle. Wrong field, bad option, or wrong-typed value is a compile error — the tightest possible write → fix loop, for humans and for Claude Code.
  • Thin over the SDK. It adds types, not a new API. Anything you know about @viingx/content-hub-sdk applies; there's nothing new to learn.
  • Offline & deterministic. Generation reads committed files — no creds, no network, identical output every run. The generated file is just code you read and diff.
  • Don't hand-edit the generated file; re-run npm run generate when the schema changes (its header says as much).

Status

Feature-complete for the preview: the generate/check/pull/schema pull CLI (config file — .js/.mjs/.cjs/.ts — or flags), all the type-narrowing artifacts (…Payload, …Path, the …PathValue query-value map, …State/…Gatenever when the type has no workflow/gates, so a wrong state won't compile — and DerivativeName) bound into the accessors, and the maintained agent docs.

License

Apache-2.0 — © viingx AG