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

@ceros-dev/markup-sdk-core

v1.0.0-rc.1

Published

Shared TypeScript types and utilities for MarkUp SDK packages

Readme

@ceros-dev/markup-sdk-core

Typed client, resource APIs, and shared types for the MarkUp SDK.

Stability. Follows semantic versioning from 1.0.0 — no breaking changes to the public API within a major version. Published to public npm.

For browser UI (commenting mode, pins, popovers) use @ceros-dev/markup-sdk — this package is the headless layer underneath it.

Installation

npm install @ceros-dev/markup-sdk-core

Quickstart

import {ApiClient, StaticTokenAdapter} from "@ceros-dev/markup-sdk-core";

const client = new ApiClient({
    baseUrl: "https://api.markup.io",
    publicKey: process.env.MARKUP_PUBLIC_KEY,
    authAdapter: new StaticTokenAdapter(process.env.MARKUP_TOKEN, {workspaceId: "ws_123"})
});

const threads = await client.threads.listOpen("project_abc");
for (const thread of threads.data) {
    console.log(thread.id, thread.priority, thread.createdAt);
}

ApiClient is the single entry point. It wires an internal HttpClient (retries, timeouts, idempotency keys, rate-limit headers) to typed resource accessors. Consumers should always use client.threads, client.comments, etc. — do not construct resource classes or HttpClient directly.

HttpClient is not exported from the main @ceros-dev/markup-sdk-core barrel. An @ceros-dev/markup-sdk-core/internal subpath exists for sibling packages in the Ceros monorepo (notably @ceros-dev/markup-sdk's AuthManager); it is @internal and not covered by the public semver contract — downstream integrators must not import from it.

Authentication

Auth is handled by an adapter passed to the client at construction. The transport pulls sessions from the adapter lazily, caches them until expiresAt, and transparently calls getSession(true) on a 401 to refresh before retrying the request exactly once. Callers never set tokens imperatively.

1. Static token

Use StaticTokenAdapter for scripts or tests that already hold a token:

import {StaticTokenAdapter} from "@ceros-dev/markup-sdk-core";

const adapter = new StaticTokenAdapter("eyJhbGciOi...", {workspaceId: "ws_123"});

2. Custom adapter (token refresh)

Implement IAuthAdapter to plug in a refresh strategy:

import type {IAuthAdapter, AuthSession} from "@ceros-dev/markup-sdk-core";

class MyAuthAdapter implements IAuthAdapter {
    private cached: AuthSession | null = null;

    async getSession(forceRefresh?: boolean): Promise<AuthSession> {
        if (!forceRefresh && this.cached && this.cached.expiresAt! > Date.now()) {
            return this.cached;
        }
        const res = await fetch("/api/markup-token");
        const {accessToken, workspaceId, expiresAt} = await res.json();
        this.cached = {accessToken, workspaceId, expiresAt};
        return this.cached;
    }

    clear() {
        this.cached = null;
    }
}

const client = new ApiClient({
    baseUrl: "https://api.markup.io",
    publicKey: process.env.MARKUP_PUBLIC_KEY,
    authAdapter: new MyAuthAdapter()
});

AuthSession carries {accessToken, workspaceId, expiresAt?}. The transport handles refresh automatically — implementations should cache internally and only hit the wire when forceRefresh === true or the cached session is missing/expired.

Error handling

Every HTTP error thrown by the client is a MarkUpError subclass. Narrow with instanceof and read code, status, requestId, and retryable for structured recovery.

import {
    ApiClient,
    MarkUpError,
    AuthenticationError,
    NotFoundError,
    RateLimitError,
    ValidationError,
    SDKErrorCode
} from "@ceros-dev/markup-sdk-core";

try {
    await client.threads.create(request);
} catch (err) {
    if (err instanceof ValidationError) {
        for (const fieldErr of err.errors) {
            console.warn(`${fieldErr.field}: ${fieldErr.message}`);
        }
    } else if (err instanceof AuthenticationError) {
        // Transport already attempted one refresh+retry. A persistent 401 means the adapter
        // cannot produce a valid token — e.g. the refresh token has expired.
        console.error("auth failed", err.requestId);
    } else if (err instanceof RateLimitError) {
        await new Promise((r) => setTimeout(r, err.retryAfter * 1000));
    } else if (err instanceof NotFoundError) {
        console.warn(`Missing ${err.resourceType}: ${err.resourceId}`);
    } else if (err instanceof MarkUpError) {
        console.error({
            code: err.code,
            status: err.status,
            requestId: err.requestId,
            retryable: err.retryable
        });
    } else {
        throw err;
    }
}

MarkUpError subclasses include ApiError, AuthenticationError, NotFoundError, ValidationError, RateLimitError, TimeoutError, and NetworkError. All carry the fields above plus sdkVersion and a toJSON() serializer for telemetry.

Two helpers are exported for code that can't rely on instanceof (e.g. across realms): isMarkUpError(err) is a type guard, and canRetryError(err) returns true for retryable MarkUpErrors (and for a raw TypeError, treated as a network failure).

The SDKErrorCode enum also defines browser-only sign-in codes — POPUP_BLOCKED, POPUP_CLOSED, WORKSPACE_FORBIDDEN, and SDK_SIGNIN_FAILED — surfaced by the SDK-managed sign-in flow in @ceros-dev/markup-sdk.

Resource APIs

Five resource groups are exposed on ApiClient:

| Accessor | Operations | | ------------------ | ------------------------------------------------------------------- | | client.projects | get, getTagData, setReadOnly | | client.threads | list, listOpen, listResolved, listByProject, get, create, resolve, unresolve, delete, movePin | | client.comments | list, listByThread, create, update, delete | | client.viewModes | list | | client.uploads | getUploadPolicy, completeUpload, directUpload |

Projects

const {data: project} = await client.projects.get("project_abc");
const {data: tagData} = await client.projects.getTagData("project_abc");
await client.projects.setReadOnly("project_abc", true);

Threads

const open = await client.threads.listOpen("project_abc", {limit: 50});
const page2 = await client.threads.listOpen("project_abc", {cursor: open.meta.cursor});
const {data: thread} = await client.threads.get("thread_123");
await client.threads.resolve("thread_123");
await client.threads.delete("thread_123");

threads.list({status: "all"}) merges two parallel requests. Cursor pagination is not supported in that mode — pass "open" or "resolved" for paginated results.

Comments

const {data: comments} = await client.comments.listByThread("thread_123");
await client.comments.create({
    threadId: "thread_123",
    content: "Looks good — shipping.",
    mentionIds: ["user_456"]
});
await client.comments.update("message_789", {content: "Looks good — shipping Monday."});
await client.comments.delete("message_789");

View modes

const viewModes = await client.viewModes.list("project_abc");

Uploads

Two flows depending on file size:

import {UploadResourceType} from "@ceros-dev/markup-sdk-core";

// Small files (up to 20MB): direct multipart upload
const fd = new FormData();
fd.append("file", file);
const {data: attachment} = await client.uploads.directUpload(fd);

// Large files: request a presigned policy, POST the file to S3, then confirm
const {data: policy} = await client.uploads.getUploadPolicy({
    resourceType: UploadResourceType.MESSAGE_ATTACHMENT,
    resourceId: "thread_123",
    filename: file.name,
    contentType: file.type,
    filesize: file.size
});
// ...POST the file to policy.policy.url with policy.policy.fields, then capture the S3 ETag...
const {data: ref} = await client.uploads.completeUpload({
    fileId: policy.fileId,
    etag
});

Subpath exports

Granular entry points for consumers that only need a subset of the package:

import {ApiClient, MarkUpError} from "@ceros-dev/markup-sdk-core/client";
import type {Thread, Comment, Project} from "@ceros-dev/markup-sdk-core/types";
import {API_PATHS} from "@ceros-dev/markup-sdk-core/api";
import {isValidUuid, formatRelativeTime} from "@ceros-dev/markup-sdk-core/utils";

./client carries the runtime client + error types, ./types is type-only domain models, ./api exposes the /api/v2 path and header constants, and ./utils carries validation and formatting helpers. The package root (@ceros-dev/markup-sdk-core) re-exports ./client, ./types, and ./api; the ./utils helpers are available only from the ./utils subpath.

Stability

  • Versioning: Semantic versioning from 1.0.0 — no breaking changes to the public API within a major version. Published to public npm.
  • Client header: Every request sends markup-client: sdk/<clientName>/<version> for telemetry. clientName defaults to core but can be overridden via the clientName constructor option (e.g. @ceros-dev/markup-sdk sets its own).

License

BSD 3-Clause — see LICENSE.