Closed viktor1190 closed 7 years ago
Thanks for the long post, it was required to explain your concerns :+1: I think your main concern is:
There is an OPTIONAL argument of IServerOptions type, a single INTERFACE. So I go to the Inversify documentation and found that I can use @decorated(), ok I try but this annotation only accept class types, no an Interface like in this case...
The problem is that you have annotated your HapiServer
class:
@injectable()
export class HapiServer extends Hapi.Server {
// ...
But you are not the owner of the source code of the Hapi.Server
class and you cannot decorate it as follows:
@injectable()
class Server extends Events.EventEmitter {
constructor(@unmanaged() options?: IServerOptions);
...
}
But you should be able to use the decorate helper in this case:
import { decorate, injectable, unmanaged } from "inversify";
import * as Hapi from "hapi";
decorate(injectable(), Hapi.Server);
decorate(unmanaged(), Hapi.Server, 1);
I haven't tried this but it should work. Please let me know how do you get on.
ps: more info about decorate()
.
Sorry for my bad english redaction, I need to practice it, hehe, Yes, that is my question, I will try it and give you my feedback.
I only added those lines to my Server implementation:
decorate(injectable(), Hapi.Server);
decorate(unmanaged(), Hapi.Server, 1);
the output is:
/Users/****/git/nodejs/myproject/node_modules/inversify/lib/planning/planner.js:107 throw new Error(error.message); ^
Error: Missing required @injectable annotation in: Anonymous function: function (options) {
Hoek.assert(this instanceof internals.Server, 'Server must be instantiated using new');
options = Schema.apply('server', options || {});
this._settings = Hoek.applyToDefaultsWithShallow(Defaults.server, options, ['connections.routes.bind']);
this._settings.connections = Hoek.applyToDefaultsWithShallow(Defaults.connection, this._settings.connections || {}, ['routes.bind']);
this._settings.connections.routes.cors = Hoek.applyToDefaults(Defaults.cors, this._settings.connections.routes.cors);
this._settings.connections.routes.security = Hoek.applyToDefaults(Defaults.security, this._settings.connections.routes.security);
this._caches = {}; // Cache clients
this._handlers = {}; // Registered handlers
this._methods = new Methods(this);
his._events = new Podium([{ name: 'log', tags: true }, 'start', 'stop']); // Server-only events
this._dependencies = []; // Plugin dependencies
this._registrations = {}; // Tracks plugins registered before connection added
this._heavy = new Heavy(this._settings.load);
this._mime = new Mimos(this._settings.mime);
this._replier = new Reply();
this._requestor = new Request();
this._decorations = {};
this._plugins = {}; // Exposed plugin properties by name
this._app = {};
this._registring = false; // true while register() is waiting for plugin callbacks
this._state = 'stopped'; // 'stopped', 'initializing', 'initialized', 'starting', 'started', 'stopping', 'invalid'
this._extensionsSeq = 0; // Used to keep absolute order of extensions based on the order added across locations
this._extensions = {
onPreStart: new Ext('onPreStart', this),
onPostStart: new Ext('onPostStart', this),
onPreStop: new Ext('onPreStop', this),
onPostStop: new Ext('onPostStop', this)
};
if (options.cache) {
this._createCache(options.cache);
}
if (!this._caches._default) {
this._createCache([{ engine: CatboxMemory }]); // Defaults to memory-based
}
Plugin.call(this, this, [], '', null);
// Subscribe to server log events
if (this._settings.debug) {
const debug = (request, event) => {
const data = event.data;
console.error('Debug:', event.tags.join(', '), (data ? '\n ' + (data.stack || (typeof data === 'object' ? Hoek.stringify(data) : data)) : ''));
};
if (this._settings.debug.log) {
this._events.on({ name: 'log', filter: this._settings.debug.log }, (event) => debug(null, event));
}
if (this._settings.debug.request) {
this.on({ name: 'request', filter: this._settings.debug.request }, debug);
this.on({ name: 'request-internal', filter: this._settings.debug.request }, debug);
}
}
}.
at _createSubRequests (/Users//git/nodejs/myproject/node_modules/inversify/lib/planning/planner.js:107:19)
at Object.plan (/Users//git/nodejs/myproject/node_modules/inversify/lib/planning/planner.js:126:5)
at /Users//git/nodejs/myproject/node_modules/inversify/lib/container/container.js:228:37
at Kernel.Container._get (/Users//git/nodejs/myproject/node_modules/inversify/lib/container/container.js:221:44)
at Kernel.Container.get (/Users//git/nodejs/myproject/node_modules/inversify/lib/container/container.js:180:21)
at Object.
at ChildProcess.exithandler (child_process.js:206:12)
at emitTwo (events.js:106:13)
at ChildProcess.emit (events.js:191:7)
at maybeClose (internal/child_process.js:877:16)
at Socket.<anonymous> (internal/child_process.js:334:11)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at Pipe._handle.close [as _onclose] (net.js:498:12)
.....
19 verbose node v6.9.5
20 verbose npm v4.4.4
Sorry about this issue. I will investigate ASAP will try to build something with Hapi to see if I can reproduce the issue.
Ok, so I've been researching this and I found an alternative way. It looks like there is something inside Hapi.Server
which makes it complicated to create instances of it using inversify. After trying a few different ways I found one that I feel is not too bad using a factory. I will copy all the source code files here:
import { Server } from 'hapi';
export interface IServerConfiguration {
port: number;
}
export interface IConfigurationManager {
getServerConfig(): IServerConfiguration;
}
export interface IServerFactory {
create(): Server;
}
export const TYPES = {
IServerFactory: "IServerFactory",
IConfigurationManager: "IConfigurationManager"
};
import { IServerConfiguration } from "./interfaces";
import { injectable } from "inversify";
@injectable()
export class Configuration {
getServerConfig(): IServerConfiguration {
return {
port: 8080
};
}
}
import { interfaces, injectable, inject } from "inversify";
import { Server } from 'hapi';
import { TYPES } from "./types";
import { IServerConfiguration, IConfigurationManager, IServerFactory } from "./interfaces";
@injectable()
export class ServerFactory implements IServerFactory {
private _serverConfig: IServerConfiguration;
public constructor(
@inject(TYPES.IConfigurationManager) config: IConfigurationManager
) {
this._serverConfig = config.getServerConfig();
}
public create() {
const instance = new Server();
instance.connection({
port: this._serverConfig.port,
routes: {
cors: false
}
});
return instance;
}
}
import { Server } from 'hapi';
import { Container } from 'inversify';
import { Configuration } from "./config";
import { IConfigurationManager, IServerFactory } from "./interfaces";
import { TYPES } from "./types";
import { ServerFactory } from "./server_factory";
export class Kernel extends Container {
constructor() {
super();
this.declareDependencies();
}
declareDependencies() {
this.bind<IConfigurationManager>(TYPES.IConfigurationManager).to(Configuration);
this.bind<IServerFactory>(TYPES.IServerFactory).to(ServerFactory);
}
}
import "reflect-metadata";
import { interfaces } from "inversify";
import { Server } from "hapi";
import { Kernel } from './inversify.config';
import { TYPES } from "./types";
import { IServerFactory } from "./interfaces";
const iocKernel = new Kernel();
const serverFactory = iocKernel.get<IServerFactory>(TYPES.IServerFactory);
const server = serverFactory.create();
server.start((err) => {
if (err) {
console.error(err);
}
console.log('Server running at:', server.info.uri);
});
I hope you find this solution better than the original one.
Thank you very much, it has served me well.
Is there a way to manage the optional injections, of Interface types arguments, in the constructor of third Party libraries like Hapi.Server?
I want to extends a Hapi.Server and I like to use principle patterns, so I decide to use Inversify to inject ALL my code dependencies. Then I create the next Classes:
file server.ts:
file interfaces.ts:
file inversify.config.ts
and the entry point file index.ts:
when I compile with tsc, there is no errors, all is well. But when I start the server with npm start command, the output console says
With a little more of patience and thoroughness I revise the constructor declaration of the Hapi.Server interface, in the third party Hapi library:
There is an OPTIONAL argument of IServerOptions type, a single INTERFACE. So I go to the Inversify documentation and found that I can use @decorated(), ok I try but this annotation only accept class types, no an Interface like in this case... The inversify docs also said something about unmanaged(), and optional() annotations... the same error about missing required injection.
I got a solution; in the inversify.config.ts file I replace the line for binding the Server type to Hapi.Server with:
it works, but this feel like a bad solution, like a very forced and no natural way, also make the next line of code about binding the ServerConfigurations useless. For me this behavior make to lose the essence of implementing code with a Dependecy injection tool, so maybe someone could help me to find another better way.
add: my package.json dependencies are:
PD 1: Sorry for the so long post, but I tried to get very detailed to get the right solution for us.
PD 2: I'm working with typescript 2.2.1