@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=6Not yet wired to the service. jobs-service still parses the older
filter[field]=valueform. The client emits theqsform 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 insrc/ui/, which the test does not read.
