npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

decorator-x

v0.0.1

Published

decorator for entity instantiation & validation, auto-generate swagger docs & graphql schema

Downloads

7

Readme

npm version

中文版本 | English Version

Decorate Once, Use Everywhere - Decorator For JavaScript and Node.js Application

swagger-decorator

Original intention of swagger-decorator is to simplify JavaScript Application(Web & Node.js)development,by reusing information defined in the entity (plain JavaScript class). Entity decorated by swagger-decorator can be used in instance creation and validation, Sequelize ORM Model generation, autogenerated Swagger doc for Koa2, etc. However, things will develop in opposite direction when they become extreme, so if you feel exhausted with too much decorator, just stop using it.


# use npm to install 

$ npm install swagger-decorator -S

$ npm install babel-plugin-transform-decorators-legacy -D

# use yarn to install

$ yarn add swagger-decorator

$ yarn add babel-plugin-transform-decorators-legacy -D

# import function or decorator you need
import { 
    wrappingKoaRouter,
    entityProperty,
    ...
} from "swagger-decorator";

entity decorator

/**
 * Description description for this property
 * @param type 
 * @param description 
 * @param required 
 * @param defaultValue 
 * @param pattern
 * @param primaryKey 
 * @returns {Function}
 */
export function entityProperty({
  // these params will be used in swagger doc generation
  type = "string",
  description = "",
  required = false,
  defaultValue = undefined,

  // this param is used in validation
  pattern = undefined,

  // this param is used in orm model
  primaryKey = false
}) {}

We can use entityProperty to define simple User entity class:

// @flow

import { entityProperty } from "../../src/entity/decorator";
import UserProperty from "./UserProperty";
/**
 * Description User Entity
 */
export default class User {

  @entityProperty({
    type: "integer",
    description: "user id, auto-generated",
    required: true
  })
  id: string = 0;

  @entityProperty({
    type: "string",
    description: "user name, 3~12 characters",
    required: false
  })
  name: string = "name";

  @entityProperty({
    type: "string",
    description: "user email",
    pattern: "email",
    required: false
  })
  email: string = "email";

  @entityProperty({
    type: UserProperty,
    description: "user property",
    required: false
  })
  property: UserProperty = new UserProperty();
}

export default class UserProperty {
  @entityProperty({
    type: ["number"],
    description: "user friends, which is user ids",
    required: false
  })
  friends: [number];
}

Swagger Inner Datatype:

| Common Name | type | format | Comments | | ----------- | ---------------------------------------- | ---------------------------------------- | ---------------------------------------- | | integer | integer | int32 | signed 32 bits | | long | integer | int64 | signed 64 bits | | float | number | float | | | double | number | double | | | string | string | | | | byte | string | byte | base64 encoded characters | | binary | string | binary | any sequence of octets | | boolean | boolean | | | | date | string | date | As defined by full-date - RFC3339 | | dateTime | string | date-time | As defined by date-time - RFC3339 | | password | string | password | Used to hint UIs the input needs to be obscured. |

instance generation and validation

After defining entity class, we can use instantiate to generate instance for this entity class. Different from the new keyword, instantiate will assign property with entity instance other than plain object recrusively; and it will also validate data.

/**
 * Description 
 * @param EntityClass  
 * @param data  
 * @param ignore  
 * @param strict  
 * @throws  
 */
export function instantiate(
  EntityClass: Function,
  data: {
    [string]: any
  },
  { ignore = false, strict = true }: { ignore: boolean, strict: boolean } = {}
): Object {}

Here we use jest:

describe("test instantiate", () => {
  test("test validation", () => {
    expect(() => {
      instantiate(User, {
        name: "name"
      }).toThrowError(/validate fail!/);
    });

    let user = instantiate(User, {
      id: 0,
      name: "name",
      email: "[email protected]"
    });

    expect(user).toBeInstanceOf(User);
  });

  test("test ignore param, which is used to ignore validation", () => {
    instantiate(
      User,
      {
        name: "name"
      },
      {
        ignore: true
      }
    );
  });

  test("test strict param, if set true will ignore external property which isn't defined in entity class", () => {
    let user = instantiate(
      User,
      {
        name: "name",
        external: "external"
      },
      {
        ignore: true,
        strict: true
      }
    );

    expect(user).not.toHaveProperty("external", "external");

    user = instantiate(
      User,
      {
        name: "name",
        external: "external"
      },
      {
        ignore: true,
        strict: false
      }
    );

    expect(user).toHaveProperty("external", "external");
  });
});

describe("test nested entity property", () => {
  test("test User", () => {
    let user = instantiate(User, {
      id: 0,
      property: {
        friends: [0]
      }
    });

    expect(user.property).toBeInstanceOf(UserProperty);
  });
});

Sequelize Model

const originUserSequelizeModel = generateSequelizeModel(
  User,
  {
    _id: {
      primaryKey: true
    }
  },
  {
    mappingCamelCaseToUnderScore: true
  }
);

const UserSequelizeModel = sequelize.define(
  "b_user",
  originUserSequelizeModel,
  {
    timestamps: false,
    underscored: true,
    freezeTableName: true
  }
);

UserSequelizeModel.findAll({
  attributes: { exclude: [] }
}).then(users => {
  console.log(users);
});

generate entity class from flow type

Here we use babel and baylon to extract type information from flow file:

// @flow

import { flowToDecorator } from '../../../../src/transform/entity/flow/flow';

test('测试从 Flow 中提取出数据类型并且转化为 Swagger 接口类', () => {
  flowToDecorator('./TestEntity.js', './TestEntity.transformed.js').then(
    codeStr => {
      console.log(codeStr);
    },
    err => {
      console.error(err);
    }
  );
});

Soucrce Entity:

// @flow

import AnotherEntity from "./AnotherEntity";

class Entity {
  // Comment
  stringProperty: string = 0;

  classProperty: Entity = null;

  rawProperty;

  @entityProperty({
    type: "string",
    description: "this is property description",
    required: true
  })
  decoratedProperty;
}

Transformed Entity:

// @flow

import { entityProperty } from 'swagger-decorator';

import AnotherEntity from './AnotherEntity';

class Entity {
  // Comment
  @entityProperty({
    type: 'string',
    required: false,
    description: 'Comment'
  })
  stringProperty: string = 0;

  @entityProperty({
    type: Entity,
    required: false
  })
  classProperty: Entity = null;

  @entityProperty({
    type: 'string',
    required: false
  })
  rawProperty;

  @entityProperty({
    type: 'string',
    description: 'this is property description',
    required: true
  })
  decoratedProperty;
}

API Decorator and Swagger Doc Generation

Wrapping router

import { wrappingKoaRouter } from "swagger-decorator";

...

const Router = require("koa-router");

const router = new Router();

wrappingKoaRouter(router, "localhost:8080", "/api", {
  title: "Node Server Boilerplate",
  version: "0.0.1",
  description: "Koa2, koa-router,Webpack"
});

// define default route
router.get("/", async function(ctx, next) {
  ctx.body = { msg: "Node Server Boilerplate" };
});

// use scan to auto add method in class
router.scan(UserController);

Defining API with decorator

export default class UserController extends UserControllerDoc {
  @apiRequestMapping("get", "/users")
  @apiDescription("get all users list")
  static async getUsers(ctx, next): [User] {
    ctx.body = [new User()];
  }

  @apiRequestMapping("get", "/user/:id")
  @apiDescription("get user object by id, only access self or friends")
  static async getUserByID(ctx, next): User {
    ctx.body = new User();
  }

  @apiRequestMapping("post", "/user")
  @apiDescription("create new user")
  static async postUser(): number {
    ctx.body = {
      statusCode: 200
    };
  }
}

too much decorator in UserController may decrease the readability of code, so we can move some description to its parent class:


export default class UserControllerDoc {
  @apiResponse(200, "get users successfully", [User])
  static async getUsers(ctx, next): [User] {}

  @pathParameter({
    name: "id",
    description: "user id",
    type: "integer",
    defaultValue: 1
  })
  @queryParameter({
    name: "tags",
    description: "user tags, for filtering users",
    required: false,
    type: "array",
    items: ["string"]
  })
  @apiResponse(200, "get user successfully", User)
  static async getUserByID(ctx, next): User {}

  @bodyParameter({
    name: "user",
    description: "the new user object, must include user name",
    required: true,
    schema: User
  })
  @apiResponse(200, "create new user successfully", {
    statusCode: 200
  })
  static async postUser(): number {}
}

run your application

  • run your application and open swagger docs (PS. swagger-decorator contains Swagger UI):
/swagger

/swagger/api.json