- Prerequisites
- Installation
- Usage
- Configuration
- Transpilation features
- Publications
- License
- 日本語ドキュメント
- Node.js 10.x or later
- Serverless Framework
npm install --save-dev escapin
Escapin provides CLI escapin
that works on Node.js project directories containing ./package.json
.
First, append the following scripts in package.json
:
{
"scripts": {
"build": "escapin",
"start": "cd build && serverless deploy"
}
}
Then, run build
and start
on the project folder:
npm run build
npm start
Escapin transpiles your source code into executable one as a serverless application, and generates serverless.yml
that can be used for deploying the programs to cloud services by Serverless Framework.
Usage: escapin [options]
Options:
-V, --version output the version number
-d, --dir <dir> working directory (default: ".")
--ignore-path <path> specify path of ignore file (default: ".gitignore")
-h, --help output usage information
You can give configuration information to Escapin CLI by using the following ways:
Place | Format |
---|---|
escapin property in package.json |
JSON |
.escapinrc |
JSON or YAML |
.escapinrc.json |
JSON |
.escapinrc.yaml or .escapinrc.yml |
YAML |
.escapinrc.js or escapin.config.js |
JavaScript |
Here is the example of JSON configuration file .escapinrc
.
{
"name": "sendmail",
"api_spec": "swagger.yaml",
"credentials": [{ "api": "mailgun API", "basicAuth": "api:<YOUR_API_KEY>" }],
"platform": "aws",
"default_storage": "table",
"output_dir": "build",
"http_client": "axios"
}
module.exports = {
name: "sendmail",
api_spec: "swagger.yaml",
credentials: [{ api: "mailgun API", basicAuth: "api:<YOUR_API_KEY>" }],
platform: "aws",
default_storage: "table",
output_dir: "build",
http_client: "axios",
};
Name | Description | Options | Default |
---|---|---|---|
name |
name of the application | ||
api_spec |
path of the specification file of the API published by the application | ||
credentials |
credentials required in calling external APIs | ||
platform |
cloud platform where the application is being deployed | aws |
aws |
default_storage |
the storage type that are selected by default | table bucket |
table |
output_dir |
directory where the transpilcation artifacts are being stored | build |
|
http_client |
http client used in generated code for requesting apis defined by OAS | axios request |
axios |
You can use several kinds of storage services just like a first-class object in JavaScript.
By declaring an empty object placing a special type annotation (e.g., bucket
) you can create a resource in that type of storage services.
You can use both canonical type platform.storageType
(e.g., aws.bucket
) and shorthand type storageType
(e.g., bucket
) for storage objects; platform
in the configuration file is used in shorthand types.
If you omit a type annotation, default_storage
is used as that type by default.
In v0.2.x, bucket
and table
is available for storage types; bucket
represents a bucket in object storage, and table
represents a table in NoSQL datastore service.
export const foo: aws.bucket = {}; // AWS S3 Bucket
export const bar: bucket = {}; // AWS S3 Bucket
export const baz: table = {}; // AWS DynamoDB Table
export const qux = {}; // AWS DyanmoDB Table
Here are the usage example of storage objects:
export const foo: bucket = {};
foo[id] = bar; // uploading data
baz = foo[id]; // downloading data
qux = Object.keys(foo); // obtaining keys of data
delete foo[id]; // deleting existing data
export const foo: table = {};
foo[id] = bar;
module.exports = {
platform: "aws",
...
};
import { DynamoDB } from "aws-sdk";
await new Promise((resolve, reject) => {
new DynamoDB().putItem(
{
TableName: "foo-9fe932f9-32e7-49f7-a341-0dca29a8bb32",
Item: {
key: { S: id },
type: { S: typeof bar },
value: {
S:
typeof bar === "object" || typeof bar === "function"
? JSON.stringify(bar)
: bar,
},
},
},
(err, _temp) => {
if (err) {
reject(err);
} else {
resolve(_temp);
}
}
);
});
resources:
Resources:
fooTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: foo-9fe932f9-32e7-49f7-a341-0dca29a8bb32
KeySchema:
- AttributeName: key
KeyType: HASH
AttributeDefinitions:
- AttributeName: key
AttributeType: S
ProvisionedThroughput:
ReadCapacityUnits: 5
WriteCapacityUnits: 5
escapinFunctionRole:
Properties:
Policies:
- PolicyName: foo-9fe932f9-32e7-49f7-a341-0dca29a8bb32-FullAccess
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- "dynamodb:ListGlobalTables"
- "dynamodb:ListTables"
Resource: "*"
- Effect: Allow
Action: "dynamodb:*"
Resource:
"Fn::GetAtt":
- fooTable
- Arn
export function handler(req) {
if (errorOccured()) {
throw new Error("[400] An error occured");
}
return { message: "Succeeded" };
}
module.exports = {
name: "myapp",
platform: "aws",
api_spec: "swagger.yaml",
...
};
swagger: "2.0"
info:
version: "1.0.0"
title: "myapp"
host: "myapp.org"
basePath: "/v1"
schemes:
- "http"
produces:
- "application/json"
paths:
/handle:
get:
summary: "handler"
x-escapin-handler: "index.handler"
parameters: []
responses:
200:
schema:
$ref: "#/definitions/Message"
400:
schema:
$ref: "#/definitions/Error"
...
export function handler(req, context, callback) {
if (errorOccured()) {
callback(new Error("[400] An error occured."));
return;
}
callback(null, { message: "Succeeded" });
return;
}
functions:
handlerFunction:
handler: index.handler
runtime: nodejs10.x
role: escapinFunctionRole
events:
- http:
path: handle
method: get
cors: true
integration: lambda
resources:
Resources:
escapinFunctionRole:
Type: "AWS::IAM::Role"
Properties:
Path: /escapin/
RoleName: myappEscapinFunctionRole
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: "sts:AssumeRole"
Policies: ...
import api from "http://path/to/swagger.yaml";
Method | Path | Header | Body | Example |
---|---|---|---|---|
GET |
/items |
items = api.items; |
||
GET |
/items/:id |
item = api.items[id]; |
||
GET |
/items/:id/props |
props = api.items[id].props; |
||
GET |
/items/:id?foo=bar |
item = api.items[id] [ { foo: 'bar' } ] ; |
||
GET |
/items/:id?foo=bar |
baz: qux |
item = api.items[id] [ { foo: 'bar', baz: 'qux' } ] ; |
|
POST |
/:domain/messages |
{ quux: 'corge' } |
api. domain[domain].messages ( { quux: 'corge' } ) ; |
|
POST |
/items |
{ quux: 'corge' } |
api.items ( { quux: 'corge' } ) ; |
|
POST |
/items/:id?foo=bar |
baz: qux |
{ quux: 'corge' } |
api.items[id] [ { foo: 'bar', baz: 'qux' } ] ( { quux: 'corge' } ) ; |
PUT |
/items/:id |
baz: qux |
{ quux: 'corge' } |
api.items[id] [ { baz: 'qux' } ] = { quux: 'corge' }; |
DELETE |
/items/:id |
delete api.items[id]; |
import api from "http://path/to/swagger.yaml";
api.items[id][{ foo: "bar", baz: "qux" }]({ quux: "corge" });
module.exports = {
http_client: "request",
...
};
swagger: "2.0"
info:
title: Awesome API
description: An awesome API
version: "1.0.0"
host: "api.endpoint.com"
schemes:
- http
basePath: /v1
produces:
- application/json
consumes:
- application/json
paths:
/items/{id}:
post:
description: Do some task regarding an item
parameters:
- name: id
in: path
type: string
required: true
description: Item ID
- name: foo
in: query
type: string
required: true
- name: baz
in: header
type: string
required: true
- name: params
in: body
schema:
$ref: "#/definitions/Params"
responses:
"200":
description: Succeeded
schema:
$ref: "#/definitions/Message"
definitions:
Params:
type: object
properties:
quux:
type: string
Message:
type: object
properties:
message:
type: string
import request from "request";
const { _res, _body } = request({
uri: `http://api.endpoint.com/v1/items/${id}`,
method: "post",
contentType: "application/json",
json: true,
qs: {
foo: "bar",
},
headers: {
baz: "qux",
},
body: {
quux: "corge",
},
});
export function handleItem(req) {
const id = req.path.id;
const foo = req.query.foo;
const baz = req.header.baz;
const quux = req.body.quux;
if (errorOccured()) {
throw new Error("[400] An error occured.");
}
return { message: "Succeeded" };
}
swagger: "2.0"
info:
title: Awesome API
description: An awesome API
version: "1.0.0"
host: "api.endpoint.com"
schemes:
- https
basePath: /v1
produces:
- application/json
consumes:
- application/json
paths:
/items/{id}:
post:
x-escapin-handler: index.handleItem
description: Do some task regarding an item
parameters:
- name: id
in: path
type: string
required: true
description: Item ID
- name: foo
in: query
type: string
required: true
- name: baz
in: header
type: string
required: true
- name: params
in: body
schema:
$ref: "#/definitions/Params"
responses:
"200":
schema:
$ref: "#/definitions/Message"
"400":
schema:
$ref: "#/definitions/Error"
definitions:
Params:
type: object
properties:
quux:
type: string
export function handleItem(req, context, callback) {
const id = req.path.id;
const foo = req.query.foo;
const baz = req.header.baz;
const quux = req.body.quux;
if (errorOccured()) {
callback(new Error("[400] An error occured."));
return;
}
callback(null, { message: "Succeeded" });
return;
}
functions:
handleItemFunction:
handler: index.handleItem
runtime: nodejs10.x
role: escapinFunctionRole
events:
- http:
path: 'items/{id}'
method: post
cors: true
integration: lambda
...
function func() {
call(arg, (err, data1, data2) => {
if (err) {
handleError(err);
} else {
doSomething(data1, data2);
}
});
}
function func() {
try {
const { data1, data2 } = call(arg);
doSomething(data1, data2);
} catch (err) {
handleError(err);
}
}
async function func() {
try {
const _data = await new Promise((resolve, reject) => {
call(arg, (err, _data1, _data2) => {
if (err) reject(err);
else resolve({ _data1, _data2 });
});
});
doSomething(_data._data1, _data._data2);
} catch (err) {
handleError(err);
}
}
for (const item of api.call(arg)) {
doSomething(item);
}
const _data = await new Promise((resolve, reject) => {
api.call(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
for (const item of _data) {
doSomething(item);
}
for (const arg of args) {
api.call(arg);
}
const _promises = [];
for (const arg of args) {
_promises.push(
(async () => {
await new Promise((resolve, reject) => {
api.call(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
})()
);
}
await Promise.all(_promises);
let sum = 0;
for (const arg of args) {
sum += api.call(arg);
}
let sum = 0;
for (const arg of args) {
const _data = await new Promise((resolve, reject) => {
api.call(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
sum += _data;
}
while ((data = api.call(arg)) === null) {
doSomething(data);
}
let _data = await new Promise((resolve, reject) => {
api.call(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
while ((data = _data) === null) {
doSomething(data);
_data = await new Promise((resolve, reject) => {
api.call(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
if (api.call(arg)) {
doSomething();
} else if (api.call2(arg)) {
doSomething2();
}
const _data = await new Promise((resolve, reject) => {
api.call(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (_data) {
doSomething();
} else {
let _data2 = await new Promise((resolve, reject) => {
api.call2(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
if (_data2) {
doSomething2();
}
}
switch (api.call(arg)) {
case "foo":
api.call2(arg);
break;
case "bar":
api.call3(arg);
break;
default:
break;
}
let _promise;
const _data = await new Promise((resolve, reject) => {
api.call(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
switch (_data) {
case "foo":
await new Promise((resolve, reject) => {
api.call2(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
break;
case "bar":
await new Promise((resolve, reject) => {
api.call3(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
break;
default:
break;
}
// special rules are applied for Array#map and Array#forEach
args.map((arg) => api.call(arg));
args.forEach((arg) => api.call(arg));
args.some((arg) => api.call(arg));
import deasync from "deasync";
await Promise.all(args.map(async (arg) => await api.call(arg)));
args.forEach(async (arg) => await api.call(arg));
args.some((arg) => {
let _data;
let done = false;
new Promise((resolve, reject) => {
api.call(arg, (err, data) => {
if (err) reject(err);
else resolve(data);
});
}).then((data) => {
_data = data;
done = true;
});
deasync.loopWhile((_) => !done);
return _data;
});
args.map(arg => await promisifiedFunc(arg));
await Promise.all(args.map(async (arg) => await promisifiedFunc(arg)));
-
Kosaku Kimura, Atsuji Sekiguchi, Shridhar Choudhary and Tadahiro Uehara, "A JavaScript Transpiler for Escaping from Complicated Usage of Cloud Services and APIs," 2018 25th Asia-Pacific Software Engineering Conference (APSEC), Nara, Japan, 2018, pp. 69-78.
-
An Introduction of a Technology for Simplifying Serverless Application Programming in AWS with Node.js, JAWS DAYS 2019
-
FaaS 上のコードをもっとシンプルに書くためのトランスパイラ, Serverless Meetup Tokyo #13