fetch-status-codes 0.1.1 → 0.3.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,32 @@ 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.3.0] - 2025-12-31
10
+ --------------------
11
+
12
+ ### Changed
13
+
14
+ - The default setting for the `resolveRedirects` option is now `false`. This reduces the operation
15
+ time by approximately one second. However, if the old behavior is desired you can manually set the
16
+ option to `true`.
17
+ - Upgraded [jsdom](https://github.com/jsdom/jsdom#readme) from v27.3.0 to v27.4.0.
18
+
19
+ [0.2.0] - 2025-12-23
20
+ --------------------
21
+
22
+ ### Added
23
+
24
+ - A `fetchStatusCodeClasses` function, to fetch the HTTP status code classes.
25
+
26
+ ### Changed
27
+
28
+ - Upgraded [jsdom](https://github.com/jsdom/jsdom#readme) from v27.2.0 to v27.3.0.
29
+
30
+ ### Fixed
31
+
32
+ - Redirect resolution. Previously it was only being done on 301 status codes,
33
+ now all 3xx responses with a location header are resolved.
34
+
9
35
  [0.1.1] - 2025-11-26
10
36
  --------------------
11
37
 
@@ -20,5 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
46
 
21
47
  - Initial release.
22
48
 
49
+ [0.3.0]: https://github.com/jbenner-radham/node-fetch-status-codes/compare/v0.2.0...v0.3.0
50
+ [0.2.0]: https://github.com/jbenner-radham/node-fetch-status-codes/compare/v0.1.1...v0.2.0
23
51
  [0.1.1]: https://github.com/jbenner-radham/node-fetch-status-codes/compare/v0.1.0...v0.1.1
24
52
  [0.1.0]: https://github.com/jbenner-radham/node-fetch-status-codes/releases/tag/v0.1.0
package/README.md CHANGED
@@ -14,8 +14,10 @@ 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
+ // Calling `fetchStatusCodes()` without any arguments implicitly sets the `resolveRedirects` option
20
+ // to `false`.
19
21
  await fetchStatusCodes();
20
22
  // >>> [
21
23
  // >>> {
@@ -24,14 +26,18 @@ await fetchStatusCodes();
24
26
  // >>> references: [
25
27
  // >>> {
26
28
  // >>> name: 'RFC9110, Section 15.2.1',
27
- // >>> url: 'https://www.rfc-editor.org/rfc/rfc9110.html#section-15.2.1'
29
+ // >>> url: 'https://www.iana.org/go/rfc9110'
28
30
  // >>> }
29
31
  // >>> ]
30
32
  // >>> },
31
33
  // >>> ...
32
34
  // >>> ]
33
35
 
34
- await fetchStatusCodes({ resolveRedirects: false });
36
+ // Calling `fetchStatusCodes()` with the `resolveRedirects` option set to `true` gives you arguably
37
+ // better URLs since they aren't redirects and they will link directly to the relevant section if
38
+ // applicable. However, this will also add approximately one second of additional execution time to
39
+ // the app e.g., ~1.616s without resolution versus ~2.655s with resolution.
40
+ await fetchStatusCodes({ resolveRedirects: true });
35
41
  // >>> [
36
42
  // >>> {
37
43
  // >>> value: 100,
@@ -39,12 +45,22 @@ await fetchStatusCodes({ resolveRedirects: false });
39
45
  // >>> references: [
40
46
  // >>> {
41
47
  // >>> name: 'RFC9110, Section 15.2.1',
42
- // >>> url: 'https://www.iana.org/go/rfc9110'
48
+ // >>> url: 'https://www.rfc-editor.org/rfc/rfc9110.html#section-15.2.1'
43
49
  // >>> }
44
50
  // >>> ]
45
51
  // >>> },
46
52
  // >>> ...
47
53
  // >>> ]
54
+
55
+ await fetchStatusCodeClasses();
56
+ // >>> [
57
+ // >>> {
58
+ // >>> value: '1xx',
59
+ // >>> name: 'Informational',
60
+ // >>> description: 'Request received, continuing process'
61
+ // >>> },
62
+ // >>> ...
63
+ // >>> ]
48
64
  ```
49
65
 
50
66
  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 p=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=p(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=!1}={}){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),f=/RFC\d+(?:, Section )?((?:\d+)?(?:.\d+){0,2})/,[,u]=f.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 = false }: {\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,EAAM,EAEtE,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.3.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.4.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 f({resolveRedirects:n=!1}={}){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{f 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 = false }: {\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,EAAM,EAEtE,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.3.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.4.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,gBAAwB,EAAE,GAAE;IAC3E,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.3.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.4.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
+ }