@prexjs/request 0.0.0-stage → 1.0.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Prexjs (https://prexjs.com)
4
+ Copyright (c) 2026 Sorabh (https://sorabh.org)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,98 @@
1
- # Temporary Holding Version
1
+ # @prexjs/request
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Extended HTTP Request prototype and interfaces for Prex and Express applications.**
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@prexjs/request)](https://www.npmjs.com/package/@prexjs/request)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@prexjs/request)](https://www.npmjs.com/package/@prexjs/request)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-supported-blue)](https://www.typescriptlang.org/)
9
+
10
+ `@prexjs/request` defines the extended HTTP Request prototype and TypeScript definitions that build on Node's native `http.IncomingMessage`. It provides high-level properties and convenience methods for content negotiation, header normalization, query string parsing, byte-range evaluation, and IP resolution.
11
+
12
+ ## Features
13
+
14
+ - **Header Access**: Case-insensitive header lookup with `.get(name)` and `.header(name)`, with special handling for `'Referrer'`.
15
+ - **Content Negotiation**: Built-in methods for `.accepts()`, `.acceptsEncodings()`, `.acceptsCharsets()`, and `.acceptsLanguages()`.
16
+ - **Content-Type Validation**: Match incoming payloads against MIME types and wildcards with `.is(types)`.
17
+ - **Proxy-Aware Network Properties**: Robust `.ip`, `.ips`, `.protocol`, `.secure`, and `.hostname` getters that respect trust proxy settings.
18
+ - **Cache Freshness Checks**: Automatic evaluation of `.fresh` and `.stale` based on client headers and response tags.
19
+ - **Byte Range Parsing**: Built-in `.range(size, options)` method leveraging `@prexjs/range-parser`.
20
+
21
+ ## Installation
22
+
23
+ Install using your preferred package manager:
24
+
25
+ ```bash
26
+ # npm
27
+ npm install @prexjs/request
28
+
29
+ # pnpm
30
+ pnpm add @prexjs/request
31
+
32
+ # Yarn
33
+ yarn add @prexjs/request
34
+
35
+ # Bun
36
+ bun add @prexjs/request
37
+ ```
38
+
39
+ ## Quick start
40
+
41
+ ```ts
42
+ import type { Request } from '@prexjs/request';
43
+ import prex from '@prexjs/prex';
44
+
45
+ const app = prex();
46
+
47
+ app.get('/api/resource', (req: Request, res) => {
48
+ // Access normalized headers
49
+ const auth = req.get('Authorization');
50
+
51
+ // Negotiate response type
52
+ if (req.accepts('json')) {
53
+ res.json({ ip: req.ip, secure: req.secure });
54
+ } else {
55
+ res.type('txt').send(`IP: ${req.ip}`);
56
+ }
57
+ });
58
+ ```
59
+
60
+ ## API
61
+
62
+ ### Methods
63
+
64
+ - `req.get(name)` / `req.header(name)`: Get the specified HTTP request header.
65
+ - `req.accepts(types)`: Check if the specified content types are acceptable.
66
+ - `req.acceptsEncodings(encodings)`: Check if specified transfer encodings are acceptable.
67
+ - `req.acceptsCharsets(charsets)`: Check if specified character sets are acceptable.
68
+ - `req.acceptsLanguages(languages)`: Check if specified languages are acceptable.
69
+ - `req.range(size, options?)`: Parse `Range` header field.
70
+ - `req.is(types)`: Check if the incoming request contains the specified Content-Type.
71
+
72
+ ### Getters
73
+
74
+ - `req.ip`: Remote IP address (respects proxy configuration).
75
+ - `req.ips`: Array of proxy IP addresses when proxy trust is enabled.
76
+ - `req.hostname`: Hostname derived from the `Host` header field.
77
+ - `req.protocol`: Protocol (`'http'` or `'https'`).
78
+ - `req.secure`: Boolean, `true` when `req.protocol === 'https'`.
79
+ - `req.query`: Parsed query string object.
80
+ - `req.fresh`: Check if response is still fresh in the client's cache.
81
+ - `req.stale`: Check if response is stale.
82
+ - `req.xhr`: Boolean, `true` if `X-Requested-With` header is `'xmlhttprequest'`.
83
+
84
+ ## Compatibility
85
+
86
+ - Node.js >= 18.0.0
87
+ - Supports both ESM and CommonJS
88
+ - TypeScript 5.0+
89
+
90
+ ## Related Packages
91
+
92
+ - [`@prexjs/response`](https://github.com/prexjs/prex.js/tree/main/packages/response): HTTP Response prototype.
93
+ - [`@prexjs/application`](https://github.com/prexjs/prex.js/tree/main/packages/application): Core application prototype.
94
+ - [`@prexjs/prex`](https://github.com/prexjs/prex.js/tree/main/packages/prex): The fast and modular web framework.
95
+
96
+ ## License
97
+
98
+ [MIT](./LICENSE)
package/lib/index.cjs ADDED
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * @prexjs/request
3
+ * Copyright (c) 2026 Prexjs (https://prexjs.com)
4
+ * Copyright (c) 2026 Sorabh (https://Sorabh.org)
5
+ * MIT Licensed
6
+ */
7
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("node:http"),t=require("node:net"),r=require("@prexjs/accepts"),s=require("@prexjs/type-is"),n=require("@prexjs/fresh"),i=require("@prexjs/range-parser"),u=require("@prexjs/parseurl"),o=require("@prexjs/proxy-address");function a(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var f=a(e),c=a(r),h=a(n),d=a(i),p=a(u),l=a(o);const g=function(e){let t=e;for(;t&&"object"==typeof t&&"default"in t&&void 0!==t.default;)t=t.default;return t}(p.default),y=Object.create(f.default.IncomingMessage.prototype);function x(e,t,r){Object.defineProperty(e,t,{configurable:!0,enumerable:!0,get:r})}y.get=y.header=function(e){if(!e)throw new TypeError("name argument is required to req.get");if("string"!=typeof e)throw new TypeError("name must be a string to req.get");const t=e.toLowerCase();switch(t){case"referer":case"referrer":return this.headers.referrer||this.headers.referer;default:return this.headers[t]}},y.accepts=function(...e){return c.default(this).types(...e)},y.acceptsEncodings=function(...e){return c.default(this).encodings(...e)},y.acceptsCharsets=function(...e){return c.default(this).charsets(...e)},y.acceptsLanguages=function(...e){return c.default(this).languages(...e)},y.range=function(e,t){const r=this.get("Range");if(r)return d.default(e,r,t)},x(y,"query",function(){const e=this.app.get("query parser fn");if(!e)return Object.create(null);return e(g(this).query)}),y.is=function(e,...t){let r;if(r=Array.isArray(e)?e:void 0===e?t:[e,...t],!s.hasBody(this))return null;if(!r||0===r.length)return null;try{return new s.TypeIs(r).request(this)||!1}catch{return!1}},x(y,"protocol",function(){const e=this.socket.encrypted?"https":"http";if(!this.app.get("trust proxy fn")(this.socket.remoteAddress,0))return e;const t=this.get("X-Forwarded-Proto")||e,r=t.indexOf(",");return-1!==r?t.substring(0,r).trim():t.trim()}),x(y,"secure",function(){return"https"===this.protocol}),x(y,"ip",function(){const e=this.app.get("trust proxy fn");return l.default(this,e)}),x(y,"ips",function(){const e=this.app.get("trust proxy fn"),t=l.default.all(this,e);return t.reverse().pop(),t}),x(y,"subdomains",function(){const e=this.hostname;if(!e)return[];const r=this.app.get("subdomain offset");return(t.isIP(e)?[e]:e.split(".").reverse()).slice(r)}),x(y,"path",function(){return g(this).pathname}),x(y,"host",function(){const e=this.app.get("trust proxy fn");let t=this.get("X-Forwarded-Host");return t&&e(this.socket.remoteAddress,0)?-1!==t.indexOf(",")&&(t=t.substring(0,t.indexOf(",")).trimEnd()):t=this.get("Host"),t||void 0}),x(y,"hostname",function(){const e=this.host;if(!e)return;const t="["===e[0]?e.indexOf("]")+1:0,r=e.indexOf(":",t);return-1!==r?e.substring(0,r):e}),x(y,"fresh",function(){const e=this.method,t=this.res,r=t.statusCode;return("GET"===e||"HEAD"===e||"QUERY"===e)&&((r>=200&&r<300||304===r)&&h.default(this.headers,{etag:t.get("ETag"),"last-modified":t.get("Last-Modified")}))}),x(y,"stale",function(){return!this.fresh}),x(y,"xhr",function(){return"xmlhttprequest"===(this.get("X-Requested-With")||"").toLowerCase()}),exports.default=y;
@@ -0,0 +1,63 @@
1
+ import http from 'node:http';
2
+ import { Options, Result, Ranges } from '@prexjs/range-parser';
3
+ import { Application } from '@prexjs/application';
4
+ import { Response } from '@prexjs/response';
5
+
6
+ interface Request extends http.IncomingMessage {
7
+ app: Application;
8
+ res: Response;
9
+ next?: (err?: unknown) => void;
10
+ secret?: string | string[];
11
+ params?: Record<string, string>;
12
+ body?: any;
13
+ /** Return request header. `Referrer` and `Referer` are interchangeable. Aliased as `req.header()`. */
14
+ get(name: "set-cookie"): string[] | undefined;
15
+ get(name: string): string | undefined;
16
+ header(name: "set-cookie"): string[] | undefined;
17
+ header(name: string): string | undefined;
18
+ /** Check if the given `type(s)` is acceptable, returning the best match, otherwise `false`. */
19
+ accepts(): string[];
20
+ accepts(type: string): string | false;
21
+ accepts(type: string[]): string | false;
22
+ accepts(...type: string[]): string | false;
23
+ acceptsEncodings(): string[];
24
+ acceptsEncodings(encoding: string): string | false;
25
+ acceptsEncodings(encoding: string[]): string | false;
26
+ acceptsEncodings(...encoding: string[]): string | false;
27
+ /** Checks if the specified `charset`s are acceptable based on `Accept-Charset`. */
28
+ acceptsCharsets(): string[];
29
+ acceptsCharsets(...charsets: string[]): string | false;
30
+ acceptsLanguages(): string[];
31
+ acceptsLanguages(...languages: string[]): string | false;
32
+ /**
33
+ * Parse Range header field, capping to the given `size`.
34
+ * Returns `undefined` if no header, `-1` when unsatisfiable, `-2` when invalid.
35
+ */
36
+ range(size: number, options?: Options): Result | Ranges | undefined;
37
+ /** Parsed query string, using the "query parser" setting. */
38
+ readonly query: any;
39
+ /** Check if the incoming request's Content-Type contains the given mime `type`. */
40
+ is(...types: string[]): string | false | null;
41
+ is(types: string[]): string | false | null;
42
+ /** "http" or "https"; honours X-Forwarded-Proto when "trust proxy" applies. */
43
+ readonly protocol: string;
44
+ readonly secure: boolean;
45
+ /** Remote address, taking "trust proxy" into account. */
46
+ readonly ip: string | undefined;
47
+ /** When "trust proxy" is set, trusted proxy addresses + client. */
48
+ readonly ips: string[];
49
+ readonly subdomains: string[];
50
+ readonly path: string;
51
+ readonly host: string | undefined;
52
+ readonly hostname: string | undefined;
53
+ /** True when Last-Modified / ETag still match. */
54
+ readonly fresh: boolean;
55
+ readonly stale: boolean;
56
+ /** True if the request was an _XMLHttpRequest_. */
57
+ readonly xhr: boolean;
58
+ }
59
+ /** Request prototype. */
60
+ declare const req: Request;
61
+
62
+ export { req as default };
63
+ export type { Request };
@@ -0,0 +1,63 @@
1
+ import http from 'node:http';
2
+ import { Options, Result, Ranges } from '@prexjs/range-parser';
3
+ import { Application } from '@prexjs/application';
4
+ import { Response } from '@prexjs/response';
5
+
6
+ interface Request extends http.IncomingMessage {
7
+ app: Application;
8
+ res: Response;
9
+ next?: (err?: unknown) => void;
10
+ secret?: string | string[];
11
+ params?: Record<string, string>;
12
+ body?: any;
13
+ /** Return request header. `Referrer` and `Referer` are interchangeable. Aliased as `req.header()`. */
14
+ get(name: "set-cookie"): string[] | undefined;
15
+ get(name: string): string | undefined;
16
+ header(name: "set-cookie"): string[] | undefined;
17
+ header(name: string): string | undefined;
18
+ /** Check if the given `type(s)` is acceptable, returning the best match, otherwise `false`. */
19
+ accepts(): string[];
20
+ accepts(type: string): string | false;
21
+ accepts(type: string[]): string | false;
22
+ accepts(...type: string[]): string | false;
23
+ acceptsEncodings(): string[];
24
+ acceptsEncodings(encoding: string): string | false;
25
+ acceptsEncodings(encoding: string[]): string | false;
26
+ acceptsEncodings(...encoding: string[]): string | false;
27
+ /** Checks if the specified `charset`s are acceptable based on `Accept-Charset`. */
28
+ acceptsCharsets(): string[];
29
+ acceptsCharsets(...charsets: string[]): string | false;
30
+ acceptsLanguages(): string[];
31
+ acceptsLanguages(...languages: string[]): string | false;
32
+ /**
33
+ * Parse Range header field, capping to the given `size`.
34
+ * Returns `undefined` if no header, `-1` when unsatisfiable, `-2` when invalid.
35
+ */
36
+ range(size: number, options?: Options): Result | Ranges | undefined;
37
+ /** Parsed query string, using the "query parser" setting. */
38
+ readonly query: any;
39
+ /** Check if the incoming request's Content-Type contains the given mime `type`. */
40
+ is(...types: string[]): string | false | null;
41
+ is(types: string[]): string | false | null;
42
+ /** "http" or "https"; honours X-Forwarded-Proto when "trust proxy" applies. */
43
+ readonly protocol: string;
44
+ readonly secure: boolean;
45
+ /** Remote address, taking "trust proxy" into account. */
46
+ readonly ip: string | undefined;
47
+ /** When "trust proxy" is set, trusted proxy addresses + client. */
48
+ readonly ips: string[];
49
+ readonly subdomains: string[];
50
+ readonly path: string;
51
+ readonly host: string | undefined;
52
+ readonly hostname: string | undefined;
53
+ /** True when Last-Modified / ETag still match. */
54
+ readonly fresh: boolean;
55
+ readonly stale: boolean;
56
+ /** True if the request was an _XMLHttpRequest_. */
57
+ readonly xhr: boolean;
58
+ }
59
+ /** Request prototype. */
60
+ declare const req: Request;
61
+
62
+ export { req as default };
63
+ export type { Request };
package/lib/index.mjs ADDED
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * @prexjs/request
3
+ * Copyright (c) 2026 Prexjs (https://prexjs.com)
4
+ * Copyright (c) 2026 Sorabh (https://Sorabh.org)
5
+ * MIT Licensed
6
+ */
7
+ import t from"node:http";import{isIP as e}from"node:net";import r from"@prexjs/accepts";import{hasBody as s,TypeIs as n}from"@prexjs/type-is";import o from"@prexjs/fresh";import i from"@prexjs/range-parser";import u from"@prexjs/parseurl";import c from"@prexjs/proxy-address";const f=function(t){let e=t;for(;e&&"object"==typeof e&&"default"in e&&void 0!==e.default;)e=e.default;return e}(u),a=Object.create(t.IncomingMessage.prototype);function p(t,e,r){Object.defineProperty(t,e,{configurable:!0,enumerable:!0,get:r})}a.get=a.header=function(t){if(!t)throw new TypeError("name argument is required to req.get");if("string"!=typeof t)throw new TypeError("name must be a string to req.get");const e=t.toLowerCase();switch(e){case"referer":case"referrer":return this.headers.referrer||this.headers.referer;default:return this.headers[e]}},a.accepts=function(...t){return r(this).types(...t)},a.acceptsEncodings=function(...t){return r(this).encodings(...t)},a.acceptsCharsets=function(...t){return r(this).charsets(...t)},a.acceptsLanguages=function(...t){return r(this).languages(...t)},a.range=function(t,e){const r=this.get("Range");if(r)return i(t,r,e)},p(a,"query",function(){const t=this.app.get("query parser fn");if(!t)return Object.create(null);return t(f(this).query)}),a.is=function(t,...e){let r;if(r=Array.isArray(t)?t:void 0===t?e:[t,...e],!s(this))return null;if(!r||0===r.length)return null;try{return new n(r).request(this)||!1}catch{return!1}},p(a,"protocol",function(){const t=this.socket.encrypted?"https":"http";if(!this.app.get("trust proxy fn")(this.socket.remoteAddress,0))return t;const e=this.get("X-Forwarded-Proto")||t,r=e.indexOf(",");return-1!==r?e.substring(0,r).trim():e.trim()}),p(a,"secure",function(){return"https"===this.protocol}),p(a,"ip",function(){const t=this.app.get("trust proxy fn");return c(this,t)}),p(a,"ips",function(){const t=this.app.get("trust proxy fn"),e=c.all(this,t);return e.reverse().pop(),e}),p(a,"subdomains",function(){const t=this.hostname;if(!t)return[];const r=this.app.get("subdomain offset");return(e(t)?[t]:t.split(".").reverse()).slice(r)}),p(a,"path",function(){return f(this).pathname}),p(a,"host",function(){const t=this.app.get("trust proxy fn");let e=this.get("X-Forwarded-Host");return e&&t(this.socket.remoteAddress,0)?-1!==e.indexOf(",")&&(e=e.substring(0,e.indexOf(",")).trimEnd()):e=this.get("Host"),e||void 0}),p(a,"hostname",function(){const t=this.host;if(!t)return;const e="["===t[0]?t.indexOf("]")+1:0,r=t.indexOf(":",e);return-1!==r?t.substring(0,r):t}),p(a,"fresh",function(){const t=this.method,e=this.res,r=e.statusCode;return("GET"===t||"HEAD"===t||"QUERY"===t)&&((r>=200&&r<300||304===r)&&o(this.headers,{etag:e.get("ETag"),"last-modified":e.get("Last-Modified")}))}),p(a,"stale",function(){return!this.fresh}),p(a,"xhr",function(){return"xmlhttprequest"===(this.get("X-Requested-With")||"").toLowerCase()});export{a as default};
package/package.json CHANGED
@@ -1,6 +1,63 @@
1
1
  {
2
2
  "name": "@prexjs/request",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Extended HTTP Request prototype and interfaces for Prex and Express applications.",
5
+ "authors": [
6
+ {
7
+ "name": "Prexjs",
8
+ "email": "hello@prexjs.com",
9
+ "url": "https://prexjs.com"
10
+ },
11
+ {
12
+ "name": "Sorabh",
13
+ "email": "hello@sorabh.org",
14
+ "url": "https://sorabh.org"
15
+ }
16
+ ],
17
+ "license": "MIT",
18
+ "type": "module",
19
+ "sideEffects": false,
20
+ "files": [
21
+ "lib",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "main": "./lib/index.cjs",
26
+ "module": "./lib/index.mjs",
27
+ "types": "./lib/index.d.cts",
28
+ "exports": {
29
+ ".": {
30
+ "import": {
31
+ "types": "./lib/index.d.mts",
32
+ "default": "./lib/index.mjs"
33
+ },
34
+ "require": {
35
+ "types": "./lib/index.d.cts",
36
+ "default": "./lib/index.cjs"
37
+ }
38
+ },
39
+ "./package.json": "./package.json"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/prexjs/prex.js.git",
44
+ "directory": "packages/request"
45
+ },
46
+ "homepage": "https://github.com/prexjs/prex.js/tree/main/packages/request#readme",
47
+ "bugs": {
48
+ "url": "https://github.com/prexjs/prex.js/issues"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "keywords": [
54
+ "prex",
55
+ "request",
56
+ "req",
57
+ "http",
58
+ "incoming-message",
59
+ "express",
60
+ "headers",
61
+ "query"
62
+ ]
63
+ }