diff --git a/.gitignore b/.gitignore index dc889ddd..f62f222b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# extra +componentsjs-error-state.json + # Logs logs *.log diff --git a/packages/semcom-node/.componentsignore b/packages/semcom-node/.componentsignore index db1ddd8b..fbc23c01 100644 --- a/packages/semcom-node/.componentsignore +++ b/packages/semcom-node/.componentsignore @@ -4,5 +4,6 @@ "LoggerService", "ComponentMetadata", "ManageComponentService", - "QueryComponentService" + "QueryComponentService", + "HttpHandler" ] diff --git a/packages/semcom-node/config/config-default.json b/packages/semcom-node/config/config-default.json index b5a18dc5..f25f0205 100644 --- a/packages/semcom-node/config/config-default.json +++ b/packages/semcom-node/config/config-default.json @@ -6,8 +6,8 @@ "files-dasn:config/presets/logger.json", "files-dasn:config/presets/serializer.json", "files-dasn:config/presets/component.json", + "files-dasn:config/presets/handlers.json", "files-dasn:config/presets/server.json", - "files-dasn:config/presets/launcher.json", "files-dasn:config/presets/store.json" ], "@graph": [ diff --git a/packages/semcom-node/config/presets/component.json b/packages/semcom-node/config/presets/component.json index f70b4d3f..d6033793 100644 --- a/packages/semcom-node/config/presets/component.json +++ b/packages/semcom-node/config/presets/component.json @@ -7,23 +7,27 @@ { "@id": "urn:semcom-node:default:QueryComponentService", "@type": "QueryComponentStoreService", - "QueryComponentStoreService:_components": { "@id": "urn:semcom-node:default:ComponentStore" } + "QueryComponentStoreService:_components": { + "@id": "urn:semcom-node:default:ComponentStore" + } }, { "@id": "urn:semcom-node:default:ManageComponentService", "@type": "ManageComponentStoreService", - "ManageComponentStoreService:_components": { "@id": "urn:semcom-node:default:ComponentStore" } + "ManageComponentStoreService:_components": { + "@id": "urn:semcom-node:default:ComponentStore" + } }, { - "@id": "urn:semcom-node:default:ComponentControllerService", - "@type": "ComponentControllerService", - "ComponentControllerService:_queryService": { + "@id": "urn:semcom-node:default:ComponentService", + "@type": "ComponentService", + "ComponentService:_queryService": { "@id": "urn:semcom-node:default:QueryComponentService" }, - "ComponentControllerService:_manageService": { + "ComponentService:_manageService": { "@id": "urn:semcom-node:default:ManageComponentService" }, - "ComponentControllerService:_logger": { + "ComponentService:_logger": { "@id": "urn:semcom-node:default:LoggerService" } }, diff --git a/packages/semcom-node/config/presets/handlers.json b/packages/semcom-node/config/presets/handlers.json new file mode 100644 index 00000000..d77c116a --- /dev/null +++ b/packages/semcom-node/config/presets/handlers.json @@ -0,0 +1,40 @@ +{ + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-core/^0.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-node/^0.0.0/components/context.jsonld" + ], + "@graph": [ + { + "@id": "urn:semcom-node:default:ComponentHttpHandler", + "@type": "ComponentHttpHandler", + "ComponentHttpHandler:_components": { + "@id": "urn:semcom-node:default:ComponentService" + } + }, + { + "@id": "urn:semcom-node:default:QueryComponentHttpHandler", + "@type": "QueryComponentHttpHandler", + "QueryComponentHttpHandler:_components": { + "@id": "urn:semcom-node:default:ComponentService" + } + }, + { + "@id": "urn:semcom-node:default:ContentNegotiationHttpHandler", + "@type": "ContentNegotiationHttpHandler", + "ContentNegotiationHttpHandler:_logger": { + "@id": "urn:semcom-node:default:LoggerService" + }, + "ContentNegotiationHttpHandler:_defaultContentType": "application/ld+json", + "ContentNegotiationHttpHandler:_transformer": { + "@id": "urn:semcom-node:default:ComponentTransformerService" + }, + "ContentNegotiationHttpHandler:_serializer": { + "@id": "urn:semcom-node:default:QuadSerializationService" + } + } + ] +} + + + + diff --git a/packages/semcom-node/config/presets/launcher.json b/packages/semcom-node/config/presets/launcher.json deleted file mode 100644 index 3e99eeb0..00000000 --- a/packages/semcom-node/config/presets/launcher.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-node/^0.0.0/components/context.jsonld", - "@graph": [ - { - "@id": "urn:semcom-node:default:LauncherService", - "@type": "LauncherService", - "LauncherService:_logger": { - "@id": "urn:semcom-node:default:LoggerService" - }, - "LauncherService:_server": { - "@id": "urn:semcom-node:default:ServerService" - }, - "LauncherService:_options": { - "@id": "urn:semcom-node:default:ServerOptions", - "@type": "ServerOptions", - "ServerOptions:_controllers": [ - { - "@id": "urn:semcom-node:default:ComponentControllerService" - } - ], - "ServerOptions:_handlers": [ - { - "@id": "urn:semcom-node:default:ServerHandlerContentNegotiationService", - "@type": "ServerHandlerContentNegotiationService", - "ServerHandlerContentNegotiationService:_logger": { - "@id": "urn:semcom-node:default:LoggerService" - }, - "ServerHandlerContentNegotiationService:_defaultContentType": "application/ld+json", - "ServerHandlerContentNegotiationService:_transformer": { - "@id": "urn:semcom-node:default:ComponentTransformerService" - }, - "ServerHandlerContentNegotiationService:_serializer": { - "@id": "urn:semcom-node:default:QuadSerializationService" - } - } - ] - } - } - ] -} diff --git a/packages/semcom-node/config/presets/server.json b/packages/semcom-node/config/presets/server.json index 441eb70a..dbfe930c 100644 --- a/packages/semcom-node/config/presets/server.json +++ b/packages/semcom-node/config/presets/server.json @@ -1,12 +1,80 @@ { - "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-node/^0.0.0/components/context.jsonld", + "@context": [ + "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/handlersjs-core/^0.0.0/components/context.jsonld", + "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/handlersjs-http/^0.0.0/components/context.jsonld" + ], "@graph": [ { - "@id": "urn:semcom-node:default:ServerService", - "@type": "ServerKoaService", - "ServerKoaService:_logger": { - "@id": "urn:semcom-node:default:LoggerService" + "@id": "urn:handlersjs-http:default:NodeHttpServer", + "@type": "NodeHttpServer", + "NodeHttpServer:_port": "3000", + "NodeHttpServer:_host": "localhost", + "NodeHttpServer:_nodeHttpStreamsHandler": { + "@id": "urn:handlersjs-http:default:NodeHttpRequestResponseHandler", + "@type": "NodeHttpRequestResponseHandler", + "NodeHttpRequestResponseHandler:_httpHandler": { + "@type": "SequenceHandler", + "SequenceHandler:_handlers": [ + { + "@id": "urn:handlersjs-http:default:RoutedHttpRequestHandler", + "@type": "RoutedHttpRequestHandler", + "RoutedHttpRequestHandler:_handlerControllerList": [ + { + "@id": "urn:handlersjs-http:default:HttpHandlerController", + "@type": "HttpHandlerController", + "HttpHandlerController:_label": "ControllerList", + "HttpHandlerController:_routes": [ + { + "@id": "urn:semcom-node:default:ComponentsRoute" + }, + { + "@id": "urn:semcom-node:default:QueryComponentsRoute" + } + ] + } + ] + }, + { + "@id": "urn:semcom-node:default:ContentNegotiationHttpHandler" + } + ] + } } + }, + { + "@id": "urn:semcom-node:default:ComponentsRoute", + "@type": "HttpHandlerRoute", + "HttpHandlerRoute:_operations": [ + { + "@type": "HttpHandlerOperation", + "HttpHandlerOperation:_method": "GET", + "HttpHandlerOperation:_publish": true + }, + { + "@type": "HttpHandlerOperation", + "HttpHandlerOperation:_method": "POST", + "HttpHandlerOperation:_publish": true + } + ], + "HttpHandlerRoute:_handler": { + "@id": "urn:semcom-node:default:ComponentHttpHandler" + }, + "HttpHandlerRoute:_path": "/component" + }, + { + "@id": "urn:semcom-node:default:QueryComponentsRoute", + "@type": "HttpHandlerRoute", + "HttpHandlerRoute:_operations": [ + { + "@type": "HttpHandlerOperation", + "HttpHandlerOperation:_method": "POST", + "HttpHandlerOperation:_publish": true + } + ], + "HttpHandlerRoute:_handler": { + "@id": "urn:semcom-node:default:QueryComponentHttpHandler" + }, + "HttpHandlerRoute:_path": "/component/query" } ] -} \ No newline at end of file +} diff --git a/packages/semcom-node/lib/component/services/component-controller.service.spec.ts b/packages/semcom-node/lib/component/services/component-controller.service.spec.ts deleted file mode 100644 index b8006690..00000000 --- a/packages/semcom-node/lib/component/services/component-controller.service.spec.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ComponentMetadata, LoggerConsoleService } from '@digita-ai/semcom-core'; -import { ComponentControllerService } from './component-controller.service'; -import { ComponentInMemoryStore } from '../../store/services/component-in-memory-store.service'; -import { ManageComponentStoreService } from './manage-component-store.service'; -import { QueryComponentStoreService } from './query-component-store.service'; -import { ServerRequest } from '../../server/models/server-request.model'; -import { initialComponents } from '../../mock/initial-components'; - -describe('ComponentControllerService', () => { - let controller: ComponentControllerService = null; - const components: ComponentMetadata[] = initialComponents; - - beforeEach(() => { - const store = new ComponentInMemoryStore(components); - - controller = new ComponentControllerService( - new QueryComponentStoreService(store), - new ManageComponentStoreService(store), - new LoggerConsoleService(), - ); - }); - - it('should be correctly instantiated', () => { - expect(controller).toBeTruthy(); - }); - - it('should return all components', async () => { - const request: ServerRequest = { - method: 'GET', - headers: { accept: '*/*' }, - }; - - const response = await controller.all(request); - - expect(response.status).toBe(200); - expect(response.body).toEqual(components); - }); - - it('should return filtered components', async () => { - const request: ServerRequest = { - method: 'POST', - headers: { accept: '*/*' }, - body: { uri: components[0].uri }, - }; - - const response = await controller.query(request); - - expect(response.status).toBe(200); - expect(response.body).toEqual([components[0]]); - }); -}); diff --git a/packages/semcom-node/lib/component/services/component-controller.service.ts b/packages/semcom-node/lib/component/services/component-controller.service.ts deleted file mode 100644 index dc892f70..00000000 --- a/packages/semcom-node/lib/component/services/component-controller.service.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { LoggerService } from '@digita-ai/semcom-core'; -import { ManageComponentStoreService } from './manage-component-store.service'; -import { QueryComponentStoreService } from './query-component-store.service'; -import { ServerBadRequestError } from '../../server/models/server-bad-request-error.model'; -import { ServerController } from '../../server/models/server-controller.model'; -import { ServerRequest } from '../../server/models/server-request.model'; -import { ServerResponse } from '../../server/models/server-response.model'; -import { ServerRoute } from '../../server/models/server-route.model'; - -export class ComponentControllerService implements ServerController { - routes: ServerRoute[] = [ - { - path: '/component', - method: 'get', - execute: (request) => this.all(request), - }, - { - path: '/component/query', - method: 'post', - execute: (request) => this.query(request), - }, - { - path: '/component', - method: 'post', - execute: (request) => this.save(request), - }, - ]; - - constructor(private queryService: QueryComponentStoreService, private manageService: ManageComponentStoreService, private logger: LoggerService) {} - - public async all(request: ServerRequest): Promise { - this.logger.log('debug', 'Getting all components', request); - - let res = null; - - const components = await this.queryService.query({}); - - this.logger.log('debug', 'Retrieved all components', components); - - res = { - body: components, - headers: { - 'content-type': 'application/json', - }, - status: 200, - }; - - return res; - } - - public async query(request: ServerRequest): Promise { - this.logger.log('debug', 'Getting filtered components', request); - - let res = null; - const components = await this.queryService.query(request.body); - - this.logger.log('debug', 'Retrieved filtered components', components); - - res = { - body: components, - headers: { - 'content-type': 'application/json', - }, - status: 200, - }; - - return res; - } - - public async save(request: ServerRequest): Promise { - this.logger.log('debug', 'Saving components', request); - - let res: ServerResponse = null; - - const contentType = request.headers['content-type']; - - if (contentType !== 'application/json') { - throw new ServerBadRequestError('Content type is not supported.'); - } - - const components = await this.manageService.save([request.body]); - - this.logger.log('debug', 'Saved components', components); - - res = { - body: components, - headers: { - 'content-type': 'application/json', - }, - status: 201, - }; - - if (components.length === 1) { - this.logger.log('debug', 'Setting location', components); - const component = components[0]; - - res.headers['Location'] = component.uri; - } - - return res; - } -} diff --git a/packages/semcom-node/lib/component/services/component-transformer.service.spec.ts b/packages/semcom-node/lib/component/services/component-transformer.service.spec.ts index a09d2c9d..43f9cbd3 100644 --- a/packages/semcom-node/lib/component/services/component-transformer.service.spec.ts +++ b/packages/semcom-node/lib/component/services/component-transformer.service.spec.ts @@ -1,7 +1,7 @@ import { ComponentTransformerService } from './component-transformer.service'; import { LoggerConsoleService } from '@digita-ai/semcom-core'; -describe('ComponentControllerService', () => { +describe('ComponentTransformerService', () => { let transformer: ComponentTransformerService = null; beforeEach(() => { diff --git a/packages/semcom-node/lib/component/services/component.service.spec.ts b/packages/semcom-node/lib/component/services/component.service.spec.ts new file mode 100644 index 00000000..542f0dc3 --- /dev/null +++ b/packages/semcom-node/lib/component/services/component.service.spec.ts @@ -0,0 +1,58 @@ +import { ComponentMetadata, LoggerConsoleService } from '@digita-ai/semcom-core'; +import { ComponentInMemoryStore } from '../../store/services/component-in-memory-store.service'; +import { ComponentService } from './component.service'; +import { ManageComponentStoreService } from './manage-component-store.service'; +import { QueryComponentStoreService } from './query-component-store.service'; +import { initialComponents } from '../../mock/initial-components'; + +describe('ComponentService', () => { + let compService: ComponentService; + const components: ComponentMetadata[] = initialComponents; + + beforeEach(() => { + const store = new ComponentInMemoryStore(initialComponents); + + compService = new ComponentService( + new QueryComponentStoreService(store), + new ManageComponentStoreService(store), + new LoggerConsoleService(), + ); + }); + + it('should be correctly instantiated', () => { + expect(compService).toBeTruthy(); + }); + + describe('all()', () => { + it('should return all components', async() => { + const spy = jest.spyOn((compService as any).queryService, 'query'); + await expect(compService.all().toPromise()).resolves.toEqual(components); + expect(spy).toHaveBeenCalledWith({}); + expect(spy).toHaveBeenCalledTimes(1); + }); + }); + + describe('query()', () => { + it('should return filtered components', async() => { + const spy = jest.spyOn((compService as any).queryService, 'query'); + const partial = { uri: components[0].uri }; + await expect(compService.query(partial).toPromise()).resolves.toEqual([components[0]]); + expect(spy).toHaveBeenCalledWith(partial); + expect(spy).toHaveBeenCalledTimes(1); + }); + }); + + describe('save()', () => { + it('should save a component', async() => { + const comp = { + ...components[0], + uri: 'newUri' + }; + const spy = jest.spyOn((compService as any).manageService, 'save'); + await expect(compService.save(comp).toPromise()).resolves.toEqual([comp]); + await expect(compService.all().toPromise()).resolves.toHaveLength(5); + expect(spy).toHaveBeenCalledWith([comp]); + expect(spy).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/packages/semcom-node/lib/component/services/component.service.ts b/packages/semcom-node/lib/component/services/component.service.ts new file mode 100644 index 00000000..89697b9e --- /dev/null +++ b/packages/semcom-node/lib/component/services/component.service.ts @@ -0,0 +1,28 @@ +import { ComponentMetadata, LoggerService } from '@digita-ai/semcom-core'; +import { Observable, from } from 'rxjs'; +import { ManageComponentStoreService } from './manage-component-store.service'; +import { QueryComponentStoreService } from './query-component-store.service'; + +export class ComponentService { + + constructor( + private queryService: QueryComponentStoreService, + private manageService: ManageComponentStoreService, + private logger: LoggerService, + ) {} + + public all(): Observable { + this.logger.log('debug', 'Getting all components'); + return from(this.queryService.query({})); + } + + public query(query: any): Observable { + this.logger.log('debug', 'Getting filtered components', query); + return from(this.queryService.query(query)); + } + + public save(body: any): Observable { + this.logger.log('debug', 'Saving components'); + return from(this.manageService.save([body])); + } +} diff --git a/packages/semcom-node/lib/handlers/component.handler.ts b/packages/semcom-node/lib/handlers/component.handler.ts new file mode 100644 index 00000000..714df3b8 --- /dev/null +++ b/packages/semcom-node/lib/handlers/component.handler.ts @@ -0,0 +1,66 @@ +import { HttpHandler, HttpHandlerContext, HttpHandlerResponse } from '@digita-ai/handlersjs-http'; +import { Observable, of, throwError } from 'rxjs'; +import { ComponentService } from '../component/services/component.service'; +import { map } from 'rxjs/operators'; + +export class ComponentHttpHandler extends HttpHandler { + + constructor( + private components: ComponentService, + ) { + super(); + } + + handle(context: HttpHandlerContext): Observable { + if (context.request.method === 'GET') { + return this.components.all().pipe( + map((components) => ({ + body: JSON.stringify(components), + headers: { + 'content-type': 'application/json', + }, + status: 200, + })), + ); + } else { + + if (context.request.headers['content-type'] !== 'application/json') { + return of({ + body: 'Header \'Content-Type\' should be \'application/json\'.', + headers: {}, + status: 400, + }); + } + if (!context.request.body) { + return throwError(new Error('body of the request cannot be null or undefined.')); + } + + return this.components.save(JSON.parse(context.request.body)).pipe( + map((result) => ({ + response: { + body: JSON.stringify(result), + headers: { + 'content-type': 'application/json', + }, + status: 201, + }, + result, + })), + map((data) => { + if (data.result.length > 1) { + data.response.headers['location'] = data.result[0].uri; + } + return data.response; + }), + ); + } + } + + + canHandle(context: HttpHandlerContext): Observable { + if (['GET', 'POST'].includes(context.request.method)) { + return of(true); + } + return of(false); + } +} diff --git a/packages/semcom-node/lib/handlers/content-negotiation.handler.spec.ts b/packages/semcom-node/lib/handlers/content-negotiation.handler.spec.ts new file mode 100644 index 00000000..f50dbd08 --- /dev/null +++ b/packages/semcom-node/lib/handlers/content-negotiation.handler.spec.ts @@ -0,0 +1,75 @@ +import { HttpHandlerContext, HttpHandlerResponse } from '@digita-ai/handlersjs-http'; +import { ComponentTransformerService } from '../component/services/component-transformer.service'; +import { ContentNegotiationHttpHandler } from '../handlers/content-negotiation.handler'; +import { LoggerConsoleService } from '@digita-ai/semcom-core'; +import { QuadSerializationService } from '../quad/services/quad-serialization.service'; + +const logger = new LoggerConsoleService(); + +describe('ServerHandlerContentNegotiationService', () => { + let handler: ContentNegotiationHttpHandler; + let mockCTX: HttpHandlerContext; + let mockResponse: HttpHandlerResponse; + + beforeEach(() => { + handler = new ContentNegotiationHttpHandler( + logger, + 'application/ld+json', + new ComponentTransformerService(logger), + new QuadSerializationService(logger) + ); + + mockCTX = { + request: { + headers: { + accept: '*/*', + }, + path: '/path', + method: 'GET', + } + }; + + mockResponse = { + body: null, + headers: {}, + status: 200, + }; + }); + + it('should be correctly instantiated', () => { + expect(handler).toBeTruthy(); + }); + + describe('canhandle()', () => { + it('should return true when accept header = */*', async() => { + mockCTX.request.headers.accept = '*/*'; + await expect(handler.canHandle(mockCTX).toPromise()).resolves.toBe(true); + }); + it('should return true when accept header = text/turtle', async() => { + mockCTX.request.headers.accept = 'text/turtle'; + await expect(handler.canHandle(mockCTX).toPromise()).resolves.toBe(true); + }); + it('should return false when accept header = application/json', async() => { + mockCTX.request.headers.accept = 'application/json'; + await expect(handler.canHandle(mockCTX).toPromise()).resolves.toBe(false); + }); + }); + + describe('handle()', () => { + it('throws when context.request is null', async() => { + await expect(handler.handle({ ...mockCTX, request: null }, mockResponse).toPromise()).rejects.toThrow( + 'Argument request should be set.' + ); + }); + it('throws when response is null', async() => { + await expect(handler.handle(mockCTX, null).toPromise()).rejects.toThrow( + 'Argument response should be set.' + ); + }); + it('should return 406 for unknown content types', async() => { + mockCTX.request.headers.accept = 'unsupportedContentType'; + const temp = await handler.handle(mockCTX, mockResponse).toPromise(); + await expect(temp.status).toBe(406); + }); + }); +}); diff --git a/packages/semcom-node/lib/handlers/content-negotiation.handler.ts b/packages/semcom-node/lib/handlers/content-negotiation.handler.ts new file mode 100644 index 00000000..2aed0afc --- /dev/null +++ b/packages/semcom-node/lib/handlers/content-negotiation.handler.ts @@ -0,0 +1,94 @@ +import { ComponentMetadata, LoggerService } from '@digita-ai/semcom-core'; +import { HttpHandler, HttpHandlerContext, HttpHandlerResponse } from '@digita-ai/handlersjs-http'; +import { Observable, Subject, from, of, throwError } from 'rxjs'; +import { map, switchMap, tap, toArray } from 'rxjs/operators'; +import { ComponentTransformerService } from '../component/services/component-transformer.service'; +import { QuadSerializationService } from '../quad/services/quad-serialization.service'; +import serialize from 'rdf-serialize'; + +export class ContentNegotiationHttpHandler extends HttpHandler { + constructor( + private logger: LoggerService, + private defaultContentType: string, + private transformer: ComponentTransformerService, + private serializer: QuadSerializationService, + ) { + super(); + } + + public canHandle(context: HttpHandlerContext): Observable { + this.logger.log('debug', 'Checking content negotiation handler', { context }); + + return of(context.request.headers['accept'] !== 'application/json'); + } + + public handle(context: HttpHandlerContext, response: HttpHandlerResponse): Observable { + this.logger.log('debug', 'Running content negotiation handler', { + context, + response, + }); + + if (!context.request) { + return throwError(new Error('Argument request should be set.')); + } + + if (!response) { + return throwError(new Error('Argument response should be set.')); + } + + const request = context.request; + const contentType = + !request.headers['accept'] || request.headers['accept'] === '*/*' + ? this.defaultContentType + : request.headers['accept']; + + return this.isContentTypeSupported(contentType).pipe( + switchMap((isContentTypeSupported) => { + if (isContentTypeSupported) { + const components: ComponentMetadata[] = JSON.parse(response.body); + + const quads = this.transformer.toQuads(components); + + const resultStream = this.serializer.serialize(quads, contentType); + + this.logger.log('debug', 'Mapped to rdf', { request, response }); + + const buffer = new Subject(); + resultStream.on('data', (chunk) => buffer.next(chunk)); + resultStream.on('end', () => buffer.complete()); + + return buffer.pipe( + toArray(), + map((chunks: string[]) => chunks.join('')), + map((body) => ({ + ...response, + body, + headers: { ...response.headers, 'content-type': contentType }, + })), + ); + } else { + return of({ + ...response, + status: 406, + body: '', + }); + } + }), + ); + } + + private isContentTypeSupported(contentType: string): Observable { + if (!contentType) { + return throwError(new Error('Argument contentType should be set.')); + } + + return from(serialize.getContentTypes()).pipe( + tap((contentTypes) => { + if (!contentTypes) { + return throwError(new Error('contentTypes should be set.')); + } + }), + map((contentTypes) => contentTypes.some((c) => c === contentType)), + ); + } +} diff --git a/packages/semcom-node/lib/handlers/query-component.handler.ts b/packages/semcom-node/lib/handlers/query-component.handler.ts new file mode 100644 index 00000000..f1c61afc --- /dev/null +++ b/packages/semcom-node/lib/handlers/query-component.handler.ts @@ -0,0 +1,37 @@ +import { HttpHandler, HttpHandlerContext, HttpHandlerResponse } from '@digita-ai/handlersjs-http'; +import { Observable, of, throwError } from 'rxjs'; +import { ComponentService } from '../component/services/component.service'; +import { map } from 'rxjs/operators'; + +export class QueryComponentHttpHandler extends HttpHandler { + + constructor( + private components: ComponentService, + ) { + super(); + } + + handle(context: HttpHandlerContext): Observable { + + if (!context.request.body) { + return throwError(new Error('body of the request cannot be null or undefined.')); + } + return this.components.query(JSON.parse(context.request.body)).pipe( + map((result) => ({ + body: JSON.stringify(result), + headers: { + 'content-type': 'application/json', + }, + status: 201, + })), + ); + } + + + canHandle(context: HttpHandlerContext): Observable { + if (context.request.method === 'POST') { + return of(true); + } + return of(false); + } +} diff --git a/packages/semcom-node/lib/launcher/services/launcher.service.spec.ts b/packages/semcom-node/lib/launcher/services/launcher.service.spec.ts deleted file mode 100644 index 2075e442..00000000 --- a/packages/semcom-node/lib/launcher/services/launcher.service.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { LauncherService } from './launcher.service'; -import { LoggerConsoleService } from '@digita-ai/semcom-core'; -import { ServerMockService } from '../../server/services/server-mock.service'; - -describe('LauncherService', () => { - - it('should be correctly instantiated', (() => { - const server = new ServerMockService(new LoggerConsoleService()); - const launcher: LauncherService = new LauncherService(new LoggerConsoleService(), server, { controllers: [], handlers: [] }); - - expect(launcher).toBeTruthy(); - })); - - it('should start server when launching', (() => { - const server = new ServerMockService(new LoggerConsoleService()); - const launcher: LauncherService = new LauncherService(new LoggerConsoleService(), server, { controllers: [], handlers: [] }); - - const mockListen = jest.fn(); - server.start = mockListen; - - launcher.launch(); - - expect(mockListen.mock.calls.length).toBe(1); - })); -}); diff --git a/packages/semcom-node/lib/launcher/services/launcher.service.ts b/packages/semcom-node/lib/launcher/services/launcher.service.ts deleted file mode 100644 index df8e493d..00000000 --- a/packages/semcom-node/lib/launcher/services/launcher.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { LoggerService } from '@digita-ai/semcom-core'; -import { ServerOptions } from '../../server/models/server-options.model'; -import { ServerService } from '../../server/services/server.service'; - -export class LauncherService { - constructor(private logger: LoggerService, private server: ServerService, private options: ServerOptions) { } - - public async launch(): Promise { - this.logger.log('debug', 'Launching application'); - - this.server.start(this.options); - } -} diff --git a/packages/semcom-node/lib/main.ts b/packages/semcom-node/lib/main.ts index 4dbcedbb..393bd22b 100644 --- a/packages/semcom-node/lib/main.ts +++ b/packages/semcom-node/lib/main.ts @@ -1,22 +1,23 @@ import * as path from 'path'; import { ComponentsManager } from 'componentsjs'; -import { LauncherService } from './launcher/services/launcher.service'; +import { NodeHttpServer } from '@digita-ai/handlersjs-http'; -const start = async () => { +const launch = async () => { const mainModulePath = path.join(__dirname, '../'); const configPath = path.join(__dirname, '../config/config-default.json'); const manager = await ComponentsManager.build({ mainModulePath, }); - + await manager.configRegistry.register(configPath); - const launcher: LauncherService = await manager.instantiate( - 'urn:semcom-node:default:LauncherService', + const server: NodeHttpServer = await manager.instantiate( + 'urn:handlersjs-http:default:NodeHttpServer', ); - launcher.launch(); + server.start(); + console.log('Server started!'); }; -start(); +launch(); diff --git a/packages/semcom-node/lib/public-api.ts b/packages/semcom-node/lib/public-api.ts index e125c189..a5ffaea0 100644 --- a/packages/semcom-node/lib/public-api.ts +++ b/packages/semcom-node/lib/public-api.ts @@ -1,19 +1,11 @@ -export * from './component/services/component-controller.service'; +export * from './component/services/component.service'; export * from './component/services/component-transformer.service'; export * from './store/services/store.service'; export * from './store/services/component-store.service'; export * from './store/services/component-in-memory-store.service'; export * from './quad/services/quad-serialization.service'; -export * from './server/models/server-options.model'; -export * from './server/models/server-route.model'; -export * from './server/models/server-controller.model'; -export * from './server/services/server.service'; -export * from './server/services/server-mock.service'; -export * from './server/services/server-handler.service'; -export * from './server/services/server-handler-content-negotiation.service'; -export * from './server/services/server-koa.service'; -export * from './launcher/services/launcher.service'; -export * from './server/models/server-error.model'; -export * from './server/models/server-bad-request-error.model'; export * from './component/services/manage-component-store.service'; export * from './component/services/query-component-store.service'; +export * from './handlers/component.handler'; +export * from './handlers/query-component.handler'; +export * from './handlers/content-negotiation.handler'; diff --git a/packages/semcom-node/lib/quad/services/quad-serialization.service.spec.ts b/packages/semcom-node/lib/quad/services/quad-serialization.service.spec.ts index e4a20450..5f4629f7 100644 --- a/packages/semcom-node/lib/quad/services/quad-serialization.service.spec.ts +++ b/packages/semcom-node/lib/quad/services/quad-serialization.service.spec.ts @@ -1,7 +1,7 @@ import { LoggerConsoleService } from '@digita-ai/semcom-core'; import { QuadSerializationService } from './quad-serialization.service'; -describe('ComponentControllerService', () => { +describe('QuadSerializationService', () => { let quads: QuadSerializationService = null; beforeEach(() => { diff --git a/packages/semcom-node/lib/server/models/server-bad-request-error.model.ts b/packages/semcom-node/lib/server/models/server-bad-request-error.model.ts deleted file mode 100644 index e1593c8b..00000000 --- a/packages/semcom-node/lib/server/models/server-bad-request-error.model.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ServerError } from './server-error.model'; - -export class ServerBadRequestError extends ServerError { - public readonly name = ServerBadRequestError.name; - - constructor(message: string, cause?: Error) { - super(message, cause); - - Object.setPrototypeOf(this, ServerBadRequestError.prototype); - } -} diff --git a/packages/semcom-node/lib/server/models/server-controller.model.ts b/packages/semcom-node/lib/server/models/server-controller.model.ts deleted file mode 100644 index 0c0b0e72..00000000 --- a/packages/semcom-node/lib/server/models/server-controller.model.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ServerRoute } from './server-route.model'; - -export abstract class ServerController { - constructor(public routes: ServerRoute[]) { } -} diff --git a/packages/semcom-node/lib/server/models/server-error.model.ts b/packages/semcom-node/lib/server/models/server-error.model.ts deleted file mode 100644 index fd3fbef1..00000000 --- a/packages/semcom-node/lib/server/models/server-error.model.ts +++ /dev/null @@ -1,9 +0,0 @@ -export class ServerError extends Error { - public readonly name = ServerError.name; - - constructor(messsage: string, public cause: Error) { - super(messsage); - - Object.setPrototypeOf(this, ServerError.prototype); - } -} diff --git a/packages/semcom-node/lib/server/models/server-options.model.ts b/packages/semcom-node/lib/server/models/server-options.model.ts deleted file mode 100644 index 72b1102c..00000000 --- a/packages/semcom-node/lib/server/models/server-options.model.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ServerController } from './server-controller.model'; -import { ServerHandlerService } from '../services/server-handler.service'; - -export class ServerOptions { - constructor( - public controllers: ServerController[], - public handlers: ServerHandlerService[], - public port?: number - ) { } -} diff --git a/packages/semcom-node/lib/server/models/server-request.model.ts b/packages/semcom-node/lib/server/models/server-request.model.ts deleted file mode 100644 index 780fd150..00000000 --- a/packages/semcom-node/lib/server/models/server-request.model.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ServerRequest { - headers: { [key: string]: string }; - method: string; - body?: any; -} diff --git a/packages/semcom-node/lib/server/models/server-response.model.ts b/packages/semcom-node/lib/server/models/server-response.model.ts deleted file mode 100644 index 0a53452c..00000000 --- a/packages/semcom-node/lib/server/models/server-response.model.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ServerResponse { - body: any; - headers: { [key: string]: string }; - status: number; -} diff --git a/packages/semcom-node/lib/server/models/server-route.model.ts b/packages/semcom-node/lib/server/models/server-route.model.ts deleted file mode 100644 index 4abcba26..00000000 --- a/packages/semcom-node/lib/server/models/server-route.model.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ServerRequest } from './server-request.model'; -import { ServerResponse } from './server-response.model'; - -export interface ServerRoute { - method: string; - path: string; - execute: (request: ServerRequest) => Promise; -} diff --git a/packages/semcom-node/lib/server/services/server-handler-content-negotiation.service.spec.ts b/packages/semcom-node/lib/server/services/server-handler-content-negotiation.service.spec.ts deleted file mode 100644 index 679f50b9..00000000 --- a/packages/semcom-node/lib/server/services/server-handler-content-negotiation.service.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { ComponentTransformerService } from '../../component/services/component-transformer.service'; -import { LoggerConsoleService } from '@digita-ai/semcom-core'; -import { QuadSerializationService } from '../../quad/services/quad-serialization.service'; -import { ServerHandlerContentNegotiationService } from './server-handler-content-negotiation.service'; -import { ServerRequest } from '../models/server-request.model'; -import { ServerResponse } from '../models/server-response.model'; - -const logger = new LoggerConsoleService(); - -describe('ServerHandlerContentNegotiationService', () => { - it('should be correctly instantiated', () => { - const server = new ServerHandlerContentNegotiationService( - logger, - 'application/ld+json', - new ComponentTransformerService(logger), - new QuadSerializationService(logger) - ); - - expect(server).toBeTruthy(); - }); - - it('should not handle accept */*', async () => { - const server = new ServerHandlerContentNegotiationService( - logger, - 'application/ld+json', - new ComponentTransformerService(logger), - new QuadSerializationService(logger) - ); - const request: ServerRequest = { method: 'GET', headers: { accept: '*/*' } }; - const response: ServerResponse = { - status: 200, - body: 'foo', - headers: { 'content-type': 'application/json' }, - }; - const canHandle = await server.canHandle(request, response); - - expect(canHandle).toBe(true); - }); - - it('should handle accept text/turtle', async () => { - const server = new ServerHandlerContentNegotiationService( - logger, - 'application/ld+json', - new ComponentTransformerService(logger), - new QuadSerializationService(logger) - ); - const request: ServerRequest = { - method: 'GET', - headers: { accept: 'text/turtle' }, - }; - const response: ServerResponse = { - status: 200, - body: 'foo', - headers: { 'content-type': 'application/json' }, - }; - const canHandle = await server.canHandle(request, response); - - expect(canHandle).toBe(true); - }); - - it('should handle accept unknown content types', async () => { - const server = new ServerHandlerContentNegotiationService( - logger, - 'application/ld+json', - new ComponentTransformerService(logger), - new QuadSerializationService(logger) - ); - const request: ServerRequest = { - method: 'GET', - headers: { accept: 'foo/bar' }, - }; - const response: ServerResponse = { - status: 200, - body: 'foo', - headers: { 'content-type': 'application/json' }, - }; - const canHandle = await server.canHandle(request, response); - - expect(canHandle).toBe(true); - }); - - it('should return 406 for unknown content types', async () => { - const server = new ServerHandlerContentNegotiationService( - logger, - 'application/ld+json', - new ComponentTransformerService(logger), - new QuadSerializationService(logger) - ); - const request: ServerRequest = { - method: 'GET', - headers: { accept: 'foo/bar' }, - }; - const response: ServerResponse = { - status: 200, - body: 'foo', - headers: { 'content-type': 'application/json' }, - }; - const updatedResponse = await server.handle(request, response); - - expect(updatedResponse.body).toBe(null); - expect(updatedResponse.status).toBe(406); - }); -}); diff --git a/packages/semcom-node/lib/server/services/server-handler-content-negotiation.service.ts b/packages/semcom-node/lib/server/services/server-handler-content-negotiation.service.ts deleted file mode 100644 index 5f399e44..00000000 --- a/packages/semcom-node/lib/server/services/server-handler-content-negotiation.service.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { ComponentMetadata, LoggerService } from '@digita-ai/semcom-core'; -import { ComponentTransformerService } from '../../component/services/component-transformer.service'; -import { QuadSerializationService } from '../../quad/services/quad-serialization.service'; -import { ServerHandlerService } from './server-handler.service'; -import { ServerRequest } from '../models/server-request.model'; -import { ServerResponse } from '../models/server-response.model'; -import serialize from 'rdf-serialize'; - -export class ServerHandlerContentNegotiationService extends ServerHandlerService { - constructor( - private logger: LoggerService, - private defaultContentType: string, - private transformer: ComponentTransformerService, - private serializer: QuadSerializationService, - ) { - super(); - } - - public async canHandle(request: ServerRequest, response: ServerResponse): Promise { - this.logger.log('debug', 'Checking content negotiation handler', { - request, - response, - }); - - if (!request) { - throw new Error('Argument request should be set.'); - } - - if (!response) { - throw new Error('Argument response should be set.'); - } - - const contentType = request.headers['accept']; - - return contentType !== 'application/json'; - } - - public async handle(request: ServerRequest, response: ServerResponse): Promise { - this.logger.log('debug', 'Running content negotiation handler', { - request, - response, - }); - - if (!request) { - throw new Error('Argument request should be set.'); - } - - if (!response) { - throw new Error('Argument response should be set.'); - } - - let res: ServerResponse = { ...response, status: 406, body: null }; - - const contentType = - !request.headers['accept'] || request.headers['accept'] === '*/*' - ? this.defaultContentType - : request.headers['accept']; - - const isContentTypeSupported = await this.isContentTypeSupported(contentType); - - if (isContentTypeSupported) { - const components: ComponentMetadata[] = response.body; - - const quads = this.transformer.toQuads(components); - - const resultStream = this.serializer.serialize(quads, contentType); - - this.logger.log('debug', 'Mapped to rdf', { request, response }); - - res = { - ...response, - body: resultStream, - headers: { ...response.headers, 'content-type': contentType }, - }; - } - - return res; - } - - private async isContentTypeSupported(contentType: string): Promise { - if (!contentType) { - throw new Error('Argument contentType should be set.'); - } - - const contentTypes = await serialize.getContentTypes(); - - this.logger.log('debug', 'Checking supported content types', { - contentTypes, - contentType, - }); - - if (!contentTypes) { - throw new Error('contentTypes should be set.'); - } - - return contentTypes.some((c) => c === contentType); - } -} diff --git a/packages/semcom-node/lib/server/services/server-handler.service.ts b/packages/semcom-node/lib/server/services/server-handler.service.ts deleted file mode 100644 index 0627fb55..00000000 --- a/packages/semcom-node/lib/server/services/server-handler.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { ServerRequest } from '../models/server-request.model'; -import { ServerResponse } from '../models/server-response.model'; - -export abstract class ServerHandlerService { - public abstract canHandle(request: ServerRequest, response: ServerResponse): Promise; - public abstract handle(request: ServerRequest, response: ServerResponse): Promise; -} diff --git a/packages/semcom-node/lib/server/services/server-koa.service.spec.ts b/packages/semcom-node/lib/server/services/server-koa.service.spec.ts deleted file mode 100644 index dc3c56dc..00000000 --- a/packages/semcom-node/lib/server/services/server-koa.service.spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import * as request from 'supertest'; -import { - ComponentMetadata, - LoggerConsoleService, -} from '@digita-ai/semcom-core'; -import { ComponentControllerService } from '../../component/services/component-controller.service'; -import { ComponentInMemoryStore } from '../../store/services/component-in-memory-store.service'; -import { ComponentTransformerService } from '../../component/services/component-transformer.service'; -import { ManageComponentStoreService } from '../../component/services/manage-component-store.service'; -import { QuadSerializationService } from '../../quad/services/quad-serialization.service'; -import { QueryComponentStoreService } from '../../component/services/query-component-store.service'; -import { ServerHandlerContentNegotiationService } from './server-handler-content-negotiation.service'; -import { ServerKoaService } from './server-koa.service'; -import { initialComponents } from '../../mock/initial-components'; - -const logger = new LoggerConsoleService(); - -describe('Server', () => { - let server: ServerKoaService = null; - const mockListen = jest.fn(); - const components: ComponentMetadata[] = initialComponents; - - afterEach(() => { - mockListen.mockReset(); - }); - - it('should be correctly instantiated without options', () => { - server = new ServerKoaService(logger); - - expect(server).toBeTruthy(); - }); - - it('should be correctly instantiated with options', () => { - server = new ServerKoaService(logger); - - expect(server).toBeTruthy(); - }); - - it('should be start on port 3000 by default', () => { - server = new ServerKoaService(logger); - server.app.listen = mockListen; - - server.start({ controllers: [], handlers: [] }); - - expect(mockListen.mock.calls.length).toBe(1); - expect(mockListen.mock.calls[0][0]).toBe(3000); - }); - - it('should be start on port specified in options', () => { - server = new ServerKoaService(logger); - server.app.listen = mockListen; - - server.start({ port: 666, controllers: [], handlers: [] }); - - expect(mockListen.mock.calls.length).toBe(1); - expect(mockListen.mock.calls[0][0]).toBe(666); - }); - - it('should return 200 on registered routes', async () => { - const store = new ComponentInMemoryStore(components); - - server.start({ - controllers: [ - new ComponentControllerService( - new QueryComponentStoreService(store), - new ManageComponentStoreService(store), - logger, - ), - ], - handlers: [], - }); - - const response = await request(server.app.callback()).get('/component'); - expect(response.status).toBe(200); - }); - - it('should call a handlers canHandle', async () => { - const handler = new ServerHandlerContentNegotiationService( - logger, - 'application/ld+json', - new ComponentTransformerService(logger), - new QuadSerializationService(logger), - ); - handler.canHandle = mockListen; - - const store = new ComponentInMemoryStore(components); - - server.start({ - controllers: [ - new ComponentControllerService( - new QueryComponentStoreService(store), - new ManageComponentStoreService(store), - logger, - ), - ], - handlers: [handler], - }); - - await request(server.app.callback()).get('/component'); - - expect(mockListen.mock.calls.length).toBe(1); - }); - - it('should call a handlers handle', async () => { - const handler = new ServerHandlerContentNegotiationService( - logger, - 'application/ld+json', - new ComponentTransformerService(logger), - new QuadSerializationService(logger), - ); - handler.handle = mockListen; - - const store = new ComponentInMemoryStore(components); - - server.start({ - controllers: [ - new ComponentControllerService( - new QueryComponentStoreService(store), - new ManageComponentStoreService(store), - logger, - ), - ], - handlers: [handler], - }); - - await request(server.app.callback()).get('/component'); - - expect(mockListen.mock.calls.length).toBe(1); - }); - - it('should return 404 on unknow routes', async () => { - const store = new ComponentInMemoryStore(components); - - server.start({ - controllers: [ - new ComponentControllerService( - new QueryComponentStoreService(store), - new ManageComponentStoreService(store), - logger, - ), - ], - handlers: [], - }); - - const response = await request(server.app.callback()).get('/foo-bar'); - expect(response.status).toBe(404); - }); -}); diff --git a/packages/semcom-node/lib/server/services/server-koa.service.ts b/packages/semcom-node/lib/server/services/server-koa.service.ts deleted file mode 100644 index 766dd810..00000000 --- a/packages/semcom-node/lib/server/services/server-koa.service.ts +++ /dev/null @@ -1,176 +0,0 @@ -import * as Koa from 'koa'; -import * as Router from '@koa/router'; -import * as bodyParser from 'koa-bodyparser'; -import * as cors from '@koa/cors'; -import type { DefaultContext, DefaultState, ParameterizedContext } from 'koa'; -import { LoggerService } from '@digita-ai/semcom-core'; -import { Server } from 'http'; -import { ServerBadRequestError } from '../models/server-bad-request-error.model'; -import { ServerHandlerService } from './server-handler.service'; -import { ServerOptions } from '../models/server-options.model'; -import { ServerRequest } from '../models/server-request.model'; -import { ServerResponse } from '../models/server-response.model'; -import { ServerRoute } from '../models/server-route.model'; -import { ServerService } from './server.service'; - -export class ServerKoaService extends ServerService { - public app: Koa = new Koa(); - public router: Router = new Router(); - public server: Server = null; - - constructor(private logger: LoggerService) { - super(); - } - - public async start(options: ServerOptions): Promise { - this.logger.log('debug', 'Starting server'); - - if (!options) { - throw new Error('Attribute options should be set'); - } - - const routes = options.controllers.map((controller) => controller.routes).reduce((acc, val) => acc.concat(val), []); - - this.logger.log('debug', 'Determined routes', routes); - - routes.forEach((route) => - this.router.register( - route.path, - [route.method], - async (ctx) => await this.executeAndTransform(route, ctx, options.handlers), - ), - ); - - this.logger.log('debug', 'Registered controllers'); - - this.app.use(cors({ origin: '*' })); - this.app.use(bodyParser({ strict: true })); - this.app.use(this.router.routes()); - this.app.use(this.router.allowedMethods()); - - const port = options?.port || 3000; - this.server = this.app.listen(port); - - this.logger.log('debug', `Server listening on port ${port}`); - } - - private async executeHandlers( - handlers: ServerHandlerService[], - request: ServerRequest, - response: ServerResponse, - ): Promise { - if (!handlers) { - throw new Error('Argument handlers should be set.'); - } - - if (!request) { - throw new Error('Argument request should be set.'); - } - - if (!response) { - throw new Error('Argument response should be set.'); - } - - let handledResponse = { ...response }; - - for (const handler of handlers) { - handledResponse = await this.executeHandler(handler, request, response); - } - - return handledResponse; - } - - private async executeHandler( - handler: ServerHandlerService, - request: ServerRequest, - response: ServerResponse, - ): Promise { - if (!handler) { - throw new Error('Argument handler should be set.'); - } - - if (!request) { - throw new Error('Argument request should be set.'); - } - - if (!response) { - throw new Error('Argument response should be set.'); - } - - let handledResponse: ServerResponse = { ...response }; - - const canHandle = await handler.canHandle(request, response); - - if (canHandle) { - handledResponse = await handler.handle(request, response); - } - - return handledResponse; - } - - private async executeAndTransform( - route: ServerRoute, - ctx: ParameterizedContext, - handlers: ServerHandlerService[], - ): Promise { - if (!route) { - throw new Error('Attribute route should be set'); - } - - if (!ctx) { - throw new Error('Attribute ctx should be set'); - } - - const request = this.generateRequest(ctx); - - let originalResponse: ServerResponse = null; - - try { - originalResponse = await route.execute(request); - } catch (error) { - if (error instanceof ServerBadRequestError) { - this.logger.log('warn', 'Bad request', { error }); - - originalResponse = { - body: error.message, - status: 400, - headers: null, - }; - } - } - - this.logger.log('debug', 'Executed route', { originalResponse }); - - const handledResponse = await this.executeHandlers( - handlers, - request, - originalResponse, - ); - - this.logger.log('debug', 'Handled response', { - originalResponse, - handledResponse, - }); - - ctx.body = handledResponse.body; - ctx.status = handledResponse.status; - - if (handledResponse.headers) { - ctx.response.set(handledResponse.headers); - } - } - - private generateRequest( - ctx: ParameterizedContext, - ): ServerRequest { - if (!ctx) { - throw new Error('Argument ctx should be set.'); - } - - return { - body: ctx.request.body, - method: ctx.req.method, - headers: ctx.req.headers as { [key: string]: string }, - }; - } -} diff --git a/packages/semcom-node/lib/server/services/server-mock.service.ts b/packages/semcom-node/lib/server/services/server-mock.service.ts deleted file mode 100644 index 4f8026c2..00000000 --- a/packages/semcom-node/lib/server/services/server-mock.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { LoggerService } from '@digita-ai/semcom-core'; -import { ServerOptions } from '../models/server-options.model'; -import { ServerService } from './server.service'; - -export class ServerMockService extends ServerService { - - constructor(private logger: LoggerService) { - super(); - } - - public async start(options: ServerOptions): Promise { - this.logger.log('debug', 'Starting server with options', options); - } -} diff --git a/packages/semcom-node/lib/server/services/server.service.ts b/packages/semcom-node/lib/server/services/server.service.ts deleted file mode 100644 index 7172f98d..00000000 --- a/packages/semcom-node/lib/server/services/server.service.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ServerOptions } from '../models/server-options.model'; - -export abstract class ServerService { - abstract start(options: ServerOptions): Promise; -} diff --git a/packages/semcom-node/lib/store/services/component-in-memory-store.service.spec.ts b/packages/semcom-node/lib/store/services/component-in-memory-store.service.spec.ts index 89b8c319..0f9eb040 100644 --- a/packages/semcom-node/lib/store/services/component-in-memory-store.service.spec.ts +++ b/packages/semcom-node/lib/store/services/component-in-memory-store.service.spec.ts @@ -15,7 +15,6 @@ describe('ComponentInMemoryStoreService', () => { }); it('should save correct component', () => { - const service = new ComponentInMemoryStore(components); const mockComponent = { uri: 'foo5/bar', description: 'test5', diff --git a/packages/semcom-node/package-lock.json b/packages/semcom-node/package-lock.json index cbb90af3..a89bfc32 100644 --- a/packages/semcom-node/package-lock.json +++ b/packages/semcom-node/package-lock.json @@ -646,6 +646,44 @@ "kuler": "^2.0.0" } }, + "@digita-ai/handlersjs-core": { + "version": "0.0.9", + "resolved": "https://npm.pkg.github.com/download/@digita-ai/handlersjs-core/0.0.9/0422364e14fc2bbdda8ee95400be997cf68c8b9c87e56966ed00aff2b151a1b7", + "integrity": "sha512-uFJN3KZRPjfNRB9NwV00JFqdGHx3loe31fKXS1rVy3+W6AnwlEsnp3KKrG373+E7h+jOts+reuBzHQIYL4Hsig==", + "requires": { + "rxjs": "6.3.3" + }, + "dependencies": { + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@digita-ai/handlersjs-http": { + "version": "0.0.9", + "resolved": "https://npm.pkg.github.com/download/@digita-ai/handlersjs-http/0.0.9/2c4d75762ca70ef88e3e92aa68492c20e1c14237b5efd0358f8c6c46d44b717d", + "integrity": "sha512-Gq7gI1cicR4WCHosFzviqxuoNnvcJMPojeDVJkN0dA4BTLQQMZpsJTGrDbpNpD3OzNP2c0nDk3yk755YaeEJqw==", + "requires": { + "@digita-ai/handlersjs-core": "0.0.9", + "mock-http": "^1.1.0", + "rxjs": "6.3.3" + }, + "dependencies": { + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "requires": { + "tslib": "^1.9.0" + } + } + } + }, "@eslint/eslintrc": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", @@ -4831,6 +4869,11 @@ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "mergee": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mergee/-/mergee-1.0.0.tgz", + "integrity": "sha512-hbbXD4LOcxVkpS+mp3BMEhkSDf+lTVENFeEeqACgjjL8WrgKuW2EyLT0fOHyTbyDiuRLZJZ1HrHNeiX4iOd79Q==" + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -4944,6 +4987,14 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, + "mock-http": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mock-http/-/mock-http-1.1.0.tgz", + "integrity": "sha512-H2HMGaHNQPWY8PdeEw4RFux2WEOHD6eJAtN3+iFELik5kGjPKAcoyPWcsC2vgDiTa2yimAEDssmMed51e+cBKQ==", + "requires": { + "mergee": "^1.0.0" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -5845,6 +5896,14 @@ "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", "dev": true }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -6745,8 +6804,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tsscmp": { "version": "1.0.6", diff --git a/packages/semcom-node/package.json b/packages/semcom-node/package.json index 98518648..fbf369bb 100644 --- a/packages/semcom-node/package.json +++ b/packages/semcom-node/package.json @@ -34,8 +34,8 @@ "lint:fix": "eslint --fix .", "prepare": "npm run build", "start": "node dist/main.js", - "test": "jest", - "test:ci": "jest --runInBand" + "test": "jest --silent", + "test:ci": "jest --runInBand --silent" }, "bugs": { "url": "https://github.com/digita-ai/semcom/issues" @@ -43,11 +43,9 @@ "lsd:module": "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-node", "lsd:components": "dist/components/components.jsonld", "lsd:contexts": { - "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-core/^0.0.0/components/context.jsonld": "node_modules/@digita-ai/semcom-core/dist/components/context.jsonld", "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-node/^0.0.0/components/context.jsonld": "dist/components/context.jsonld" }, "lsd:importPaths": { - "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-core/^0.0.0/components/": "node_modules/@digita-ai/semcom-core/dist/components/", "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-node/^0.0.0/components/": "dist/components/", "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-node/^0.0.0/config/": "config/", "https://linkedsoftwaredependencies.org/bundles/npm/@digita-ai/semcom-node/^0.0.0/dist/": "dist/" @@ -56,6 +54,8 @@ "node": ">=12.17 <15" }, "dependencies": { + "@digita-ai/handlersjs-core": "^0.0.9", + "@digita-ai/handlersjs-http": "^0.0.9", "@digita-ai/semcom-core": "0.3.4", "@koa/cors": "^3.1.0", "@koa/router": "^10.0.0", @@ -65,6 +65,7 @@ "koa-bodyparser": "^4.3.0", "rdf-quad": "^1.5.0", "rdf-serialize": "^1.0.1", + "rxjs": "6.3.3", "streamify-array": "^1.0.1" }, "devDependencies": { diff --git a/packages/semcom-node/tsconfig.json b/packages/semcom-node/tsconfig.json index 1d9c0759..61743727 100644 --- a/packages/semcom-node/tsconfig.json +++ b/packages/semcom-node/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "module": "commonjs", - "target": "es5", + "target": "es2020", + "lib": [ + "es2020", + "dom" + ], "sourceMap": true, "declaration": true, "outDir": "dist", @@ -17,4 +21,4 @@ "**/*.spec.ts", "dist" ] -} \ No newline at end of file +} diff --git a/requests/components.http b/requests/components.http index eeb91f07..930a7932 100644 --- a/requests/components.http +++ b/requests/components.http @@ -43,7 +43,7 @@ Accept: text/turtle Content-Type: application/json { - "uri": "https://node.semcom.digita.ai/c/001" + "uri": "https://components.semcom.digita.ai/components/profile.js" } ########