@adonisjs/http-server 6.2.0-0 → 6.4.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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/build/index.d.ts +1 -4
  3. package/build/index.js +1 -4
  4. package/build/src/exceptions/main.d.ts +78 -0
  5. package/build/src/exceptions/{http_exception.js → main.js} +10 -3
  6. package/build/src/request.js +1 -1
  7. package/build/src/response.d.ts +3 -1
  8. package/build/src/response.js +11 -5
  9. package/build/src/router/brisk.d.ts +6 -2
  10. package/build/src/router/brisk.js +11 -3
  11. package/build/src/router/executor.js +5 -4
  12. package/build/src/{server → router}/factories/use_return_value.d.ts +0 -0
  13. package/build/src/{server → router}/factories/use_return_value.js +0 -0
  14. package/build/src/router/group.d.ts +3 -2
  15. package/build/src/router/group.js +8 -1
  16. package/build/src/router/lookup_store/route_finder.js +2 -2
  17. package/build/src/router/lookup_store/url_builder.js +11 -10
  18. package/build/src/router/resource.d.ts +1 -1
  19. package/build/src/router/resource.js +2 -2
  20. package/build/src/router/route.d.ts +3 -3
  21. package/build/src/router/route.js +1 -1
  22. package/build/src/server/factories/final_handler.js +2 -2
  23. package/build/src/server/main.d.ts +2 -3
  24. package/build/src/server/main.js +22 -14
  25. package/build/src/types/base.d.ts +1 -0
  26. package/build/src/types/server.d.ts +5 -1
  27. package/build/test_factories/http_context.d.ts +15 -0
  28. package/build/test_factories/http_context.js +24 -0
  29. package/build/test_factories/qs_parser_factory.d.ts +10 -0
  30. package/build/test_factories/qs_parser_factory.js +26 -0
  31. package/build/test_factories/request.d.ts +19 -0
  32. package/build/test_factories/request.js +42 -0
  33. package/build/test_factories/response.d.ts +19 -0
  34. package/build/test_factories/response.js +43 -0
  35. package/build/test_factories/router.d.ts +13 -0
  36. package/build/test_factories/router.js +20 -0
  37. package/build/test_factories/server_factory.d.ts +17 -0
  38. package/build/test_factories/server_factory.js +27 -0
  39. package/package.json +25 -21
  40. package/build/src/exceptions/abort_exception.d.ts +0 -5
  41. package/build/src/exceptions/abort_exception.js +0 -6
  42. package/build/src/exceptions/cannot_lookup_route.d.ts +0 -5
  43. package/build/src/exceptions/cannot_lookup_route.js +0 -5
  44. package/build/src/exceptions/http_exception.d.ts +0 -6
  45. package/build/src/exceptions/route_not_found.d.ts +0 -5
  46. package/build/src/exceptions/route_not_found.js +0 -5
package/README.md CHANGED
@@ -21,7 +21,7 @@ In order to ensure that the AdonisJS community is welcoming to all, please revie
21
21
  ## License
22
22
  AdonisJS HTTP server is open-sourced software licensed under the [MIT license](LICENSE.md).
23
23
 
24
- [gh-workflow-image]: https://img.shields.io/github/workflow/status/adonisjs/http-server/test?style=for-the-badge
24
+ [gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/adonisjs/http-server/test.yml?style=for-the-badge
25
25
  [gh-workflow-url]: https://github.com/adonisjs/http-server/actions/workflows/test.yml "Github action"
26
26
 
27
27
  [typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
package/build/index.d.ts CHANGED
@@ -9,7 +9,4 @@ export { defineConfig } from './src/define_config.js';
9
9
  export { RouteResource } from './src/router/resource.js';
10
10
  export { BriskRoute } from './src/router/brisk.js';
11
11
  export { HttpContext } from './src/http_context/main.js';
12
- export { HttpException } from './src/exceptions/http_exception.js';
13
- export { AbortException } from './src/exceptions/abort_exception.js';
14
- export { RouteNotFoundException } from './src/exceptions/route_not_found.js';
15
- export { CannotLookupRouteException } from './src/exceptions/cannot_lookup_route.js';
12
+ export * as errors from './src/exceptions/main.js';
package/build/index.js CHANGED
@@ -9,7 +9,4 @@ export { defineConfig } from './src/define_config.js';
9
9
  export { RouteResource } from './src/router/resource.js';
10
10
  export { BriskRoute } from './src/router/brisk.js';
11
11
  export { HttpContext } from './src/http_context/main.js';
12
- export { HttpException } from './src/exceptions/http_exception.js';
13
- export { AbortException } from './src/exceptions/abort_exception.js';
14
- export { RouteNotFoundException } from './src/exceptions/route_not_found.js';
15
- export { CannotLookupRouteException } from './src/exceptions/cannot_lookup_route.js';
12
+ export * as errors from './src/exceptions/main.js';
@@ -0,0 +1,78 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { Exception } from '@poppinss/utils';
3
+ import type { HttpContext } from '../http_context/main.js';
4
+ export declare const E_ROUTE_NOT_FOUND: new (args: [method: string, url: string], options?: ErrorOptions | undefined) => Exception;
5
+ export declare const E_CANNOT_LOOKUP_ROUTE: new (args: [routeIdentifier: string], options?: ErrorOptions | undefined) => Exception;
6
+ export declare const E_HTTP_EXCEPTION: {
7
+ new (message?: string | undefined, options?: (ErrorOptions & {
8
+ code?: string | undefined;
9
+ status?: number | undefined;
10
+ }) | undefined): {
11
+ body: any;
12
+ name: string;
13
+ help?: string | undefined;
14
+ code?: string | undefined;
15
+ status: number;
16
+ toString(): string;
17
+ readonly [Symbol.toStringTag]: string;
18
+ message: string;
19
+ stack?: string | undefined;
20
+ cause?: unknown;
21
+ };
22
+ code: string;
23
+ invoke(body: any, status: number, code?: string): {
24
+ body: any;
25
+ name: string;
26
+ help?: string | undefined;
27
+ code?: string | undefined;
28
+ status: number;
29
+ toString(): string;
30
+ readonly [Symbol.toStringTag]: string;
31
+ message: string;
32
+ stack?: string | undefined;
33
+ cause?: unknown;
34
+ };
35
+ help?: string | undefined;
36
+ status?: number | undefined;
37
+ message?: string | undefined;
38
+ captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
39
+ prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
40
+ stackTraceLimit: number;
41
+ };
42
+ export declare const E_HTTP_REQUEST_ABORTED: {
43
+ new (message?: string | undefined, options?: (ErrorOptions & {
44
+ code?: string | undefined;
45
+ status?: number | undefined;
46
+ }) | undefined): {
47
+ handle(error: any, ctx: HttpContext): void;
48
+ body: any;
49
+ name: string;
50
+ help?: string | undefined;
51
+ code?: string | undefined;
52
+ status: number;
53
+ toString(): string;
54
+ readonly [Symbol.toStringTag]: string;
55
+ message: string;
56
+ stack?: string | undefined;
57
+ cause?: unknown;
58
+ };
59
+ code: string;
60
+ invoke(body: any, status: number, code?: string): {
61
+ body: any;
62
+ name: string;
63
+ help?: string | undefined;
64
+ code?: string | undefined;
65
+ status: number;
66
+ toString(): string;
67
+ readonly [Symbol.toStringTag]: string;
68
+ message: string;
69
+ stack?: string | undefined;
70
+ cause?: unknown;
71
+ };
72
+ help?: string | undefined;
73
+ status?: number | undefined;
74
+ message?: string | undefined;
75
+ captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void;
76
+ prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined;
77
+ stackTraceLimit: number;
78
+ };
@@ -1,5 +1,7 @@
1
- import { Exception } from '@poppinss/utils';
2
- export class HttpException extends Exception {
1
+ import { createError, Exception } from '@poppinss/utils';
2
+ export const E_ROUTE_NOT_FOUND = createError('Cannot %s:%s', 'E_ROUTE_NOT_FOUND', 404);
3
+ export const E_CANNOT_LOOKUP_ROUTE = createError('Cannot lookup route "%s"', 'E_CANNOT_LOOKUP_ROUTE', 500);
4
+ export const E_HTTP_EXCEPTION = class HttpException extends Exception {
3
5
  body;
4
6
  static code = 'E_HTTP_EXCEPTION';
5
7
  static invoke(body, status, code = 'E_HTTP_EXCEPTION') {
@@ -17,4 +19,9 @@ export class HttpException extends Exception {
17
19
  error.body = body;
18
20
  return error;
19
21
  }
20
- }
22
+ };
23
+ export const E_HTTP_REQUEST_ABORTED = class AbortException extends E_HTTP_EXCEPTION {
24
+ handle(error, ctx) {
25
+ ctx.response.status(error.status).send(error.body);
26
+ }
27
+ };
@@ -62,7 +62,7 @@ export class Request extends Macroable {
62
62
  throw new Error('Cannot re-set initial body. Use "request.updateBody" instead');
63
63
  }
64
64
  this.updateBody(body);
65
- this.#originalRequestData = Object.freeze({ ...this.#requestData });
65
+ this.#originalRequestData = Object.freeze(lodash.cloneDeep(this.#requestData));
66
66
  }
67
67
  updateBody(body) {
68
68
  this.#requestBody = body;
@@ -13,7 +13,9 @@ export declare class Response extends Macroable {
13
13
  request: IncomingMessage;
14
14
  response: ServerResponse;
15
15
  get hasLazyBody(): boolean;
16
- get isStreamResponse(): boolean;
16
+ get hasContent(): boolean;
17
+ get hasStream(): boolean;
18
+ get content(): [any, boolean, (string | undefined)?] | undefined;
17
19
  lazyBody: Partial<{
18
20
  content: [any, boolean, string?];
19
21
  stream: [ResponseStream, ((error: NodeJS.ErrnoException) => [string, number?])?];
@@ -13,7 +13,7 @@ import { RuntimeException } from '@poppinss/utils';
13
13
  import contentDisposition from 'content-disposition';
14
14
  import { Redirect } from './redirect.js';
15
15
  import { CookieSerializer } from './cookies/serializer.js';
16
- import { AbortException } from './exceptions/abort_exception.js';
16
+ import { E_HTTP_REQUEST_ABORTED } from './exceptions/main.js';
17
17
  const statFn = promisify(stat);
18
18
  const CACHEABLE_HTTP_METHODS = ['GET', 'HEAD'];
19
19
  export class Response extends Macroable {
@@ -28,9 +28,15 @@ export class Response extends Macroable {
28
28
  get hasLazyBody() {
29
29
  return !!(this.lazyBody.content || this.lazyBody.fileToStream || this.lazyBody.stream);
30
30
  }
31
- get isStreamResponse() {
31
+ get hasContent() {
32
+ return !!this.lazyBody.content;
33
+ }
34
+ get hasStream() {
32
35
  return !!(this.lazyBody.stream || this.lazyBody.fileToStream);
33
36
  }
37
+ get content() {
38
+ return this.lazyBody.content;
39
+ }
34
40
  lazyBody = {};
35
41
  ctx;
36
42
  constructor(request, response, encryption, config, router, qs) {
@@ -351,7 +357,7 @@ export class Response extends Macroable {
351
357
  return handler;
352
358
  }
353
359
  abort(body, status) {
354
- throw AbortException.invoke(body, status || 400);
360
+ throw E_HTTP_REQUEST_ABORTED.invoke(body, status || 400);
355
361
  }
356
362
  abortIf(condition, body, status) {
357
363
  if (condition) {
@@ -402,8 +408,8 @@ export class Response extends Macroable {
402
408
  if (!this.isPending) {
403
409
  return;
404
410
  }
405
- if (this.lazyBody.content) {
406
- this.writeBody(...this.lazyBody.content);
411
+ if (this.content) {
412
+ this.writeBody(...this.content);
407
413
  return;
408
414
  }
409
415
  if (this.lazyBody.stream) {
@@ -11,6 +11,10 @@ export declare class BriskRoute extends Macroable {
11
11
  globalMatchers: RouteMatchers;
12
12
  });
13
13
  setHandler(handler: RouteFn): Route;
14
- redirect(identifier: string, params?: any[] | Record<string, any>, options?: MakeUrlOptions): Route;
15
- redirectToPath(url: string): Route;
14
+ redirect(identifier: string, params?: any[] | Record<string, any>, options?: MakeUrlOptions & {
15
+ status: number;
16
+ }): Route;
17
+ redirectToPath(url: string, options?: {
18
+ status: number;
19
+ }): Route;
16
20
  }
@@ -24,12 +24,20 @@ export class BriskRoute extends Macroable {
24
24
  }
25
25
  redirect(identifier, params, options) {
26
26
  return this.setHandler(async (ctx) => {
27
- return ctx.response.redirect().toRoute(identifier, params || ctx.params, options);
27
+ const redirector = ctx.response.redirect();
28
+ if (options?.status) {
29
+ redirector.status(options.status);
30
+ }
31
+ return redirector.toRoute(identifier, params || ctx.params, options);
28
32
  });
29
33
  }
30
- redirectToPath(url) {
34
+ redirectToPath(url, options) {
31
35
  return this.setHandler(async (ctx) => {
32
- return ctx.response.redirect().toPath(url);
36
+ const redirector = ctx.response.redirect();
37
+ if (options?.status) {
38
+ redirector.status(options.status);
39
+ }
40
+ return redirector.toPath(url);
33
41
  });
34
42
  }
35
43
  }
@@ -1,13 +1,14 @@
1
+ import { useReturnValue } from './factories/use_return_value.js';
1
2
  export function execute(route, resolver, ctx) {
2
3
  return route.middleware
3
4
  .runner()
4
- .finalHandler(() => {
5
+ .finalHandler(async () => {
5
6
  if (typeof route.handler === 'function') {
6
- return route.handler(ctx);
7
+ return Promise.resolve(route.handler(ctx)).then(useReturnValue(ctx));
7
8
  }
8
- return route.handler.handle(resolver, ctx);
9
+ return route.handler.handle(resolver, ctx).then(useReturnValue(ctx));
9
10
  })
10
- .run((middleware, next) => {
11
+ .run(async (middleware, next) => {
11
12
  if (typeof middleware === 'function') {
12
13
  return middleware(ctx, next);
13
14
  }
@@ -4,6 +4,7 @@ import type { MiddlewareFn, ParsedNamedMiddleware } from '../types/middleware.js
4
4
  import { Route } from './route.js';
5
5
  import { BriskRoute } from './brisk.js';
6
6
  import { RouteResource } from './resource.js';
7
+ import { OneOrMore } from '../types/base.js';
7
8
  export declare class RouteGroup extends Macroable {
8
9
  #private;
9
10
  routes: (Route | RouteGroup | RouteResource | BriskRoute)[];
@@ -12,6 +13,6 @@ export declare class RouteGroup extends Macroable {
12
13
  prefix(prefix: string): this;
13
14
  domain(domain: string): this;
14
15
  as(name: string): this;
15
- use(middleware: MiddlewareFn | ParsedNamedMiddleware): this;
16
- middleware(middleware: MiddlewareFn | ParsedNamedMiddleware): this;
16
+ use(middleware: OneOrMore<MiddlewareFn | ParsedNamedMiddleware>): this;
17
+ middleware(middleware: OneOrMore<MiddlewareFn | ParsedNamedMiddleware>): this;
17
18
  }
@@ -103,7 +103,14 @@ export class RouteGroup extends Macroable {
103
103
  if (!this.#middleware.length) {
104
104
  this.routes.forEach((route) => this.#shareMiddlewareStackWithRoutes(route));
105
105
  }
106
- this.#middleware.push(middleware);
106
+ if (Array.isArray(middleware)) {
107
+ for (let one of middleware) {
108
+ this.#middleware.push(one);
109
+ }
110
+ }
111
+ else {
112
+ this.#middleware.push(middleware);
113
+ }
107
114
  return this;
108
115
  }
109
116
  middleware(middleware) {
@@ -1,4 +1,4 @@
1
- import { CannotLookupRouteException } from '../../exceptions/cannot_lookup_route.js';
1
+ import * as errors from '../../exceptions/main.js';
2
2
  export class RouteFinder {
3
3
  #routes;
4
4
  constructor(routes) {
@@ -18,7 +18,7 @@ export class RouteFinder {
18
18
  findOrFail(routeIdentifier) {
19
19
  const route = this.find(routeIdentifier);
20
20
  if (!route) {
21
- throw new CannotLookupRouteException(`Cannot lookup route "${routeIdentifier}"`);
21
+ throw new errors.E_CANNOT_LOOKUP_ROUTE([routeIdentifier]);
22
22
  }
23
23
  return route;
24
24
  }
@@ -1,3 +1,4 @@
1
+ import matchit from '@poppinss/matchit';
1
2
  import { RuntimeException } from '@poppinss/utils';
2
3
  export class UrlBuilder {
3
4
  #qsParser;
@@ -27,30 +28,30 @@ export class UrlBuilder {
27
28
  const paramsArray = Array.isArray(this.#params) ? this.#params : null;
28
29
  const paramsObject = !Array.isArray(this.#params) ? this.#params : {};
29
30
  let paramsIndex = 0;
30
- const tokens = pattern.split('/');
31
+ const tokens = matchit.parse(pattern);
31
32
  for (const token of tokens) {
32
- if (token === '*') {
33
+ if (token.type === 0) {
34
+ uriSegments.push(`${token.val}${token.end}`);
35
+ }
36
+ else if (token.type === 2) {
33
37
  const values = paramsArray ? paramsArray.slice(paramsIndex) : paramsObject['*'];
34
38
  this.#ensureHasWildCardValues(pattern, values);
35
- values.forEach((value) => uriSegments.push(value));
39
+ uriSegments.push(`${values.join('/')}${token.end}`);
36
40
  break;
37
41
  }
38
- else if (!token.startsWith(':')) {
39
- uriSegments.push(token);
40
- }
41
42
  else {
42
- const paramName = token.replace(/^:/, '').replace(/\?$/, '');
43
+ const paramName = token.val;
43
44
  const value = paramsArray ? paramsArray[paramsIndex] : paramsObject[paramName];
44
- if (!token.endsWith('?')) {
45
+ if (token.type === 1) {
45
46
  this.#ensureHasParamValue(pattern, paramName, value);
46
47
  }
47
48
  paramsIndex++;
48
49
  if (value !== undefined && value !== null) {
49
- uriSegments.push(value);
50
+ uriSegments.push(`${value}${token.end}`);
50
51
  }
51
52
  }
52
53
  }
53
- return uriSegments.join('/');
54
+ return `/${uriSegments.join('/')}`;
54
55
  }
55
56
  #suffixQueryString(url, qs) {
56
57
  if (qs) {
@@ -21,5 +21,5 @@ export declare class RouteResource extends Macroable {
21
21
  params(resources: {
22
22
  [resource: string]: string;
23
23
  }): this;
24
- as(name: string): this;
24
+ as(name: string, normalizeName?: boolean): this;
25
25
  }
@@ -113,8 +113,8 @@ export class RouteResource extends Macroable {
113
113
  });
114
114
  return this;
115
115
  }
116
- as(name) {
117
- name = string.snakeCase(name);
116
+ as(name, normalizeName = true) {
117
+ name = normalizeName ? string.snakeCase(name) : name;
118
118
  this.routes.forEach((route) => {
119
119
  route.as(route.getName().replace(this.#routesBaseName, name), false);
120
120
  });
@@ -1,6 +1,6 @@
1
1
  import { Macroable } from '@poppinss/macroable';
2
2
  import type { Application } from '@adonisjs/application';
3
- import type { Constructor, LazyImport } from '../types/base.js';
3
+ import type { Constructor, LazyImport, OneOrMore } from '../types/base.js';
4
4
  import type { MiddlewareFn, ParsedNamedMiddleware, ParsedGlobalMiddleware } from '../types/middleware.js';
5
5
  import type { GetControllerHandlers, RouteFn, RouteJSON, RouteMatcher, RouteMatchers, StoreRouteMiddleware } from '../types/route.js';
6
6
  export declare class Route<Controller extends Constructor<any> = any> extends Macroable {
@@ -14,8 +14,8 @@ export declare class Route<Controller extends Constructor<any> = any> extends Ma
14
14
  where(param: string, matcher: RouteMatcher | string | RegExp): this;
15
15
  prefix(prefix: string): this;
16
16
  domain(domain: string, overwrite?: boolean): this;
17
- use(middleware: MiddlewareFn | ParsedNamedMiddleware): this;
18
- middleware(middleware: MiddlewareFn | ParsedNamedMiddleware): this;
17
+ use(middleware: OneOrMore<MiddlewareFn | ParsedNamedMiddleware>): this;
18
+ middleware(middleware: OneOrMore<MiddlewareFn | ParsedNamedMiddleware>): this;
19
19
  as(name: string, prepend?: boolean): this;
20
20
  isDeleted(): boolean;
21
21
  markAsDeleted(): void;
@@ -86,7 +86,7 @@ export class Route extends Macroable {
86
86
  return this;
87
87
  }
88
88
  use(middleware) {
89
- this.#middleware.push([middleware]);
89
+ this.#middleware.push(Array.isArray(middleware) ? middleware : [middleware]);
90
90
  return this;
91
91
  }
92
92
  middleware(middleware) {
@@ -1,4 +1,4 @@
1
- import { RouteNotFoundException } from '../../exceptions/route_not_found.js';
1
+ import * as errors from '../../exceptions/main.js';
2
2
  export function finalHandler(router, resolver, ctx) {
3
3
  return function () {
4
4
  const url = ctx.request.url();
@@ -12,6 +12,6 @@ export function finalHandler(router, resolver, ctx) {
12
12
  ctx.routeKey = route.routeKey;
13
13
  return route.route.execute(route.route, resolver, ctx);
14
14
  }
15
- throw new RouteNotFoundException(`Cannot ${method}:${url}`);
15
+ return Promise.reject(new errors.E_ROUTE_NOT_FOUND([method, url]));
16
16
  };
17
17
  }
@@ -1,5 +1,6 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
2
  /// <reference types="node" resolution-mode="require"/>
3
+ import { Emitter } from '@adonisjs/events';
3
4
  import type { Encryption } from '@adonisjs/encryption';
4
5
  import type { Server as HttpsServer } from 'node:https';
5
6
  import type { Application } from '@adonisjs/application';
@@ -8,17 +9,15 @@ import type { LazyImport } from '../types/base.js';
8
9
  import type { MiddlewareAsClass } from '../types/middleware.js';
9
10
  import type { ErrorHandlerAsAClass, ServerConfig } from '../types/server.js';
10
11
  import { Router } from '../router/main.js';
11
- import { HttpContext } from '../http_context/main.js';
12
12
  export declare class Server {
13
13
  #private;
14
14
  get usingAsyncLocalStorage(): boolean;
15
- constructor(app: Application<any, any>, encryption: Encryption, config: ServerConfig);
15
+ constructor(app: Application<any, any>, encryption: Encryption, emitter: Emitter<any>, config: ServerConfig);
16
16
  use(middleware: LazyImport<MiddlewareAsClass>[]): this;
17
17
  errorHandler(handler: LazyImport<ErrorHandlerAsAClass>): this;
18
18
  boot(): Promise<void>;
19
19
  setNodeServer(server: HttpServer | HttpsServer): void;
20
20
  getNodeServer(): HttpServer<typeof IncomingMessage, typeof ServerResponse> | HttpsServer<typeof IncomingMessage, typeof ServerResponse> | undefined;
21
21
  getRouter(): Router;
22
- onRequest(callback: (ctx: HttpContext) => void): this;
23
22
  handle(req: IncomingMessage, res: ServerResponse): Promise<any>;
24
23
  }
@@ -1,3 +1,4 @@
1
+ import onFinished from 'on-finished';
1
2
  import Middleware from '@poppinss/middleware';
2
3
  import { moduleImporter } from '@adonisjs/fold';
3
4
  import { Qs } from '../qs.js';
@@ -8,17 +9,17 @@ import { Router } from '../router/main.js';
8
9
  import { HttpContext } from '../http_context/main.js';
9
10
  import { finalHandler } from './factories/final_handler.js';
10
11
  import { writeResponse } from './factories/write_response.js';
11
- import { useReturnValue } from './factories/use_return_value.js';
12
12
  import { asyncLocalStorage } from '../http_context/local_storage.js';
13
13
  import { middlewareHandler } from './factories/middleware_handler.js';
14
14
  export class Server {
15
- #requestHooks = new Set();
16
- #errorHandler;
17
- #resolvedErrorHandler = {
15
+ #defaultErrorHandler = {
18
16
  handle(error, ctx) {
19
17
  ctx.response.status(error.status || 500).send(error.message || 'Internal server error');
20
18
  },
21
19
  };
20
+ #errorHandler;
21
+ #resolvedErrorHandler = this.#defaultErrorHandler;
22
+ #emitter;
22
23
  #app;
23
24
  #encryption;
24
25
  #config;
@@ -30,8 +31,9 @@ export class Server {
30
31
  get usingAsyncLocalStorage() {
31
32
  return asyncLocalStorage.isEnabled;
32
33
  }
33
- constructor(app, encryption, config) {
34
+ constructor(app, encryption, emitter, config) {
34
35
  this.#app = app;
36
+ this.#emitter = emitter;
35
37
  this.#config = config;
36
38
  this.#encryption = encryption;
37
39
  this.#qsParser = new Qs(this.#config.qs);
@@ -56,10 +58,13 @@ export class Server {
56
58
  }
57
59
  #handleRequest(ctx, resolver) {
58
60
  return this.#serverMiddlewareStack.runner()
61
+ .errorHandler((error) => this.#resolvedErrorHandler.handle(error, ctx))
59
62
  .finalHandler(finalHandler(this.#router, resolver, ctx))
60
63
  .run(middlewareHandler(resolver, ctx))
61
- .then(useReturnValue(ctx))
62
- .catch((error) => this.#resolvedErrorHandler.handle(error, ctx))
64
+ .catch((error) => {
65
+ ctx.logger.fatal({ err: error }, 'Exception raised by error handler');
66
+ return this.#defaultErrorHandler.handle(error, ctx);
67
+ })
63
68
  .finally(writeResponse(ctx));
64
69
  }
65
70
  use(middleware) {
@@ -91,17 +96,20 @@ export class Server {
91
96
  getRouter() {
92
97
  return this.#router;
93
98
  }
94
- onRequest(callback) {
95
- this.#requestHooks.add(callback);
96
- return this;
97
- }
98
99
  handle(req, res) {
100
+ const hasRequestListener = this.#emitter.hasListeners('http:request_finished');
101
+ const startTime = hasRequestListener ? process.hrtime() : null;
99
102
  const resolver = this.#app.container.createResolver();
100
103
  const request = new Request(req, res, this.#encryption, this.#config, this.#qsParser);
101
104
  const response = new Response(req, res, this.#encryption, this.#config, this.#router, this.#qsParser);
102
- const ctx = new HttpContext(request, response, this.#app.logger.child({}), resolver);
103
- for (let hook of this.#requestHooks) {
104
- hook(ctx);
105
+ const ctx = new HttpContext(request, response, this.#app.logger.child({ request_id: request.id() }), resolver);
106
+ if (startTime) {
107
+ onFinished(res, () => {
108
+ this.#emitter.emit('http:request_finished', {
109
+ ctx: ctx,
110
+ duration: process.hrtime(startTime),
111
+ });
112
+ });
105
113
  }
106
114
  if (this.usingAsyncLocalStorage) {
107
115
  return asyncLocalStorage.storage.run(ctx, () => this.#handleRequest(ctx, resolver));
@@ -1,4 +1,5 @@
1
1
  export type { NextFn } from '@poppinss/middleware/types';
2
+ export type OneOrMore<T> = T | T[];
2
3
  export type Constructor<T> = new (...args: any[]) => T;
3
4
  export type LazyImport<DefaultExport> = () => Promise<{
4
5
  default: DefaultExport;
@@ -1,8 +1,12 @@
1
- import type { QSParserConfig } from './qs.js';
2
1
  import type { Constructor } from './base.js';
2
+ import type { QSParserConfig } from './qs.js';
3
3
  import type { RequestConfig } from './request.js';
4
4
  import type { ResponseConfig } from './response.js';
5
5
  import type { HttpContext } from '../http_context/main.js';
6
+ export type HttpRequestFinishedPayload = {
7
+ ctx: HttpContext;
8
+ duration: [number, number];
9
+ };
6
10
  export type ServerErrorHandler = {
7
11
  handle: (error: any, ctx: HttpContext) => any;
8
12
  };
@@ -0,0 +1,15 @@
1
+ import type { Logger } from '@adonisjs/logger';
2
+ import type { Request } from '../src/request.js';
3
+ import type { Response } from '../src/response.js';
4
+ import { HttpContext } from '../src/http_context/main.js';
5
+ type FactoryParameters = {
6
+ request: Request;
7
+ response: Response;
8
+ logger: Logger;
9
+ };
10
+ export declare class HttpContextFactory {
11
+ #private;
12
+ merge(params: Partial<FactoryParameters>): this;
13
+ create(): HttpContext;
14
+ }
15
+ export {};
@@ -0,0 +1,24 @@
1
+ import { Container } from '@adonisjs/fold';
2
+ import { LoggerFactory } from '@adonisjs/logger/test_factories/logger';
3
+ import { RequestFactory } from './request.js';
4
+ import { ResponseFactory } from './response.js';
5
+ import { HttpContext } from '../src/http_context/main.js';
6
+ export class HttpContextFactory {
7
+ #parameters = {};
8
+ #createRequest() {
9
+ return this.#parameters.request || new RequestFactory().create();
10
+ }
11
+ #createResponse() {
12
+ return this.#parameters.response || new ResponseFactory().create();
13
+ }
14
+ #createLogger() {
15
+ return this.#parameters.logger || new LoggerFactory().create();
16
+ }
17
+ merge(params) {
18
+ Object.assign(this.#parameters, params);
19
+ return this;
20
+ }
21
+ create() {
22
+ return new HttpContext(this.#createRequest(), this.#createResponse(), this.#createLogger(), new Container().createResolver());
23
+ }
24
+ }
@@ -0,0 +1,10 @@
1
+ import { Qs } from '../src/qs.js';
2
+ import type { QSParserConfig } from '../src/types/qs.js';
3
+ export declare class QsParserFactory {
4
+ #private;
5
+ merge(options: Partial<{
6
+ parse: Partial<QSParserConfig['parse']>;
7
+ stringify: Partial<QSParserConfig['stringify']>;
8
+ }>): this;
9
+ create(): Qs;
10
+ }
@@ -0,0 +1,26 @@
1
+ import { Qs } from '../src/qs.js';
2
+ export class QsParserFactory {
3
+ #options = {
4
+ parse: {
5
+ depth: 5,
6
+ parameterLimit: 1000,
7
+ allowSparse: false,
8
+ arrayLimit: 20,
9
+ comma: true,
10
+ },
11
+ stringify: {
12
+ encode: true,
13
+ encodeValuesOnly: false,
14
+ arrayFormat: 'indices',
15
+ skipNulls: false,
16
+ },
17
+ };
18
+ merge(options) {
19
+ Object.assign(this.#options.parse, options.parse);
20
+ Object.assign(this.#options.stringify, options.stringify);
21
+ return this;
22
+ }
23
+ create() {
24
+ return new Qs(this.#options);
25
+ }
26
+ }
@@ -0,0 +1,19 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import type { Encryption } from '@adonisjs/encryption';
3
+ import { IncomingMessage, ServerResponse } from 'node:http';
4
+ import { Request } from '../src/request.js';
5
+ import { RequestConfig } from '../src/types/request.js';
6
+ type FactoryParameters = {
7
+ url: string;
8
+ method: string;
9
+ req: IncomingMessage;
10
+ res: ServerResponse;
11
+ encryption: Encryption;
12
+ config: Partial<RequestConfig>;
13
+ };
14
+ export declare class RequestFactory {
15
+ #private;
16
+ merge(params: Partial<FactoryParameters>): this;
17
+ create(): Request;
18
+ }
19
+ export {};
@@ -0,0 +1,42 @@
1
+ import { Socket } from 'node:net';
2
+ import proxyAddr from 'proxy-addr';
3
+ import { IncomingMessage, ServerResponse } from 'node:http';
4
+ import { EncryptionFactory } from '@adonisjs/encryption/test_factories/encryption';
5
+ import { Request } from '../src/request.js';
6
+ import { QsParserFactory } from './qs_parser_factory.js';
7
+ export class RequestFactory {
8
+ #parameters = {};
9
+ #getConfig() {
10
+ return {
11
+ allowMethodSpoofing: false,
12
+ trustProxy: proxyAddr.compile('loopback'),
13
+ subdomainOffset: 2,
14
+ generateRequestId: true,
15
+ ...this.#parameters.config,
16
+ };
17
+ }
18
+ #createRequest() {
19
+ const req = this.#parameters.req || new IncomingMessage(new Socket());
20
+ if (this.#parameters.url) {
21
+ req.url = this.#parameters.url;
22
+ }
23
+ if (this.#parameters.method) {
24
+ req.method = this.#parameters.method;
25
+ }
26
+ return req;
27
+ }
28
+ #createResponse(req) {
29
+ return this.#parameters.res || new ServerResponse(req);
30
+ }
31
+ #createEncryption() {
32
+ return this.#parameters.encryption || new EncryptionFactory().create();
33
+ }
34
+ merge(params) {
35
+ Object.assign(this.#parameters, params);
36
+ return this;
37
+ }
38
+ create() {
39
+ const req = this.#createRequest();
40
+ return new Request(req, this.#createResponse(req), this.#createEncryption(), this.#getConfig(), new QsParserFactory().create());
41
+ }
42
+ }
@@ -0,0 +1,19 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import type { Encryption } from '@adonisjs/encryption';
3
+ import { IncomingMessage, ServerResponse } from 'node:http';
4
+ import { Response } from '../src/response.js';
5
+ import { Router } from '../src/router/main.js';
6
+ import { ResponseConfig } from '../src/types/response.js';
7
+ type FactoryParameters = {
8
+ req: IncomingMessage;
9
+ res: ServerResponse;
10
+ encryption: Encryption;
11
+ config: Partial<ResponseConfig>;
12
+ router: Router;
13
+ };
14
+ export declare class ResponseFactory {
15
+ #private;
16
+ merge(params: Partial<FactoryParameters>): this;
17
+ create(): Response;
18
+ }
19
+ export {};
@@ -0,0 +1,43 @@
1
+ import { Socket } from 'node:net';
2
+ import { IncomingMessage, ServerResponse } from 'node:http';
3
+ import { EncryptionFactory } from '@adonisjs/encryption/test_factories/encryption';
4
+ import { RouterFactory } from './router.js';
5
+ import { Response } from '../src/response.js';
6
+ import { QsParserFactory } from './qs_parser_factory.js';
7
+ export class ResponseFactory {
8
+ #parameters = {};
9
+ #getConfig() {
10
+ return {
11
+ etag: false,
12
+ jsonpCallbackName: 'callback',
13
+ cookie: {
14
+ maxAge: 90,
15
+ path: '/',
16
+ httpOnly: true,
17
+ sameSite: false,
18
+ secure: false,
19
+ },
20
+ ...this.#parameters.config,
21
+ };
22
+ }
23
+ #createRequest() {
24
+ return this.#parameters.req || new IncomingMessage(new Socket());
25
+ }
26
+ #createRouter() {
27
+ return this.#parameters.router || new RouterFactory().create();
28
+ }
29
+ #createResponse(req) {
30
+ return this.#parameters.res || new ServerResponse(req);
31
+ }
32
+ #createEncryption() {
33
+ return this.#parameters.encryption || new EncryptionFactory().create();
34
+ }
35
+ merge(params) {
36
+ Object.assign(this.#parameters, params);
37
+ return this;
38
+ }
39
+ create() {
40
+ const req = this.#createRequest();
41
+ return new Response(req, this.#createResponse(req), this.#createEncryption(), this.#getConfig(), this.#createRouter(), new QsParserFactory().create());
42
+ }
43
+ }
@@ -0,0 +1,13 @@
1
+ import type { Encryption } from '@adonisjs/encryption';
2
+ import type { Application } from '@adonisjs/application';
3
+ import { Router } from '../src/router/main.js';
4
+ type FactoryParameters = {
5
+ app: Application<any, any>;
6
+ encryption: Encryption;
7
+ };
8
+ export declare class RouterFactory {
9
+ #private;
10
+ merge(params: Partial<FactoryParameters>): this;
11
+ create(): Router;
12
+ }
13
+ export {};
@@ -0,0 +1,20 @@
1
+ import { AppFactory } from '@adonisjs/application/test_factories/app';
2
+ import { EncryptionFactory } from '@adonisjs/encryption/test_factories/encryption';
3
+ import { Router } from '../src/router/main.js';
4
+ import { QsParserFactory } from './qs_parser_factory.js';
5
+ export class RouterFactory {
6
+ #parameters = {};
7
+ #getApp() {
8
+ return this.#parameters.app || new AppFactory().create(new URL('./app/', import.meta.url));
9
+ }
10
+ #createEncryption() {
11
+ return this.#parameters.encryption || new EncryptionFactory().create();
12
+ }
13
+ merge(params) {
14
+ Object.assign(this.#parameters, params);
15
+ return this;
16
+ }
17
+ create() {
18
+ return new Router(this.#getApp(), this.#createEncryption(), new QsParserFactory().create());
19
+ }
20
+ }
@@ -0,0 +1,17 @@
1
+ import { Emitter } from '@adonisjs/events';
2
+ import type { Encryption } from '@adonisjs/encryption';
3
+ import type { Application } from '@adonisjs/application';
4
+ import { Server } from '../src/server/main.js';
5
+ import type { ServerConfig } from '../src/types/server.js';
6
+ type FactoryParameters = {
7
+ app: Application<any, any>;
8
+ encryption: Encryption;
9
+ emitter: Emitter<any>;
10
+ config: Partial<ServerConfig>;
11
+ };
12
+ export declare class ServerFactory {
13
+ #private;
14
+ merge(params: Partial<FactoryParameters>): this;
15
+ create(): Server;
16
+ }
17
+ export {};
@@ -0,0 +1,27 @@
1
+ import { Emitter } from '@adonisjs/events';
2
+ import { AppFactory } from '@adonisjs/application/test_factories/app';
3
+ import { EncryptionFactory } from '@adonisjs/encryption/test_factories/encryption';
4
+ import { Server } from '../src/server/main.js';
5
+ import { defineConfig } from '../src/define_config.js';
6
+ export class ServerFactory {
7
+ #parameters = {};
8
+ #getEmitter() {
9
+ return this.#parameters.emitter || new Emitter(this.#getApp());
10
+ }
11
+ #getConfig() {
12
+ return defineConfig(this.#parameters.config || {});
13
+ }
14
+ #getApp() {
15
+ return this.#parameters.app || new AppFactory().create(new URL('./app/', import.meta.url));
16
+ }
17
+ #createEncryption() {
18
+ return this.#parameters.encryption || new EncryptionFactory().create();
19
+ }
20
+ merge(params) {
21
+ Object.assign(this.#parameters, params);
22
+ return this;
23
+ }
24
+ create() {
25
+ return new Server(this.#getApp(), this.#createEncryption(), this.#getEmitter(), this.#getConfig());
26
+ }
27
+ }
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "@adonisjs/http-server",
3
- "version": "6.2.0-0",
3
+ "version": "6.4.0-0",
4
4
  "description": "AdonisJS HTTP server with support packed with Routing and Cookies",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
7
7
  "files": [
8
8
  "build/src",
9
+ "build/test_factories",
9
10
  "build/index.d.ts",
10
11
  "build/index.js"
11
12
  ],
12
13
  "exports": {
13
14
  ".": "./build/index.js",
14
- "./types": "./build/src/types/main.js"
15
+ "./types": "./build/src/types/main.js",
16
+ "./test_factories/*": "./build/test_factories/*.js"
15
17
  },
16
18
  "scripts": {
17
19
  "pretest": "npm run lint",
@@ -37,13 +39,14 @@
37
39
  "author": "virk,adonisjs",
38
40
  "license": "MIT",
39
41
  "devDependencies": {
40
- "@adonisjs/application": "^6.4.0-0",
41
- "@adonisjs/config": "^4.1.2-0",
42
- "@adonisjs/encryption": "^5.0.2-0",
43
- "@adonisjs/fold": "^9.8.0-0",
44
- "@adonisjs/logger": "^5.1.0-0",
45
- "@commitlint/cli": "^17.3.0",
46
- "@commitlint/config-conventional": "^17.3.0",
42
+ "@adonisjs/application": "^6.6.0-0",
43
+ "@adonisjs/config": "^4.1.3-0",
44
+ "@adonisjs/encryption": "^5.1.0-0",
45
+ "@adonisjs/events": "^8.4.0-0",
46
+ "@adonisjs/fold": "^9.9.0-0",
47
+ "@adonisjs/logger": "^5.2.0-0",
48
+ "@commitlint/cli": "^17.4.1",
49
+ "@commitlint/config-conventional": "^17.4.0",
47
50
  "@fastify/middie": "^8.1.0",
48
51
  "@japa/api-client": "^1.4.2",
49
52
  "@japa/assert": "^1.3.6",
@@ -51,7 +54,7 @@
51
54
  "@japa/run-failed-tests": "^1.1.0",
52
55
  "@japa/runner": "^2.2.2",
53
56
  "@japa/spec-reporter": "^1.3.2",
54
- "@swc/core": "^1.3.22",
57
+ "@swc/core": "^1.3.26",
55
58
  "@types/accepts": "^1.3.5",
56
59
  "@types/content-disposition": "^0.5.5",
57
60
  "@types/cookie": "^0.5.1",
@@ -59,7 +62,7 @@
59
62
  "@types/encodeurl": "^1.0.0",
60
63
  "@types/etag": "^1.8.1",
61
64
  "@types/fresh": "^0.5.0",
62
- "@types/fs-extra": "^9.0.13",
65
+ "@types/fs-extra": "^11.0.1",
63
66
  "@types/http-status-codes": "^1.2.0",
64
67
  "@types/mime-types": "^2.1.1",
65
68
  "@types/on-finished": "^2.3.1",
@@ -73,16 +76,16 @@
73
76
  "c8": "^7.12.0",
74
77
  "cross-env": "^7.0.3",
75
78
  "del-cli": "^5.0.0",
76
- "eslint": "^8.28.0",
77
- "eslint-config-prettier": "^8.5.0",
79
+ "eslint": "^8.31.0",
80
+ "eslint-config-prettier": "^8.6.0",
78
81
  "eslint-plugin-adonis": "^3.0.3",
79
82
  "eslint-plugin-prettier": "^4.2.1",
80
- "fastify": "^4.10.2",
83
+ "fastify": "^4.11.0",
81
84
  "fs-extra": "^11.1.0",
82
85
  "github-label-sync": "^2.2.0",
83
86
  "http-status-codes": "^2.2.0",
84
- "husky": "^8.0.2",
85
- "np": "^7.6.2",
87
+ "husky": "^8.0.3",
88
+ "np": "^7.6.3",
86
89
  "pem": "^1.14.6",
87
90
  "prettier": "^2.8.1",
88
91
  "reflect-metadata": "^0.1.13",
@@ -93,8 +96,8 @@
93
96
  "dependencies": {
94
97
  "@poppinss/macroable": "^1.0.0-0",
95
98
  "@poppinss/matchit": "^3.1.2",
96
- "@poppinss/middleware": "^3.0.0",
97
- "@poppinss/utils": "^6.1.0-0",
99
+ "@poppinss/middleware": "^3.1.0",
100
+ "@poppinss/utils": "^6.3.1-0",
98
101
  "@sindresorhus/is": "^5.3.0",
99
102
  "accepts": "^1.3.8",
100
103
  "content-disposition": "^0.5.4",
@@ -113,9 +116,10 @@
113
116
  "vary": "^1.1.2"
114
117
  },
115
118
  "peerDependencies": {
116
- "@adonisjs/application": "^6.4.0-0",
117
- "@adonisjs/encryption": "^5.0.2-0",
118
- "@adonisjs/fold": "^9.8.0-0"
119
+ "@adonisjs/application": "^6.6.0-0",
120
+ "@adonisjs/encryption": "^5.1.0-0",
121
+ "@adonisjs/events": "^8.4.0-0",
122
+ "@adonisjs/fold": "^9.9.0-0"
119
123
  },
120
124
  "repository": {
121
125
  "type": "git",
@@ -1,5 +0,0 @@
1
- import { HttpException } from './http_exception.js';
2
- import type { HttpContext } from '../http_context/main.js';
3
- export declare class AbortException extends HttpException {
4
- handle(error: HttpException, ctx: HttpContext): void;
5
- }
@@ -1,6 +0,0 @@
1
- import { HttpException } from './http_exception.js';
2
- export class AbortException extends HttpException {
3
- handle(error, ctx) {
4
- ctx.response.status(error.status).send(error.body);
5
- }
6
- }
@@ -1,5 +0,0 @@
1
- import { Exception } from '@poppinss/utils';
2
- export declare class CannotLookupRouteException extends Exception {
3
- static status: number;
4
- static code: string;
5
- }
@@ -1,5 +0,0 @@
1
- import { Exception } from '@poppinss/utils';
2
- export class CannotLookupRouteException extends Exception {
3
- static status = 500;
4
- static code = 'E_CANNOT_LOOKUP_ROUTE';
5
- }
@@ -1,6 +0,0 @@
1
- import { Exception } from '@poppinss/utils';
2
- export declare class HttpException extends Exception {
3
- body: any;
4
- static code: string;
5
- static invoke(body: any, status: number, code?: string): HttpException;
6
- }
@@ -1,5 +0,0 @@
1
- import { HttpException } from './http_exception.js';
2
- export declare class RouteNotFoundException extends HttpException {
3
- static status: number;
4
- static code: string;
5
- }
@@ -1,5 +0,0 @@
1
- import { HttpException } from './http_exception.js';
2
- export class RouteNotFoundException extends HttpException {
3
- static status = 404;
4
- static code = 'E_ROUTE_NOT_FOUND';
5
- }