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/jobs-client

v1.1.0

Published

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

Downloads

598

Readme

@thejob/jobs-client

Typed client for thejob-jobs-service. Published by the service that owns the data, so consumers never hand-roll HTTP calls or redeclare job types.

import { JobsClient } from '@thejob/jobs-client';

const jobs = new JobsClient({
  baseUrl: process.env.JOBS_SERVICE_URL!,
  apiKey: process.env.JOBS_SERVICE_API_KEY,   // backend-to-backend
  // token: userJwt,                          // user-scoped calls (bookmarks)
});

const { results, total } = await jobs.listJobs({
  search: 'angular developer',
  workMode: ['remote', 'hybrid'],
  category: ['Technology, Data & Digital'],
  salary: { min: 50000 },
  limit: 6,
});
// -> ?search=angular+developer&workMode[0]=remote&workMode[1]=hybrid
//    &category[0]=Technology%2C+Data+%26+Digital&salary[min]=50000&limit=6

Not yet wired to the service. jobs-service still parses the older filter[field]=value form. The client emits the qs form above; migrating the service and its ~20 existing call sites is a separate, approved step.

Methods

Twenty methods for twenty endpoints, one to one. No wrappers, no helpers.

| | | |---|---| | Read | listJobs · getJob · listSimilarJobs · searchJobsV3 · searchJobsMeili | | Write | createJob · updateJob · deleteJob · closeJob · reopenJob · toggleJobFeatured · purgeJobCache | | Bookmarks | addBookmark · removeBookmark | | Expiry | listJobsDueForExpiryCheck · closeLapsedJobs | | Indexing | getIndexingQuota · indexJobs | | Admin | purgeAllJobsCache · health |

There is no countJobs: a count is listJobs({ ...filter, limit: 1 }) read off total, which is one line at the call site and keeps what total means visible.

Design

One method per endpoint, never per use case. Methods are named for the resource and operation (listJobs, closeJob), not for why a caller wants the data. A getNewestJobsForADigest would be the same mistake as an endpoint called /jobs/newest-for-digest: one caller's intent baked into a shared contract. Narrowing is the filter's job.

Facets are flat and typed, sitting next to paging exactly as they appear in the URL, so the type mirrors the request it produces. A typo in a declared facet is a compile error rather than a filter that saves, renders, and silently matches nothing.

Encoding is qs with its standard defaults — arrays as key[0], objects as key[nested]. Because arrays are a structure rather than a delimited string, a comma inside a value is just a character: no escaping, and no delimiter convention left for a consumer to get wrong. (7 of the 9 job categories in use contain a comma.)

Query strings are built by buildQuerySuffix from @thejob/util/query, shared with every other service client. It adds the one thing qs cannot know — which inputs must produce no parameter at all. qs drops undefined and [], but would happily emit workMode=, skills[0]=, or a key with untrimmed spaces, each of which matches nothing while looking correctly configured. Blank values and empty facets are dropped; false and 0 are kept, being meaningful.

total is the filter's count, not the page size. Six cards under "142 new jobs" is honest; counting the cards makes limit silently change the headline.

Types

JobDoc, JobListQuery, JobPage, BookmarkResult are the canonical definitions, owned here and imported back by jobs-service, so there is exactly one definition of each.

The client covers jobs and nothing else. Saved searches are user data and belong to user-service; alert sweeps belong to notification-service. Neither has a method here.

It returns jobs as the service stores them and does no presentation: no renaming to a template's field names, no joining a location into one string, no formatting a salary. A consumer that needs a narrower payload asks for it with fields; how a job is displayed is the consumer's own decision, not a shared contract.

Coverage

client.test.ts reads src/routes/jobs.ts and src/app.ts, collects every route the service registers, then drives a fake fetch through all twenty methods and asserts nothing is left over. It records what each method actually requests rather than grepping the source, so it proves the method calls the path.

A client that covers only part of an API is how consumers end up hand-rolling "just this one call" alongside it — the duplication this package exists to remove.

Two exclusions, both deliberate:

  • /webhooks/* — called into jobs-service by dory and the data-processor and authenticated by signature, not by a consumer holding this client.
  • GET /, /login, /logout, /dashboard/api/* — the operations dashboard. Cookie-gated, same-origin, browser-only, so an API-key client could not authenticate against them anyway. These live in src/ui/, which the test does not read.