-
Notifications
You must be signed in to change notification settings - Fork 579
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: vercel starter and trigger (#1199)
- Loading branch information
Showing
18 changed files
with
598 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
# serverless vercel starter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'node', | ||
testPathIgnorePatterns: ['<rootDir>/test/fixtures'], | ||
coveragePathIgnorePatterns: ['<rootDir>/test/'], | ||
setupFilesAfterEnv: ['./jest.setup.js'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
process.env.MIDWAY_TS_MODE = 'true'; | ||
jest.setTimeout(30000); |
35 changes: 35 additions & 0 deletions
35
packages-serverless/serverless-vercel-starter/package.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"name": "serverless-vercel-starter", | ||
"version": "2.12.1", | ||
"main": "dist/index", | ||
"typings": "dist/index.d.ts", | ||
"dependencies": { | ||
"@midwayjs/runtime-engine": "^2.11.2", | ||
"@midwayjs/serverless-http-parser": "^2.11.4", | ||
"parseurl": "^1.3.3" | ||
}, | ||
"devDependencies": { | ||
"@midwayjs/core": "^2.12.1", | ||
"@midwayjs/decorator": "^2.11.5", | ||
"@midwayjs/runtime-mock": "^2.11.2", | ||
"raw-body": "^2.4.1" | ||
}, | ||
"engines": { | ||
"node": ">= 10" | ||
}, | ||
"files": [ | ||
"dist/**/*.js", | ||
"dist/**/*.d.ts" | ||
], | ||
"scripts": { | ||
"build": "tsc", | ||
"test": "node --require=ts-node/register ../../node_modules/.bin/jest", | ||
"cov": "node --require=ts-node/register ../../node_modules/.bin/jest --coverage --forceExit" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git@github.com:midwayjs/midway.git" | ||
}, | ||
"license": "MIT", | ||
"gitHead": "1afeb34cbb6c0ad49ccdb9cfaebd254ae73afc6a" | ||
} |
28 changes: 28 additions & 0 deletions
28
packages-serverless/serverless-vercel-starter/src/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { BaseBootstrap } from '@midwayjs/runtime-engine'; | ||
import { VercelRuntime } from './runtime'; | ||
|
||
export { asyncWrapper } from '@midwayjs/runtime-engine'; | ||
export * from './runtime'; | ||
|
||
let bootstrap; | ||
|
||
export const start = async (options: any = {}) => { | ||
if (!process.env.NODE_ENV) { | ||
process.env.NODE_ENV = 'production'; | ||
} | ||
|
||
bootstrap = new BaseBootstrap( | ||
Object.assign( | ||
{ | ||
runtime: new VercelRuntime(), | ||
}, | ||
options | ||
) | ||
); | ||
await bootstrap.start(); | ||
return bootstrap.getRuntime(); | ||
}; | ||
|
||
export const close = async () => { | ||
return bootstrap.close(); | ||
}; |
134 changes: 134 additions & 0 deletions
134
packages-serverless/serverless-vercel-starter/src/runtime.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
import * as getRawBody from 'raw-body'; | ||
import { ServerlessLightRuntime } from '@midwayjs/runtime-engine'; | ||
import { Application, HTTPResponse } from '@midwayjs/serverless-http-parser'; | ||
import * as parseurl from 'parseurl'; | ||
|
||
const isOutputError = () => { | ||
return ( | ||
process.env.SERVERLESS_OUTPUT_ERROR_STACK === 'true' || | ||
['local', 'development'].includes(process.env.MIDWAY_SERVER_ENV) || | ||
['local', 'development'].includes(process.env.NODE_ENV) | ||
); | ||
}; | ||
|
||
export class VercelRuntime extends ServerlessLightRuntime { | ||
app; | ||
respond; | ||
|
||
init(contextExtensions) { | ||
super.init(contextExtensions); | ||
this.app = new Application(); | ||
} | ||
|
||
/** | ||
* for handler wrapper | ||
* @param handler | ||
*/ | ||
asyncEvent(handler) { | ||
if (handler && handler.constructor.name !== 'AsyncFunction') { | ||
throw new TypeError('Must be an AsyncFunction'); | ||
} | ||
|
||
return (...args) => { | ||
const [req, res] = args; | ||
return this.wrapperWebInvoker(handler, req, res); | ||
}; | ||
} | ||
|
||
async wrapperWebInvoker(handler, req, res) { | ||
if (!this.respond) { | ||
this.respond = this.app.callback(); | ||
} | ||
|
||
const newRes = new HTTPResponse(); | ||
|
||
req.getOriginContext = () => { | ||
return {}; | ||
}; | ||
|
||
// vercel req is IncomingMessage , no path | ||
const newUrlInfo = parseurl({ url: req.url }); | ||
req.path = newUrlInfo.pathname; | ||
|
||
if ( | ||
['post', 'put', 'delete'].indexOf(req.method.toLowerCase()) !== -1 && | ||
!req.body && | ||
typeof req.on === 'function' | ||
) { | ||
req.body = await getRawBody(req); // TODO: body parser | ||
} | ||
const newReq = req; | ||
|
||
return this.respond.apply(this.respond, [ | ||
newReq, | ||
newRes, | ||
ctx => { | ||
return this.invokeHandlerWrapper(ctx, async () => { | ||
if (!handler) { | ||
const args = [ctx]; | ||
return this.defaultInvokeHandler(...args); | ||
} | ||
return handler.apply(handler, [ctx]); | ||
}) | ||
.then(result => { | ||
if (res.headersSent) { | ||
return; | ||
} | ||
|
||
if (result) { | ||
ctx.body = result; | ||
} | ||
|
||
if (!ctx.response._explicitStatus) { | ||
if (ctx.body === null || ctx.body === undefined) { | ||
ctx.body = ''; | ||
ctx.type = 'text'; | ||
ctx.status = 204; | ||
} | ||
} | ||
|
||
const newHeader = {}; | ||
for (const key in ctx.res.headers) { | ||
// The length after base64 is wrong. | ||
if (!['content-length'].includes(key)) { | ||
newHeader[key] = ctx.res.headers[key]; | ||
} | ||
} | ||
|
||
for (const key in newHeader) { | ||
res.setHeader(key, newHeader[key]); | ||
} | ||
|
||
if (res.statusCode !== ctx.status) { | ||
res.status(ctx.status); | ||
} | ||
// vercel support object/string/buffer | ||
res.send(ctx.body); | ||
}) | ||
.catch(err => { | ||
ctx.logger.error(err); | ||
res.status(500); | ||
res.send(isOutputError() ? err.stack : 'Internal Server Error'); | ||
}); | ||
}, | ||
]); | ||
} | ||
|
||
async beforeInvokeHandler(context) {} | ||
|
||
async afterInvokeHandler(err, result, context) {} | ||
|
||
getApplication() { | ||
return this.app; | ||
} | ||
|
||
getFunctionName(): string { | ||
return this.options?.initContext?.function?.name || super.getFunctionName(); | ||
} | ||
|
||
getFunctionServiceName(): string { | ||
return ( | ||
this.options?.initContext?.service?.name || super.getFunctionServiceName() | ||
); | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
packages-serverless/serverless-vercel-starter/test/fixtures/http/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { asyncWrapper, start, close } from '../../../src'; | ||
|
||
let runtime; | ||
let inited; | ||
|
||
exports.handler = asyncWrapper(async (...args) => { | ||
if (!inited) { | ||
inited = true; | ||
runtime = await start(); | ||
} | ||
const res = await runtime.asyncEvent(async function (ctx) { | ||
const app = runtime.getApplication(); | ||
const functionName = runtime.getFunctionName(); | ||
const functionServiceName = runtime.getFunctionServiceName(); | ||
if (ctx.query?.str) { | ||
return '123'; | ||
} | ||
if (ctx.query?.noReturn) { | ||
return; | ||
} | ||
if (ctx.query?.undefined) { | ||
return undefined; | ||
} | ||
if (ctx.query?.error) { | ||
throw new Error('error') | ||
} | ||
if (ctx.query?.buffer) { | ||
ctx.status = 401; | ||
return Buffer.from('123'); | ||
} | ||
return { path: ctx.path, functionName, functionServiceName, isExistsApp: !!app }; | ||
})(...args); | ||
await close(); | ||
return res; | ||
}); |
104 changes: 104 additions & 0 deletions
104
packages-serverless/serverless-vercel-starter/test/index.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { createRuntime } from '@midwayjs/runtime-mock'; | ||
import * as assert from 'assert'; | ||
// 这里不能用包引,会循环依赖 | ||
import { HTTPTrigger } from '../../serverless-vercel-trigger/src'; | ||
import { join } from 'path'; | ||
|
||
describe('/test/index.test.ts', () => { | ||
it('should invoke normal code', async () => { | ||
const runtime = createRuntime({ | ||
functionDir: join(__dirname, './fixtures/http'), | ||
}); | ||
await runtime.start(); | ||
const trigger = new HTTPTrigger({ | ||
path: '/help', | ||
method: 'GET', | ||
}); | ||
const result = await runtime.invoke(trigger); | ||
assert.equal(JSON.parse(result.body).path, '/help'); | ||
await runtime.close(); | ||
}); | ||
it('should get string response', async () => { | ||
const runtime = createRuntime({ | ||
functionDir: join(__dirname, './fixtures/http'), | ||
}); | ||
await runtime.start(); | ||
const trigger = new HTTPTrigger({ | ||
path: '/help', | ||
method: 'GET', | ||
query: { | ||
str: true | ||
} | ||
}); | ||
const result = await runtime.invoke(trigger); | ||
assert.equal(result.body, '123'); | ||
assert(result.headers['content-type'].includes('text/plain')); | ||
await runtime.close(); | ||
}); | ||
it('should get buffer response', async () => { | ||
const runtime = createRuntime({ | ||
functionDir: join(__dirname, './fixtures/http'), | ||
}); | ||
await runtime.start(); | ||
const trigger = new HTTPTrigger({ | ||
path: '/help', | ||
method: 'GET', | ||
query: { | ||
buffer: true | ||
} | ||
}); | ||
const result = await runtime.invoke(trigger); | ||
assert.equal(result.body, '123'); | ||
assert.equal(result.statusCode, 401); | ||
assert.equal(result.headers['content-type'], 'application/octet-stream'); | ||
await runtime.close(); | ||
}); | ||
it('should get empty response', async () => { | ||
const runtime = createRuntime({ | ||
functionDir: join(__dirname, './fixtures/http'), | ||
}); | ||
await runtime.start(); | ||
const trigger = new HTTPTrigger({ | ||
path: '/help', | ||
method: 'GET', | ||
query: { | ||
noReturn: true | ||
} | ||
}); | ||
const result = await runtime.invoke(trigger); | ||
assert.equal(result.statusCode, 204); | ||
await runtime.close(); | ||
}); | ||
it('should get error', async () => { | ||
const runtime = createRuntime({ | ||
functionDir: join(__dirname, './fixtures/http'), | ||
}); | ||
await runtime.start(); | ||
const trigger = new HTTPTrigger({ | ||
path: '/help', | ||
method: 'GET', | ||
query: { | ||
error: true | ||
} | ||
}); | ||
const result = await runtime.invoke(trigger); | ||
assert.equal(result.statusCode, 500); | ||
await runtime.close(); | ||
}); | ||
it('should get error', async () => { | ||
const runtime = createRuntime({ | ||
functionDir: join(__dirname, './fixtures/http'), | ||
}); | ||
await runtime.start(); | ||
const trigger = new HTTPTrigger({ | ||
path: '/help', | ||
method: 'GET', | ||
query: { | ||
undefined: true | ||
} | ||
}); | ||
const result = await runtime.invoke(trigger); | ||
assert.equal(result.statusCode, 204); | ||
await runtime.close(); | ||
}); | ||
}); |
11 changes: 11 additions & 0 deletions
11
packages-serverless/serverless-vercel-starter/tsconfig.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
"compileOnSave": true, | ||
"compilerOptions": { | ||
"rootDir": "src", | ||
"outDir": "dist" | ||
}, | ||
"include": [ | ||
"./src/**/*.ts" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module.exports = { | ||
preset: 'ts-jest', | ||
testEnvironment: 'node', | ||
testPathIgnorePatterns: ['<rootDir>/test/fixtures'], | ||
coveragePathIgnorePatterns: ['<rootDir>/test/'], | ||
setupFilesAfterEnv: ['./jest.setup.js'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
process.env.MIDWAY_TS_MODE = 'true'; | ||
jest.setTimeout(30000); |
Oops, something went wrong.