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

@taskp3/sdk

v0.1.1

Published

TaskP3 SDK — server-side client and Express adapter

Readme

@taskp3/sdk

Server-side SDK for TaskP3 — API client and Express adapter.

For React components, see @taskp3/react.

Installation

npm install @taskp3/sdk

Why Use the SDK?

Without the SDK, integrating TaskP3 means writing and maintaining a lot of plumbing yourself. Here's what our own app looked like before adopting the SDK:

~200 lines of API service functions:

// services/triage.ts — hand-rolled fetch wrappers for every endpoint
export const createTriage = async ({
  name,
  description,
  path,
  type,
  loomUrl,
  fileIds,
}) => {
  const res = await api.post("/triage/create", {
    name,
    description,
    path,
    type,
    loomUrl,
    fileIds,
  });
  return res.data;
};

export const uploadTriageFile = async ({ data }) => {
  const res = await api.post("/triage/file", data);
  return res.data;
};

export const getTriageSubmissions = async ({
  sortOrder,
  open,
  type,
  page,
  limit,
}) => {
  /* ... */
};
export const getTriageFile = async ({ fileId }) => {
  /* ... */
};
export const getTriageSubmissionResponses = async ({ taskId, page, limit }) => {
  /* ... */
};
export const getChangelog = async ({ page, limit, types }) => {
  /* ... */
};
// ... plus all the TypeScript interfaces

~175 lines of React Query hooks:

// hooks/triage.tsx — pagination, caching, stale times, etc.
export function useUserTriageSubmissions({
  externalCreatedById,
  sortOrder,
  open,
  type,
  limit,
}) {
  return useInfiniteQuery(
    [
      "user",
      externalCreatedById,
      "triage_submissions",
      sortOrder,
      open,
      type,
      limit,
    ],
    ({ pageParam = 1 }) =>
      getTriageSubmissions({ sortOrder, open, type, page: pageParam, limit }),
    {
      getNextPageParam: (lastPage) =>
        lastPage.page < lastPage.totalPages ? lastPage.page + 1 : undefined,
    },
  );
}

export function useChangelog({ limit, types }) {
  /* ... */
}
export function useLatestChangelog() {
  /* ... */
}
export function useChangelogCodeChanges({ changelogId }) {
  /* ... */
}

~500 lines of triage UI (category tabs, file upload, presigned URL handling, paste support):

// components/triage/triageMenu.tsx — reimplementing the widget from scratch
const handleFileUpload = async (files: FileList) => {
  const filePromises = Array.from(files).map(async (file) => {
    const { file: fileData, signedUrl } = await uploadTriageFile({
      data: {
        fileName: file.name,
        contentType: file.type,
        fileSize: file.size,
      },
    });
    await axios.put(signedUrl, file, {
      headers: { "Content-Type": file.type },
    });
    return {
      id: fileData.id,
      name: file.name,
      size: file.size,
      type: file.type,
    };
  });
  // ...error handling, state updates, alerts...
};

~100 lines of Zustand state management, ~80 lines of changelog modal auto-show logic, and more.

With the SDK, all of that becomes:

// Server — 1 route mount replaces your custom API layer
app.use(
  "/api/triage",
  createTriageRouter({
    apiKey: process.env.TASKP3_API_KEY!,
    projectId: process.env.TASKP3_PROJECT_ID!,
    authMiddleware: requireAuth,
    resolveUser: (req) => ({ id: req.user.id, name: req.user.name }),
  }),
);
// Client — shared components replace a large amount of UI, hooks, and services
<TaskP3Provider
  apiUrl="https://your-app.com/api/triage"
  getHeaders={() => ({ Authorization: `Bearer ${token}` })}
>
  <App />
  <TriageButton />
  <WhatsNew />
</TaskP3Provider>

The SDK handles presigned uploads, pagination, caching, auto-show logic, theming, and many of the integration edge cases so you don't have to.

For CP9-style parity, the strongest match today is the initial submission happy path when @taskp3/react is used with the Express adapter. Past-submissions, reply composition, host routing, and alert orchestration still have documented parity gaps. See CP_PARITY_MATRIX.md for the current status.

Usage

API Client

import TaskP3 from "@taskp3/sdk";

const taskp3 = new TaskP3("sk_live_...", { projectId: "proj_..." });

const submission = await taskp3.triage.createTask({
  taskName: "Safari button broken",
  description: "The submit button doesn't respond on Safari 17",
  type: "Problem",
  externalCreatedById: "user_123",
  externalCreatedByName: "Jane Doe",
});

Express Adapter

Mount the router to proxy frontend requests — keeps your API key server-side:

Supports express 4.18+ and 5.x.

import express from "express";
import { createTriageRouter, TRIAGE_ROUTE_PATHS } from "@taskp3/sdk";

const app = express();

app.use(
  "/api/triage",
  createTriageRouter({
    apiKey: process.env.TASKP3_API_KEY!,
    projectId: process.env.TASKP3_PROJECT_ID!,

    // Plug in your existing auth middleware
    authMiddleware: requireAuth,

    // Optional per-route middleware using SDK route constants
    routeMiddleware: {
      [TRIAGE_ROUTE_PATHS.CREATE_SUBMISSION]: requireCanCreateTriage,
      [TRIAGE_ROUTE_PATHS.LIST_SUBMISSIONS]: requireCanReadTriage,
    },

    // Map your user to a TaskP3 user (id + name are required)
    resolveUser: (req) => ({
      id: req.user.id,
      name: req.user.name,
    }),

    // Attach arbitrary metadata to every submission
    resolveMetaData: (req) => ({
      orgId: req.user.orgId,
      plan: req.user.plan,
    }),
  }),
);

The router includes express.json() internally — no need to apply it yourself.

API Reference

new TaskP3(apiKey, options)

| Param | Type | Required | Description | | ------------------- | -------- | -------- | --------------------------------------------------------- | | apiKey | string | Yes | Your TaskP3 secret key | | options.projectId | string | Yes | Project identifier | | options.apiUrl | string | No | Override API base URL (default: https://api.taskp3.com) |

Returns a TaskP3 instance with the following resource:

taskp3.triage

| Method | Returns | Description | | ----------------------------------------------- | --------------------------------------- | ---------------------------------- | | createTask(data) | Promise<TriageSubmission> | Create a triage submission | | getSubmission(taskId) | Promise<TriageSubmission> | Get a single submission | | getSubmissions(params) | Promise<TriageSubmissionsResponse> | List submissions (paginated) | | getSubmissionResponses(taskId, params?) | Promise<TriageResponsesResult> | List responses for a submission | | createResponse(taskId, data) | Promise<TriageSubmission> | Add a response to a submission | | uploadFile(data) | Promise<TriageFileUpload> | Get a presigned upload URL | | createSubmissionFile(taskId, data) | Promise<TriageLinkedFileUploadResponse> | Create and attach submission file | | createResponseFile(responseId, data) | Promise<TriageLinkedFileUploadResponse> | Create and attach response file | | getFile(fileId) | Promise<TriageFile> | Get file metadata + download URL | | getSubmissionFiles(taskId, params?) | Promise<TriageFilesResponse> | List files on a submission | | getResponseFiles(responseId, params?) | Promise<TriageFilesResponse> | List files on a response | | getChangelog(params?) | Promise<ChangelogResponse> | List changelog entries (paginated) | | getChangelogCodeChanges(changelogId, params?) | Promise<ChangelogCodeChangesResponse> | List code changes for an entry |

createTriageRouter(config)

| Param | Type | Required | Description | | ----------------- | ---------------------------------- | -------- | --------------------------------------------------------- | | apiKey | string | Yes | Your TaskP3 secret key | | projectId | string | Yes | Project identifier | | apiUrl | string | No | Optional API URL override (default: https://api.taskp3.com) | | resolveUser | (req) => TriageUser | Yes | Map your request to a TaskP3 user ({ id, name, type? }) | | authMiddleware | RequestHandler | No | Guard that runs before every route | | routeMiddleware | Partial<Record<TriageRoutePath, RequestHandler \| RequestHandler[]>> | No | Per-route middleware (runs after authMiddleware when both are set; prefer TRIAGE_ROUTE_PATHS.* keys) | | resolveMetaData | (req) => Record<string, unknown> | No | Extra metadata attached to submissions | | resolveExternalCreatedById | ({ req, user, submitter }) => string \| string[] \| undefined | No | Override submission user scope (for me/team/all flows) | | resolveUserScope | ({ req, user, submitter }) => string[] | No | Resolve scoped user ids from role/tenant context | | buildTaskName | ({ req, type, description, user }) => string | No | Custom task naming strategy | | resolveTags | (req) => TriageTag[] | No | Attach tags on create |

For hierarchical scoping, you can use createHierarchicalScopeResolver() from @taskp3/sdk.

taskp3.webhooks

Webhook endpoint management client:

| Method | Returns | Description | |---|---|---| | createEndpoint(projectId, data) | Promise<CreateWebhookEndpointResponse> | Create endpoint and return signing secret | | getEndpoints(projectId) | Promise<WebhookEndpoint[]> | List project endpoints | | getEndpoint(projectId, endpointId) | Promise<WebhookEndpoint> | Get one endpoint | | updateEndpoint(projectId, endpointId, data) | Promise<WebhookEndpoint> | Update endpoint | | deleteEndpoint(projectId, endpointId) | Promise<{ message: string }> | Delete endpoint | | rollSecret(projectId, endpointId) | Promise<RollWebhookSecretResponse> | Rotate endpoint secret | | getEvents(projectId, params?) | Promise<WebhookEventRecord[]> | List recent webhook events |

Webhook Verification Helpers

Use SDK helpers in your webhook receiver:

import {
  getRawRequestBody,
  verifyP3WebhookSignature,
  parseP3WebhookEvent,
} from "@taskp3/sdk";

Helpers:

  • getRawRequestBody(body) converts raw express body to UTF-8 string
  • verifyP3WebhookSignature({ rawBody, signatureHeader, secret }) validates x-p3-signature
  • parseP3WebhookEvent(rawBody) validates and parses event payload

Parity and Migration

  • Internal parity mapping: CP_PARITY_MATRIX.md
  • Migration recipes from custom integration services/routes are documented in code examples above.
  • Transcript APIs were removed from @taskp3/sdk in this SDK pass (getFileTranscript and the Express /file/:fileId/transcript adapter route).

Related Packages

| Package | Description | | -------------------------------------------------------------- | ---------------------------------------------------------------- | | @taskp3/sdk | Server-side client + Express adapter | | @taskp3/react | React components — <TriageButton>, <Changelog>, <WhatsNew> |