hapii-js 0.0.0-stage → 2.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.
@@ -0,0 +1,79 @@
1
+ import { StateOptions, SameSitePolicy } from '@hapi/statehood';
2
+
3
+ import { Request } from '../request';
4
+
5
+ export { SameSitePolicy };
6
+
7
+ /**
8
+ * Optional cookie settings
9
+ * [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-serverstatename-options)
10
+ */
11
+ export interface ServerStateCookieOptions extends StateOptions<Request> {}
12
+
13
+ /**
14
+ * A single object or an array of object where each contains:
15
+ * * name - the cookie name.
16
+ * * value - the cookie value.
17
+ * * options - cookie configuration to override the server settings.
18
+ */
19
+ export interface ServerStateFormat {
20
+ name: string;
21
+ value: string;
22
+ options: ServerStateCookieOptions;
23
+ }
24
+
25
+ /**
26
+ * For context [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-serverstatename-options)
27
+ * For context [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-serveroptionsstate)
28
+ */
29
+ export interface ServerState {
30
+ /**
31
+ * The server cookies manager.
32
+ * Access: read only and statehood public interface.
33
+ */
34
+ readonly states: object;
35
+
36
+ /**
37
+ * The server cookies manager settings. The settings are based on the values configured in [server.options.state](https://github.com/hapijs/hapi/blob/master/API.md#server.options.state).
38
+ */
39
+ readonly settings: ServerStateCookieOptions;
40
+
41
+ /**
42
+ * An object containing the configuration of each cookie added via [server.state()](https://github.com/hapijs/hapi/blob/master/API.md#server.state()) where each key is the
43
+ * cookie name and value is the configuration object.
44
+ */
45
+ readonly cookies: {
46
+ [key: string]: ServerStateCookieOptions;
47
+ };
48
+
49
+ /**
50
+ * An array containing the names of all configured cookies.
51
+ */
52
+ readonly names: string[];
53
+
54
+ /**
55
+ * Same as calling [server.state()](https://github.com/hapijs/hapi/blob/master/API.md#server.state()).
56
+ */
57
+ add(name: string, options?: ServerStateCookieOptions | undefined): void;
58
+
59
+ /**
60
+ * Formats an HTTP 'Set-Cookie' header based on the server.options.state where:
61
+ * @param cookies - a single object or an array of object where each contains:
62
+ * * name - the cookie name.
63
+ * * value - the cookie value.
64
+ * * options - cookie configuration to override the server settings.
65
+ * @return Return value: a header string.
66
+ * Note that this utility uses the server configuration but does not change the server state. It is provided for manual cookie formatting (e.g. when headers are set manually).
67
+ * [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-async-serverstatesformatcookies)
68
+ */
69
+ format(cookies: ServerStateFormat | ServerStateFormat[]): Promise<string>;
70
+
71
+ /**
72
+ * Parses an HTTP 'Cookies' header based on the server.options.state where:
73
+ * @param header - the HTTP header.
74
+ * @return Return value: an object where each key is a cookie name and value is the parsed cookie.
75
+ * Note that this utility uses the server configuration but does not change the server state. It is provided for manual cookie parsing (e.g. when server parsing is disabled).
76
+ * [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-async-serverstatesparseheader)
77
+ */
78
+ parse(header: string): Promise<Record<string, string>>;
79
+ }
@@ -0,0 +1,113 @@
1
+ import * as https from 'https';
2
+ import * as stream from 'stream';
3
+
4
+ import { Boom } from '@hapi/boom';
5
+ import { ResponseObject as ShotResponseObject } from '@hapi/shot';
6
+
7
+ import {
8
+ ReqRef,
9
+ ReqRefDefaults,
10
+ MergeRefs,
11
+ Request} from './request';
12
+ import { ResponseToolkit, Auth } from './response';
13
+
14
+ /**
15
+ * All http parser [supported HTTP methods](https://nodejs.org/api/http.html#httpmethods).
16
+ */
17
+ export type HTTP_METHODS = 'ACL' | 'BIND' | 'CHECKOUT' | 'CONNECT' | 'COPY' | 'DELETE' | 'GET' | 'HEAD' | 'LINK' | 'LOCK' |
18
+ 'M-SEARCH' | 'MERGE' | 'MKACTIVITY' | 'MKCALENDAR' | 'MKCOL' | 'MOVE' | 'NOTIFY' | 'OPTIONS' | 'PATCH' | 'POST' |
19
+ 'PROPFIND' | 'PROPPATCH' | 'PURGE' | 'PUT' | 'REBIND' | 'REPORT' | 'SEARCH' | 'SOURCE' | 'SUBSCRIBE' | 'TRACE' |
20
+ 'UNBIND' | 'UNLINK' | 'UNLOCK' | 'UNSUBSCRIBE';
21
+
22
+ export type PeekListener = (chunk: string, encoding: string) => void;
23
+
24
+ export namespace Json {
25
+ /**
26
+ * @see {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#The_replacer_parameter}
27
+ */
28
+ type StringifyReplacer = ((key: string, value: any) => any) | (string | number)[] | undefined;
29
+
30
+ /**
31
+ * Any value greater than 10 is truncated.
32
+ */
33
+ type StringifySpace = number | string;
34
+
35
+ /**
36
+ * For context [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-routeoptionsjson)
37
+ */
38
+ interface StringifyArguments {
39
+ /** the replacer function or array. Defaults to no action. */
40
+ replacer?: StringifyReplacer | undefined;
41
+ /** number of spaces to indent nested object keys. Defaults to no indentation. */
42
+ space?: StringifySpace | undefined;
43
+ /* string suffix added after conversion to JSON string. Defaults to no suffix. */
44
+ suffix?: string | undefined;
45
+ /* calls Hoek.jsonEscape() after conversion to JSON string. Defaults to false. */
46
+ escape?: boolean | undefined;
47
+ }
48
+ }
49
+
50
+ export namespace Lifecycle {
51
+ /**
52
+ * Lifecycle methods are the interface between the framework and the application. Many of the request lifecycle steps:
53
+ * extensions, authentication, handlers, pre-handler methods, and failAction function values are lifecycle methods
54
+ * provided by the developer and executed by the framework.
55
+ * Each lifecycle method is a function with the signature await function(request, h, [err]) where:
56
+ * * request - the request object.
57
+ * * h - the response toolkit the handler must call to set a response and return control back to the framework.
58
+ * * err - an error object available only when the method is used as a failAction value.
59
+ */
60
+ type Method<
61
+ Refs extends ReqRef = ReqRefDefaults,
62
+ R extends ReturnValue<any> = ReturnValue<Refs>
63
+ > = (
64
+ this: MergeRefs<Refs>['Bind'],
65
+ request: Request<Refs>,
66
+ h: ResponseToolkit<Refs>,
67
+ err?: Error | undefined
68
+ ) => R;
69
+
70
+ /**
71
+ * Each lifecycle method must return a value or a promise that resolves into a value. If a lifecycle method returns
72
+ * without a value or resolves to an undefined value, an Internal Server Error (500) error response is sent.
73
+ * The return value must be one of:
74
+ * - Plain value: null, string, number, boolean
75
+ * - Buffer object
76
+ * - Error object: plain Error OR a Boom object.
77
+ * - Stream object
78
+ * - any object or array
79
+ * - a toolkit signal:
80
+ * - a toolkit method response:
81
+ * - a promise object that resolve to any of the above values
82
+ * For more info please [See docs](https://github.com/hapijs/hapi/blob/master/API.md#lifecycle-methods)
83
+ */
84
+ type ReturnValue<Refs extends ReqRef = ReqRefDefaults> = ReturnValueTypes<Refs> | (Promise<ReturnValueTypes<Refs>>);
85
+ type ReturnValueTypes<Refs extends ReqRef = ReqRefDefaults> =
86
+ (null | string | number | boolean) |
87
+ (Buffer) |
88
+ (Error | Boom) |
89
+ (stream.Stream) |
90
+ (object | object[]) |
91
+ symbol |
92
+ Auth<
93
+ MergeRefs<Refs>['AuthUser'],
94
+ MergeRefs<Refs>['AuthApp'],
95
+ MergeRefs<Refs>['AuthCredentialsExtra'],
96
+ MergeRefs<Refs>['AuthArtifactsExtra']
97
+ > |
98
+ ShotResponseObject;
99
+
100
+ /**
101
+ * Various configuration options allows defining how errors are handled. For example, when invalid payload is received or malformed cookie, instead of returning an error, the framework can be
102
+ * configured to perform another action. When supported the failAction option supports the following values:
103
+ * * 'error' - return the error object as the response.
104
+ * * 'log' - report the error but continue processing the request.
105
+ * * 'ignore' - take no action and continue processing the request.
106
+ * * a lifecycle method with the signature async function(request, h, err) where:
107
+ * * * request - the request object.
108
+ * * * h - the response toolkit.
109
+ * * * err - the error object.
110
+ * [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-failaction-configuration)
111
+ */
112
+ type FailAction = 'error' | 'log' | 'ignore' | Method;
113
+ }
@@ -0,0 +1,250 @@
1
+ 'use strict';
2
+
3
+ const Boom = require('@hapi/boom');
4
+ const Hoek = require('@hapi/hoek');
5
+ const Validate = require('@hapi/validate');
6
+
7
+
8
+ const internals = {};
9
+
10
+
11
+ exports.validator = function (validator) {
12
+
13
+ Hoek.assert(validator, 'Missing validator');
14
+ Hoek.assert(typeof validator.compile === 'function', 'Invalid validator compile method');
15
+
16
+ return validator;
17
+ };
18
+
19
+
20
+ exports.compile = function (rule, validator, realm, core) {
21
+
22
+ validator = validator ?? internals.validator(realm, core);
23
+
24
+ // false - nothing allowed
25
+
26
+ if (rule === false) {
27
+ return Validate.object({}).allow(null);
28
+ }
29
+
30
+ // Custom function
31
+
32
+ if (typeof rule === 'function') {
33
+ return rule;
34
+ }
35
+
36
+ // null, undefined, true - anything allowed
37
+
38
+ if (!rule || // false tested above
39
+ rule === true) {
40
+
41
+ return null;
42
+ }
43
+
44
+ // {...} - ... allowed
45
+
46
+ if (typeof rule.validate === 'function') {
47
+ return rule;
48
+ }
49
+
50
+ Hoek.assert(validator, 'Cannot set uncompiled validation rules without configuring a validator');
51
+ return validator.compile(rule);
52
+ };
53
+
54
+
55
+ internals.validator = function (realm, core) {
56
+
57
+ while (realm) {
58
+ if (realm.validator) {
59
+ return realm.validator;
60
+ }
61
+
62
+ realm = realm.parent;
63
+ }
64
+
65
+ return core.validator;
66
+ };
67
+
68
+
69
+ exports.headers = function (request) {
70
+
71
+ return internals.input('headers', request);
72
+ };
73
+
74
+
75
+ exports.params = function (request) {
76
+
77
+ return internals.input('params', request);
78
+ };
79
+
80
+
81
+ exports.payload = function (request) {
82
+
83
+ if (request.method === 'get' ||
84
+ request.method === 'head') { // When route.method is '*'
85
+
86
+ return;
87
+ }
88
+
89
+ return internals.input('payload', request);
90
+ };
91
+
92
+
93
+ exports.query = function (request) {
94
+
95
+ return internals.input('query', request);
96
+ };
97
+
98
+
99
+ exports.state = function (request) {
100
+
101
+ return internals.input('state', request);
102
+ };
103
+
104
+
105
+ internals.input = async function (source, request) {
106
+
107
+ const localOptions = {
108
+ context: {
109
+ headers: request.headers,
110
+ params: request.params,
111
+ query: request.query,
112
+ payload: request.payload,
113
+ state: request.state,
114
+ auth: request.auth,
115
+ app: {
116
+ route: request.route.settings.app,
117
+ request: request.app
118
+ }
119
+ }
120
+ };
121
+
122
+ delete localOptions.context[source];
123
+ Hoek.merge(localOptions, request.route.settings.validate.options);
124
+
125
+ try {
126
+ const schema = request.route.settings.validate[source];
127
+ const bind = request.route.settings.bind;
128
+
129
+ var value = await (typeof schema !== 'function' ? internals.validate(request[source], schema, localOptions) : schema.call(bind, request[source], localOptions));
130
+ return;
131
+ }
132
+ catch (err) {
133
+ var validationError = err;
134
+ }
135
+ finally {
136
+ request.orig[source] = request[source];
137
+ if (value !== undefined) {
138
+ request[source] = value;
139
+ }
140
+ }
141
+
142
+ if (request.route.settings.validate.failAction === 'ignore') {
143
+ return;
144
+ }
145
+
146
+ // Prepare error
147
+
148
+ const defaultError = validationError.isBoom ? validationError : Boom.badRequest(`Invalid request ${source} input`);
149
+ const detailedError = Boom.boomify(validationError, { statusCode: 400, override: false, data: { defaultError } });
150
+ detailedError.output.payload.validation = { source, keys: [] };
151
+ if (validationError.details) {
152
+ for (const details of validationError.details) {
153
+ const path = details.path;
154
+ detailedError.output.payload.validation.keys.push(Hoek.escapeHtml(path.join('.')));
155
+ }
156
+ }
157
+
158
+ if (request.route.settings.validate.errorFields) {
159
+ for (const field in request.route.settings.validate.errorFields) {
160
+ detailedError.output.payload[field] = request.route.settings.validate.errorFields[field];
161
+ }
162
+ }
163
+
164
+ return request._core.toolkit.failAction(request, request.route.settings.validate.failAction, defaultError, { details: detailedError, tags: ['validation', 'error', source] });
165
+ };
166
+
167
+
168
+ exports.response = async function (request) {
169
+
170
+ if (request.route.settings.response.sample) {
171
+ const currentSample = Math.ceil(Math.random() * 100);
172
+ if (currentSample > request.route.settings.response.sample) {
173
+ return;
174
+ }
175
+ }
176
+
177
+ const response = request.response;
178
+ const statusCode = response.isBoom ? response.output.statusCode : response.statusCode;
179
+
180
+ const statusSchema = request.route.settings.response.status[statusCode];
181
+ if (statusCode >= 400 &&
182
+ !statusSchema) {
183
+
184
+ return; // Do not validate errors by default
185
+ }
186
+
187
+ const schema = statusSchema !== undefined ? statusSchema : request.route.settings.response.schema;
188
+ if (schema === null) {
189
+ return; // No rules
190
+ }
191
+
192
+ if (!response.isBoom &&
193
+ request.response.variety !== 'plain') {
194
+
195
+ throw Boom.badImplementation('Cannot validate non-object response');
196
+ }
197
+
198
+ const localOptions = {
199
+ context: {
200
+ headers: request.headers,
201
+ params: request.params,
202
+ query: request.query,
203
+ payload: request.payload,
204
+ state: request.state,
205
+ auth: request.auth,
206
+ app: {
207
+ route: request.route.settings.app,
208
+ request: request.app
209
+ }
210
+ }
211
+ };
212
+
213
+ const source = response.isBoom ? response.output.payload : response.source;
214
+ Hoek.merge(localOptions, request.route.settings.response.options);
215
+
216
+ try {
217
+ let value;
218
+
219
+ if (typeof schema !== 'function') {
220
+ value = await internals.validate(source, schema, localOptions);
221
+ }
222
+ else {
223
+ value = await schema(source, localOptions);
224
+ }
225
+
226
+ if (value !== undefined &&
227
+ request.route.settings.response.modify) {
228
+
229
+ if (response.isBoom) {
230
+ response.output.payload = value;
231
+ }
232
+ else {
233
+ response.source = value;
234
+ }
235
+ }
236
+ }
237
+ catch (err) {
238
+ return request._core.toolkit.failAction(request, request.route.settings.response.failAction, err, { tags: ['validation', 'response', 'error'] });
239
+ }
240
+ };
241
+
242
+
243
+ internals.validate = function (value, schema, options) {
244
+
245
+ if (typeof schema.validateAsync === 'function') {
246
+ return schema.validateAsync(value, options);
247
+ }
248
+
249
+ return schema.validate(value, options);
250
+ };
package/package.json CHANGED
@@ -1,6 +1,65 @@
1
1
  {
2
2
  "name": "hapii-js",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "description": "HTTP Server framework",
4
+ "version": "2.0.0",
5
+ "repository": "git://github.com/hapii-js/hapii",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "engines": {
9
+ "node": ">=14.15.0"
10
+ },
11
+ "files": [
12
+ "lib"
13
+ ],
14
+ "keywords": [
15
+ "framework",
16
+ "http",
17
+ "api",
18
+ "web"
19
+ ],
20
+ "eslintConfig": {
21
+ "extends": [
22
+ "plugin:@hapi/module"
23
+ ]
24
+ },
25
+ "dependencies": {
26
+ "@hapi/accept": "^6.0.3",
27
+ "@hapi/ammo": "^6.0.1",
28
+ "@hapi/boom": "^10.0.1",
29
+ "@hapi/bounce": "^3.0.2",
30
+ "@hapi/call": "^9.0.1",
31
+ "@hapi/catbox": "^12.1.1",
32
+ "@hapi/catbox-memory": "^6.0.2",
33
+ "@hapi/heavy": "^8.0.1",
34
+ "@hapi/hoek": "^11.0.7",
35
+ "@hapi/mimos": "^7.0.1",
36
+ "@hapi/podium": "^5.0.2",
37
+ "@hapi/shot": "^6.0.2",
38
+ "@hapi/somever": "^4.1.1",
39
+ "@hapi/statehood": "^8.2.1",
40
+ "@hapi/subtext": "^8.1.3",
41
+ "@hapi/teamwork": "^6.0.1",
42
+ "@hapi/topo": "^6.0.2",
43
+ "@hapi/validate": "^2.0.1"
44
+ },
45
+ "devDependencies": {
46
+ "@hapi/code": "^9.0.3",
47
+ "@hapi/eslint-plugin": "^6.0.0",
48
+ "@hapi/inert": "^7.1.0",
49
+ "@hapi/joi-legacy-test": "npm:@hapi/joi@^15.0.0",
50
+ "@hapi/lab": "^25.3.2",
51
+ "@hapi/vision": "^7.0.3",
52
+ "@hapi/wreck": "^18.1.0",
53
+ "@types/node": "^18.19.130",
54
+ "handlebars": "^4.7.9",
55
+ "joi": "^17.13.3",
56
+ "legacy-readable-stream": "npm:readable-stream@^1.0.34",
57
+ "typescript": "^5.9.3"
58
+ },
59
+ "scripts": {
60
+ "test": "lab -a @hapi/code -t 100 -L -m 5000 -Y",
61
+ "test-tap": "lab -a @hapi/code -r tap -o tests.tap -m 5000",
62
+ "test-cov-html": "lab -a @hapi/code -r html -o coverage.html -m 5000"
63
+ },
64
+ "license": "BSD-3-Clause"
65
+ }