0.4.9 • Published 3 years ago

escapin v0.4.9

Weekly downloads
70
License
MIT
Repository
github
Last release
3 years ago

Escapin

Table of Contents

Prerequisites

  1. Node.js 10.x or later
  2. Serverless Framework

Installation

npm install --save-dev escapin

Usage

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.

CLI options

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

Configuration

You can give configuration information to Escapin CLI by using the following ways:

PlaceFormat
escapin property in package.jsonJSON
.escapinrcJSON or YAML
.escapinrc.jsonJSON
.escapinrc.yaml or .escapinrc.ymlYAML
.escapinrc.js or escapin.config.jsJavaScript

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",
};
NameDescriptionOptionsDefault
namename of the application
api_specpath of the specification file of the API published by the application
credentialscredentials required in calling external APIs
platformcloud platform where the application is being deployedawsaws
default_storagethe storage type that are selected by defaulttablebuckettable
output_dirdirectory where the transpilcation artifacts are being storedbuild
http_clienthttp client used in generated code for requesting apis defined by OASaxiosrequestaxios

Transpilation features


Storage

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

Input


index.js
export const foo: table = {};

foo[id] = bar;
.escapinrc.js
module.exports = {
  platform: "aws",
  ...
};

Output


index.js
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);
      }
    }
  );
});
serverless.yml
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

Function


Input


index.js
export function handler(req) {
  if (errorOccured()) {
    throw new Error("[400] An error occured");
  }

  return { message: "Succeeded" };
}
.escapinrc.js
module.exports = {
  name: "myapp",
  platform: "aws",
  api_spec: "swagger.yaml",
  ...
};
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"
  ...

Output


index.js
export function handler(req, context, callback) {
  if (errorOccured()) {
    callback(new Error("[400] An error occured."));
    return;
  }

  callback(null, { message: "Succeeded" });
  return;
}
serverless.yml
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: ...

Importing open APIs


Usage

import api from "http://path/to/swagger.yaml";
MethodPathHeaderBodyExample
GET/itemsitems = api.items;
GET/items/:iditem = api.items[id];
GET/items/:id/propsprops = api.items[id].props;
GET/items/:id?foo=baritem = api.items[id] [ { foo: 'bar' } ] ;
GET/items/:id?foo=barbaz: quxitem = 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=barbaz: qux{ quux: 'corge' }api.items[id] [ { foo: 'bar', baz: 'qux' } ] ( { quux: 'corge' } ) ;
PUT/items/:idbaz: qux{ quux: 'corge' }api.items[id] [ { baz: 'qux' } ] = { quux: 'corge' };
DELETE/items/:iddelete api.items[id];

Input


index.js
import api from "http://path/to/swagger.yaml";
api.items[id][{ foo: "bar", baz: "qux" }]({ quux: "corge" });
.escapinrc.js
module.exports = {
  http_client: "request",
  ...
};
http://path/to/swagger.yaml
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

Output


index.js
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",
  },
});

Publishing your API


Input


index.js
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.yaml
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

Output


index.js
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;
}
serverless.yml
functions:
  handleItemFunction:
    handler: index.handleItem
    runtime: nodejs10.x
    role: escapinFunctionRole
    events:
      - http:
          path: 'items/{id}'
          method: post
          cors: true
          integration: lambda
  ...

Auto-completing asynchronous features


Destructuring nesting callbacks


Original
function func() {
  call(arg, (err, data1, data2) => {
    if (err) {
      handleError(err);
    } else {
      doSomething(data1, data2);
    }
  });
}

Destructured
function func() {
  try {
    const { data1, data2 } = call(arg);
    doSomething(data1, data2);
  } catch (err) {
    handleError(err);
  }
}

Asynchronized
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, for-in, for-of (collection should be obtained asynchronously)


Input
for (const item of api.call(arg)) {
  doSomething(item);
}

Output
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, for-in, for-of (executable in parallel)


Input
for (const arg of args) {
  api.call(arg);
}

Output
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);

for, for-in, for-of (NOT executable in parallel)


Input
let sum = 0;
for (const arg of args) {
  sum += api.call(arg);
}

Output
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,do-while


Input
while ((data = api.call(arg)) === null) {
  doSomething(data);
}

Output
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-else


Input
if (api.call(arg)) {
  doSomething();
} else if (api.call2(arg)) {
  doSomething2();
}

Output
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-case


Input
switch (api.call(arg)) {
  case "foo":
    api.call2(arg);
    break;
  case "bar":
    api.call3(arg);
    break;
  default:
    break;
}

Output
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;
}

functions that require callback functions as an argument (e.g., Array#forEach)


Input
// 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));

Output
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;
});

asynchronous features appearing in input


Input
args.map(arg => await promisifiedFunc(arg));

Output
await Promise.all(args.map(async (arg) => await promisifiedFunc(arg)));

Publications

License

MIT

FOSSA Status

0.4.9

3 years ago

0.4.8

4 years ago

0.4.7

4 years ago

0.4.6

4 years ago

0.4.5

4 years ago

0.4.4

4 years ago

0.4.3

4 years ago

0.4.2

4 years ago

0.4.1

4 years ago

0.4.0

4 years ago

0.3.8

4 years ago

0.3.7

4 years ago

0.3.6

4 years ago

0.3.5

4 years ago

0.3.4

4 years ago

0.3.3

4 years ago

0.3.2

4 years ago

0.3.1

4 years ago

0.3.0

4 years ago

0.2.28

4 years ago

0.2.27

4 years ago

0.2.26

4 years ago

0.2.25

4 years ago

0.2.24

4 years ago

0.2.23

4 years ago

0.2.22

4 years ago

0.2.21

4 years ago

0.2.20

4 years ago

0.2.19

4 years ago

0.2.18

4 years ago

0.2.17

4 years ago

0.2.16

4 years ago

0.2.14

4 years ago

0.2.13

4 years ago

0.2.12

4 years ago

0.2.11

4 years ago

0.2.10

4 years ago

0.2.9

5 years ago

0.2.8

5 years ago

0.2.7

5 years ago

0.2.6

5 years ago

0.2.5

5 years ago

0.2.4

5 years ago

0.2.3

5 years ago

0.2.2

5 years ago

0.2.1

5 years ago

0.2.0

5 years ago

0.1.39

5 years ago

0.1.38

5 years ago

0.1.37

5 years ago

0.1.36

5 years ago

0.1.35

5 years ago

0.1.34

5 years ago

0.1.33

5 years ago

0.1.32

5 years ago

0.1.31

5 years ago

0.1.30

5 years ago

0.1.29

5 years ago

0.1.28

5 years ago

0.1.27

5 years ago

0.1.26

5 years ago

0.1.25

5 years ago

0.1.24

5 years ago

0.1.23

5 years ago

0.1.22

5 years ago

0.1.21

5 years ago

0.1.20

5 years ago

0.1.19

5 years ago

0.1.18

5 years ago

0.1.16

5 years ago

0.1.15

5 years ago

0.1.14

5 years ago

0.1.12

5 years ago

0.1.11

5 years ago

0.1.10

5 years ago

0.1.9

5 years ago

0.1.8

5 years ago

0.1.7

5 years ago

0.1.6

5 years ago

0.1.5

5 years ago

0.1.4

5 years ago

0.1.3

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago