fetch-status-codes 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,22 @@ All notable changes to this project will be documented in this file.
6
6
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
7
7
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ [0.2.0] - 2025-12-23
10
+ --------------------
11
+
12
+ ### Added
13
+
14
+ - A `fetchStatusCodeClasses` function, to fetch the HTTP status code classes.
15
+
16
+ ### Changed
17
+
18
+ - Upgraded [jsdom](https://github.com/jsdom/jsdom#readme) from v27.2.0 to v27.3.0.
19
+
20
+ ### Fixed
21
+
22
+ - Redirect resolution. Previously it was only being done on 301 status codes,
23
+ now all 3xx responses with a location header are resolved.
24
+
9
25
  [0.1.1] - 2025-11-26
10
26
  --------------------
11
27
 
@@ -20,5 +36,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
36
 
21
37
  - Initial release.
22
38
 
39
+ [0.2.0]: https://github.com/jbenner-radham/node-fetch-status-codes/compare/v0.1.1...v0.2.0
23
40
  [0.1.1]: https://github.com/jbenner-radham/node-fetch-status-codes/compare/v0.1.0...v0.1.1
24
41
  [0.1.0]: https://github.com/jbenner-radham/node-fetch-status-codes/releases/tag/v0.1.0
package/README.md CHANGED
@@ -14,7 +14,7 @@ Usage
14
14
  -----
15
15
 
16
16
  ```typescript
17
- import fetchStatusCodes from 'fetch-status-codes';
17
+ import fetchStatusCodes, { fetchStatusCodeClasses } from 'fetch-status-codes';
18
18
 
19
19
  await fetchStatusCodes();
20
20
  // >>> [
@@ -45,6 +45,16 @@ await fetchStatusCodes({ resolveRedirects: false });
45
45
  // >>> },
46
46
  // >>> ...
47
47
  // >>> ]
48
+
49
+ await fetchStatusCodeClasses();
50
+ // >>> [
51
+ // >>> {
52
+ // >>> value: '1xx',
53
+ // >>> name: 'Informational',
54
+ // >>> description: 'Request received, continuing process'
55
+ // >>> },
56
+ // >>> ...
57
+ // >>> ]
48
58
  ```
49
59
 
50
60
  License
package/dist/cjs/index.js CHANGED
@@ -1,2 +1,3 @@
1
- "use strict";var c=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var x=(n,t)=>{for(var o in t)c(n,o,{get:t[o],enumerable:!0})},C=(n,t,o,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of g(t))!h.call(n,r)&&r!==o&&c(n,r,{get:()=>t[r],enumerable:!(a=y(t,r))||a.enumerable});return n};var S=n=>C(c({},"__esModule",{value:!0}),n);var A={};x(A,{default:()=>u});module.exports=S(A);var m=require("jsdom");async function u({resolveRedirects:n=!0}={}){let a=await(await fetch("https://www.iana.org/assignments/http-status-codes")).text(),d=new m.JSDOM(a).window.document.getElementById("table-http-status-codes-1"),f=Array.from(d.querySelectorAll("tbody > tr")),p=async e=>{if(!n)return e.href;let s=await fetch(e.href,{method:"HEAD",redirect:"manual"}),i=new URL(s.status===301?s.headers.get("Location"):e.href),w=/RFC\d+(?:, Section )?((?:\d+)?(?:.\d+){0,2})/,[,l]=w.exec(e.textContent)??[];return l&&(i.hash=`section-${l}`),i.toString()};return Promise.all(f.map(e=>Array.from(e.querySelectorAll("td"))).filter(e=>e.at(1).textContent!=="Unassigned").map(async e=>({value:Number.parseInt(e.at(0).textContent),description:e.at(1).textContent,references:await Promise.all(Array.from(e.at(2).querySelectorAll("a")).map(async s=>({name:s.textContent,url:await p(s)})))})))}
1
+ "use strict";var i=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var g=(e,n)=>{for(var r in n)i(e,r,{get:n[r],enumerable:!0})},x=(e,n,r,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let s of y(n))!w.call(e,s)&&s!==r&&i(e,s,{get:()=>n[s],enumerable:!(o=f(n,s))||o.enumerable});return e};var h=e=>x(i({},"__esModule",{value:!0}),e);var A={};g(A,{default:()=>d,fetchStatusCodeClasses:()=>S});module.exports=h(A);var l=require("jsdom");function C(e){try{return e()}catch{}}async function m(){let r=await(await fetch("https://www.iana.org/assignments/http-status-codes")).text();return new l.JSDOM(r)}async function d({resolveRedirects:e=!0}={}){let r=(await m()).window.document.getElementById("table-http-status-codes-1"),o=Array.from(r.querySelectorAll("tbody > tr")),s=async t=>{if(!e)return t.href;let a=await fetch(t.href,{method:"HEAD",redirect:"manual"}),c=C(()=>new URL(a.status.toString().startsWith("3")&&a.headers.has("Location")?a.headers.get("Location"):t.href))??new URL(t.href),p=/RFC\d+(?:, Section )?((?:\d+)?(?:.\d+){0,2})/,[,u]=p.exec(t.textContent)??[];return u&&(c.hash=`section-${u}`),c.toString()};return Promise.all(o.map(t=>Array.from(t.querySelectorAll("td"))).filter(t=>t.at(1).textContent!=="Unassigned").map(async t=>({value:Number.parseInt(t.at(0).textContent),description:t.at(1).textContent,references:await Promise.all(Array.from(t.at(2).querySelectorAll("a")).map(async a=>({name:a.textContent,url:await s(a)})))})))}async function S(){let e=await m(),n=Array.from(e.window.document.querySelectorAll("dt")).find(o=>o.textContent.trim()==="Note")?.nextElementSibling.textContent.trim(),r=/^(?<value>\dxx): (?<name>[ A-Za-z]+) - (?<description>[ ,A-Za-z]+)$/;return n?.split(`
2
+ `).map(o=>{let{value:s="",name:t="",description:a=""}=r.exec(o)?.groups??{};return{value:s,name:t,description:a}})??[]}0&&(module.exports={fetchStatusCodeClasses});
2
3
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import { JSDOM } from 'jsdom';\n\nexport type Reference = { name: string; url: string };\n\nexport type StatusCode = { value: number; description: string; references: Reference[] };\n\nexport default async function fetchStatusCodes({ resolveRedirects = true }: {\n resolveRedirects?: boolean;\n} = {}): Promise<StatusCode[]> {\n const url = 'https://www.iana.org/assignments/http-status-codes';\n const response = await fetch(url);\n const html = await response.text();\n const dom = new JSDOM(html);\n const table = dom.window.document.getElementById('table-http-status-codes-1')!;\n const rows = Array.from(table.querySelectorAll<HTMLTableRowElement>('tbody > tr'));\n const getReferenceLink = async (anchor: HTMLAnchorElement): Promise<string> => {\n if (!resolveRedirects) {\n return anchor.href;\n }\n\n // The IANA links are redirects, but we want direct links with a section hash if applicable.\n const response = await fetch(anchor.href, { method: 'HEAD', redirect: 'manual' });\n const url = new URL(response.status === 301 ? response.headers.get('Location')! : anchor.href);\n const pattern = /RFC\\d+(?:, Section )?((?:\\d+)?(?:.\\d+){0,2})/;\n const [, section] = pattern.exec(anchor.textContent) ?? [];\n\n if (section) {\n url.hash = `section-${section}`;\n }\n\n return url.toString();\n };\n\n return Promise.all(rows.map(row => Array.from(row.querySelectorAll('td')))\n .filter(cells => cells.at(1)!.textContent !== 'Unassigned')\n .map(async cells => ({\n value: Number.parseInt(cells.at(0)!.textContent),\n description: cells.at(1)!.textContent,\n references: await Promise.all(Array.from(cells.at(2)!.querySelectorAll('a'))\n .map(async anchor =>\n ({ name: anchor.textContent!, url: await getReferenceLink(anchor) })\n )\n )\n })));\n}\n"],
5
- "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,IAAA,eAAAC,EAAAH,GAAA,IAAAI,EAAsB,iBAMtB,eAAOF,EAAwC,CAAE,iBAAAG,EAAmB,EAAK,EAErE,CAAC,EAA0B,CAG7B,IAAMC,EAAO,MADI,MAAM,MADX,oDACoB,GACJ,KAAK,EAE3BC,EADM,IAAI,QAAMD,CAAI,EACR,OAAO,SAAS,eAAe,2BAA2B,EACtEE,EAAO,MAAM,KAAKD,EAAM,iBAAsC,YAAY,CAAC,EAC3EE,EAAmB,MAAOC,GAA+C,CAC7E,GAAI,CAACL,EACH,OAAOK,EAAO,KAIhB,IAAMC,EAAW,MAAM,MAAMD,EAAO,KAAM,CAAE,OAAQ,OAAQ,SAAU,QAAS,CAAC,EAC1EE,EAAM,IAAI,IAAID,EAAS,SAAW,IAAMA,EAAS,QAAQ,IAAI,UAAU,EAAKD,EAAO,IAAI,EACvFG,EAAU,+CACV,CAAC,CAAEC,CAAO,EAAID,EAAQ,KAAKH,EAAO,WAAW,GAAK,CAAC,EAEzD,OAAII,IACFF,EAAI,KAAO,WAAWE,CAAO,IAGxBF,EAAI,SAAS,CACtB,EAEA,OAAO,QAAQ,IAAIJ,EAAK,IAAIO,GAAO,MAAM,KAAKA,EAAI,iBAAiB,IAAI,CAAC,CAAC,EACtE,OAAOC,GAASA,EAAM,GAAG,CAAC,EAAG,cAAgB,YAAY,EACzD,IAAI,MAAMA,IAAU,CACnB,MAAO,OAAO,SAASA,EAAM,GAAG,CAAC,EAAG,WAAW,EAC/C,YAAaA,EAAM,GAAG,CAAC,EAAG,YAC1B,WAAY,MAAM,QAAQ,IAAI,MAAM,KAAKA,EAAM,GAAG,CAAC,EAAG,iBAAiB,GAAG,CAAC,EACxE,IAAI,MAAMN,IACR,CAAE,KAAMA,EAAO,YAAc,IAAK,MAAMD,EAAiBC,CAAM,CAAE,EACpE,CACF,CACF,EAAE,CAAC,CACP",
6
- "names": ["index_exports", "__export", "fetchStatusCodes", "__toCommonJS", "import_jsdom", "resolveRedirects", "html", "table", "rows", "getReferenceLink", "anchor", "response", "url", "pattern", "section", "row", "cells"]
4
+ "sourcesContent": ["import { JSDOM } from 'jsdom';\n\nexport type Reference = { name: string; url: string };\n\nexport type StatusCode = { value: number; description: string; references: Reference[] };\n\nexport type StatusCodeClass = { value: string; name: string; description: string };\n\nfunction attempt<T>(callback: () => T): T | undefined {\n try {\n return callback();\n } catch { /* Do nothing. */ }\n}\n\nasync function fetchRegistryDom(): Promise<JSDOM> {\n const url = 'https://www.iana.org/assignments/http-status-codes';\n const response = await fetch(url);\n const html = await response.text();\n\n return new JSDOM(html);\n}\n\nexport default async function fetchStatusCodes({ resolveRedirects = true }: {\n resolveRedirects?: boolean;\n} = {}): Promise<StatusCode[]> {\n const dom = await fetchRegistryDom();\n const table = dom.window.document.getElementById('table-http-status-codes-1')!;\n const rows = Array.from(table.querySelectorAll<HTMLTableRowElement>('tbody > tr'));\n const getReferenceLink = async (anchor: HTMLAnchorElement): Promise<string> => {\n if (!resolveRedirects) {\n return anchor.href;\n }\n\n // The IANA links are redirects, but we want direct links with a section hash if applicable.\n const response = await fetch(anchor.href, { method: 'HEAD', redirect: 'manual' });\n\n // The location header returned by some ietf.org pages is relative\n // (e.g., /doc/status-change-http-experiments-to-historic/) so check to make\n // sure the location is a valid URL and fall back to the anchor href if not.\n const url = attempt(\n () => new URL(\n response.status.toString().startsWith('3') && response.headers.has('Location')\n ? response.headers.get('Location')!\n : anchor.href\n )\n ) ?? new URL(anchor.href);\n const pattern = /RFC\\d+(?:, Section )?((?:\\d+)?(?:.\\d+){0,2})/;\n const [, section] = pattern.exec(anchor.textContent) ?? [];\n\n if (section) {\n url.hash = `section-${section}`;\n }\n\n return url.toString();\n };\n\n return Promise.all(rows.map(row => Array.from(row.querySelectorAll('td')))\n .filter(cells => cells.at(1)!.textContent !== 'Unassigned')\n .map(async cells => ({\n value: Number.parseInt(cells.at(0)!.textContent),\n description: cells.at(1)!.textContent,\n references: await Promise.all(Array.from(cells.at(2)!.querySelectorAll('a'))\n .map(async anchor =>\n ({ name: anchor.textContent!, url: await getReferenceLink(anchor) })\n )\n )\n })));\n}\n\nexport async function fetchStatusCodeClasses(): Promise<StatusCodeClass[]> {\n const dom = await fetchRegistryDom();\n const note = Array.from(dom.window.document.querySelectorAll('dt'))\n .find(element => element.textContent.trim() === 'Note')\n ?.nextElementSibling!.textContent.trim();\n const pattern = /^(?<value>\\dxx): (?<name>[ A-Za-z]+) - (?<description>[ ,A-Za-z]+)$/;\n\n return note?.split('\\n').map(line => {\n const { value = '', name = '', description = '' } = pattern.exec(line)?.groups ?? {};\n\n return { value, name, description };\n }) ?? [];\n}\n"],
5
+ "mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,2BAAAC,IAAA,eAAAC,EAAAJ,GAAA,IAAAK,EAAsB,iBAQtB,SAASC,EAAWC,EAAkC,CACpD,GAAI,CACF,OAAOA,EAAS,CAClB,MAAQ,CAAoB,CAC9B,CAEA,eAAeC,GAAmC,CAGhD,IAAMC,EAAO,MADI,MAAM,MADX,oDACoB,GACJ,KAAK,EAEjC,OAAO,IAAI,QAAMA,CAAI,CACvB,CAEA,eAAOP,EAAwC,CAAE,iBAAAQ,EAAmB,EAAK,EAErE,CAAC,EAA0B,CAE7B,IAAMC,GADM,MAAMH,EAAiB,GACjB,OAAO,SAAS,eAAe,2BAA2B,EACtEI,EAAO,MAAM,KAAKD,EAAM,iBAAsC,YAAY,CAAC,EAC3EE,EAAmB,MAAOC,GAA+C,CAC7E,GAAI,CAACJ,EACH,OAAOI,EAAO,KAIhB,IAAMC,EAAW,MAAM,MAAMD,EAAO,KAAM,CAAE,OAAQ,OAAQ,SAAU,QAAS,CAAC,EAK1EE,EAAMV,EACV,IAAM,IAAI,IACRS,EAAS,OAAO,SAAS,EAAE,WAAW,GAAG,GAAKA,EAAS,QAAQ,IAAI,UAAU,EACzEA,EAAS,QAAQ,IAAI,UAAU,EAC/BD,EAAO,IACb,CACF,GAAK,IAAI,IAAIA,EAAO,IAAI,EAClBG,EAAU,+CACV,CAAC,CAAEC,CAAO,EAAID,EAAQ,KAAKH,EAAO,WAAW,GAAK,CAAC,EAEzD,OAAII,IACFF,EAAI,KAAO,WAAWE,CAAO,IAGxBF,EAAI,SAAS,CACtB,EAEA,OAAO,QAAQ,IAAIJ,EAAK,IAAIO,GAAO,MAAM,KAAKA,EAAI,iBAAiB,IAAI,CAAC,CAAC,EACtE,OAAOC,GAASA,EAAM,GAAG,CAAC,EAAG,cAAgB,YAAY,EACzD,IAAI,MAAMA,IAAU,CACnB,MAAO,OAAO,SAASA,EAAM,GAAG,CAAC,EAAG,WAAW,EAC/C,YAAaA,EAAM,GAAG,CAAC,EAAG,YAC1B,WAAY,MAAM,QAAQ,IAAI,MAAM,KAAKA,EAAM,GAAG,CAAC,EAAG,iBAAiB,GAAG,CAAC,EACxE,IAAI,MAAMN,IACR,CAAE,KAAMA,EAAO,YAAc,IAAK,MAAMD,EAAiBC,CAAM,CAAE,EACpE,CACF,CACF,EAAE,CAAC,CACP,CAEA,eAAsBX,GAAqD,CACzE,IAAMkB,EAAM,MAAMb,EAAiB,EAC7Bc,EAAO,MAAM,KAAKD,EAAI,OAAO,SAAS,iBAAiB,IAAI,CAAC,EAC/D,KAAKE,GAAWA,EAAQ,YAAY,KAAK,IAAM,MAAM,GACpD,mBAAoB,YAAY,KAAK,EACnCN,EAAU,sEAEhB,OAAOK,GAAM,MAAM;AAAA,CAAI,EAAE,IAAIE,GAAQ,CACnC,GAAM,CAAE,MAAAC,EAAQ,GAAI,KAAAC,EAAO,GAAI,YAAAC,EAAc,EAAG,EAAIV,EAAQ,KAAKO,CAAI,GAAG,QAAU,CAAC,EAEnF,MAAO,CAAE,MAAAC,EAAO,KAAAC,EAAM,YAAAC,CAAY,CACpC,CAAC,GAAK,CAAC,CACT",
6
+ "names": ["index_exports", "__export", "fetchStatusCodes", "fetchStatusCodeClasses", "__toCommonJS", "import_jsdom", "attempt", "callback", "fetchRegistryDom", "html", "resolveRedirects", "table", "rows", "getReferenceLink", "anchor", "response", "url", "pattern", "section", "row", "cells", "dom", "note", "element", "line", "value", "name", "description"]
7
7
  }
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "fetch-status-codes",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Fetch the HTTP status codes from the IANA registry.",
5
5
  "keywords": [
6
+ "class",
6
7
  "code",
7
8
  "fetch",
8
9
  "http",
@@ -25,26 +26,27 @@
25
26
  "exports": "./index.js",
26
27
  "types": "../types/index.d.ts",
27
28
  "dependencies": {
28
- "jsdom": "^27.2.0"
29
+ "jsdom": "^27.3.0"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@eslint/compat": "^2.0.0",
32
33
  "@eslint/json": "^0.14.0",
33
- "@radham/eslint-config": "^11.0.0",
34
+ "@radham/changelog": "^0.1.0",
35
+ "@radham/eslint-config": "^12.0.0",
34
36
  "@types/jsdom": "^27.0.0",
35
37
  "@types/node": "^22.18.8",
36
- "@vitest/coverage-v8": "^4.0.14",
38
+ "@vitest/coverage-v8": "^4.0.16",
37
39
  "duo-build": "^0.2.1",
38
- "eslint": "^9.39.1",
40
+ "eslint": "^9.39.2",
39
41
  "globals": "^16.5.0",
40
42
  "husky": "^9.1.7",
41
43
  "lint-staged": "^16.2.7",
42
44
  "rimraf": "^6.1.2",
43
- "sort-package-json": "^3.4.0",
45
+ "sort-package-json": "^3.6.0",
44
46
  "typescript": "^5.9.3",
45
- "vitest": "^4.0.14"
47
+ "vitest": "^4.0.16"
46
48
  },
47
- "packageManager": "pnpm@10.23.0",
49
+ "packageManager": "pnpm@10.26.2",
48
50
  "engines": {
49
51
  "node": ">=22"
50
52
  }
package/dist/esm/index.js CHANGED
@@ -1,2 +1,3 @@
1
- import{JSDOM as m}from"jsdom";async function u({resolveRedirects:o=!0}={}){let s=await(await fetch("https://www.iana.org/assignments/http-status-codes")).text(),a=new m(s).window.document.getElementById("table-http-status-codes-1"),c=Array.from(a.querySelectorAll("tbody > tr")),i=async e=>{if(!o)return e.href;let t=await fetch(e.href,{method:"HEAD",redirect:"manual"}),n=new URL(t.status===301?t.headers.get("Location"):e.href),l=/RFC\d+(?:, Section )?((?:\d+)?(?:.\d+){0,2})/,[,r]=l.exec(e.textContent)??[];return r&&(n.hash=`section-${r}`),n.toString()};return Promise.all(c.map(e=>Array.from(e.querySelectorAll("td"))).filter(e=>e.at(1).textContent!=="Unassigned").map(async e=>({value:Number.parseInt(e.at(0).textContent),description:e.at(1).textContent,references:await Promise.all(Array.from(e.at(2).querySelectorAll("a")).map(async t=>({name:t.textContent,url:await i(t)})))})))}export{u as default};
1
+ import{JSDOM as m}from"jsdom";function d(n){try{return n()}catch{}}async function u(){let r=await(await fetch("https://www.iana.org/assignments/http-status-codes")).text();return new m(r)}async function p({resolveRedirects:n=!0}={}){let r=(await u()).window.document.getElementById("table-http-status-codes-1"),s=Array.from(r.querySelectorAll("tbody > tr")),a=async t=>{if(!n)return t.href;let e=await fetch(t.href,{method:"HEAD",redirect:"manual"}),i=d(()=>new URL(e.status.toString().startsWith("3")&&e.headers.has("Location")?e.headers.get("Location"):t.href))??new URL(t.href),l=/RFC\d+(?:, Section )?((?:\d+)?(?:.\d+){0,2})/,[,c]=l.exec(t.textContent)??[];return c&&(i.hash=`section-${c}`),i.toString()};return Promise.all(s.map(t=>Array.from(t.querySelectorAll("td"))).filter(t=>t.at(1).textContent!=="Unassigned").map(async t=>({value:Number.parseInt(t.at(0).textContent),description:t.at(1).textContent,references:await Promise.all(Array.from(t.at(2).querySelectorAll("a")).map(async e=>({name:e.textContent,url:await a(e)})))})))}async function y(){let n=await u(),o=Array.from(n.window.document.querySelectorAll("dt")).find(s=>s.textContent.trim()==="Note")?.nextElementSibling.textContent.trim(),r=/^(?<value>\dxx): (?<name>[ A-Za-z]+) - (?<description>[ ,A-Za-z]+)$/;return o?.split(`
2
+ `).map(s=>{let{value:a="",name:t="",description:e=""}=r.exec(s)?.groups??{};return{value:a,name:t,description:e}})??[]}export{p as default,y as fetchStatusCodeClasses};
2
3
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/index.ts"],
4
- "sourcesContent": ["import { JSDOM } from 'jsdom';\n\nexport type Reference = { name: string; url: string };\n\nexport type StatusCode = { value: number; description: string; references: Reference[] };\n\nexport default async function fetchStatusCodes({ resolveRedirects = true }: {\n resolveRedirects?: boolean;\n} = {}): Promise<StatusCode[]> {\n const url = 'https://www.iana.org/assignments/http-status-codes';\n const response = await fetch(url);\n const html = await response.text();\n const dom = new JSDOM(html);\n const table = dom.window.document.getElementById('table-http-status-codes-1')!;\n const rows = Array.from(table.querySelectorAll<HTMLTableRowElement>('tbody > tr'));\n const getReferenceLink = async (anchor: HTMLAnchorElement): Promise<string> => {\n if (!resolveRedirects) {\n return anchor.href;\n }\n\n // The IANA links are redirects, but we want direct links with a section hash if applicable.\n const response = await fetch(anchor.href, { method: 'HEAD', redirect: 'manual' });\n const url = new URL(response.status === 301 ? response.headers.get('Location')! : anchor.href);\n const pattern = /RFC\\d+(?:, Section )?((?:\\d+)?(?:.\\d+){0,2})/;\n const [, section] = pattern.exec(anchor.textContent) ?? [];\n\n if (section) {\n url.hash = `section-${section}`;\n }\n\n return url.toString();\n };\n\n return Promise.all(rows.map(row => Array.from(row.querySelectorAll('td')))\n .filter(cells => cells.at(1)!.textContent !== 'Unassigned')\n .map(async cells => ({\n value: Number.parseInt(cells.at(0)!.textContent),\n description: cells.at(1)!.textContent,\n references: await Promise.all(Array.from(cells.at(2)!.querySelectorAll('a'))\n .map(async anchor =>\n ({ name: anchor.textContent!, url: await getReferenceLink(anchor) })\n )\n )\n })));\n}\n"],
5
- "mappings": "AAAA,OAAS,SAAAA,MAAa,QAMtB,eAAOC,EAAwC,CAAE,iBAAAC,EAAmB,EAAK,EAErE,CAAC,EAA0B,CAG7B,IAAMC,EAAO,MADI,MAAM,MADX,oDACoB,GACJ,KAAK,EAE3BC,EADM,IAAIJ,EAAMG,CAAI,EACR,OAAO,SAAS,eAAe,2BAA2B,EACtEE,EAAO,MAAM,KAAKD,EAAM,iBAAsC,YAAY,CAAC,EAC3EE,EAAmB,MAAOC,GAA+C,CAC7E,GAAI,CAACL,EACH,OAAOK,EAAO,KAIhB,IAAMC,EAAW,MAAM,MAAMD,EAAO,KAAM,CAAE,OAAQ,OAAQ,SAAU,QAAS,CAAC,EAC1EE,EAAM,IAAI,IAAID,EAAS,SAAW,IAAMA,EAAS,QAAQ,IAAI,UAAU,EAAKD,EAAO,IAAI,EACvFG,EAAU,+CACV,CAAC,CAAEC,CAAO,EAAID,EAAQ,KAAKH,EAAO,WAAW,GAAK,CAAC,EAEzD,OAAII,IACFF,EAAI,KAAO,WAAWE,CAAO,IAGxBF,EAAI,SAAS,CACtB,EAEA,OAAO,QAAQ,IAAIJ,EAAK,IAAIO,GAAO,MAAM,KAAKA,EAAI,iBAAiB,IAAI,CAAC,CAAC,EACtE,OAAOC,GAASA,EAAM,GAAG,CAAC,EAAG,cAAgB,YAAY,EACzD,IAAI,MAAMA,IAAU,CACnB,MAAO,OAAO,SAASA,EAAM,GAAG,CAAC,EAAG,WAAW,EAC/C,YAAaA,EAAM,GAAG,CAAC,EAAG,YAC1B,WAAY,MAAM,QAAQ,IAAI,MAAM,KAAKA,EAAM,GAAG,CAAC,EAAG,iBAAiB,GAAG,CAAC,EACxE,IAAI,MAAMN,IACR,CAAE,KAAMA,EAAO,YAAc,IAAK,MAAMD,EAAiBC,CAAM,CAAE,EACpE,CACF,CACF,EAAE,CAAC,CACP",
6
- "names": ["JSDOM", "fetchStatusCodes", "resolveRedirects", "html", "table", "rows", "getReferenceLink", "anchor", "response", "url", "pattern", "section", "row", "cells"]
4
+ "sourcesContent": ["import { JSDOM } from 'jsdom';\n\nexport type Reference = { name: string; url: string };\n\nexport type StatusCode = { value: number; description: string; references: Reference[] };\n\nexport type StatusCodeClass = { value: string; name: string; description: string };\n\nfunction attempt<T>(callback: () => T): T | undefined {\n try {\n return callback();\n } catch { /* Do nothing. */ }\n}\n\nasync function fetchRegistryDom(): Promise<JSDOM> {\n const url = 'https://www.iana.org/assignments/http-status-codes';\n const response = await fetch(url);\n const html = await response.text();\n\n return new JSDOM(html);\n}\n\nexport default async function fetchStatusCodes({ resolveRedirects = true }: {\n resolveRedirects?: boolean;\n} = {}): Promise<StatusCode[]> {\n const dom = await fetchRegistryDom();\n const table = dom.window.document.getElementById('table-http-status-codes-1')!;\n const rows = Array.from(table.querySelectorAll<HTMLTableRowElement>('tbody > tr'));\n const getReferenceLink = async (anchor: HTMLAnchorElement): Promise<string> => {\n if (!resolveRedirects) {\n return anchor.href;\n }\n\n // The IANA links are redirects, but we want direct links with a section hash if applicable.\n const response = await fetch(anchor.href, { method: 'HEAD', redirect: 'manual' });\n\n // The location header returned by some ietf.org pages is relative\n // (e.g., /doc/status-change-http-experiments-to-historic/) so check to make\n // sure the location is a valid URL and fall back to the anchor href if not.\n const url = attempt(\n () => new URL(\n response.status.toString().startsWith('3') && response.headers.has('Location')\n ? response.headers.get('Location')!\n : anchor.href\n )\n ) ?? new URL(anchor.href);\n const pattern = /RFC\\d+(?:, Section )?((?:\\d+)?(?:.\\d+){0,2})/;\n const [, section] = pattern.exec(anchor.textContent) ?? [];\n\n if (section) {\n url.hash = `section-${section}`;\n }\n\n return url.toString();\n };\n\n return Promise.all(rows.map(row => Array.from(row.querySelectorAll('td')))\n .filter(cells => cells.at(1)!.textContent !== 'Unassigned')\n .map(async cells => ({\n value: Number.parseInt(cells.at(0)!.textContent),\n description: cells.at(1)!.textContent,\n references: await Promise.all(Array.from(cells.at(2)!.querySelectorAll('a'))\n .map(async anchor =>\n ({ name: anchor.textContent!, url: await getReferenceLink(anchor) })\n )\n )\n })));\n}\n\nexport async function fetchStatusCodeClasses(): Promise<StatusCodeClass[]> {\n const dom = await fetchRegistryDom();\n const note = Array.from(dom.window.document.querySelectorAll('dt'))\n .find(element => element.textContent.trim() === 'Note')\n ?.nextElementSibling!.textContent.trim();\n const pattern = /^(?<value>\\dxx): (?<name>[ A-Za-z]+) - (?<description>[ ,A-Za-z]+)$/;\n\n return note?.split('\\n').map(line => {\n const { value = '', name = '', description = '' } = pattern.exec(line)?.groups ?? {};\n\n return { value, name, description };\n }) ?? [];\n}\n"],
5
+ "mappings": "AAAA,OAAS,SAAAA,MAAa,QAQtB,SAASC,EAAWC,EAAkC,CACpD,GAAI,CACF,OAAOA,EAAS,CAClB,MAAQ,CAAoB,CAC9B,CAEA,eAAeC,GAAmC,CAGhD,IAAMC,EAAO,MADI,MAAM,MADX,oDACoB,GACJ,KAAK,EAEjC,OAAO,IAAIJ,EAAMI,CAAI,CACvB,CAEA,eAAOC,EAAwC,CAAE,iBAAAC,EAAmB,EAAK,EAErE,CAAC,EAA0B,CAE7B,IAAMC,GADM,MAAMJ,EAAiB,GACjB,OAAO,SAAS,eAAe,2BAA2B,EACtEK,EAAO,MAAM,KAAKD,EAAM,iBAAsC,YAAY,CAAC,EAC3EE,EAAmB,MAAOC,GAA+C,CAC7E,GAAI,CAACJ,EACH,OAAOI,EAAO,KAIhB,IAAMC,EAAW,MAAM,MAAMD,EAAO,KAAM,CAAE,OAAQ,OAAQ,SAAU,QAAS,CAAC,EAK1EE,EAAMX,EACV,IAAM,IAAI,IACRU,EAAS,OAAO,SAAS,EAAE,WAAW,GAAG,GAAKA,EAAS,QAAQ,IAAI,UAAU,EACzEA,EAAS,QAAQ,IAAI,UAAU,EAC/BD,EAAO,IACb,CACF,GAAK,IAAI,IAAIA,EAAO,IAAI,EAClBG,EAAU,+CACV,CAAC,CAAEC,CAAO,EAAID,EAAQ,KAAKH,EAAO,WAAW,GAAK,CAAC,EAEzD,OAAII,IACFF,EAAI,KAAO,WAAWE,CAAO,IAGxBF,EAAI,SAAS,CACtB,EAEA,OAAO,QAAQ,IAAIJ,EAAK,IAAIO,GAAO,MAAM,KAAKA,EAAI,iBAAiB,IAAI,CAAC,CAAC,EACtE,OAAOC,GAASA,EAAM,GAAG,CAAC,EAAG,cAAgB,YAAY,EACzD,IAAI,MAAMA,IAAU,CACnB,MAAO,OAAO,SAASA,EAAM,GAAG,CAAC,EAAG,WAAW,EAC/C,YAAaA,EAAM,GAAG,CAAC,EAAG,YAC1B,WAAY,MAAM,QAAQ,IAAI,MAAM,KAAKA,EAAM,GAAG,CAAC,EAAG,iBAAiB,GAAG,CAAC,EACxE,IAAI,MAAMN,IACR,CAAE,KAAMA,EAAO,YAAc,IAAK,MAAMD,EAAiBC,CAAM,CAAE,EACpE,CACF,CACF,EAAE,CAAC,CACP,CAEA,eAAsBO,GAAqD,CACzE,IAAMC,EAAM,MAAMf,EAAiB,EAC7BgB,EAAO,MAAM,KAAKD,EAAI,OAAO,SAAS,iBAAiB,IAAI,CAAC,EAC/D,KAAKE,GAAWA,EAAQ,YAAY,KAAK,IAAM,MAAM,GACpD,mBAAoB,YAAY,KAAK,EACnCP,EAAU,sEAEhB,OAAOM,GAAM,MAAM;AAAA,CAAI,EAAE,IAAIE,GAAQ,CACnC,GAAM,CAAE,MAAAC,EAAQ,GAAI,KAAAC,EAAO,GAAI,YAAAC,EAAc,EAAG,EAAIX,EAAQ,KAAKQ,CAAI,GAAG,QAAU,CAAC,EAEnF,MAAO,CAAE,MAAAC,EAAO,KAAAC,EAAM,YAAAC,CAAY,CACpC,CAAC,GAAK,CAAC,CACT",
6
+ "names": ["JSDOM", "attempt", "callback", "fetchRegistryDom", "html", "fetchStatusCodes", "resolveRedirects", "table", "rows", "getReferenceLink", "anchor", "response", "url", "pattern", "section", "row", "cells", "fetchStatusCodeClasses", "dom", "note", "element", "line", "value", "name", "description"]
7
7
  }
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "fetch-status-codes",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Fetch the HTTP status codes from the IANA registry.",
5
5
  "keywords": [
6
+ "class",
6
7
  "code",
7
8
  "fetch",
8
9
  "http",
@@ -25,26 +26,27 @@
25
26
  "exports": "./index.js",
26
27
  "types": "../types/index.d.ts",
27
28
  "dependencies": {
28
- "jsdom": "^27.2.0"
29
+ "jsdom": "^27.3.0"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@eslint/compat": "^2.0.0",
32
33
  "@eslint/json": "^0.14.0",
33
- "@radham/eslint-config": "^11.0.0",
34
+ "@radham/changelog": "^0.1.0",
35
+ "@radham/eslint-config": "^12.0.0",
34
36
  "@types/jsdom": "^27.0.0",
35
37
  "@types/node": "^22.18.8",
36
- "@vitest/coverage-v8": "^4.0.14",
38
+ "@vitest/coverage-v8": "^4.0.16",
37
39
  "duo-build": "^0.2.1",
38
- "eslint": "^9.39.1",
40
+ "eslint": "^9.39.2",
39
41
  "globals": "^16.5.0",
40
42
  "husky": "^9.1.7",
41
43
  "lint-staged": "^16.2.7",
42
44
  "rimraf": "^6.1.2",
43
- "sort-package-json": "^3.4.0",
45
+ "sort-package-json": "^3.6.0",
44
46
  "typescript": "^5.9.3",
45
- "vitest": "^4.0.14"
47
+ "vitest": "^4.0.16"
46
48
  },
47
- "packageManager": "pnpm@10.23.0",
49
+ "packageManager": "pnpm@10.26.2",
48
50
  "engines": {
49
51
  "node": ">=22"
50
52
  }
@@ -7,7 +7,13 @@ export type StatusCode = {
7
7
  description: string;
8
8
  references: Reference[];
9
9
  };
10
+ export type StatusCodeClass = {
11
+ value: string;
12
+ name: string;
13
+ description: string;
14
+ };
10
15
  export default function fetchStatusCodes({ resolveRedirects }?: {
11
16
  resolveRedirects?: boolean;
12
17
  }): Promise<StatusCode[]>;
18
+ export declare function fetchStatusCodeClasses(): Promise<StatusCodeClass[]>;
13
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC;AAEzF,wBAA8B,gBAAgB,CAAC,EAAE,gBAAuB,EAAE,GAAE;IAC1E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAoC7B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC;AAEzF,MAAM,MAAM,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAgBnF,wBAA8B,gBAAgB,CAAC,EAAE,gBAAuB,EAAE,GAAE;IAC1E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CA2C7B;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAYzE"}
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "fetch-status-codes",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Fetch the HTTP status codes from the IANA registry.",
5
5
  "keywords": [
6
+ "class",
6
7
  "code",
7
8
  "fetch",
8
9
  "http",
@@ -36,37 +37,41 @@
36
37
  "dist",
37
38
  "CHANGELOG.md"
38
39
  ],
40
+ "scripts": {
41
+ "prebuild": "rimraf ./dist",
42
+ "build": "duo-build --packages external --platform node",
43
+ "lint": "eslint .",
44
+ "lint:fix": "eslint --fix .",
45
+ "prepare": "husky",
46
+ "prepublishOnly": "pnpm run build",
47
+ "test": "vitest run",
48
+ "test:coverage": "vitest run --coverage",
49
+ "test:log": "vitest run --silent passed-only",
50
+ "test:watch": "vitest"
51
+ },
39
52
  "dependencies": {
40
- "jsdom": "^27.2.0"
53
+ "jsdom": "^27.3.0"
41
54
  },
42
55
  "devDependencies": {
43
56
  "@eslint/compat": "^2.0.0",
44
57
  "@eslint/json": "^0.14.0",
45
- "@radham/eslint-config": "^11.0.0",
58
+ "@radham/changelog": "^0.1.0",
59
+ "@radham/eslint-config": "^12.0.0",
46
60
  "@types/jsdom": "^27.0.0",
47
61
  "@types/node": "^22.18.8",
48
- "@vitest/coverage-v8": "^4.0.14",
62
+ "@vitest/coverage-v8": "^4.0.16",
49
63
  "duo-build": "^0.2.1",
50
- "eslint": "^9.39.1",
64
+ "eslint": "^9.39.2",
51
65
  "globals": "^16.5.0",
52
66
  "husky": "^9.1.7",
53
67
  "lint-staged": "^16.2.7",
54
68
  "rimraf": "^6.1.2",
55
- "sort-package-json": "^3.4.0",
69
+ "sort-package-json": "^3.6.0",
56
70
  "typescript": "^5.9.3",
57
- "vitest": "^4.0.14"
71
+ "vitest": "^4.0.16"
58
72
  },
73
+ "packageManager": "pnpm@10.26.2",
59
74
  "engines": {
60
75
  "node": ">=22"
61
- },
62
- "scripts": {
63
- "prebuild": "rimraf ./dist",
64
- "build": "duo-build --packages external --platform node",
65
- "lint": "eslint .",
66
- "lint:fix": "eslint --fix .",
67
- "test": "vitest run",
68
- "test:coverage": "vitest run --coverage",
69
- "test:log": "vitest run --silent passed-only",
70
- "test:watch": "vitest"
71
76
  }
72
- }
77
+ }