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

@synchronized-studio/cmsassets-core

v0.4.2

Published

Universal CMS integration foundation for CMS Assets — provider-aware request classification, URL rewriting, and fetch adapter.

Downloads

610

Readme

@synchronized-studio/cmsassets-core

Universal CMS integration layer for CMS Assets.

Routes CMS API and asset requests through the CMS Assets edge proxy — adding edge caching, image optimization, and server-side token injection without changing your application logic.

Works in any JavaScript environment: Node.js 18+, browsers, Cloudflare Workers, Deno.

Built-in providers: Contentful, Prismic — plus any CMS via createGenericProvider


Install

npm install @synchronized-studio/cmsassets-core
pnpm add @synchronized-studio/cmsassets-core
yarn add @synchronized-studio/cmsassets-core

How it works

CMS Assets sits between your app and your CMS. Instead of calling Contentful or Prismic directly, requests go through your tenant's proxy endpoint:

Your app
  → CMS Assets edge (tenant.cmsassets.com)
    → Contentful / Prismic / ...

@synchronized-studio/cmsassets-core handles the URL rewriting at the transport layer. You configure it once and drop it into your CMS SDK — no other code changes needed.


Quick start

Contentful

import { createContentfulAdapter } from '@synchronized-studio/cmsassets-core'
import { createClient } from 'contentful'

const client = createClient({
  space: 'abc123xyz',
  accessToken: 'proxy-injected',  // any truthy string — real token is injected server-side by the proxy
  adapter: createContentfulAdapter({
    tenant: 'my-site',
    provider: 'contentful',
    providerConfig: { spaceId: 'abc123xyz' },
  }),
})

// All API calls go through the proxy automatically:
// https://cdn.contentful.com/spaces/abc123xyz/entries
// → https://my-site.cmsassets.com/~api/spaces/abc123xyz/entries

Why createContentfulAdapter instead of createCmsAssetsFetch? The Contentful SDK uses Axios internally, which expects an adapter with a different signature than fetch. It also injects Authorization and X-Contentful-User-Agent headers that trigger CORS preflight in browsers. createContentfulAdapter handles both: it bridges Axios to fetch and strips the headers that would cause CORS issues. For direct fetch usage without the SDK, use createCmsAssetsFetch.

Prismic

import { createCmsAssetsFetch } from '@synchronized-studio/cmsassets-core'
import * as prismic from '@prismicio/client'

const cmsFetch = createCmsAssetsFetch({
  tenant: 'my-site',
  provider: 'prismic',
})

const client = prismic.createClient('my-repo', {
  fetch: cmsFetch,
})

// All API calls go through the proxy automatically:
// https://my-repo.cdn.prismic.io/api/v2/documents/search?ref=...
// → https://my-site.cmsassets.com/~api/api/v2/documents/search?ref=...

Any CMS via generic provider

import { createGenericProvider, createCmsAssetsFetch } from '@synchronized-studio/cmsassets-core'

const storyblok = createGenericProvider({
  id: 'storyblok',
  apiHosts: ['api.storyblok.com'],
  assetHosts: ['a.storyblok.com'],
})

const cmsFetch = createCmsAssetsFetch({
  tenant: 'my-site',
  provider: storyblok,   // pass ProviderDefinition directly — no registry needed
})

// https://api.storyblok.com/v2/stories → https://my-site.cmsassets.com/~api/v2/stories
// https://a.storyblok.com/f/123/hero.jpg → https://my-site.cmsassets.com/f/123/hero.jpg

URL rewriting without fetch

Use createUrlRewriter when you need to transform URLs without intercepting fetch — useful for server-side response body processing, logging, or building custom adapters.

import { createUrlRewriter } from '@synchronized-studio/cmsassets-core'

const rewrite = createUrlRewriter({
  tenant: 'my-site',
  provider: 'contentful',
  providerConfig: { spaceId: 'abc123xyz' },
})

// API URL
rewrite('https://cdn.contentful.com/spaces/abc123xyz/entries?locale=en')
// → { url: 'https://my-site.cmsassets.com/~api/spaces/abc123xyz/entries?locale=en', kind: 'api', rewritten: true }

// Asset URL — image optimization params are preserved
rewrite('https://images.ctfassets.net/abc123xyz/assetId/token/hero.jpg?w=1200&fm=webp')
// → { url: 'https://my-site.cmsassets.com/assetId/token/hero.jpg?w=1200&fm=webp', kind: 'asset', rewritten: true }

// Non-CMS URL — passes through unchanged
rewrite('https://fonts.googleapis.com/css2?family=Inter')
// → { url: 'https://fonts.googleapis.com/css2?family=Inter', kind: 'unknown', rewritten: false }

API reference

createCmsAssetsFetch(options)

Returns a fetch-compatible function that rewrites CMS URLs through the proxy. Safe to use as a global fetch replacement — non-CMS requests pass through unchanged.

const cmsFetch = createCmsAssetsFetch({
  tenant: 'my-site',          // required — CMS Assets tenant slug
  provider: 'contentful',     // required — provider id string OR a ProviderDefinition object
  providerConfig: {           // optional — provider-specific config
    spaceId: 'abc123xyz',     //   Contentful: spaceId
  },
  enableApiProxy: true,       // optional — rewrite API requests (default: true)
  enableAssetProxy: true,     // optional — rewrite asset requests (default: true)
  registry: myRegistry,       // optional — custom provider registry (used when provider is a string)
})

Using enableApiProxy / enableAssetProxy:

// Asset proxy only — API calls go directly to Contentful
const cmsFetch = createCmsAssetsFetch({
  tenant: 'my-site',
  provider: 'contentful',
  enableApiProxy: false,
})

// API proxy only — images are not proxied
const cmsFetch = createCmsAssetsFetch({
  tenant: 'my-site',
  provider: 'prismic',
  enableAssetProxy: false,
})

createContentfulAdapter(options)

Returns an Axios-compatible adapter function for the Contentful JS SDK. Internally creates a cmsFetch for URL rewriting and bridges the Axios adapter interface to fetch. Automatically strips Authorization and X-Contentful-User-Agent headers to prevent CORS preflight requests.

import { createContentfulAdapter } from '@synchronized-studio/cmsassets-core'
import { createClient } from 'contentful'

const client = createClient({
  space: 'abc123xyz',
  accessToken: 'proxy-injected',
  adapter: createContentfulAdapter({
    tenant: 'my-site',
    provider: 'contentful',
    providerConfig: { spaceId: 'abc123xyz' },
  }),
})

Options: Same as createCmsAssetsFetch, plus:

| Option | Type | Description | |--------|------|-------------| | wrapFetch | (baseFetch) => fetch | Optional middleware to wrap the internal fetch before use (e.g. cache bypass) |

// With preview cache bypass:
const adapter = createContentfulAdapter({
  tenant: 'my-site',
  provider: 'contentful',
  providerConfig: { spaceId: 'abc123xyz' },
  wrapFetch: (baseFetch) => withCacheBypass(baseFetch, () => isPreview),
})

createUrlRewriter(options)

Returns a bound (url: string | URL) => RewriteResult function. Same options as createCmsAssetsFetch.

import { createUrlRewriter } from '@synchronized-studio/cmsassets-core'

const rewrite = createUrlRewriter({
  tenant: 'my-site',
  provider: 'prismic',
})

const { url, kind, rewritten } = rewrite('https://images.prismic.io/my-repo/hero.jpg?w=800&fm=webp')
// url:       'https://my-site.cmsassets.com/my-repo/hero.jpg?w=800&fm=webp'
// kind:      'asset'
// rewritten: true

classifyUrl(url, provider)

Classify a URL as 'api', 'asset', or 'unknown' based on its hostname.

import { classifyUrl, contentful, prismic } from '@synchronized-studio/cmsassets-core'

classifyUrl('https://cdn.contentful.com/spaces/abc/entries', contentful)
// → 'api'

classifyUrl('https://images.ctfassets.net/abc/assetId/token/img.jpg', contentful)
// → 'asset'

classifyUrl('https://my-repo.cdn.prismic.io/api/v2/documents/search', prismic)
// → 'api'

classifyUrl('https://images.prismic.io/my-repo/photo.jpg', prismic)
// → 'asset'

classifyUrl('https://example.com/logo.svg', contentful)
// → 'unknown'

rewriteUrl(url, kind, provider, tenant, config?)

Low-level rewrite — takes a pre-classified URL and returns a RewriteResult.

import { classifyUrl, rewriteUrl, contentful } from '@synchronized-studio/cmsassets-core'

const url = new URL('https://cdn.contentful.com/spaces/abc/entries?locale=en')
const kind = classifyUrl(url, contentful)
const result = rewriteUrl(url, kind, contentful, 'my-site', { spaceId: 'abc' })

// result.url       → 'https://my-site.cmsassets.com/~api/spaces/abc/entries?locale=en'
// result.kind      → 'api'
// result.rewritten → true

ProviderRegistry

Register custom or extended provider definitions.

import { ProviderRegistry, contentful, prismic } from '@synchronized-studio/cmsassets-core'

// Create a custom registry
const registry = new ProviderRegistry()
  .register(contentful)
  .register(prismic)

// Use it with the fetch adapter
const cmsFetch = createCmsAssetsFetch({
  tenant: 'my-site',
  provider: 'contentful',
  registry,
})

The defaultRegistry is a shared singleton pre-populated with contentful and prismic. You only need a custom registry if you want to override a built-in provider or register your own.


createGenericProvider(config)

Create a ProviderDefinition from a config object — use any CMS or API origin without writing a custom provider from scratch.

import { createGenericProvider } from '@synchronized-studio/cmsassets-core'

const provider = createGenericProvider({
  id: 'storyblok',
  apiHosts: ['api.storyblok.com'],        // exact or wildcard (e.g. '*.api.example.com')
  assetHosts: ['a.storyblok.com'],

  // Optional — reserved for future Worker-side auth injection
  auth: {
    header: 'Authorization',
    format: 'Bearer {token}',
  },

  // Optional — reserved for future proxy preview bypass support
  previewBypass: {
    queryParams: ['preview', 'draft'],
  },
})

URL rewriting follows standard conventions:

API:   https://api.storyblok.com/v2/stories  → https://{tenant}.cmsassets.com/~api/v2/stories
Asset: https://a.storyblok.com/f/123/img.jpg → https://{tenant}.cmsassets.com/f/123/img.jpg

API_PREFIX

The /~api path prefix used by the CMS Assets API proxy. Exported for use in custom integrations.

import { API_PREFIX } from '@synchronized-studio/cmsassets-core'

console.log(API_PREFIX) // '/~api'

Providers

Contentful

| Kind | Hosts | |---|---| | API | cdn.contentful.com, preview.contentful.com | | Assets | images.ctfassets.net, videos.ctfassets.net, assets.ctfassets.net, downloads.ctfassets.net |

providerConfig.spaceId — Contentful space ID. Required for correct asset path stripping. When provided, the space ID segment is removed from the proxy URL path (matching the proxy worker's configured origin).

// Without spaceId:
// https://images.ctfassets.net/abc/assetId/token/img.jpg
// → https://my-site.cmsassets.com/abc/assetId/token/img.jpg

// With spaceId: 'abc':
// https://images.ctfassets.net/abc/assetId/token/img.jpg
// → https://my-site.cmsassets.com/assetId/token/img.jpg  ← spaceId stripped

Prismic

| Kind | Hosts | |---|---| | API | *.cdn.prismic.io (wildcard — matches any repo subdomain) | | Assets | images.prismic.io, prismic-io.imgix.net |

Prismic image optimization params (w, h, q, fm, auto, fit) are preserved end-to-end. The proxy forwards them to images.prismic.io (Imgix CDN) where they are processed.

// Image optimization is preserved:
// https://images.prismic.io/my-repo/hero.jpg?auto=format,compress&w=920&fm=webp&q=80
// → https://my-site.cmsassets.com/my-repo/hero.jpg?auto=format,compress&w=920&fm=webp&q=80

Prismic's ref parameter changes on every content publish, making each published version a distinct cache key — no manual cache invalidation needed.


TypeScript

All types are exported from the package root:

import type {
  RequestKind,           // 'api' | 'asset' | 'unknown'
  ProviderDefinition,    // shape of a provider
  ProviderConfig,        // Record<string, string | undefined>
  RewriteResult,         // { url, kind, rewritten }
  CmsAssetsFetchOptions, // options for createCmsAssetsFetch
  UrlRewriterOptions,    // options for createUrlRewriter
  GenericProviderConfig, // config shape for createGenericProvider
} from '@synchronized-studio/cmsassets-core'

Custom providers

Using createGenericProvider (recommended)

import { createGenericProvider, createCmsAssetsFetch } from '@synchronized-studio/cmsassets-core'

const sanity = createGenericProvider({
  id: 'sanity',
  apiHosts: ['*.api.sanity.io'],
  assetHosts: ['cdn.sanity.io'],
  auth: { header: 'Authorization', format: 'Bearer {token}' },
})

// Pass directly — no registry needed
const cmsFetch = createCmsAssetsFetch({ tenant: 'my-site', provider: sanity })

// Or register and use by id string
import { defaultRegistry } from '@synchronized-studio/cmsassets-core'
defaultRegistry.register(sanity)
const cmsFetch2 = createCmsAssetsFetch({ tenant: 'my-site', provider: 'sanity' })

Using ProviderDefinition directly (advanced)

For full control over rewrite logic:

import { ProviderDefinition, defaultRegistry } from '@synchronized-studio/cmsassets-core'

const sanity: ProviderDefinition = {
  id: 'sanity',
  apiHosts: ['*.api.sanity.io'],
  assetHosts: ['cdn.sanity.io'],
  rewriteApiUrl(url, tenant) {
    return `https://${tenant}.cmsassets.com/~api${url.pathname}${url.search}`
  },
  rewriteAssetUrl(url, tenant) {
    return `https://${tenant}.cmsassets.com${url.pathname}${url.search}`
  },
}

defaultRegistry.register(sanity)

Requirements

  • Node.js 18+ (or any runtime with the Fetch API)
  • No dependencies