Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/oc/src/registry/domain/http-server/express-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,16 @@ function normaliseParams(raw: Record<string, unknown>): Record<string, string> {
}

export default function createExpressAdapter(
port?: number | string
options?: unknown
): HttpServerAdapter {
return new ExpressHttpServerAdapter(port);
const adapterOptions = options as
| { port?: number | string }
| number
| string
| undefined;
return new ExpressHttpServerAdapter(
typeof adapterOptions === 'object' ? adapterOptions.port : adapterOptions
);
}

class ExpressHttpServerAdapter implements HttpServerAdapter {
Expand Down
16 changes: 16 additions & 0 deletions packages/oc/src/registry/domain/options-sanitiser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { compileSync } from 'oc-client-browser';
import settings from '../../resources/settings';
import type { Config } from '../../types';
import * as auth from './authentication';
import createExpressAdapter from './http-server/express-adapter';

const DEFAULT_NODE_KEEPALIVE_MS = 5000;

Expand Down Expand Up @@ -197,6 +198,21 @@ export default function optionsSanitiser(input: RegistryOptions): Config {
options.keepAliveTimeout =
options.keepAliveTimeout || DEFAULT_NODE_KEEPALIVE_MS;

if (!options.server) {
options.server = {
adapter: createExpressAdapter,
options: { port: options.port }
};
}

if (!options.server.adapter) {
options.server.adapter = createExpressAdapter;
}

if (typeof options.server.options === 'undefined') {
options.server.options = { port: options.port };
}

if (options.s3) {
options.storage = {
adapter: require('oc-s3-storage-adapter'),
Expand Down
29 changes: 29 additions & 0 deletions packages/oc/src/registry/domain/server-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { HttpServerAdapter } from './http-server/types';

type HttpServerAdapterFactory<T = unknown> = (options?: T) => HttpServerAdapter;

function isHttpServerAdapter(adapter: unknown): adapter is HttpServerAdapter {
return (
!!adapter &&
typeof adapter === 'object' &&
typeof (adapter as HttpServerAdapter).native === 'function' &&
typeof (adapter as HttpServerAdapter).listen === 'function' &&
typeof (adapter as HttpServerAdapter).httpServer === 'function'
);
}

export default function getHttpServerAdapter<T = unknown>(
adapter: HttpServerAdapter | HttpServerAdapterFactory<T>,
options?: T
): HttpServerAdapter {
if (isHttpServerAdapter(adapter)) {
return adapter;
}

const instance = adapter(options);
if (!isHttpServerAdapter(instance)) {
throw new Error('Invalid HTTP server adapter');
}

return instance;
}
7 changes: 5 additions & 2 deletions packages/oc/src/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import type express from 'express';
import type { Plugin } from '../types';
import appStart from './app-start';
import eventsHandler from './domain/events-handler';
import createExpressAdapter from './domain/http-server/express-adapter';
import sanitiseOptions, { RegistryOptions } from './domain/options-sanitiser';
import * as pluginsInitialiser from './domain/plugins-initialiser';
import Repository from './domain/repository';
import getHttpServerAdapter from './domain/server-adapter';
import * as validator from './domain/validators';
import * as middleware from './middleware';
import { create as createRouter } from './router';
Expand All @@ -24,7 +24,10 @@ export default function registry<T = any>(inputOptions: RegistryOptions<T>) {
const options = sanitiseOptions(inputOptions);

const plugins: Plugin[] = [];
const adapter = middleware.bind(createExpressAdapter(options.port), options);
const adapter = middleware.bind(
getHttpServerAdapter(options.server.adapter, options.server.options),
options
);
const app = adapter.native() as express.Express;
const repository = Repository(options);

Expand Down
8 changes: 8 additions & 0 deletions packages/oc/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { NextFunction, Request, Response } from 'express';
import type { MetadataStore as MetadataStoreType } from 'oc-metadata-adapters-utils';
import type { StorageAdapter } from 'oc-storage-adapters-utils';
import type { PackageJson } from 'type-fest';
import type { HttpServerAdapter } from './registry/domain/http-server/types';

export type { ComponentRow, MetadataStore } from 'oc-metadata-adapters-utils';

Expand Down Expand Up @@ -426,6 +427,13 @@ export interface Config<T = any> {
adapter: (options: T) => StorageAdapter;
options: T & { componentsDir: string };
};
/**
* Low-level HTTP server adapter used by the registry.
*/
server: {
adapter: (options?: unknown) => HttpServerAdapter;
options?: unknown;
};
/**
* Directory used by the registry for temporary files.
*/
Expand Down
46 changes: 46 additions & 0 deletions packages/oc/test/unit/registry-domain-options-sanitiser.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,52 @@ describe('registry : domain : options-sanitiser', () => {
expect(sanitise(options)[property]).to.eql(value);
});
}

it('should default to the Express HTTP server adapter', () => {
expect(sanitise(options).server.adapter).to.equal(
require('../../dist/registry/domain/http-server/express-adapter').default
);
expect(sanitise(options).server.options).to.eql({ port: 3000 });
});
});

describe('server adapter configuration', () => {
describe('when no server adapter is provided', () => {
const options = { server: {}, port: 1234, baseUrl: 'dummy' };

it('should default to the Express HTTP server adapter', () => {
expect(sanitise(options).server.adapter).to.equal(
require('../../dist/registry/domain/http-server/express-adapter')
.default
);
});
});

describe('when no server options are provided', () => {
const serverAdapter = () => ({});
const options = {
server: { adapter: serverAdapter },
port: 1234,
baseUrl: 'dummy'
};

it('should pass the port to the server options', () => {
expect(sanitise(options).server.options).to.eql({ port: 1234 });
});
});

describe('when server options are provided', () => {
const serverAdapter = () => ({});
const options = {
server: { adapter: serverAdapter, options: { custom: true } },
port: 1234,
baseUrl: 'dummy'
};

it('should leave them untouched', () => {
expect(sanitise(options).server.options).to.eql({ custom: true });
});
});
});

describe('when verbosity is provided', () => {
Expand Down
40 changes: 40 additions & 0 deletions packages/oc/test/unit/registry-domain-server-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const expect = require('chai').expect;
const sinon = require('sinon');

describe('registry : domain : server-adapter', () => {
const getHttpServerAdapter =
require('../../dist/registry/domain/server-adapter').default;

const createAdapter = () => ({
native: sinon.stub(),
listen: sinon.stub(),
httpServer: sinon.stub()
});

describe('when given an adapter factory', () => {
it('should return the created adapter', () => {
const adapter = createAdapter();
const factory = sinon.stub().returns(adapter);
const options = { custom: true };

expect(getHttpServerAdapter(factory, options)).to.equal(adapter);
expect(factory.calledWith(options)).to.be.true;
});
});

describe('when given an adapter instance', () => {
it('should return the adapter untouched', () => {
const adapter = createAdapter();

expect(getHttpServerAdapter(adapter)).to.equal(adapter);
});
});

describe('when the factory returns an invalid adapter', () => {
it('should throw', () => {
expect(() => getHttpServerAdapter(() => ({}))).to.throw(
'Invalid HTTP server adapter'
);
});
});
});
26 changes: 20 additions & 6 deletions packages/oc/test/unit/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ describe('registry', () => {
close: sinon.stub()
});

const serverAdapterFactory = sinon.stub();

const deps = {
'./app-start': sinon.stub(),
'./domain/events-handler': { fire: sinon.stub() },
'./domain/http-server/express-adapter': sinon.stub().callsFake(() => {
'./domain/server-adapter': sinon.stub().callsFake(() => {
adapter = createAdapter();
return adapter;
}),
Expand Down Expand Up @@ -65,20 +67,29 @@ describe('registry', () => {
deps['./domain/validators'].validateRegistryConfiguration.returns({
isValid: true
});
deps['./domain/options-sanitiser'].returns({ port: 3000 });
deps['./domain/options-sanitiser'].returns({
port: 3000,
server: { adapter: serverAdapterFactory, options: { port: 3000 } }
});
registry = Registry({});
});

it('should instantiate the HTTP server adapter', () => {
expect(deps['./domain/http-server/express-adapter'].calledWith(3000)).to
.be.true;
expect(
deps['./domain/server-adapter'].calledWith(serverAdapterFactory, {
port: 3000
})
).to.be.true;
});

it('should bind the middleware', () => {
const bind = deps['./middleware'].bind;
expect(bind.called).to.be.true;
expect(bind.lastCall.args[0]).to.equal(adapter);
expect(bind.lastCall.args[1]).to.eql({ port: 3000 });
expect(bind.lastCall.args[1]).to.eql({
port: 3000,
server: { adapter: serverAdapterFactory, options: { port: 3000 } }
});
});

it('should instanciate the repository', () => {
Expand Down Expand Up @@ -247,7 +258,10 @@ describe('registry', () => {
deps['./domain/validators'].validateRegistryConfiguration.returns({
isValid: true
});
deps['./domain/options-sanitiser'].returns({ port: 3000 });
deps['./domain/options-sanitiser'].returns({
port: 3000,
server: { adapter: serverAdapterFactory, options: { port: 3000 } }
});
});

it('should close the repository when the server is not listening', (done) => {
Expand Down
Loading