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

@thejob/common-client

v1.0.1

Published

Typed TypeScript/JavaScript client for the thejob-common-service HTTP API.

Downloads

388

Readme

@thejob/common-client

Typed client for thejob-common-service: the shared taxonomy lookups and the feedback inbox.

import { CommonClient } from '@thejob/common-client';

const common = new CommonClient({ baseUrl: process.env.COMMON_SERVICE_URL! });

Two surfaces, two auth postures

| surface | auth | |---|---| | /skills, /languages, /locations | public | | POST /feedback | any signed-in user's Bearer token | | GET/PATCH /feedback… | admin token |

There is no API-key path, so apiKey alone reaches nothing gated. Pass token.

The API

| method | endpoint | |---|---| | listSkills(query?) | GET /skills | | listLanguages(query?) | GET /languages | | autocompleteLocations(search, mode?) | GET /locations | | submitFeedback(body) | POST /feedback | | listFeedback(query?) | GET /feedback | | getFeedback(id) | GET /feedback/:id | | updateFeedbackStatus(id, status) | PATCH /feedback/:id | | health() | GET /health |

The filter is nested, not flat

Unlike @thejob/jobs-client, this service reads facets from a filter[name] wrapper. The client flattens them for you:

await common.listSkills({ filter: { category: ['tech', 'design'] } });
// -> ?filter[category]=tech,design

This is not cosmetic. The service parses filters with /^filter\[(.+)\]$/, which is greedy, so a naively nested filter[category][0]=tech is read as a field literally named category][0. That is a query for a field no document has: 200 OK, zero rows, no error anywhere. The client flattens every value to a single string before qs can nest it, and a test asserts no [0] appears.

Two consequences worth knowing:

  • A comma-bearing value throws. The service splits on commas unconditionally with no escape convention, so "Banking, Investments" would shred into two terms matching nothing. Failing loudly is the only honest option until the service grows an escape.
  • Feedback facets are single-valued. GET /feedback reads type and status with params.get(), not the comma-splitting parser, so there is no way to ask for two types in one call. It also reads only those two keys; the client rejects anything else rather than letting an ignored key return the unfiltered page.

/locations fails soft, and callers must know

This endpoint never errors. A missing upstream Geoapify key, a blank query, or an upstream outage all return { results: [] } with a 200, which is indistinguishable from "no place matched". An empty result is not evidence the place does not exist, and an autocomplete that silently stops working produces no client-side signal at all.

Feedback attribution cannot be spoofed

submitFeedback takes only type, message, email and pageUrl. The user id, email, name, user agent and locale are attached server-side from the verified token and request headers, which is exactly why they are absent from FeedbackSubmission.

Note that updateFeedbackStatus returns 404, not 400, for an unknown status, so a typo reads as a missing record.

Development

pnpm build      # tsc -p tsconfig.json
pnpm test       # vitest run

The test suite reads the service's own route files and asserts this client has a method for every endpoint they register, so an endpoint added without a client method fails here.