@opra/nestjs 1.0.0-alpha.8 → 1.0.0-beta.1

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.
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const external_exception_filter_1 = require("@nestjs/core/exceptions/external-exception-filter");
4
- const oldCatchMethod = external_exception_filter_1.ExternalExceptionFilter.prototype.catch;
5
- external_exception_filter_1.ExternalExceptionFilter.prototype.catch = function (exception, host) {
3
+ const external_exception_filter_js_1 = require("@nestjs/core/exceptions/external-exception-filter.js");
4
+ const oldCatchMethod = external_exception_filter_js_1.ExternalExceptionFilter.prototype.catch;
5
+ external_exception_filter_js_1.ExternalExceptionFilter.prototype.catch = function (exception, host) {
6
6
  const opraContext = host.getArgByIndex(3);
7
7
  // Prevents error logging for all Opra controllers
8
8
  if (opraContext && opraContext.request && opraContext.response)
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OPRA_HTTP_MODULE_OPTIONS = exports.IS_PUBLIC_KEY = void 0;
4
+ exports.IS_PUBLIC_KEY = 'opra:isPublic';
5
+ exports.OPRA_HTTP_MODULE_OPTIONS = 'OPRA_HTTP_MODULE_OPTIONS';
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Public = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const constants_js_1 = require("../constants.js");
6
+ const Public = () => (0, common_1.SetMetadata)(constants_js_1.IS_PUBLIC_KEY, true);
7
+ exports.Public = Public;
package/cjs/index.js CHANGED
@@ -3,5 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  require("reflect-metadata");
5
5
  require("./augmentation/nestjs.augmentation.js");
6
+ tslib_1.__exportStar(require("./constants.js"), exports);
7
+ tslib_1.__exportStar(require("./decorators/public.decorator.js"), exports);
6
8
  tslib_1.__exportStar(require("./opra-http.module.js"), exports);
7
9
  tslib_1.__exportStar(require("./opra-nestjs-adapter.js"), exports);
@@ -4,9 +4,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.OpraHttpCoreModule = void 0;
5
5
  const tslib_1 = require("tslib");
6
6
  const common_1 = require("@nestjs/common");
7
+ const core_1 = require("@nestjs/core");
7
8
  const common_2 = require("@opra/common");
8
9
  const ts_gems_1 = require("ts-gems");
10
+ const constants_js_1 = require("./constants.js");
9
11
  const opra_nestjs_adapter_js_1 = require("./opra-nestjs-adapter.js");
12
+ const opra_exception_filter_js_1 = require("./services/opra-exception-filter.js");
10
13
  const opra_middleware_js_1 = require("./services/opra-middleware.js");
11
14
  let OpraHttpCoreModule = OpraHttpCoreModule_1 = class OpraHttpCoreModule {
12
15
  constructor(opraAdapter) {
@@ -18,21 +21,40 @@ let OpraHttpCoreModule = OpraHttpCoreModule_1 = class OpraHttpCoreModule {
18
21
  method: common_1.RequestMethod.ALL,
19
22
  });
20
23
  }
21
- static forRoot(options) {
22
- const opraAdapter = new opra_nestjs_adapter_js_1.OpraNestAdapter(options);
23
- const token = options?.id || opra_nestjs_adapter_js_1.OpraNestAdapter;
24
+ static forRoot(init, options) {
25
+ const opraAdapter = new opra_nestjs_adapter_js_1.OpraNestAdapter(init, options);
26
+ const token = init?.id || opra_nestjs_adapter_js_1.OpraNestAdapter;
24
27
  const providers = [
25
- ...(options?.providers || []),
28
+ ...(init?.providers || []),
29
+ {
30
+ provide: constants_js_1.OPRA_HTTP_MODULE_OPTIONS,
31
+ useValue: { ...options },
32
+ },
26
33
  {
27
34
  provide: opra_nestjs_adapter_js_1.OpraNestAdapter,
28
- useFactory: async () => {
35
+ inject: [core_1.ModuleRef],
36
+ useFactory: async (moduleRef) => {
29
37
  (0, ts_gems_1.asMutable)(opraAdapter).document = await common_2.ApiDocumentFactory.createDocument({
30
- ...options,
31
- api: { protocol: 'http', name: options.name, controllers: options.controllers },
38
+ ...init,
39
+ api: { protocol: 'http', name: init.name, controllers: init.controllers },
40
+ });
41
+ opraAdapter.interceptors.map(x => {
42
+ if ((0, common_2.isConstructor)(x)) {
43
+ return (ctx, next) => {
44
+ const interceptor = moduleRef.get(x);
45
+ if (typeof interceptor.intercept === 'function')
46
+ return interceptor.intercept(ctx, next());
47
+ };
48
+ }
49
+ return x;
32
50
  });
33
51
  return opraAdapter;
34
52
  },
35
53
  },
54
+ {
55
+ provide: core_1.APP_FILTER,
56
+ useClass: opra_exception_filter_js_1.OpraExceptionFilter,
57
+ },
36
58
  ];
37
59
  if (token !== opra_nestjs_adapter_js_1.OpraNestAdapter) {
38
60
  providers.push({
@@ -43,8 +65,8 @@ let OpraHttpCoreModule = OpraHttpCoreModule_1 = class OpraHttpCoreModule {
43
65
  return {
44
66
  module: OpraHttpCoreModule_1,
45
67
  controllers: opraAdapter.nestControllers,
46
- imports: [...(options?.imports || [])],
47
- exports: [...(options?.exports || []), token],
68
+ imports: [...(init?.imports || [])],
69
+ exports: [...(init?.exports || []), token],
48
70
  providers,
49
71
  };
50
72
  }
@@ -8,12 +8,13 @@ const opra_http_core_module_js_1 = require("./opra-http-core.module.js");
8
8
  let OpraHttpModule = OpraHttpModule_1 = class OpraHttpModule {
9
9
  /**
10
10
  *
11
+ * @param init
11
12
  * @param options
12
13
  */
13
- static forRoot(options) {
14
+ static forRoot(init, options) {
14
15
  return {
15
16
  module: OpraHttpModule_1,
16
- imports: [opra_http_core_module_js_1.OpraHttpCoreModule.forRoot(options)],
17
+ imports: [opra_http_core_module_js_1.OpraHttpCoreModule.forRoot(init, options)],
17
18
  };
18
19
  }
19
20
  };
@@ -1,28 +1,32 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OpraNestAdapter = exports.kHandler = void 0;
3
+ exports.OpraNestAdapter = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
5
6
  const common_1 = require("@nestjs/common");
7
+ const constants_js_1 = require("@nestjs/common/constants.js");
6
8
  const common_2 = require("@opra/common");
7
9
  const core_1 = require("@opra/core");
8
- const path_1 = tslib_1.__importDefault(require("path"));
9
10
  const ts_gems_1 = require("ts-gems");
10
- const opra_exception_filter_js_1 = require("./services/opra-exception-filter.js");
11
- exports.kHandler = Symbol.for('kHandler');
11
+ const public_decorator_js_1 = require("./decorators/public.decorator.js");
12
12
  class OpraNestAdapter extends core_1.HttpAdapter {
13
- constructor(options) {
13
+ constructor(init, options) {
14
14
  super((function () {
15
15
  const document = new common_2.ApiDocument();
16
16
  document.api = new common_2.HttpApi(document);
17
17
  return document;
18
- })(), options);
18
+ })(), {
19
+ ...options,
20
+ interceptors: options?.interceptors,
21
+ });
19
22
  this.nestControllers = [];
20
- let basePath = options.basePath || '/';
23
+ this.options = options;
24
+ let basePath = options?.basePath || '/';
21
25
  if (!basePath.startsWith('/'))
22
26
  basePath = '/' + basePath;
23
27
  this._addRootController(basePath);
24
- if (options.controllers)
25
- options.controllers.forEach(c => this._addToNestControllers(c, basePath));
28
+ if (init.controllers)
29
+ init.controllers.forEach(c => this._addToNestControllers(c, basePath, []));
26
30
  }
27
31
  async close() {
28
32
  //
@@ -31,7 +35,7 @@ class OpraNestAdapter extends core_1.HttpAdapter {
31
35
  const _this = this;
32
36
  let RootController = class RootController {
33
37
  schema(_req, next) {
34
- _this[exports.kHandler].sendDocumentSchema(_req.opraContext).catch(next);
38
+ _this.handler.sendDocumentSchema(_req.opraContext).catch(() => next());
35
39
  }
36
40
  };
37
41
  tslib_1.__decorate([
@@ -47,9 +51,12 @@ class OpraNestAdapter extends core_1.HttpAdapter {
47
51
  path: basePath,
48
52
  })
49
53
  ], RootController);
54
+ if (this.options?.schemaRouteIsPublic) {
55
+ (0, public_decorator_js_1.Public)()(RootController.prototype, 'schema', Object.getOwnPropertyDescriptor(RootController.prototype, 'schema'));
56
+ }
50
57
  this.nestControllers.push(RootController);
51
58
  }
52
- _addToNestControllers(sourceClass, currentPath) {
59
+ _addToNestControllers(sourceClass, currentPath, parentTree) {
53
60
  const metadata = Reflect.getMetadata(common_2.HTTP_CONTROLLER_METADATA, sourceClass);
54
61
  if (!metadata)
55
62
  return;
@@ -57,10 +64,16 @@ class OpraNestAdapter extends core_1.HttpAdapter {
57
64
  [sourceClass.name]: class extends sourceClass {
58
65
  },
59
66
  }[sourceClass.name];
60
- const newPath = metadata.path ? path_1.default.join(currentPath, metadata.path) : currentPath;
67
+ /** Copy metadata keys from source class to new one */
68
+ let metadataKeys;
69
+ OpraNestAdapter.copyDecoratorMetadataToChild(newClass, parentTree);
70
+ const newPath = metadata.path ? node_path_1.default.join(currentPath, metadata.path) : currentPath;
61
71
  const adapter = this;
62
- /** Inject exception filter */
63
- (0, common_1.UseFilters)(new opra_exception_filter_js_1.OpraExceptionFilter(adapter))(newClass);
72
+ // adapter.logger =
73
+ /** Disable default error handler. Errors will be handled by OpraExceptionFilter */
74
+ adapter.handler.onError = (context, error) => {
75
+ throw error;
76
+ };
64
77
  (0, common_1.Controller)()(newClass);
65
78
  this.nestControllers.push(newClass);
66
79
  if (metadata.operations) {
@@ -69,7 +82,8 @@ class OpraNestAdapter extends core_1.HttpAdapter {
69
82
  Object.defineProperty(newClass.prototype, k, {
70
83
  writable: true,
71
84
  /** NestJS handler method */
72
- async value(_req) {
85
+ async value(_req, _res) {
86
+ _res.statusCode = 200;
73
87
  const api = adapter.document.api;
74
88
  const controller = api.findController(sourceClass);
75
89
  const operation = controller?.operations.get(k);
@@ -84,18 +98,16 @@ class OpraNestAdapter extends core_1.HttpAdapter {
84
98
  });
85
99
  }
86
100
  /** Configure the HttpContext */
87
- context.adapter = adapter;
88
- context.document = adapter.document;
89
101
  context.operation = operation;
90
102
  context.controller = operation.owner;
91
103
  context.controllerInstance = this;
92
104
  context.operationHandler = operationHandler;
93
105
  /** Handle request */
94
- await adapter[exports.kHandler].handleRequest(context);
106
+ await adapter.handler.handleRequest(context);
95
107
  },
96
108
  });
97
109
  /** Copy metadata keys from source function to new one */
98
- const metadataKeys = Reflect.getOwnMetadataKeys(operationHandler);
110
+ metadataKeys = Reflect.getOwnMetadataKeys(operationHandler);
99
111
  const newFn = newClass.prototype[k];
100
112
  for (const key of metadataKeys) {
101
113
  const m = Reflect.getMetadata(key, operationHandler);
@@ -104,7 +116,7 @@ class OpraNestAdapter extends core_1.HttpAdapter {
104
116
  (0, common_1.Req)()(newClass.prototype, k, 0);
105
117
  (0, common_1.Res)()(newClass.prototype, k, 1);
106
118
  const descriptor = Object.getOwnPropertyDescriptor(newClass.prototype, k);
107
- const operationPath = newPath + (v.path || '');
119
+ const operationPath = v.mergePath ? newPath + (v.path || '') : node_path_1.default.posix.join(newPath, v.path || '');
108
120
  switch (v.method || 'GET') {
109
121
  case 'DELETE':
110
122
  /** Call @Delete decorator over new property */
@@ -147,7 +159,30 @@ class OpraNestAdapter extends core_1.HttpAdapter {
147
159
  for (const child of metadata.controllers) {
148
160
  if (!(0, common_2.isConstructor)(child))
149
161
  throw new TypeError('Controllers should be injectable a class');
150
- this._addToNestControllers(child, newPath);
162
+ this._addToNestControllers(child, newPath, [...parentTree, sourceClass]);
163
+ }
164
+ }
165
+ }
166
+ static copyDecoratorMetadataToChild(target, parentTree) {
167
+ for (const parent of parentTree) {
168
+ const metadataKeys = Reflect.getOwnMetadataKeys(parent);
169
+ for (const key of metadataKeys) {
170
+ if (typeof key === 'string' && key.startsWith('opra:') && !Reflect.hasOwnMetadata(key, target)) {
171
+ const metadata = Reflect.getMetadata(key, parent);
172
+ Reflect.defineMetadata(key, metadata, target);
173
+ continue;
174
+ }
175
+ if (key === constants_js_1.GUARDS_METADATA || key === constants_js_1.INTERCEPTORS_METADATA || key === constants_js_1.EXCEPTION_FILTERS_METADATA) {
176
+ const m1 = Reflect.getMetadata(key, target) || [];
177
+ const metadata = [...m1];
178
+ const m2 = Reflect.getOwnMetadata(key, parent) || [];
179
+ m2.forEach((t) => {
180
+ if (!metadata.includes(t)) {
181
+ metadata.push(t);
182
+ }
183
+ });
184
+ Reflect.defineMetadata(key, metadata, target);
185
+ }
151
186
  }
152
187
  }
153
188
  }
@@ -1,18 +1,26 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OpraExceptionFilter = exports.kHandler = void 0;
4
- const core_1 = require("@opra/core");
5
- exports.kHandler = Symbol.for('kHandler');
6
- class OpraExceptionFilter {
7
- constructor(adapter) {
8
- this.adapter = adapter;
3
+ exports.OpraExceptionFilter = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const core_1 = require("@nestjs/core");
7
+ const opra_nestjs_adapter_js_1 = require("../opra-nestjs-adapter.js");
8
+ let OpraExceptionFilter = class OpraExceptionFilter extends core_1.BaseExceptionFilter {
9
+ constructor(moduleRef) {
10
+ super();
11
+ this.moduleRef = moduleRef;
9
12
  }
10
13
  catch(exception, host) {
11
- const ctx = host.switchToHttp();
12
- const _res = ctx.getResponse();
13
- const error = (0, core_1.wrapException)(exception);
14
- const response = core_1.HttpOutgoing.from(_res);
15
- return this.adapter[exports.kHandler].sendErrorResponse(response, [error]);
14
+ const ctx = host.switchToHttp().getRequest().opraContext;
15
+ if (ctx) {
16
+ const adapter = this.moduleRef.get(opra_nestjs_adapter_js_1.OpraNestAdapter);
17
+ ctx.errors.push(exception);
18
+ return adapter.handler.sendResponse(ctx);
19
+ }
16
20
  }
17
- }
21
+ };
18
22
  exports.OpraExceptionFilter = OpraExceptionFilter;
23
+ exports.OpraExceptionFilter = OpraExceptionFilter = tslib_1.__decorate([
24
+ (0, common_1.Catch)(),
25
+ tslib_1.__metadata("design:paramtypes", [core_1.ModuleRef])
26
+ ], OpraExceptionFilter);
@@ -4,21 +4,33 @@ exports.OpraMiddleware = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const common_1 = require("@nestjs/common");
6
6
  const core_1 = require("@opra/core");
7
+ const constants_js_1 = require("../constants.js");
8
+ const opra_nestjs_adapter_js_1 = require("../opra-nestjs-adapter.js");
7
9
  let OpraMiddleware = class OpraMiddleware {
10
+ constructor(opraAdapter, options) {
11
+ this.opraAdapter = opraAdapter;
12
+ this.options = options;
13
+ }
8
14
  use(req, res, next) {
9
15
  const request = core_1.HttpIncoming.from(req);
10
16
  const response = core_1.HttpOutgoing.from(res);
11
17
  /** Create the HttpContext */
12
- req.opraContext = new core_1.HttpContext({
13
- adapter: {},
18
+ const context = new core_1.HttpContext({
19
+ adapter: this.opraAdapter,
14
20
  platform: req.route ? 'express' : 'fastify',
15
21
  request,
16
22
  response,
17
23
  });
18
- next();
24
+ req.opraContext = context;
25
+ this.opraAdapter
26
+ .emitAsync('createContext', context)
27
+ .then(() => next())
28
+ .catch(next);
19
29
  }
20
30
  };
21
31
  exports.OpraMiddleware = OpraMiddleware;
22
32
  exports.OpraMiddleware = OpraMiddleware = tslib_1.__decorate([
23
- (0, common_1.Injectable)()
33
+ (0, common_1.Injectable)(),
34
+ tslib_1.__param(1, (0, common_1.Inject)(constants_js_1.OPRA_HTTP_MODULE_OPTIONS)),
35
+ tslib_1.__metadata("design:paramtypes", [opra_nestjs_adapter_js_1.OpraNestAdapter, Object])
24
36
  ], OpraMiddleware);
@@ -1,4 +1,4 @@
1
- import { ExternalExceptionFilter } from '@nestjs/core/exceptions/external-exception-filter';
1
+ import { ExternalExceptionFilter } from '@nestjs/core/exceptions/external-exception-filter.js';
2
2
  const oldCatchMethod = ExternalExceptionFilter.prototype.catch;
3
3
  ExternalExceptionFilter.prototype.catch = function (exception, host) {
4
4
  const opraContext = host.getArgByIndex(3);
@@ -0,0 +1,2 @@
1
+ export const IS_PUBLIC_KEY = 'opra:isPublic';
2
+ export const OPRA_HTTP_MODULE_OPTIONS = 'OPRA_HTTP_MODULE_OPTIONS';
@@ -0,0 +1,3 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+ import { IS_PUBLIC_KEY } from '../constants.js';
3
+ export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
package/esm/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import 'reflect-metadata';
2
2
  import './augmentation/nestjs.augmentation.js';
3
+ export * from './constants.js';
4
+ export * from './decorators/public.decorator.js';
3
5
  export * from './opra-http.module.js';
4
6
  export * from './opra-nestjs-adapter.js';
@@ -1,9 +1,12 @@
1
1
  var OpraHttpCoreModule_1;
2
2
  import { __decorate, __metadata } from "tslib";
3
3
  import { Global, Module, RequestMethod, } from '@nestjs/common';
4
- import { ApiDocumentFactory } from '@opra/common';
4
+ import { APP_FILTER, ModuleRef } from '@nestjs/core';
5
+ import { ApiDocumentFactory, isConstructor } from '@opra/common';
5
6
  import { asMutable } from 'ts-gems';
7
+ import { OPRA_HTTP_MODULE_OPTIONS } from './constants.js';
6
8
  import { OpraNestAdapter } from './opra-nestjs-adapter.js';
9
+ import { OpraExceptionFilter } from './services/opra-exception-filter.js';
7
10
  import { OpraMiddleware } from './services/opra-middleware.js';
8
11
  let OpraHttpCoreModule = OpraHttpCoreModule_1 = class OpraHttpCoreModule {
9
12
  constructor(opraAdapter) {
@@ -15,21 +18,40 @@ let OpraHttpCoreModule = OpraHttpCoreModule_1 = class OpraHttpCoreModule {
15
18
  method: RequestMethod.ALL,
16
19
  });
17
20
  }
18
- static forRoot(options) {
19
- const opraAdapter = new OpraNestAdapter(options);
20
- const token = options?.id || OpraNestAdapter;
21
+ static forRoot(init, options) {
22
+ const opraAdapter = new OpraNestAdapter(init, options);
23
+ const token = init?.id || OpraNestAdapter;
21
24
  const providers = [
22
- ...(options?.providers || []),
25
+ ...(init?.providers || []),
26
+ {
27
+ provide: OPRA_HTTP_MODULE_OPTIONS,
28
+ useValue: { ...options },
29
+ },
23
30
  {
24
31
  provide: OpraNestAdapter,
25
- useFactory: async () => {
32
+ inject: [ModuleRef],
33
+ useFactory: async (moduleRef) => {
26
34
  asMutable(opraAdapter).document = await ApiDocumentFactory.createDocument({
27
- ...options,
28
- api: { protocol: 'http', name: options.name, controllers: options.controllers },
35
+ ...init,
36
+ api: { protocol: 'http', name: init.name, controllers: init.controllers },
37
+ });
38
+ opraAdapter.interceptors.map(x => {
39
+ if (isConstructor(x)) {
40
+ return (ctx, next) => {
41
+ const interceptor = moduleRef.get(x);
42
+ if (typeof interceptor.intercept === 'function')
43
+ return interceptor.intercept(ctx, next());
44
+ };
45
+ }
46
+ return x;
29
47
  });
30
48
  return opraAdapter;
31
49
  },
32
50
  },
51
+ {
52
+ provide: APP_FILTER,
53
+ useClass: OpraExceptionFilter,
54
+ },
33
55
  ];
34
56
  if (token !== OpraNestAdapter) {
35
57
  providers.push({
@@ -40,8 +62,8 @@ let OpraHttpCoreModule = OpraHttpCoreModule_1 = class OpraHttpCoreModule {
40
62
  return {
41
63
  module: OpraHttpCoreModule_1,
42
64
  controllers: opraAdapter.nestControllers,
43
- imports: [...(options?.imports || [])],
44
- exports: [...(options?.exports || []), token],
65
+ imports: [...(init?.imports || [])],
66
+ exports: [...(init?.exports || []), token],
45
67
  providers,
46
68
  };
47
69
  }
@@ -5,12 +5,13 @@ import { OpraHttpCoreModule } from './opra-http-core.module.js';
5
5
  let OpraHttpModule = OpraHttpModule_1 = class OpraHttpModule {
6
6
  /**
7
7
  *
8
+ * @param init
8
9
  * @param options
9
10
  */
10
- static forRoot(options) {
11
+ static forRoot(init, options) {
11
12
  return {
12
13
  module: OpraHttpModule_1,
13
- imports: [OpraHttpCoreModule.forRoot(options)],
14
+ imports: [OpraHttpCoreModule.forRoot(init, options)],
14
15
  };
15
16
  }
16
17
  };
@@ -1,25 +1,29 @@
1
1
  import { __decorate, __metadata, __param } from "tslib";
2
- import { Controller, Delete, Get, Head, Next, Options, Patch, Post, Put, Req, Res, Search, UseFilters, } from '@nestjs/common';
2
+ import nodePath from 'node:path';
3
+ import { Controller, Delete, Get, Head, Next, Options, Patch, Post, Put, Req, Res, Search, } from '@nestjs/common';
4
+ import { EXCEPTION_FILTERS_METADATA, GUARDS_METADATA, INTERCEPTORS_METADATA } from '@nestjs/common/constants.js';
3
5
  import { ApiDocument, HTTP_CONTROLLER_METADATA, HttpApi, isConstructor, NotFoundError, } from '@opra/common';
4
6
  import { HttpAdapter } from '@opra/core';
5
- import nodePath from 'path';
6
7
  import { asMutable } from 'ts-gems';
7
- import { OpraExceptionFilter } from './services/opra-exception-filter.js';
8
- export const kHandler = Symbol.for('kHandler');
8
+ import { Public } from './decorators/public.decorator.js';
9
9
  export class OpraNestAdapter extends HttpAdapter {
10
- constructor(options) {
10
+ constructor(init, options) {
11
11
  super((function () {
12
12
  const document = new ApiDocument();
13
13
  document.api = new HttpApi(document);
14
14
  return document;
15
- })(), options);
15
+ })(), {
16
+ ...options,
17
+ interceptors: options?.interceptors,
18
+ });
16
19
  this.nestControllers = [];
17
- let basePath = options.basePath || '/';
20
+ this.options = options;
21
+ let basePath = options?.basePath || '/';
18
22
  if (!basePath.startsWith('/'))
19
23
  basePath = '/' + basePath;
20
24
  this._addRootController(basePath);
21
- if (options.controllers)
22
- options.controllers.forEach(c => this._addToNestControllers(c, basePath));
25
+ if (init.controllers)
26
+ init.controllers.forEach(c => this._addToNestControllers(c, basePath, []));
23
27
  }
24
28
  async close() {
25
29
  //
@@ -28,7 +32,7 @@ export class OpraNestAdapter extends HttpAdapter {
28
32
  const _this = this;
29
33
  let RootController = class RootController {
30
34
  schema(_req, next) {
31
- _this[kHandler].sendDocumentSchema(_req.opraContext).catch(next);
35
+ _this.handler.sendDocumentSchema(_req.opraContext).catch(() => next());
32
36
  }
33
37
  };
34
38
  __decorate([
@@ -44,9 +48,12 @@ export class OpraNestAdapter extends HttpAdapter {
44
48
  path: basePath,
45
49
  })
46
50
  ], RootController);
51
+ if (this.options?.schemaRouteIsPublic) {
52
+ Public()(RootController.prototype, 'schema', Object.getOwnPropertyDescriptor(RootController.prototype, 'schema'));
53
+ }
47
54
  this.nestControllers.push(RootController);
48
55
  }
49
- _addToNestControllers(sourceClass, currentPath) {
56
+ _addToNestControllers(sourceClass, currentPath, parentTree) {
50
57
  const metadata = Reflect.getMetadata(HTTP_CONTROLLER_METADATA, sourceClass);
51
58
  if (!metadata)
52
59
  return;
@@ -54,10 +61,16 @@ export class OpraNestAdapter extends HttpAdapter {
54
61
  [sourceClass.name]: class extends sourceClass {
55
62
  },
56
63
  }[sourceClass.name];
64
+ /** Copy metadata keys from source class to new one */
65
+ let metadataKeys;
66
+ OpraNestAdapter.copyDecoratorMetadataToChild(newClass, parentTree);
57
67
  const newPath = metadata.path ? nodePath.join(currentPath, metadata.path) : currentPath;
58
68
  const adapter = this;
59
- /** Inject exception filter */
60
- UseFilters(new OpraExceptionFilter(adapter))(newClass);
69
+ // adapter.logger =
70
+ /** Disable default error handler. Errors will be handled by OpraExceptionFilter */
71
+ adapter.handler.onError = (context, error) => {
72
+ throw error;
73
+ };
61
74
  Controller()(newClass);
62
75
  this.nestControllers.push(newClass);
63
76
  if (metadata.operations) {
@@ -66,7 +79,8 @@ export class OpraNestAdapter extends HttpAdapter {
66
79
  Object.defineProperty(newClass.prototype, k, {
67
80
  writable: true,
68
81
  /** NestJS handler method */
69
- async value(_req) {
82
+ async value(_req, _res) {
83
+ _res.statusCode = 200;
70
84
  const api = adapter.document.api;
71
85
  const controller = api.findController(sourceClass);
72
86
  const operation = controller?.operations.get(k);
@@ -81,18 +95,16 @@ export class OpraNestAdapter extends HttpAdapter {
81
95
  });
82
96
  }
83
97
  /** Configure the HttpContext */
84
- context.adapter = adapter;
85
- context.document = adapter.document;
86
98
  context.operation = operation;
87
99
  context.controller = operation.owner;
88
100
  context.controllerInstance = this;
89
101
  context.operationHandler = operationHandler;
90
102
  /** Handle request */
91
- await adapter[kHandler].handleRequest(context);
103
+ await adapter.handler.handleRequest(context);
92
104
  },
93
105
  });
94
106
  /** Copy metadata keys from source function to new one */
95
- const metadataKeys = Reflect.getOwnMetadataKeys(operationHandler);
107
+ metadataKeys = Reflect.getOwnMetadataKeys(operationHandler);
96
108
  const newFn = newClass.prototype[k];
97
109
  for (const key of metadataKeys) {
98
110
  const m = Reflect.getMetadata(key, operationHandler);
@@ -101,7 +113,7 @@ export class OpraNestAdapter extends HttpAdapter {
101
113
  Req()(newClass.prototype, k, 0);
102
114
  Res()(newClass.prototype, k, 1);
103
115
  const descriptor = Object.getOwnPropertyDescriptor(newClass.prototype, k);
104
- const operationPath = newPath + (v.path || '');
116
+ const operationPath = v.mergePath ? newPath + (v.path || '') : nodePath.posix.join(newPath, v.path || '');
105
117
  switch (v.method || 'GET') {
106
118
  case 'DELETE':
107
119
  /** Call @Delete decorator over new property */
@@ -144,7 +156,30 @@ export class OpraNestAdapter extends HttpAdapter {
144
156
  for (const child of metadata.controllers) {
145
157
  if (!isConstructor(child))
146
158
  throw new TypeError('Controllers should be injectable a class');
147
- this._addToNestControllers(child, newPath);
159
+ this._addToNestControllers(child, newPath, [...parentTree, sourceClass]);
160
+ }
161
+ }
162
+ }
163
+ static copyDecoratorMetadataToChild(target, parentTree) {
164
+ for (const parent of parentTree) {
165
+ const metadataKeys = Reflect.getOwnMetadataKeys(parent);
166
+ for (const key of metadataKeys) {
167
+ if (typeof key === 'string' && key.startsWith('opra:') && !Reflect.hasOwnMetadata(key, target)) {
168
+ const metadata = Reflect.getMetadata(key, parent);
169
+ Reflect.defineMetadata(key, metadata, target);
170
+ continue;
171
+ }
172
+ if (key === GUARDS_METADATA || key === INTERCEPTORS_METADATA || key === EXCEPTION_FILTERS_METADATA) {
173
+ const m1 = Reflect.getMetadata(key, target) || [];
174
+ const metadata = [...m1];
175
+ const m2 = Reflect.getOwnMetadata(key, parent) || [];
176
+ m2.forEach((t) => {
177
+ if (!metadata.includes(t)) {
178
+ metadata.push(t);
179
+ }
180
+ });
181
+ Reflect.defineMetadata(key, metadata, target);
182
+ }
148
183
  }
149
184
  }
150
185
  }
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -1,14 +1,23 @@
1
- import { HttpOutgoing, wrapException } from '@opra/core';
2
- export const kHandler = Symbol.for('kHandler');
3
- export class OpraExceptionFilter {
4
- constructor(adapter) {
5
- this.adapter = adapter;
1
+ import { __decorate, __metadata } from "tslib";
2
+ import { Catch } from '@nestjs/common';
3
+ import { BaseExceptionFilter, ModuleRef } from '@nestjs/core';
4
+ import { OpraNestAdapter } from '../opra-nestjs-adapter.js';
5
+ let OpraExceptionFilter = class OpraExceptionFilter extends BaseExceptionFilter {
6
+ constructor(moduleRef) {
7
+ super();
8
+ this.moduleRef = moduleRef;
6
9
  }
7
10
  catch(exception, host) {
8
- const ctx = host.switchToHttp();
9
- const _res = ctx.getResponse();
10
- const error = wrapException(exception);
11
- const response = HttpOutgoing.from(_res);
12
- return this.adapter[kHandler].sendErrorResponse(response, [error]);
11
+ const ctx = host.switchToHttp().getRequest().opraContext;
12
+ if (ctx) {
13
+ const adapter = this.moduleRef.get(OpraNestAdapter);
14
+ ctx.errors.push(exception);
15
+ return adapter.handler.sendResponse(ctx);
16
+ }
13
17
  }
14
- }
18
+ };
19
+ OpraExceptionFilter = __decorate([
20
+ Catch(),
21
+ __metadata("design:paramtypes", [ModuleRef])
22
+ ], OpraExceptionFilter);
23
+ export { OpraExceptionFilter };
@@ -1,21 +1,33 @@
1
- import { __decorate } from "tslib";
2
- import { Injectable } from '@nestjs/common';
1
+ import { __decorate, __metadata, __param } from "tslib";
2
+ import { Inject, Injectable } from '@nestjs/common';
3
3
  import { HttpContext, HttpIncoming, HttpOutgoing } from '@opra/core';
4
+ import { OPRA_HTTP_MODULE_OPTIONS } from '../constants.js';
5
+ import { OpraNestAdapter } from '../opra-nestjs-adapter.js';
4
6
  let OpraMiddleware = class OpraMiddleware {
7
+ constructor(opraAdapter, options) {
8
+ this.opraAdapter = opraAdapter;
9
+ this.options = options;
10
+ }
5
11
  use(req, res, next) {
6
12
  const request = HttpIncoming.from(req);
7
13
  const response = HttpOutgoing.from(res);
8
14
  /** Create the HttpContext */
9
- req.opraContext = new HttpContext({
10
- adapter: {},
15
+ const context = new HttpContext({
16
+ adapter: this.opraAdapter,
11
17
  platform: req.route ? 'express' : 'fastify',
12
18
  request,
13
19
  response,
14
20
  });
15
- next();
21
+ req.opraContext = context;
22
+ this.opraAdapter
23
+ .emitAsync('createContext', context)
24
+ .then(() => next())
25
+ .catch(next);
16
26
  }
17
27
  };
18
28
  OpraMiddleware = __decorate([
19
- Injectable()
29
+ Injectable(),
30
+ __param(1, Inject(OPRA_HTTP_MODULE_OPTIONS)),
31
+ __metadata("design:paramtypes", [OpraNestAdapter, Object])
20
32
  ], OpraMiddleware);
21
33
  export { OpraMiddleware };
package/package.json CHANGED
@@ -1,55 +1,45 @@
1
1
  {
2
2
  "name": "@opra/nestjs",
3
- "version": "1.0.0-alpha.8",
3
+ "version": "1.0.0-beta.1",
4
4
  "description": "Opra NestJS module",
5
5
  "author": "Panates",
6
6
  "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/panates/opra.git",
10
- "directory": "packages/nestjs"
11
- },
12
- "scripts": {
13
- "compile": "tsc",
14
- "prebuild": "npm run lint && npm run clean",
15
- "build": "npm run build:cjs && npm run build:esm",
16
- "build:cjs": "tsc -b tsconfig-build-cjs.json",
17
- "build:esm": "tsc -b tsconfig-build-esm.json",
18
- "postbuild": "cp README.md package.json ../../LICENSE ../../build/nestjs && cp ../../package.cjs.json ../../build/nestjs/cjs/package.json",
19
- "lint": "eslint . --max-warnings=0",
20
- "format": "prettier . --write --log-level=warn",
21
- "test": "jest --passWithNoTests",
22
- "cover": "jest --passWithNoTests --collect-coverage",
23
- "clean": "npm run clean:src && npm run clean:test && npm run clean:dist && npm run clean:cover",
24
- "clean:src": "ts-cleanup -s src --all",
25
- "clean:test": "ts-cleanup -s test --all",
26
- "clean:dist": "rimraf ../../build/client",
27
- "clean:cover": "rimraf ../../coverage/client"
28
- },
29
7
  "dependencies": {
30
- "@opra/common": "^1.0.0-alpha.8",
31
- "@opra/core": "^1.0.0-alpha.8",
32
- "fast-tokenizer": "^1.3.0",
8
+ "@opra/common": "^1.0.0-beta.1",
9
+ "@opra/core": "^1.0.0-beta.1",
10
+ "fast-tokenizer": "^1.7.0",
33
11
  "lodash.head": "^4.0.1",
34
- "reflect-metadata": "^0.2.2"
12
+ "putil-promisify": "^1.10.1",
13
+ "reflect-metadata": "^0.2.2",
14
+ "tslib": "^2.7.0"
35
15
  },
36
16
  "peerDependencies": {
37
- "@nestjs/common": "^10.3.10",
38
- "@nestjs/core": "^10.3.10"
39
- },
40
- "devDependencies": {
41
- "@nestjs/platform-express": "^10.3.10",
42
- "@nestjs/testing": "^10.3.10",
43
- "@types/lodash.head": "^4.0.9",
44
- "filedirname": "^3.4.0",
45
- "rxjs": "^7.8.1",
46
- "supertest": "^7.0.0",
47
- "ts-gems": "^3.4.0"
17
+ "@nestjs/common": "^10.4.1",
18
+ "@nestjs/core": "^10.4.1"
48
19
  },
49
20
  "type": "module",
50
- "module": "./esm/index.js",
21
+ "exports": {
22
+ ".": {
23
+ "import": {
24
+ "types": "./types/index.d.ts",
25
+ "default": "./esm/index.js"
26
+ },
27
+ "require": {
28
+ "types": "./types/index.d.cts",
29
+ "default": "./cjs/index.js"
30
+ },
31
+ "default": "./esm/index.js"
32
+ },
33
+ "./package.json": "./package.json"
34
+ },
51
35
  "main": "./cjs/index.js",
36
+ "module": "./esm/index.js",
52
37
  "types": "./types/index.d.ts",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/panates/opra.git",
41
+ "directory": "packages/nestjs"
42
+ },
53
43
  "engines": {
54
44
  "node": ">=16.0",
55
45
  "npm": ">=7.0.0"
@@ -66,4 +56,4 @@
66
56
  "opra",
67
57
  "nestjs"
68
58
  ]
69
- }
59
+ }
@@ -0,0 +1,2 @@
1
+ export declare const IS_PUBLIC_KEY = "opra:isPublic";
2
+ export declare const OPRA_HTTP_MODULE_OPTIONS = "OPRA_HTTP_MODULE_OPTIONS";
@@ -0,0 +1 @@
1
+ export declare const Public: () => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,6 @@
1
+ import 'reflect-metadata';
2
+ import './augmentation/nestjs.augmentation.js';
3
+ export * from './constants.js';
4
+ export * from './decorators/public.decorator.js';
5
+ export * from './opra-http.module.js';
6
+ export * from './opra-nestjs-adapter.js';
package/types/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import 'reflect-metadata';
2
2
  import './augmentation/nestjs.augmentation.js';
3
+ export * from './constants.js';
4
+ export * from './decorators/public.decorator.js';
3
5
  export * from './opra-http.module.js';
4
6
  export * from './opra-nestjs-adapter.js';
@@ -1,10 +1,10 @@
1
- import { DynamicModule, MiddlewareConsumer, NestModule, OnModuleDestroy } from '@nestjs/common';
2
- import type { OpraHttpModule } from './opra-http.module';
1
+ import { type DynamicModule, type MiddlewareConsumer, type NestModule, type OnModuleDestroy } from '@nestjs/common';
2
+ import type { OpraHttpModule } from './opra-http.module.js';
3
3
  import { OpraNestAdapter } from './opra-nestjs-adapter.js';
4
4
  export declare class OpraHttpCoreModule implements OnModuleDestroy, NestModule {
5
5
  protected opraAdapter: OpraNestAdapter;
6
6
  constructor(opraAdapter: OpraNestAdapter);
7
7
  configure(consumer: MiddlewareConsumer): void;
8
- static forRoot(options: OpraHttpModule.Options): DynamicModule;
8
+ static forRoot(init: OpraHttpModule.Initiator, options?: OpraHttpModule.Options): DynamicModule;
9
9
  onModuleDestroy(): Promise<void>;
10
10
  }
@@ -1,16 +1,22 @@
1
- import { DynamicModule } from '@nestjs/common';
1
+ import { type DynamicModule, type Type } from '@nestjs/common';
2
2
  import { ApiDocumentFactory } from '@opra/common';
3
+ import { HttpAdapter } from '@opra/core';
3
4
  export declare namespace OpraHttpModule {
4
- interface Options extends Pick<DynamicModule, 'imports' | 'providers' | 'exports' | 'controllers'>, Pick<ApiDocumentFactory.InitArguments, 'types' | 'references' | 'info'> {
5
+ interface Initiator extends Pick<DynamicModule, 'imports' | 'providers' | 'exports' | 'controllers'>, Pick<ApiDocumentFactory.InitArguments, 'types' | 'references' | 'info'> {
5
6
  id?: any;
6
7
  name: string;
8
+ }
9
+ interface Options {
7
10
  basePath?: string;
11
+ schemaRouteIsPublic?: boolean;
12
+ interceptors?: (HttpAdapter.InterceptorFunction | HttpAdapter.IHttpInterceptor | Type<HttpAdapter.IHttpInterceptor>)[];
8
13
  }
9
14
  }
10
15
  export declare class OpraHttpModule {
11
16
  /**
12
17
  *
18
+ * @param init
13
19
  * @param options
14
20
  */
15
- static forRoot(options: OpraHttpModule.Options): DynamicModule;
21
+ static forRoot(init: OpraHttpModule.Initiator, options?: OpraHttpModule.Options): DynamicModule;
16
22
  }
@@ -1,11 +1,12 @@
1
- import { Type } from '@nestjs/common';
1
+ import { type Type } from '@nestjs/common';
2
2
  import { HttpAdapter } from '@opra/core';
3
- import type { OpraHttpModule } from './opra-http.module';
4
- export declare const kHandler: unique symbol;
3
+ import type { OpraHttpModule } from './opra-http.module.js';
5
4
  export declare class OpraNestAdapter extends HttpAdapter {
6
5
  readonly nestControllers: Type[];
7
- constructor(options: OpraHttpModule.Options);
6
+ readonly options?: OpraHttpModule.Options;
7
+ constructor(init: OpraHttpModule.Initiator, options?: OpraHttpModule.Options);
8
8
  close(): Promise<void>;
9
9
  protected _addRootController(basePath: string): void;
10
- protected _addToNestControllers(sourceClass: Type, currentPath: string): void;
10
+ protected _addToNestControllers(sourceClass: Type, currentPath: string, parentTree: Type[]): void;
11
+ static copyDecoratorMetadataToChild(target: Type, parentTree: Type[]): void;
11
12
  }
@@ -1,8 +1,7 @@
1
- import { ArgumentsHost, ExceptionFilter } from '@nestjs/common';
2
- import { OpraNestAdapter } from '../opra-nestjs-adapter.js';
3
- export declare const kHandler: unique symbol;
4
- export declare class OpraExceptionFilter implements ExceptionFilter {
5
- readonly adapter: OpraNestAdapter;
6
- constructor(adapter: OpraNestAdapter);
7
- catch(exception: any, host: ArgumentsHost): Promise<void>;
1
+ import { type ArgumentsHost } from '@nestjs/common';
2
+ import { BaseExceptionFilter, ModuleRef } from '@nestjs/core';
3
+ export declare class OpraExceptionFilter extends BaseExceptionFilter {
4
+ private moduleRef;
5
+ constructor(moduleRef: ModuleRef);
6
+ catch(exception: any, host: ArgumentsHost): Promise<void> | undefined;
8
7
  }
@@ -1,5 +1,10 @@
1
- import { NestMiddleware } from '@nestjs/common';
1
+ import { type NestMiddleware } from '@nestjs/common';
2
2
  import type { NextFunction, Request, Response } from 'express';
3
+ import type { OpraHttpModule } from '../opra-http.module.js';
4
+ import { OpraNestAdapter } from '../opra-nestjs-adapter.js';
3
5
  export declare class OpraMiddleware implements NestMiddleware {
6
+ protected opraAdapter: OpraNestAdapter;
7
+ protected options: OpraHttpModule.Options;
8
+ constructor(opraAdapter: OpraNestAdapter, options: OpraHttpModule.Options);
4
9
  use(req: Request, res: Response, next: NextFunction): void;
5
10
  }
@@ -1,17 +0,0 @@
1
- "use strict";
2
- // import { ApiDocumentFactory } from '@opra/common';
3
- // import { ExpressAdapter, HttpAdapter } from '@opra/core';
4
- // export interface OpraModuleOptions extends HttpAdapter.Options {
5
- // id?: any;
6
- // document?: Partial<ApiDocumentFactory.InitArguments>;
7
- //
8
- // /**
9
- // * @default true
10
- // */
11
- // useGlobalPrefix?: boolean;
12
- // }
13
- // export interface ExpressModuleOptions extends OpraModuleOptions, HttpAdapter.Options {}
14
- // type OpraModuleOptionsWithoutId = Omit<OpraModuleOptions, 'id'>;
15
- // export interface OpraModuleOptionsFactory {
16
- // createOptions(): Promise<OpraModuleOptionsWithoutId> | OpraModuleOptionsWithoutId;
17
- // }
@@ -1,17 +0,0 @@
1
- "use strict";
2
- // import { ApiDocumentFactory } from '@opra/common';
3
- // import { ExpressAdapter, HttpAdapter } from '@opra/core';
4
- // export interface OpraModuleOptions extends HttpAdapter.Options {
5
- // id?: any;
6
- // document?: Partial<ApiDocumentFactory.InitArguments>;
7
- //
8
- // /**
9
- // * @default true
10
- // */
11
- // useGlobalPrefix?: boolean;
12
- // }
13
- // export interface ExpressModuleOptions extends OpraModuleOptions, HttpAdapter.Options {}
14
- // type OpraModuleOptionsWithoutId = Omit<OpraModuleOptions, 'id'>;
15
- // export interface OpraModuleOptionsFactory {
16
- // createOptions(): Promise<OpraModuleOptionsWithoutId> | OpraModuleOptionsWithoutId;
17
- // }