Skip to content

Commit aae3cd8

Browse files
committed
.
0 parents  commit aae3cd8

11 files changed

+387
-0
lines changed

.editorconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# EditorConfig is awesome: http://EditorConfig.org
2+
3+
root = true
4+
5+
[*]
6+
indent_size = 2
7+
indent_style = space
8+
end_of_line = lf
9+
charset = utf-8
10+
trim_trailing_whitespace = true
11+
insert_final_newline = true

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.DS_Store
2+
.vscode
3+
coverage/
4+
node_modules/
5+
npm-debug.log
6+
dist/
7+
typings/

.travis.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
sudo: false
2+
language: node_js
3+
4+
notifications:
5+
email:
6+
on_success: never
7+
on_failure: change
8+
9+
node_js:
10+
- '4'
11+
- stable
12+
13+
after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls"

LICENSE

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright 2017 Blake Embrey ([email protected])
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Servie Lambda
2+
3+
[![NPM version][npm-image]][npm-url]
4+
[![NPM downloads][downloads-image]][downloads-url]
5+
[![Build status][travis-image]][travis-url]
6+
[![Test coverage][coveralls-image]][coveralls-url]
7+
8+
> Servie transport for AWS lambda proxy.
9+
10+
## Installation
11+
12+
```
13+
npm install servie-lambda --save
14+
```
15+
16+
## Usage
17+
18+
Wrap a standard Servie middleware function in `createServer` to return a AWS Lambda handler.
19+
20+
```ts
21+
import { createServer } from 'servie-lambda'
22+
import { compose } from 'throwback'
23+
import { get } from 'servie-route'
24+
25+
export const handler = createServer(compose([
26+
get('/test', (req, res) => res.body = 'hello world')
27+
]))
28+
```
29+
30+
## TypeScript
31+
32+
This project is written using [TypeScript](https://github.com/Microsoft/TypeScript) and publishes the definitions directly to NPM.
33+
34+
## License
35+
36+
MIT
37+
38+
[npm-image]: https://img.shields.io/npm/v/servie-lambda.svg?style=flat
39+
[npm-url]: https://npmjs.org/package/servie-lambda
40+
[downloads-image]: https://img.shields.io/npm/dm/servie-lambda.svg?style=flat
41+
[downloads-url]: https://npmjs.org/package/servie-lambda
42+
[travis-image]: https://img.shields.io/travis/blakeembrey/node-servie-lambda.svg?style=flat
43+
[travis-url]: https://travis-ci.org/blakeembrey/node-servie-lambda
44+
[coveralls-image]: https://img.shields.io/coveralls/blakeembrey/node-servie-lambda.svg?style=flat
45+
[coveralls-url]: https://coveralls.io/r/blakeembrey/node-servie-lambda?branch=master

package.json

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
"name": "servie-lambda",
3+
"version": "0.0.0",
4+
"description": "Servie transport for AWS lambda proxy",
5+
"main": "dist/index.js",
6+
"typings": "dist/index.d.ts",
7+
"files": [
8+
"dist/"
9+
],
10+
"scripts": {
11+
"lint": "tslint \"src/**/*.ts\"",
12+
"build": "rimraf dist && tsc",
13+
"specs": "jest --coverage",
14+
"test": "npm run -s lint && npm run -s build && npm run -s specs",
15+
"prepublish": "typings install && npm run build"
16+
},
17+
"repository": {
18+
"type": "git",
19+
"url": "git://github.com/blakeembrey/node-servie-lambda.git"
20+
},
21+
"keywords": [
22+
"servie",
23+
"aws",
24+
"lambda",
25+
"request",
26+
"async",
27+
"http"
28+
],
29+
"author": {
30+
"name": "Blake Embrey",
31+
"email": "[email protected]",
32+
"url": "http://blakeembrey.me"
33+
},
34+
"license": "Apache-2.0",
35+
"bugs": {
36+
"url": "https://github.com/blakeembrey/node-servie-lambda/issues"
37+
},
38+
"homepage": "https://github.com/blakeembrey/node-servie-lambda",
39+
"devDependencies": {
40+
"jest": "^18.1.0",
41+
"rimraf": "^2.5.4",
42+
"servie": "0.0.6",
43+
"tslint": "^4.3.1",
44+
"tslint-config-standard": "^2.0.0",
45+
"typescript": "^2.1.5",
46+
"typings": "^2.1.0"
47+
},
48+
"peerDependencies": {
49+
"servie": "^0.0.3"
50+
}
51+
}

src/index.spec.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { createServer, Event, Context, Result } from './index'
2+
3+
describe('servie-lambda', () => {
4+
const event: Event = {
5+
path: '/test',
6+
pathParameters: null,
7+
httpMethod: 'GET',
8+
body: null,
9+
resource: '/test',
10+
headers: null,
11+
queryStringParameters: null,
12+
requestContext: {
13+
identity: {
14+
sourceIp: ''
15+
}
16+
}
17+
}
18+
19+
const context: Context = {
20+
functionName: '',
21+
functionVersion: '$LATEST',
22+
memoryLimitInMB: '128',
23+
invokeid: '',
24+
awsRequestId: '',
25+
invokedFunctionArn: ''
26+
}
27+
28+
it('should support routers', (done) => {
29+
const handler = createServer(function (_req, res) {
30+
res.status = 200
31+
res.body = 'response'
32+
33+
return Promise.resolve()
34+
})
35+
36+
return handler(event, context, (err: Error | null, res: Result) => {
37+
if (err) {
38+
return done(err)
39+
}
40+
41+
expect(res).toEqual({
42+
statusCode: 200,
43+
body: 'response',
44+
headers: {
45+
'content-type': 'text/plain',
46+
'content-length': '8'
47+
},
48+
isBase64Encoded: false
49+
})
50+
51+
return done()
52+
})
53+
})
54+
55+
it('should fall through to 404', (done) => {
56+
const handler = createServer((_req, _res, next) => next())
57+
58+
return handler(event, context, (err: Error | null, res: Result) => {
59+
if (err) {
60+
return done(err)
61+
}
62+
63+
expect(res).toEqual({
64+
statusCode: 404,
65+
body: 'Cannot GET /test',
66+
headers: {
67+
'content-type': 'text/plain',
68+
'content-length': '16'
69+
},
70+
isBase64Encoded: false
71+
})
72+
73+
return done()
74+
})
75+
})
76+
})

src/index.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { format } from 'url'
2+
import { Request, Response } from 'servie'
3+
4+
export type Middleware = (req: Request, res: Response, next: () => Promise<void>) => Promise<void>
5+
6+
/**
7+
* AWS Lambda event object.
8+
*/
9+
export interface Event {
10+
path: string
11+
httpMethod: string
12+
body: string | null
13+
isBase64Encoded?: boolean
14+
resource: string
15+
headers: {
16+
[key: string]: string
17+
} | null
18+
queryStringParameters: {
19+
[key: string]: string
20+
} | null
21+
pathParameters: {
22+
[key: string]: string
23+
} | null
24+
requestContext: {
25+
identity: {
26+
sourceIp: string
27+
}
28+
}
29+
}
30+
31+
/**
32+
* AWS lambda context object.
33+
*/
34+
export interface Context {
35+
functionName: string
36+
memoryLimitInMB: string
37+
functionVersion: string
38+
invokeid: string
39+
awsRequestId: string
40+
invokedFunctionArn: string
41+
}
42+
43+
/**
44+
* Standard lambda HTTP response.
45+
*/
46+
export interface Result {
47+
body?: string
48+
statusCode?: number
49+
headers?: {
50+
[key: string]: string | string[]
51+
}
52+
isBase64Encoded?: boolean
53+
}
54+
55+
/**
56+
* Lambda server options.
57+
*/
58+
export interface Options {
59+
isBinary?: (res: Response) => boolean
60+
}
61+
62+
/**
63+
* Create a server for handling AWS Lambda requests.
64+
*/
65+
export function createServer (fn: Middleware, options: Options = {}) {
66+
return function (event: Event, _context: Context, cb: (err: Error | null, res?: Result) => void): void {
67+
const { httpMethod: method, headers, isBase64Encoded } = event
68+
const url = format({ pathname: event.path, query: event.queryStringParameters })
69+
const body = event.body ? new Buffer(event.body, isBase64Encoded ? 'base64' : 'utf8') : undefined
70+
71+
const connection = { encrypted: true, remoteAddress: event.requestContext.identity.sourceIp }
72+
73+
const request = new Request({ method, url, connection, headers, body })
74+
const response = new Response(request, {})
75+
76+
// Handle request and response errors.
77+
request.events.on('error', done)
78+
response.events.on('error', done)
79+
80+
// Marked request as finished.
81+
request.started = true
82+
request.finished = true
83+
request.bytesTransferred = body ? body.length : 0
84+
85+
function done (err: Error | null, res?: Result) {
86+
if (err && (request.aborted || response.started)) {
87+
console.error(err)
88+
return
89+
}
90+
91+
response.started = true
92+
response.finished = true
93+
94+
return cb(err, res)
95+
}
96+
97+
fn(request, response, finalhandler(request, response))
98+
.then((): void | Promise<void> => {
99+
if (request.aborted || response.started) {
100+
return
101+
}
102+
103+
return response.buffer().then((body) => {
104+
const isBase64Encoded = options.isBinary ? options.isBinary(response) : false
105+
106+
// Set bytes transferred.
107+
response.bytesTransferred = body ? body.length : 0
108+
109+
return done(null, {
110+
statusCode: response.status,
111+
body: body ? (isBase64Encoded ? body.toString('base64') : body.toString('utf8')) : undefined,
112+
headers: response.headers.object(),
113+
isBase64Encoded
114+
})
115+
})
116+
})
117+
.catch((err) => done(err))
118+
}
119+
}
120+
121+
/**
122+
* Final throwback server handler.
123+
*/
124+
function finalhandler (req: Request, res: Response) {
125+
return function () {
126+
res.status = 404
127+
res.type = 'text/plain'
128+
res.body = `Cannot ${req.method} ${req.url}`
129+
130+
return Promise.resolve()
131+
}
132+
}

tsconfig.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"lib": [
5+
"es2015"
6+
],
7+
"rootDir": "src",
8+
"outDir": "dist",
9+
"module": "commonjs",
10+
"moduleResolution": "node",
11+
"declaration": true,
12+
"noImplicitAny": true,
13+
"noImplicitThis": true,
14+
"noImplicitReturns": true,
15+
"noUnusedLocals": true,
16+
"noUnusedParameters": true,
17+
"removeComments": false,
18+
"sourceMap": true,
19+
"inlineSources": true
20+
},
21+
"include": [
22+
"src/**/*",
23+
"typings/**/*"
24+
]
25+
}

tslint.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "tslint-config-standard"
3+
}

typings.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"globalDependencies": {
3+
"node": "registry:env/node#6.0.0+20170119204930"
4+
},
5+
"globalDevDependencies": {
6+
"jest": "registry:dt/jest#16.0.0+20170112172802"
7+
},
8+
"dependencies": {
9+
"debug": "registry:npm/debug#2.0.0+20160723033700"
10+
}
11+
}

0 commit comments

Comments
 (0)