Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Thin caller for the org-wide reusable workflow in `tselect-npm/.github`.
# Every input has a default suited to this toolchain (pnpm + tsdown + Vitest +
# Biome + tsc), so there is nothing to configure here. In particular
# `es-check-target` defaults to es2015, which is this package's declared
# emitted-syntax ceiling — unlike `thrown`, which has to override it to es2016.
#
# The `@v1` pin is deliberate and must never become `@main`: an edit upstream
# would otherwise change all seven repos' CI on their next run with no review in
# any of them. `v1` moves for fixes and backwards-compatible additions; anything
# that could turn a passing build red gets `v2` and repos migrate one at a time.

name: CI

on:
push:
branches: [main]
pull_request:

# Set here rather than in the reusable workflow: inside a reusable workflow
# `github.workflow` and `github.ref` resolve to the caller's, so every repo
# calling it would share one concurrency group and cancel each other.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
ci:
uses: tselect-npm/.github/.github/workflows/ci.yml@v1
65 changes: 65 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Thin caller for the org-wide reusable release workflow in `tselect-npm/.github`.
# Publishing happens here and nowhere else — there is no npm token on anyone's
# laptop once trusted publishing is configured for this package.
#
# **This file must stay named `publish.yml`.** npm's trusted publisher config for
# `@tselect/status-code` names one workflow file, and for a reusable workflow npm
# validates the *caller's* filename rather than the reusable one. Renaming this
# file breaks publishing with a 404 that mentions nothing about filenames.
#
# The `@v1` pin is deliberate and must never become `@main`, for the same reason
# as ci.yml: an edit upstream would otherwise change every repo's release
# behaviour with no review in any of them.

name: Publish

on:
workflow_dispatch:
inputs:
bump:
description: 'Version bump. `auto` reads it from the gitmoji since the last release.'
type: choice
options: [auto, patch, minor, major]
default: auto
dry-run:
description: 'Plan, rehearse the publish, then stop before anything is pushed or published.'
type: boolean
default: false
dist-tag:
description: 'npm dist-tag.'
type: string
default: latest
since-ref:
description: >-
Count commits from this ref instead of the tag matching the published
version. Should not be needed: unlike the other repos in this scope,
this one's v1.0.2 tag already sits on a1569df, exactly the commit the
registry reports as 1.0.2's gitHead, so the range resolves on its own.
Leave empty unless it does not.
type: string
default: ''

# A release must never be cancelled by a later one: the tag is pushed before
# `pnpm publish` runs, so an interruption in that window leaves a tagged version
# the registry does not have. Re-running recovers it, but not being interrupted
# is better. Note this differs from ci.yml, where cancelling is the point.
concurrency:
group: publish-${{ github.ref }}
cancel-in-progress: false

jobs:
publish:
permissions:
# Push the release commit and the tag to `main`.
contents: write
# Mint the OIDC token npm exchanges for publish rights. Required *here* as
# well as in the reusable workflow: a called workflow can only narrow the
# permissions it is given, so omitting this produces a job with no token
# and a 404 from the registry.
id-token: write
uses: tselect-npm/.github/.github/workflows/publish.yml@v1
with:
bump: ${{ inputs.bump }}
dry-run: ${{ inputs.dry-run }}
dist-tag: ${{ inputs.dist-tag }}
since-ref: ${{ inputs.since-ref }}
14 changes: 7 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
.idea/
.vscode/
dts/
**/*.js
node_modules/
npm-debug.log
.nyc_output/
dist/
.log*
coverage/
.nyc_output/
*.log
npm-debug.log*
.DS_Store
.idea/
.vscode/
8 changes: 0 additions & 8 deletions .npmignore

This file was deleted.

21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017-2026 Sylvain Estevez

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
177 changes: 169 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,182 @@
# StatusCode
# @tselect/status-code

[![npm](https://img.shields.io/npm/v/@tselect/status-code.svg?style=flat-square)](https://www.npmjs.com/package/@tselect/status-code)
[![npm](https://img.shields.io/npm/dm/@tselect/status-code.svg?style=flat-square)](https://www.npmjs.com/package/@tselect/status-code)
[![npm](https://img.shields.io/npm/l/@tselect/status-code.svg?style=flat-square)](https://www.npmjs.com/package/@tselect/status-code)
[![npm](https://img.shields.io/npm/dm/@tselect/status-code.svg?style=flat-square)](https://www.npmjs.com/package/@tselect/status-code)
[![CI](https://img.shields.io/github/actions/workflow/status/tselect-npm/status-code/ci.yml?branch=main&style=flat-square)](https://github.com/tselect-npm/status-code/actions/workflows/ci.yml)
[![coverage](https://img.shields.io/coverallsCoverage/github/tselect-npm/status-code?branch=main&style=flat-square)](https://coveralls.io/github/tselect-npm/status-code?branch=main)
[![license](https://img.shields.io/npm/l/@tselect/status-code.svg?style=flat-square)](./LICENSE)

Enums and utilities around status codes.
HTTP status codes utility.

Zero runtime dependencies. Ships both ESM and CommonJS builds, with TypeScript types for each.

## Requirements

**Node 22 or newer** (`engines.node` is `>=22`) — every line still receiving security support. Each release is tested on 22, 24 and 26; the declared floor is the lowest version CI actually runs, not a guess.

## Installation

`npm i @tselect/status-code`
```bash
npm i @tselect/status-code
```

```bash
pnpm add @tselect/status-code
```

## Usage

```typescript
import { StatusCode, isStatusCode, is5xx } from '@tselect/status-code';
import { is5xx, isStatusCode, StatusCode } from '@tselect/status-code';

StatusCode.OK; // 200
isStatusCode(200); // true
is5xx(StatusCode.INTERNAL_SERVER_ERROR); // true
```

Namespace imports and `require()` both work:

```typescript
import * as StatusCodes from '@tselect/status-code';
```

```javascript
const { StatusCode, is4xx } = require('@tselect/status-code');
```

## API

### `StatusCode`

A numeric enum of 43 HTTP status codes.

```typescript
StatusCode.OK; // 200
StatusCode.NOT_FOUND; // 404
StatusCode.INTERNAL_SERVER_ERROR; // 500
```

| Class | Members |
| --- | --- |
| 1xx | `CONTINUE` `SWITCHING_PROTOCOLS` |
| 2xx | `OK` `CREATED` `ACCEPTED` `NON_AUTHORITATIVE_INFORMATION` `NO_CONTENT` `RESET_CONTENT` `PARTIAL_CONTENT` `MULTI_STATUS` |
| 3xx | `MULTIPLE_CHOICES` `MOVED_PERMANENTLY` `MOVED_TEMPORARILY` `SEE_OTHER` `NOT_MODIFIED` `USE_PROXY` |
| 4xx | `BAD_REQUEST` `UNAUTHORIZED` `PAYMENT_REQUIRED` `FORBIDDEN` `NOT_FOUND` `METHOD_NOT_ALLOWED` `NOT_ACCEPTABLE` `PROXY_AUTHENTICATION_REQUIRED` `REQUEST_TIME_OUT` `CONFLICT` `GONE` `LENGTH_REQUIRED` `PRECONDITION_FAILED` `REQUEST_ENTITY_TOO_LARGE` `REQUEST_URI_TOO_LONG` `UNSUPPORTED_MEDIA_TYPE` `REQUEST_RANGE_UNSATISFIABLE` `EXPECTATION_FAILED` `UNPROCESSABLE_ENTITY` `TOO_MANY_REQUESTS` `UNAVAILABLE_FOR_LEGAL_REASONS` |
| 5xx | `INTERNAL_SERVER_ERROR` `NOT_IMPLEMENTED` `BAD_GATEWAY` `SERVICE_UNAVAILABLE` `GATEWAY_TIME_OUT` `HTTP_VERSION_NOT_SUPPORTED` |

### `isStatusCode(value: unknown): value is StatusCode`

Type guard. Returns `true` when `value` is one of the enum's members.

```typescript
isStatusCode(200); // true
isStatusCode(2000); // false
```

⚠️ **It also returns `true` for a member's *name*.** `StatusCode` is a numeric enum, so its runtime object carries a reverse mapping and holds the 43 names alongside the 43 numbers:

```typescript
isStatusCode('OK'); // true — not a typo
```

This has been the behaviour since 1.0.0 and is preserved deliberately. Narrow with `typeof value === 'number' && isStatusCode(value)` if you need numbers only.

### `is1xx(code: StatusCode): boolean`

Returns `true` for an informational status code. Returns `false` for anything that is not a status code at all.

```typescript
is1xx(StatusCode.CONTINUE); // true
is1xx(StatusCode.CONFLICT); // false
is1xx(1000 as StatusCode); // false
```

### `is2xx(code: StatusCode): boolean`

Returns `true` for a success status code.

```typescript
is2xx(StatusCode.OK); // true
is2xx(StatusCode.CONFLICT); // false
```

### `is3xx(code: StatusCode): boolean`

Returns `true` for a redirection status code.

StatusCode.OK; // 200
isStatusCode(200); // true
```typescript
is3xx(StatusCode.MOVED_PERMANENTLY); // true
is3xx(StatusCode.CONFLICT); // false
```

### `is4xx(code: StatusCode): boolean`

Returns `true` for a client error status code.

```typescript
is4xx(StatusCode.CONFLICT); // true
is4xx(StatusCode.INTERNAL_SERVER_ERROR); // false
```

### `is5xx(code: StatusCode): boolean`

Returns `true` for a server error status code.

```typescript
is5xx(StatusCode.INTERNAL_SERVER_ERROR); // true
is5xx(StatusCode.CONFLICT); // false
```

### `isErrorStatusCode(code: StatusCode): boolean`

Returns `true` for any status code at or above 400, i.e. 4xx and 5xx together.

```typescript
isErrorStatusCode(StatusCode.CONFLICT); // true
isErrorStatusCode(StatusCode.OK); // false
```

### `isNonErrorStatusCode(code: StatusCode): boolean`

The complement of `isErrorStatusCode` over valid status codes. Note that a value which is not a status code at all is neither, so both return `false` for it.

```typescript
isNonErrorStatusCode(StatusCode.OK); // true
isNonErrorStatusCode(StatusCode.BAD_REQUEST); // false
isNonErrorStatusCode(4000 as StatusCode); // false
```

### `isServerErrorStatusCode(code: StatusCode): boolean`

Alias of `is5xx`.

```typescript
isServerErrorStatusCode(StatusCode.INTERNAL_SERVER_ERROR); // true
```

### `isConsumerErrorStatusCode(code: StatusCode): boolean`

Alias of `is4xx`.

```typescript
isConsumerErrorStatusCode(StatusCode.CONFLICT); // true
```

### `toString(code: StatusCode): string | null`

Returns the enum member's name for a code, or `null` when the code is not in the enum.

```typescript
toString(StatusCode.CONFLICT); // 'CONFLICT'
toString(9999 as StatusCode); // null
```

⚠️ Because of the same reverse mapping described under `isStatusCode`, passing a member *name* returns the code as a string:

```typescript
toString('OK' as unknown as StatusCode); // '200'
```

## License

[MIT](./LICENSE) © Sylvain Estevez
40 changes: 40 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.11/schema.json",
"files": {
"includes": ["**", "!dist", "!coverage"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 120,
"includes": ["**", "!package.json"]
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "always",
"trailingCommas": "all"
}
},
"linter": {
"enabled": true,
"rules": {
"preset": "recommended",
"complexity": {
"noArguments": "error"
},
"suspicious": {
"noConsole": { "level": "error", "options": { "allow": ["warn", "error"] } },
"noDoubleEquals": "error"
}
}
},
"assist": {
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}
Loading