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

@brand-map/ts-client

v0.0.10-alpha.34

Published

Brand-Map TypeScript SDK client.

Readme

TypeScript SDK Client

TypeScript client for the Brand-Map HTTP API.

The SDK mirrors the HTTP API as a typed resource tree. Published admin routes appear under sdk.admin; published storefront routes appear under sdk.store when they are included in the SDK build.

Install

npm:

npm install @brand-map/ts-client

Yarn:

yarn add @brand-map/ts-client

pnpm:

pnpm add @brand-map/ts-client

Bun:

bun add @brand-map/ts-client

This build is version 0.0.10-alpha.33.

Import and create a client

import { BrandMapClient } from "@brand-map/ts-client"

const sdk = new BrandMapClient({
  baseUrl: "https://brand-map.site/api",
  auth: {
    tokenStore: createMemoryTokenStore(),
  },
  timeoutMs: 15_000,
})

await sdk.auth.login({
  email: "[email protected]",
  password: "password1234",
})

Client configuration

  • baseUrl is required. It is the API origin without a trailing path requirement.
  • auth.tokenStore stores Better Auth bearer session tokens. The generated default is in-memory only.
  • auth.clearTokenOnUnauthorized clears the token store after a 401 response. It defaults to true.
  • auth.credentials sets default fetch credentials for cookie-backed Better Auth sessions.
  • defaultHeaders are sent with every request.
  • getHeaders runs before every request and can provide caller-managed auth headers.
  • Browser apps normally omit fetch. The SDK calls globalThis.fetch safely by default, and custom fetch functions are invoked with the correct global binding.
  • fetch is mainly for tests or non-standard runtimes that need to inject a custom implementation.
  • timeoutMs sets a default request timeout. A method call can override it through method options.

For persistent tokens, provide your own BrandMapTokenStore from application code:

const tokenStore = {
  async get() {
    return await secureStore.get("brand-map-token")
  },
  async set(token: string) {
    await secureStore.set("brand-map-token", token)
  },
  async clear() {
    await secureStore.delete("brand-map-token")
  },
}

const sdk = new BrandMapClient({
  baseUrl: "https://brand-map.site/api",
  auth: { tokenStore },
})

Resource tree

Resources are grouped by API scope and resource name:

// Storefront API
await sdk.store.collection.list()
await sdk.store.collection.get({ id: "collection_123" })

Use editor autocomplete on sdk.admin, sdk.platform, and sdk.store to discover available resources and methods. Method names are the public SDK surface; this README intentionally does not duplicate the full route list.

Method argument order

SDK methods preserve the HTTP route shape but use typed positional arguments:

  • path params first
  • request body second
  • query object third, or earlier when there are no params/body
  • method options last

Examples:

// list route: query, options
await sdk.store.collection.list({ pagination: { take: 20 } })

// detail route: params, query, options
await sdk.store.collection.get({ id: "collection_123" }, { fields: ["id", "title"] })

Querying lists

List methods accept the same query model as the HTTP API: fields, populate, filters, sort, and pagination when supported by that route.

const collections = await sdk.store.collection.list({
  fields: ["id", "title", "createdAt"],
  filters: {
    isActive: { $eq: true },
  },
  sort: ["createdAt:desc"],
  pagination: {
    take: 20,
    skip: 0,
  },
})

assertSuccess(collections)

collections.data
collections.pagination

Pagination lives on the response envelope, not inside data. Offset pagination uses { take, skip }; page pagination uses { page, pageSize }.

assertSuccess(collections)

if (collections.pagination) {
  console.log(collections.pagination.total)
}

The SDK serializes query objects to the API bracket notation, so callers should pass normal JavaScript objects instead of pre-encoded query strings.

Selecting fields

For operations that support fields, inline field arrays narrow the response type without as const:

const res = await sdk.store.collection.list({
  fields: ["createdAt", "description", "title"],
})

assertSuccess(res)

res.data.at(0)?.createdAt
// string | undefined

res.data.at(0)?.slug
// TypeScript error: slug was not selected

If you store a query in a widened query type, TypeScript cannot recover literal field names and the response falls back to the broad DTO-compatible shape.

Populating relations

Routes that expose relation metadata accept populate. Relations are included only when they are explicitly populated. Relation nodes must describe what they need; empty nodes are rejected before the request is sent.

// Collection routes currently expose only scalar selection in the published SDK.
const res = await sdk.store.collection.list({
  fields: ["id", "title"],
})

assertSuccess(res)

res.data.at(0)?.title
// string | undefined

When a route supports populate, nested selection follows the same relation-node shape and narrows nested response data.

Invalid empty selection shapes throw in the SDK client before fetch is called:

await sdk.store.collection.list({ fields: [] })
// Error: query.fields must not be an empty array

await sdk.store.collection.list({ sort: [] })
// Error: query.sort must not be an empty array

await sdk.store.collection.list({ pagination: {} })
// Error: query.pagination must not be an empty object

await sdk.store.collection.list({ take: 20 })
// Error: query.take is not supported. Use query.pagination.take.

Autocomplete is available throughout query objects: top-level fields, supported populate keys, relation node options, nested fields, and nested populate keys.

Method options

The final argument of every SDK method is BrandMapMethodOptions:

await sdk.store.collection.get(
  { id: "collection_123" },
  { fields: ["id", "title"] },
  {
    headers: {
      "X-Request-Source": "collection-page",
    },
    timeoutMs: 30_000,
  },
)

Use idempotencyKey for routes that support idempotency. For routes that do not support it, the SDK does not send the Idempotency-Key header.

Response envelope

Every successful request resolves to the Brand-Map response envelope:

type BrandMapErrorStatusMap = {
  400: "badRequest"
  401: "unauthorized"
  403: "forbidden"
  404: "notFound"
  409: "conflict"
  422: "unprocessableEntity"
  429: "tooManyRequests"
  500: "internalServerError"
  504: "gatewayTimeout"
}

type BrandMapErrorStatus = keyof BrandMapErrorStatusMap

type BrandMapErrorStatusKey = BrandMapErrorStatusMap[BrandMapErrorStatus]

type BrandMapErrorPayload<Status extends BrandMapErrorStatus = BrandMapErrorStatus> = {
  [CurrentStatus in Status]: {
    cause?: unknown
    code?: string
    message: string
    name: string
    status: CurrentStatus
    statusKey: BrandMapErrorStatusMap[CurrentStatus]
    type?: string
  }
}[Status]

type BrandMapSuccessResponse<Data, Paginated extends boolean = false> = {
  data: Data
  error: null
  meta: Record<string, JsonValue> | null
  pagination: Paginated extends true ? BrandMapPagination : null
}

type BrandMapErrorResponse<ErrorPayload extends BrandMapErrorPayload = BrandMapErrorPayload> = {
  data: null
  error: ErrorPayload
  meta: Record<string, JsonValue> | null
  pagination: null
}

type BrandMapResponse<
  Data,
  Paginated extends boolean = false,
  ErrorPayload extends BrandMapErrorPayload = BrandMapErrorPayload,
> = BrandMapSuccessResponse<Data, Paginated> | BrandMapErrorResponse<ErrorPayload>

function isSuccess<Data, Paginated extends boolean, ErrorPayload extends BrandMapErrorPayload = BrandMapErrorPayload>(
  response: BrandMapResponse<Data, Paginated, ErrorPayload>,
): response is BrandMapSuccessResponse<Data, Paginated>

function isError<Data, Paginated extends boolean, ErrorPayload extends BrandMapErrorPayload = BrandMapErrorPayload>(
  response: BrandMapResponse<Data, Paginated, ErrorPayload>,
): response is BrandMapErrorResponse<ErrorPayload>

function assertSuccess<Data, Paginated extends boolean, ErrorPayload extends BrandMapErrorPayload = BrandMapErrorPayload>(
  response: BrandMapResponse<Data, Paginated, ErrorPayload>,
): asserts response is BrandMapSuccessResponse<Data, Paginated>

async function unwrap<Data, Paginated extends boolean, ErrorPayload extends BrandMapErrorPayload = BrandMapErrorPayload>(
  responsePromise: PromiseLike<BrandMapResponse<Data, Paginated, ErrorPayload>>,
): Promise<Data>

Use isSuccess, isError, or assertSuccess to narrow the union. Use unwrap when a caller only needs success data. For list routes, success data is an array. For item routes, success data is a single object.

import { assertSuccess, isError, unwrap } from "@brand-map/ts-client"

const response = await sdk.store.collection.list({ pagination: { take: 10 } })

if (isError(response)) {
  console.error(response.error)
  return
}

response.data
response.pagination

assertSuccess(response)
response.data

const collections = await unwrap(sdk.store.collection.list({ fields: ["id", "title"] }))
collections[0]?.id

Errors

Non-2xx responses throw BrandMapFetchError. The error contains the original Response and parsed response payload.

import { BrandMapFetchError } from "@brand-map/ts-client"

try {
  await sdk.store.collection.get({ id: "missing" })
} catch (error) {
  if (error instanceof BrandMapFetchError) {
    console.error(error.response.status)
    console.error(error.payload)
  }
}

Browser usage

Modern bundlers should import the ESM entry automatically:

import { BrandMapClient } from "@brand-map/ts-client"

The package also publishes a UMD bundle for script-tag usage. It exposes BrandMapSdk on globalThis:

<script src="/path/to/ts-client.umd.js"></script>
<script>
  const sdk = new BrandMapSdk.BrandMapClient({
    baseUrl: "https://brand-map.site/api",
  })
</script>

Testing

Pass a custom fetch implementation to test code without hitting the network:

const sdk = new BrandMapClient({
  baseUrl: "https://api.test",
  fetch: async () =>
    new Response(JSON.stringify({ data: [], error: null, meta: null, pagination: null }), {
      headers: { "Content-Type": "application/json" },
      status: 200,
    }),
})

Build and publish

The workspace package is private and source-oriented. The npm package is the dist directory.

cd typescript
bun run build
cd dist
npm publish

Do not publish typescript directly; publish typescript/dist.

Regenerating

After changing API metadata or the SDK renderer, refresh the package from the server repo root:

bun run script gen sdk-client --module all --force