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

plansolve

v0.33.0

Published

Official JavaScript/TypeScript client for the [PlanSolve](https://getplansolve.com) optimization API. One fully typed client covers three solvers (field service routing, professional-services task assignment, and shift scheduling) with built-in polling an

Downloads

1,067

Readme

PlanSolve for JavaScript / TypeScript

Official JavaScript/TypeScript client for the PlanSolve optimization API. One fully typed client covers three solvers (field service routing, professional-services task assignment, and shift scheduling) with built-in polling and clean error messages. Runs anywhere JS does: Node.js, Deno, Bun, and edge runtimes.

Installation

npm install plansolve

Ships both ESM and CommonJS builds with bundled TypeScript declarations (for both import and require). Requires Node.js 18 or later (the SDK uses the global fetch).

PlanSolveClient is the default export and is also available as a named export, so import PlanSolveClient from "plansolve" and import { PlanSolveClient } from "plansolve" both work. With CommonJS, use const { PlanSolveClient } = require("plansolve").

Quick start

import PlanSolveClient from "plansolve";

const client = new PlanSolveClient("YOUR_API_KEY");

const request = {
  vehicles: [
    {
      id: "tech1",
      location: [40.7128, -74.006],
      skills: ["repair"],
      shifts: [
        { id: "morning", minStartTime: "2026-04-02T08:00:00", maxEndTime: "2026-04-02T17:00:00" },
      ],
    },
  ],
  visits: [
    {
      id: "visit1",
      name: "AC Repair - Downtown Office",
      location: [40.7589, -73.9851],
      serviceDuration: "PT60M",
      priority: "HIGH",
      requiredSkills: ["repair"],
      timeWindows: [
        { minStartTime: "2026-04-02T09:00:00", maxEndTime: "2026-04-02T17:00:00" },
      ],
    },
  ],
};

// Submit and await the optimized plan in a single call
const result = await client.fieldService.startAndWaitForCompletion(request);

for (const vehicle of result.vehicles) {
  console.log(`Vehicle ${vehicle.id}: ${vehicle.visits.length} visits`);
}

Import PlanSolveClient as a named export, not a default.

Solvers

One client, three solvers, all sharing the same submit, poll, result workflow:

| Solver | Accessor | Use for | |--------|----------|---------| | Field Service | client.fieldService | Vehicle routing with travel time, time windows, and skills | | Professional Services | client.professionalServices | Task assignment by skill, availability, priority, and deadlines | | Shift | client.shift | Shift scheduling across contracts, availability, and fairness |

Each accessor exposes the same async methods:

| Method | HTTP | Description | |--------|------|-------------| | start(request) | POST /api/v1/{solver} | Submit a solve; resolves to a response with jobId. | | getStatus(jobId) | GET /api/v1/{solver}/{jobId}/status | Point-in-time solver status. | | getResult(jobId) | GET /api/v1/{solver}/{jobId} | The solved plan, with jobId stamped on it. | | stop(jobId) | DELETE /api/v1/{solver}/{jobId} | Stop a running solve and return the best solution found so far (same shape as getResult). | | analyze(jobId) | GET /api/v1/{solver}/{jobId}/analyze | Constraint analysis (score, per-constraint score and matches) as raw JSON. | | waitForCompletion(jobId, pollIntervalMs?, maxAttempts?) | | Poll until the job is done, then return getResult. | | startAndWaitForCompletion(request, pollIntervalMs?, maxAttempts?) | | start followed by waitForCompletion. |

{solver} is fieldservice, professionalservices or shift.

const { jobId } = await client.shift.start(request);
// ... later, if you do not want to wait any longer:
const best = await client.shift.stop(jobId);
const analysis = await client.shift.analyze(jobId);

Score

Score is an exported interface with hard, medium and soft levels (number); a solution is feasible when hard >= 0. parseScore and formatScore are exported for converting to and from the canonical "Xhard/Ymedium/Zsoft" string.

Every Score this SDK hands back (i.e. anything produced by parseScore, including the score field on result and status responses) carries a hidden toJSON(), so JSON.stringify on a response serializes its score as that canonical string, e.g. "score":"0hard/0medium/-5soft" — never the raw {hard, medium, soft} object. That means a response you cache to disk or log as JSON can be read back with JSON.parse and fed straight back through the client (or parseScore directly) without throwing. score.hard/.medium/.soft still read normally, and Object.keys/spreading a score only ever shows those three fields — the toJSON is non-enumerable. A plain { hard, medium, soft } object literal you construct yourself still satisfies the Score type and reads back the same way; it just won't serialize to the canonical string, since it never went through parseScore.

Score fields on the result and status responses are optional (score?: Score): undefined while a job is still solving, and also for a job the solver has not scored yet.

const result = await client.fieldService.getResult(jobId);
if (result.score) {
  console.log(`${formatScore(result.score)} (feasible: ${result.score.hard >= 0})`);
}

Levels are number, not bigint, so values beyond 2^53 would lose precision — not reachable in practice. scoreString no longer exists on the Shift and Professional Services result responses; use score instead.

Polling

waitForCompletion and startAndWaitForCompletion wait pollIntervalMs before each status check and give up after maxAttempts checks. The defaults are the same for every solver: 5000 ms x 150 attempts (12.5 minutes, which covers the server's 10-minute solve cap). Values that are omitted or <= 0 fall back to these defaults.

A job is complete when its status reports solving: false and solverStatus: "NOT_SOLVING"; a score is not required.

Configuration

Pass your API key to the constructor: new PlanSolveClient("..."). It is sent as the X-API-KEY header on every request.

Error handling

Every failure throws a plain Error; the SDK never writes to the console.

  • HTTP errors (any non-2xx response, from any method): API error: status <code>: <message>, where the message comes from the API's error body (field validation messages first, then error, detail or title). Example: API error: status 400: vehicles: At least one vehicle is required. A solve that fails on the server makes the status endpoint answer 422, so waitForCompletion rejects with API error: status 422: ....
  • Timeout (waitForCompletion / startAndWaitForCompletion): Solver still running after <maxAttempts> polls; raise maxAttempts or lower options.spentLimit.
  • Empty job id (waitForCompletion): JobId was not returned from waitForCompletion.
try {
  const result = await client.fieldService.startAndWaitForCompletion(request);
} catch (e) {
  console.error(e.message);
}

Documentation

Full guides, per-solver data models, and parameter reference live on the docs site:

  • Field Service: https://getplansolve.com/docs/fieldservice/sdk/javascript
  • Professional Services: https://getplansolve.com/docs/professionalservices/sdk/javascript
  • Shift: https://getplansolve.com/docs/shiftsolver/sdk/javascript

Package: npm

License

Apache-2.0. See LICENSE.