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 🙏

© 2024 – Pkg Stats / Ryan Hefner

pijoy

v0.4.0

Published

Create standardized JSON objects for API error responses, per RFC 9457.

Downloads

23

Readme

pijoy

Create standardized Javascript objects for API error responses, per RFC 9457; which obsoletes RFC 7807

Pronounced "Pie Joy", the name is an acronym for Problem Instance Javascript Object Yo!.

Why pijoy?

  • Easily create API error responses.
  • No need to come up with your own format.
  • Get started by just passing in an HTTP status code or Error.
  • Customize with your own error instances.
  • Interoperability with other systems that use the standard.

Definitions

"problem details" - An RFC-defined JSON format that provides actionable information about errors returned from an API.

"problem instance" - A specific instance of a problem detail, guaranteed to have a status, type, and title.

RFC Explainer

If you're new to this concept, below are a few snippets from the RFC. Embedded links have been removed.

"This document defines a "problem detail" to carry machine-readable details of errors in HTTP response content to avoid the need to define new error response formats for HTTP APIs.

HTTP status codes (Section 15 of HTTP) cannot always convey enough information about errors to be helpful.

To address that shortcoming, this specification defines simple JSON and XML document formats to describe the specifics of a problem encountered -- "problem details".

For example, consider a response indicating that the client's account doesn't have enough credit. The API's designer might decide to use the 403 Forbidden status code to inform generic HTTP software (such as client libraries, caches, and proxies) of the response's general semantics. API-specific problem details (such as why the server refused the request and the applicable account balance) can be carried in the response content so that the client can act upon them appropriately (for example, triggering a transfer of more credit into the account)."

Install

npm install pijoy

Usage

The defined members of a problem detail are status, type, title, detail, and instance. The rest are extension members, whose names you define.

The Content-Type for responses must be application/problem+json; which is set in the pijoy json function, if you choose to use it.

Status code example

pijoy returns a problem instance based on the status code you pass in. Problem Details can be passed as a second argument.

import { pijoy, problem } from "pijoy"

/* Create a Problem Instance. */
const instance = pijoy(402, { balance: 30 })

/*
  instance:
  {
    status: 402,
    type: "https://www.rfc-editor.org/rfc/rfc9110#name-403-forbidden",
    title: "Forbidden",
    balance: 30
  }
*/

/* Return an 'application/problem+json' JSON response. */
return problem(instance)

Error example

pijoy returns a problem instance based on the Error you pass in.

import { pijoy, problem } from "pijoy"

class AuthError extends Error {
  constructor(...args) {
    super(...args)
    this.name = 'AuthError'
    this.details = {
      status: 401
    }
  }
}

const someFunction = () => {
  try {
    ...
    /* Something went wrong */
    throw new AuthError('Login Failed')
  } catch (err) {
    const instance = pijoy(err, { instance: `${LOGS_ORIGIN}/audit/202410120023-0203.log` })
    /*
      instance:
      {
        status: 401,
        type: "https://www.rfc-editor.org/rfc/rfc9110#name-401-unauthorized",
        title: "AuthError",
        detail: "Login Failed",
        instance: 'https://site.example/logs/audit/202410120023-0203.log'
      }
    */

    /* Return an 'application/problem+json' JSON response. */
    return problem(instance)
  }
}

Example with custom Problem Details

Use your own problem details, and we will match against them using the title. Therefore, the title of each must be unique across all errors.

/* lib/problems.js */
import { Pijoy } from "pijoy"

const problems = [
  {
    status: 402,
    type: "https://example.com/errors/lack-of-credit",
    title: "LackOfCredit",
    detail: "You do not have enough credit in your account."
  },
  {
    status: 403,
    type: "https://example.com/errors/unauthorized-account-access",
    title: "UnauthorizedAccountAccess",
    detail: "You do not have authorization to access this account."
  },
  {
    status: 403,
    type: "https://example.com/errors/unauthorized-credit",
    title: "UnauthorizedCredit",
    detail: "Credit authorization failed for payment method."
  }
]

export const Problem = new Pijoy(problems)
/* API endpoint to purchase a product. */
import { Problem } from "./lib/problems.js"
import { problem } from "pijoy"

export const POST = async ({ request }) => {
  const { data, error } = someFunctionorFetch(request)

  if (error) {
    /* Assumes `error` has a `name` property that matches the `title` of a custom error above. e.g. LackOfCredit */
    /* If passing in additional details (optional), you'll want to destucture the property that would be used for the value of the `instance` member, if it exists and is not already named "instance". */ 
    const { name, audit_log_path, ...rest } = error

    const instance = Problem.create(name, {
      instance: audit_log_path,
      ...rest
     })

    /*
      instance:
      {
        "status": 402,
        "type": "https://example.com/errors/lack-of-credit",
        "title": "LackOfCredit",
        "detail": "You do not have enough credit in your account.",
        "instance": "https://site.example/logs/audit/202410120023-0203.log",
        "balance": 30,
        "cost": 50,
        "accounts": [ "/account/12345", "/account/67890" ]
      }
    */

    /* Return an 'application/problem+json' JSON response. */
    return problem(instance)
  }
  
  ...
}

Definitions and Types

Problem Detail members

It's important to note that none of the RFC-defined members are required, but this library ensures that type, status, and title are all present within a problem instance.

The below definitions are based on the RFC, but may contain different links and slightly different verbiage.

status - A number indicating a valid HTTP status code, for this occurrence of the problem. This member is only advisory, is used for the convenience of the consumer, and if present, must be used in the actual HTTP response.

type - A string containing a URI reference that identifies the problem type. If you pass in an HTTP status code to status that isn't defined in RFC 9110, and don't also pass in this member, pijoy sets type to "about:blank".

title - A string containing a short, human-readable summary of the problem type. This member is only advisory. If you pass in a valid HTTP status code to status, that isn't defined in RFC 9110, and don't also pass in this member, pijoy sets title to "Unknown Error".

detail - A string containing a human-readable explanation specific to this occurrence of the problem. This member, if present, ought to focus on helping the client correct the problem, rather than giving debugging information.

instance - A string containing a URI reference that identifies this specific occurrence of the problem. This is typically a path to a log or other audit trail for debugging, and it is recommended to be an absolute URI.

Types

type ProblemDetail = {
  status?: number;
  type?: string;
  title?: string;
  detail?: string;
  instance?: string;
  [key:string]: any;
}

type ProblemInstance = {
  status: number;
  type: string;
  title: string;
  detail?: string;
  instance?: string;
  [key:string]: any;
}

API

problem() - Create a problem JSON Response. This includes a Content-Type header set to application/problem+json, and also a Content-Length header.

function problem(data: ProblemInstance): Response

pijoy() - Create a Problem Instance.

/* Either an HTTP status or Error must be passed in. Custom errors, extended from Error, can also be used. */
function pijoy(arg: number | Error, details?: ProblemDetail): ProblemInstance

When passing in a custom Error, pijoy recognizes the properties name, status, cause, code, and details from the error, and reflects them in the problem instance. name is used for the title of the instance. stack is ignored.

Pijoy - Create a Problem Instance factory. It's recommended to export Problem from a file, where you can import it into other files where you'll create problem instances (see real-world example further above).

class Pijoy {
  constructor(problems: ProblemDetail<{ title: string; }>[])
  create(title: string, details?: ProblemDetail): ProblemInstance
}

/* At a minimum, each problem requires a `title`. All other properties are optional. */
const problems = [
  {
    status: 402,
    type: "https://example.com/errors/lack-of-credit",
    title: "LackOfCredit",
    detail: "You do not have enough credit in your account."
  },
  {
    status: 403,
    type: "https://example.com/errors/unauthorized-account-access",
    title: "UnauthorizedAccountAccess",
    detail: "You do not have authorization to access this account."
  },
  {
    status: 403,
    title: "UnauthorizedCredit",
    detail: "Credit authorization failed for payment method."
  }
]
const Problem = new Pijoy(problems)
const instance = Problem.create('LackOfCredit')