From cd45246ca24f9287a26fc2d867774cd1d5443c86 Mon Sep 17 00:00:00 2001 From: Cody Dalton Date: Fri, 18 May 2018 05:45:49 -0700 Subject: [PATCH 1/2] lit-50: offer next in res class passed to component handler --- README.md | 328 +++++++++---------- lib/compiler/classes/compiler.class.spec.ts | 211 +++++++++++- lib/compiler/classes/compiler.class.ts | 47 +-- lib/compiler/classes/injector.class.spec.ts | 27 ++ lib/compiler/classes/injector.class.ts | 4 + lib/compiler/components/default.component.ts | 14 + lib/http/classes/response.class.ts | 37 ++- lib/http/index.ts | 1 + lib/http/models/next.model.ts | 12 + package-lock.json | 103 ++---- package.json | 3 +- test/mocha.opts | 1 + 12 files changed, 500 insertions(+), 288 deletions(-) create mode 100644 lib/compiler/components/default.component.ts create mode 100644 lib/http/models/next.model.ts diff --git a/README.md b/README.md index 569abc2..701c9dd 100644 --- a/README.md +++ b/README.md @@ -2,90 +2,45 @@ # Litstack -## Vision - -Using Angular and Spring boot design patterns, create a Typescript REST framework that developers already know how to use, keeping their code organized and concise, and pushing off all the express wiring to the library. +Using Angular and Spring boot design patterns, Litstack is a Typescript REST framework that Angular/Spring Boot engineers already know how to use, keeping their code organized and concise, and pushing off the express wiring to the library. ## Getting Started ### Option 1: Clone the seed app -You can [clone the litstack seed](https://github.com/codyjdalton/litstack-seed) and get started right away. Or you can set up a project manually. +Follow the directions in the [the litstack seed](https://github.com/codyjdalton/litstack-seed) to get started right away. ### Option 2: Start with a blank slate -Create a project and install the Litstack core library. +Create a new project and install the Litstack core library. ``` -> mkdir my-project -> cd my-project -> npm init -y -> npm install typescript -D -> npm install @litstack/core +> npm install @litstack/core --save ``` -## Components -Components are used as route listeners. - -### Basic Component - -```typescript -// in app.component.ts -import { LitComponent } from '@litstack/core'; -import { HttpRequest, HttpResponse } from '@litstack/core/dist/http'; -import { GetMapping } from '@litstack/core/dist/http/mappings'; - -@LitComponent() -export class AppComponent { +Make sure experimental decorators are on in your tsconfig.json at the root level: - private message = 'Hello World!'; - - @GetMapping({ - path: '' // GET / is routed to onHello - }) - onHello(req: HttpRequest, res: HttpResponse): void { - res.success({ - message: this.message - }); +```json +{ + "compilerOptions": { + "experimentalDecorators": true } } ``` -### Testing - -Test your components [using supertest methods](https://github.com/visionmedia/supertest) and the Litstack TestBed: - -```typescript -import { TestBed, LitComponentTest } from '@litstack/core/dist/testing'; - -describe('AppComponent', () => { +For more on building, see the minimum configuration section below. - let component: LitComponentTest; - - beforeEach(() => { - - component = TestBed.start(AppComponent); - }); +## Bootstrapping - afterEach(() => { +Bootstrap app.module at the index.ts level: - TestBed.stop(); - }); +```typescript +// in index.ts +import { LitCompiler } from '@litstack/core/dist/compiler'; - it('should return a welcome message', (done) => { +import { AppModule } from './modules/app.module'; - component - .get('/') - .expect(200) - .expect((res) => { - expect(res.body.message).to.equal('Hello World!'); - }) - .end((err, res) => { - if (err) return done(err); - done(); - }); - }); -}); +LitCompiler.bootstrap(AppModule); ``` ## Modules @@ -112,7 +67,7 @@ export class AppModule { ### Module with Imports -Modules can also include imports. +Modules can also import other modules with components: ```typescript import { LitModule } from '@litstack/core'; @@ -123,8 +78,8 @@ import { PeopleModule } from './modules/people/people.module'; import { VersionComponent } from './components/version/version.component'; @LitModule({ - path: '', // this could be 'your-path' and all packaged routes - imports: [ // would be registered at '/your-path/...' + path: 'api', // will add all imported routes at '/api/..' + imports: [ ItemsModule, PeopleModule, OrdersModule @@ -137,168 +92,185 @@ export class AppModule { } ``` -## Bootstrapping +## Components +Components register route listeners: -Bootstrap app.module at the index.ts level: +### Basic Component ```typescript -// in index.ts -import { LitCompiler } from '@litstack/core/dist/compiler'; -import { AppModule } from './modules/app.module'; - -LitCompiler.bootstrap(AppModule); -``` - -## Services - -### Basic Service +// in app.component.ts +import { LitComponent } from '@litstack/core'; +import { HttpResponse } from '@litstack/core/dist/http'; +import { GetMapping } from '@litstack/core/dist/http/mappings'; -Services can be defined and injected into components: +@LitComponent() +export class AppComponent { -```typescript -// in hello.service -import { LitService } from '@litstack/core'; + private message = 'Hello World!'; -@LitService() -export class HelloService { - - getMessage(id: string) { - return { - message: 'Hello at ' + id - }; + @GetMapping() // defaults to '/' + onHello(res: HttpResponse): void { + res.success({ + message: this.message + }); } } ``` +### Using path params + +Specify params in the path: + ```typescript -// in app.component import { LitComponent } from '@litstack/core'; import { HttpRequest, HttpResponse } from '@litstack/core/dist/http'; import { GetMapping } from '@litstack/core/dist/http/mappings'; -import { HelloService } from './hello.service'; - @LitComponent() -export class AppComponent { +export class ItemsComponent { - constructor(private helloService: HelloService) { - } - /** - * @function sayHello - * @description return a success response with a message - */ @GetMapping({ path: ':id' }) - sayHello(req: HttpRequest, res: HttpResponse): void { + getItem(req: HttpRequest, res: HttpResponse): void { res.success({ - message: this.helloService.getMessage(req.params.id) + id: req.params.id }); } } ``` -## Full Component Example +### Chaining methods with next -Notice the 'produces' param on the get mapping. That will add a 'Content-Type' header to the response. +We can keep our route functionality isolated by using the "next" param: ```typescript -// people.component.ts - -// listack imports: import { LitComponent } from '@litstack/core'; -import { HttpRequest, HttpResponse } from '@litstack/core/dist/http'; -import { GetMapping, - PutMapping, - PostMapping } from '@litstack/core/dist/http/mappings'; - -// custom imports: -import { Person } from '../../common/models/person.model'; -import { PersonService } from '../../common/services/person.service'; -import { ResourceVersions } from '../common/enums/resource-versions.enum'; +import { HttpRequest, HttpResponse, HttpNext } from '@litstack/core/dist/http'; +import { PutMapping } from '@litstack/core/dist/http/mappings'; @LitComponent() -export class PeopleComponent { +export class ItemsComponent { - constructor(private personService: PersonService) { - } - - /** - * @function getPeople - * @description Return a list of people - */ - @GetMapping({ - produces: ResourceVersions.PEOPLE_V1 // Content-Type header + // NOTE: The order matters here: + @PutMapping({ + path: ':id' }) - getPeople(req: HttpRequest, res: HttpResponse): void { - this.personService.fetchAll() - .subscribe( - (people: Person[]) => res.success(people), - (err) => res.error(401) - ) - } + updateItem(req: HttpRequest, res: HttpResponse, next: HttpNext) { - /** - * @function getPerson - * @description Return a single person - */ - @GetMapping({ - path: ':id', - produces: ResourceVersions.PEOPLE_V1 // Content-Type header - }) - getPerson(req: HttpRequest, res: HttpResponse): void { - this.personService.fetchById(req.params.id) - .subscribe( - (people: Person[]) => res.success(people), - (err) => res.error(401) - ) + if(req.param.id === 'some-id') { + + // do some update + res.success({ id: 'some-id' }); + return; + } + + next(); } - /** - * @function updatePerson - * @description Update a 'person' record - */ + // same route as above, will run only if "next" is called @PutMapping({ - path: ':id', // accessed by PUT /people/:id - produces: ResourceVersions.PERSON_V1 // Content-Type header + path: ':id' }) - updatePerson(req: HttpRequest, res: HttpResponse): void { - this.personService.update(req.params.id, req.body) - .subscribe( - (person: Person) => res.success(person), - (err) => res.error(404) - ) + updateItemErr(res: HttpResponse) { + + res.error(404); } +} +``` - /** - * @function createPerson - * @description Create a 'person' record - */ - @PostMapping({ - produces: ResourceVersions.PERSON_V1 // Content-Type header - }) - createPerson(req: HttpRequest, res: HttpResponse): void { - // update person - this.personService.update(null, req.body) - .subscribe( - (person: Person) => res.created(person), - (err) => res.error(404) - ) +### Dependency Injection + +Services are a great place for business logic: + +```typescript +// ./services/items.service +import { LitService } from '@litstack/core'; + +@LitService() +export class ItemsService { + + get description(): string { + return 'This is an item description'; } } ``` +And then in our component: -## Building the app +```typescript +import { LitComponent } from '@litstack/core'; +import { HttpResponse } from '@litstack/core/dist/http'; +import { GetMapping } from '@litstack/core/dist/http/mappings'; -Your build process can be customized to your needs, but a minimum configuration could look like this: +import { ItemsService } from './services/items.service'; +@LitComponent() +export class ItemsComponent { + + constructor(private itemsService: ItemsService) {} + + @GetMapping() + getItems(res: HttpResponse) { + res.success({ + description: this.itemsService.description + }); + } +} ``` + +## Testing + +Test components [using supertest methods](https://github.com/visionmedia/supertest) and the Litstack TestBed: + +```typescript +import { TestBed, LitComponentTest } from '@litstack/core/dist/testing'; + +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + + let component: LitComponentTest; + + beforeEach(() => { + + component = TestBed.start(AppComponent); + }); + + afterEach(() => { + + TestBed.stop(); + }); + + it('should return a welcome message', (done) => { + + component + .get('/') + .expect(200) + .expect((res) => { + expect(res.body.message).to.equal('Hello World!'); + }) + .end((err, res) => { + if (err) return done(err); + done(); + }); + }); +}); +``` + +## Minimum Configuration + +The build process can be customized to the needs of the project, but a minimum configuration could look like this: + +``` +> mkdir my-project +> cd my-project +> npm init -y +> npm install @litstack/core --save +> npm install typescript -D > npm install ts-node -D -> npm install @types/node -D -> npm install @types/express -D ``` -Change the following in your package.json: +Change the following in package.json: ```json { @@ -309,7 +281,7 @@ Change the following in your package.json: } ``` -You'll also need a tsconfig.json. +A tsconfig.json could look like this: ```json { @@ -329,7 +301,7 @@ You'll also need a tsconfig.json. } ``` -Now you should be able to run your app: +Now, run the app: ``` > npm start diff --git a/lib/compiler/classes/compiler.class.spec.ts b/lib/compiler/classes/compiler.class.spec.ts index 080050f..846fb47 100644 --- a/lib/compiler/classes/compiler.class.spec.ts +++ b/lib/compiler/classes/compiler.class.spec.ts @@ -10,9 +10,10 @@ import { mapTo, delay } from 'rxjs/operators'; import { GetMapping, PostMapping, PutMapping, PatchMapping, DeleteMapping } from '../../http/mappings'; import { Injector } from './injector.class'; -import { LitModule, LitComponent } from '../..'; +import { LitModule, LitComponent, LitService } from '../..'; import { ServiceCompiler, LitCompiler } from './compiler.class'; -import { HttpResponse } from '../../http'; +import { HttpRequest, HttpResponse, HttpNext } from '../../http'; +import { TestBed, LitComponentTest } from '../../testing'; describe('Class: Compiler', () => { @@ -350,4 +351,208 @@ describe('Class: Compiler', () => { done(); }); }); - }); \ No newline at end of file + + it('should inject res if the handler has a single param', (done) => { + + @LitComponent() + class TestComponent { + + @GetMapping() + getItems(res: HttpResponse): void { + res.success({ message: 'succeeded' }); + } + } + + let component: LitComponentTest = TestBed.start(TestComponent); + + component + .get('/') + .expect(200) + .expect((res) => { + expect(res.body.message).to.equal('succeeded'); + }) + .end(function(err, res) { + TestBed.stop(); + if (err) return done(err); + done(); + }); + }); + + it('should inject req and res if the handler has two params', (done) => { + + @LitComponent() + class TestComponent { + + @GetMapping({ + path: ':id' + }) + getItems(req: HttpRequest, res: HttpResponse): void { + res.success({ message: req.params.id }); + } + } + + let component: LitComponentTest = TestBed.start(TestComponent); + + component + .get('/another-test') + .expect(200) + .expect((res) => { + expect(res.body.message).to.equal('another-test'); + }) + .end(function(err, res) { + TestBed.stop(); + if (err) return done(err); + done(); + }); + }); + + it('should inject req, res, and next if the handler has three params', (done) => { + + @LitComponent() + class TestComponent { + + @GetMapping({ + path: ':id' + }) + getItems(req: HttpRequest, res: HttpResponse, next: HttpNext): void { + + if(req.params.id === 'test') { + res.success({ message: req.params.id }); + return; + } + + next(); + } + + @GetMapping({ + path: ':id' + }) + getItemsErr(res: HttpResponse): void { + res.errored(404, { message: 'error' }); + } + } + + let component: LitComponentTest = TestBed.start(TestComponent); + + component + .get('/yet-another-test') + .expect(404) + .expect((res) => { + expect(res.body.message).to.equal('error'); + }) + .end(function(err, res) { + TestBed.stop(); + if (err) return done(err); + done(); + }); + }); + + it('should only handle the next route if next is called', (done) => { + + @LitComponent() + class TestComponent { + + @GetMapping({ + path: ':id' + }) + getItems(req: HttpRequest, res: HttpResponse, next: HttpNext): void { + + if(req.params.id === 'test') { + res.success({ message: req.params.id }); + return; + } + + next(); + } + + @GetMapping({ + path: ':id' + }) + getItemsErr(res: HttpResponse): void { + res.errored(404, { message: 'error' }); + } + } + + + let component: LitComponentTest = TestBed.start(TestComponent); + + component + .get('/test') + .expect(200) + .expect((res) => { + expect(res.body.message).to.equal('test'); + }) + .end(function(err, res) { + TestBed.stop(); + if (err) return done(err); + done(); + }); + }); + + it('should return 404 if no params are defined', (done) => { + + @LitComponent() + class TestComponent { + + @GetMapping({ + path: ':id' + }) + getItems(): void { + + // I'm not really sure what they are doing here + // but hey, someone will do it + // we want to fail if this method is called + // and instead send them to the default component + // which will throw a 501 err + expect(true).to.be.false; + } + } + + let component: LitComponentTest = TestBed.start(TestComponent); + + component + .get('/test') + .expect(501) + .end(function(err, res) { + TestBed.stop(); + if (err) return done(err); + done(); + }); + }); + + it('should inject dependencies', (done) => { + + @LitService() + class TestService { + message: string = 'test-val' + } + + @LitComponent() + class TestComponent { + + constructor(private testService: TestService) { + } + + @GetMapping() + getItems(res: HttpResponse) { + res.success({ + message: this.testService.message + }); + } + } + + let component: LitComponentTest = TestBed.start(TestComponent); + + component + .get('/') + .expect(200) + .expect(res => { + expect(res.body.message).to.equal('test-val'); + }) + .end(function(err, res) { + TestBed.stop(); + if (err) return done(err); + done(); + }); + }); +}); \ No newline at end of file diff --git a/lib/compiler/classes/compiler.class.ts b/lib/compiler/classes/compiler.class.ts index f3a70de..2d9b12e 100644 --- a/lib/compiler/classes/compiler.class.ts +++ b/lib/compiler/classes/compiler.class.ts @@ -6,12 +6,13 @@ import BodyParser = require('body-parser'); import { Application, RequestHandler } from 'express'; - import { CoreCompiler, ILitComponent, ILitModule } from '../utils/compiler.utils'; +import { DefaultComponent } from '../components/default.component'; import { HttpServer } from '../../http/utils/http.utils'; -import { RequestMethod } from '../../http/enums/request-method.enum'; import { HttpResponse } from '../../http/classes/response.class'; +import { HttpNext } from '../../http/models/next.model'; import { Injector } from './injector.class'; +import { RequestMethod } from '../../http/enums/request-method.enum'; export class ServiceCompiler extends CoreCompiler { @@ -115,21 +116,26 @@ export class ServiceCompiler extends CoreCompiler { /** * @function getHandler - * @param {ILitComponent} aComponent + * @param {ILitComponent} component * @param {string} name */ - private getHandler(aComponent: ILitComponent, name: string): RequestHandler { + private getHandler(component: ILitComponent, name: string): RequestHandler { return (req: express.Request, res: express.Response, - next: express.NextFunction): RequestHandler => { + next: HttpNext): RequestHandler => { - // include metadata to send with response const meta: Object = Injector.getAll( - aComponent, + component.prototype, name ); - - return aComponent[name](req, new HttpResponse(res, meta)); + const wrappedRes: HttpResponse = new HttpResponse(res, meta); + const paramLen: number = Injector.getParams(component, name).length; + const aComponent: ILitComponent = Injector.resolve(component); + + return paramLen === 1 ? aComponent[name](wrappedRes) : + paramLen === 2 ? aComponent[name](req, wrappedRes) : + paramLen === 3 ? aComponent[name](req, wrappedRes, next) : + DefaultComponent.prototype.notImplemented(wrappedRes); }; } @@ -137,18 +143,18 @@ export class ServiceCompiler extends CoreCompiler { * @function addRoute * @param {string} method * @param {string} path - * @param {string} aComponent + * @param {string} component * @param {string} name * Usage: * - * LitCompiler.addRoute('post', 'some/route' aComponent, 'someMethod'); + * LitCompiler.addRoute('post', 'some/route' SomeComponent, 'someMethod'); * * Sets: * POST /some/route - * adds handler aComponent.someMethod + * adds handler SomeComponent.someMethod */ - private addRoute(method: RequestMethod, path: string, aComponent: ILitComponent, name: string): void { - this.app[method]('/' + path, this.getHandler(aComponent, name)); + private addRoute(method: RequestMethod, path: string, component: ILitComponent, name: string): void { + this.app[method]('/' + path, this.getHandler(component, name)); } /** @@ -167,19 +173,22 @@ export class ServiceCompiler extends CoreCompiler { */ private addRouteFromMethod(component: ILitComponent, method: string, path: string) { - // get a new instance of the component - const aComponent: ILitComponent = Injector.resolve(component); - const reqMethod: RequestMethod = Injector.get(aComponent, 'method', null, method); + const reqMethod: RequestMethod = Injector.get( + component.prototype, + 'method', + null, + method + ); // check if method is elligible for route and add if(reqMethod) { path = this.getPath([ path, - Injector.get(aComponent, 'path', null, method) + Injector.get(component.prototype, 'path', null, method) ]); - this.addRoute(reqMethod, path, aComponent, method); + this.addRoute(reqMethod, path, component, method); } } diff --git a/lib/compiler/classes/injector.class.spec.ts b/lib/compiler/classes/injector.class.spec.ts index d508fb5..2f7142f 100644 --- a/lib/compiler/classes/injector.class.spec.ts +++ b/lib/compiler/classes/injector.class.spec.ts @@ -106,4 +106,31 @@ describe('Class: Injector', () => { expect(metadata.path).to.equal(expectedMetadata.path); expect(metadata.produces).to.equal(expectedMetadata.produces); }); + + it('should return a list of param types', () => { + + @LitComponent() + class TestComponent { + + @GetMapping() + resOnly(res) { + + } + + @GetMapping() + reqRes(req, res) { + + } + + @GetMapping() + reqResNext(req, res, next) { + + } + } + + expect(Injector.getParams(TestComponent, 'noMethod').length).to.equal(0); + expect(Injector.getParams(TestComponent, 'resOnly').length).to.equal(1); + expect(Injector.getParams(TestComponent, 'reqRes').length).to.equal(2); + expect(Injector.getParams(TestComponent, 'reqResNext').length).to.equal(3); + }); }); \ No newline at end of file diff --git a/lib/compiler/classes/injector.class.ts b/lib/compiler/classes/injector.class.ts index 052f239..0184185 100644 --- a/lib/compiler/classes/injector.class.ts +++ b/lib/compiler/classes/injector.class.ts @@ -56,4 +56,8 @@ export const Injector = new class { }, {} ); } + + getParams(target: Type, propertyKey: string) { + return Reflect.getMetadata('design:paramtypes', target.prototype, propertyKey) || []; + } }; \ No newline at end of file diff --git a/lib/compiler/components/default.component.ts b/lib/compiler/components/default.component.ts new file mode 100644 index 0000000..3da1d3b --- /dev/null +++ b/lib/compiler/components/default.component.ts @@ -0,0 +1,14 @@ +/** + * default.component + */ +import { HttpResponse } from "../../http"; +import { LitComponent } from "../.."; + +@LitComponent() +export class DefaultComponent { + + notImplemented(res: HttpResponse): void { + + res.errored(501); + } +} \ No newline at end of file diff --git a/lib/http/classes/response.class.ts b/lib/http/classes/response.class.ts index af426c3..456eb15 100644 --- a/lib/http/classes/response.class.ts +++ b/lib/http/classes/response.class.ts @@ -4,17 +4,19 @@ * The HTTP response class is a wrapper for the * http response object */ - import express = require('express'); +import defaultResponse = require('default-response'); import { Response } from '../models/response.model'; import { Injector } from '../../compiler/classes/injector.class'; -// @TODO MOVE THIS TO ITS OWN FILE!!!! +// @TODO MOVE THESE TO THEIR OWN FILE!!!! export interface metaConfig { produces?: string; } +export type Body = Object | any[] | null; + export class HttpResponse { constructor(public response: express.Response, @@ -33,7 +35,7 @@ export class HttpResponse { /** * @function success - * @param {any} obj + * @param {Body} body * * Usage: * res.success({ id: 'some-id' }) @@ -41,17 +43,14 @@ export class HttpResponse { * Would respond with 200 OK * { id: 'some-id' } */ - success(obj: Object | any[], status: number = 200): void { - - // set the produces header if one exists - this.setProduces(); + success(body: Body, status: number = 200): void { - this.response.status(status).json(obj); + this.respond(status, body); } /** * @function created - * @param {any} obj + * @param {Body} body * * Usage: * res.created({ id: 'some-id' }) @@ -59,13 +58,14 @@ export class HttpResponse { * Would respond with 201 Created * { id: 'some-id' } */ - created(obj: Object | any[]): void { - this.success(obj, 201); + created(body: Body): void { + this.success(body, 201); } /** * @function errored - * @param {any} obj + * @param {number} status + * @param {Object | any[]} body * * Usage: * res.errored(404, { message: 'The resource was not found on this server' }) @@ -73,7 +73,16 @@ export class HttpResponse { * Would respond with 404 Created * { message: 'The resource was not found on this server' } */ - errored(status: number | null = null, messageObj: Object = {}): void { - this.response.status(status || 500).json(messageObj); + errored(status: number = 500, body: Body = null): void { + + this.respond(status, body); + } + + private respond(status: number, body: Object | any[] | null): void { + + // set the produces header if one exists + this.setProduces(); + + this.response.status(status).json(body || defaultResponse.status(status)); } } \ No newline at end of file diff --git a/lib/http/index.ts b/lib/http/index.ts index d185e45..70398c4 100644 --- a/lib/http/index.ts +++ b/lib/http/index.ts @@ -1,4 +1,5 @@ /* Export mapping decorator */ export * from './models/request.model'; +export * from './models/next.model'; export * from './classes/response.class'; \ No newline at end of file diff --git a/lib/http/models/next.model.ts b/lib/http/models/next.model.ts new file mode 100644 index 0000000..f05d4ff --- /dev/null +++ b/lib/http/models/next.model.ts @@ -0,0 +1,12 @@ +/** + * next.model + */ +import express = require('express'); + +/** + * This type is to emulate the supported types + * wrapping around an express next method + */ +export interface HttpNext extends express.NextFunction { + // .. +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 927f8ab..db0e299 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@litstack/core", - "version": "0.2.2", + "version": "0.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -115,7 +115,6 @@ "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, "requires": { "co": "^4.6.0", "fast-deep-equal": "^1.0.0", @@ -155,14 +154,12 @@ "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.1.0", @@ -178,14 +175,12 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", - "dev": true + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" }, "balanced-match": { "version": "1.0.0", @@ -197,7 +192,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, "optional": true, "requires": { "tweetnacl": "^0.14.3" @@ -231,7 +225,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "dev": true, "requires": { "hoek": "4.x.x" } @@ -266,8 +259,7 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "chai": { "version": "4.1.2", @@ -320,8 +312,7 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "color-convert": { "version": "1.9.1", @@ -410,7 +401,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, "requires": { "boom": "5.x.x" }, @@ -419,7 +409,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, "requires": { "hoek": "4.x.x" } @@ -430,7 +419,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -452,6 +440,11 @@ "type-detect": "^4.0.0" } }, + "default-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/default-response/-/default-response-1.0.1.tgz", + "integrity": "sha1-1HuDsG/0TtR2LU5rfzFhEx3u5Vo=" + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -477,7 +470,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, "optional": true, "requires": { "jsbn": "~0.1.0" @@ -623,20 +615,17 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "finalhandler": { "version": "1.1.1", @@ -662,8 +651,7 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { "version": "2.3.2", @@ -706,7 +694,6 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -734,14 +721,12 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "dev": true, "requires": { "ajv": "^5.1.0", "har-schema": "^2.0.0" @@ -757,7 +742,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "dev": true, "requires": { "boom": "4.x.x", "cryptiles": "3.x.x", @@ -774,8 +758,7 @@ "hoek": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", - "dev": true + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" }, "http-errors": { "version": "1.6.3", @@ -792,7 +775,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -830,8 +812,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "isarray": { "version": "1.0.0", @@ -841,8 +822,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "js-yaml": { "version": "3.11.0", @@ -858,32 +838,27 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, "optional": true }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -3647,8 +3622,7 @@ "oauth-sign": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" }, "on-finished": { "version": "2.3.0", @@ -3692,8 +3666,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "process-nextick-args": { "version": "2.0.0", @@ -3712,8 +3685,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "qs": { "version": "6.5.1", @@ -3756,10 +3728,9 @@ "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==" }, "request": { - "version": "2.85.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", - "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", - "dev": true, + "version": "2.86.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.86.0.tgz", + "integrity": "sha512-BQZih67o9r+Ys94tcIW4S7Uu8pthjrQVxhsZ/weOwHbDfACxvIyvnAbzFQxjy1jMtvFSzv5zf4my6cZsJBbVzw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.6.0", @@ -3779,7 +3750,6 @@ "performance-now": "^2.1.0", "qs": "~6.5.1", "safe-buffer": "^5.1.1", - "stringstream": "~0.0.5", "tough-cookie": "~2.3.3", "tunnel-agent": "^0.6.0", "uuid": "^3.1.0" @@ -3850,7 +3820,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "dev": true, "requires": { "hoek": "4.x.x" } @@ -3881,7 +3850,6 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", - "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -3906,12 +3874,6 @@ "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true - }, "superagent": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", @@ -3961,7 +3923,6 @@ "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "dev": true, "requires": { "punycode": "^1.4.1" } @@ -3991,7 +3952,6 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -4000,7 +3960,6 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, "optional": true }, "type-detect": { @@ -4042,8 +4001,7 @@ "uuid": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", - "dev": true + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" }, "vary": { "version": "1.1.2", @@ -4054,7 +4012,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", diff --git a/package.json b/package.json index 0fe6adc..7fc94f2 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "@litstack/core", - "version": "0.2.2", + "version": "0.3.0", "description": "Typescript REST Framework", "main": "dist/index.js", "types": "dist/index.d.ts", "dependencies": { "body-parser": "^1.18.3", + "default-response": "^1.0.1", "express": "^4.16.3", "reflect-metadata": "^0.1.12", "rxjs": "^6.1.0", diff --git a/test/mocha.opts b/test/mocha.opts index 9bb54e0..f295e1b 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,4 +1,5 @@ --require ts-node/register --require source-map-support/register --full-trace +--bail **/*.spec.ts \ No newline at end of file From 76ce6c4deea1f82747c673cf8e588e86a5ad10f6 Mon Sep 17 00:00:00 2001 From: Cody Dalton Date: Fri, 18 May 2018 05:55:35 -0700 Subject: [PATCH 2/2] lit-50: Fix typo in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 701c9dd..b2aabd8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Using Angular and Spring boot design patterns, Litstack is a Typescript REST fra ### Option 1: Clone the seed app -Follow the directions in the [the litstack seed](https://github.com/codyjdalton/litstack-seed) to get started right away. +Follow the directions in [the litstack seed](https://github.com/codyjdalton/litstack-seed) to get started right away. ### Option 2: Start with a blank slate