refluxion
A TypeScript code-first full-stack generator for GraphQL, Redux, Normalizr and Sequelizer.
Note: This is an experimental tool testing some ideas of using TypeScript with Decorators as data model across client and server.
What is this?
A code-generator that can take a set of decorated model classes written in TypeScript to generate different boiler-plate code fragments useful for full-stack development. The goal is avoid the code generation iteration time going through model schema changes, especially for React/Redux/GraphQL based web, mobile and server side applications. Generated code fragments can be used independently of each other or all together. Supported right now is:
- GraphQL - node.js server side code that works with express-graphql
- GraphQL Client - JavaScript client side support for constructing type-safe GraphQL queries
- Sequelize - Server side code to create a Sequelize Model from the TypeScript Model
- Interfaces - Client and Server side TypeScript interfaces from the classes defined below
- Normalizr - Normalizr relationship schema useful on the server or client side to convert a hierarchical graph to a flat structure (more useful for redux and flux based applications)
- Redux - Redux Actions for processing GraphQL queryies onto a Redux Store
Installation
This is a develop-time tool to generate code that is best installed locally in the repository.
npm install https://github.com/joewood/refluxion --save-dev
Installing locally is preferred. The tool can then be used with an npm script to re-generate
the code artifacts. e.g. in package.json
:
"scripts": {
"generate": "refluxion --interfaces --sequelize --graphql --client-ql --normalizr src/model.ts -o src/refluxion"
},
Regnerating with simply: npm run generate
.
The Model
This library contains a set of decorators that are used on your existing TypeScript data model classes to describe how the model entities are related. Using these simple decorators the refluxion tool generates a set of boilerplate artifacts to help implement a full-stack GraphQL/node.js/redux based application. As the model changes these artifacts can be re-generated.
The data-model is defined by a set of classes representing each entity, and single root container - equivalent to a store in redux - which contains everything.
;
The contents of this class defines entity collections as Dictionaries. The example shows a simple data model for a blog, with articles, comments and users. These entities are defined as follows:
The Article entity defined as follows
// Define the article class, contains a foreign key to user /** Define how Articles are queried, using which parameters */
The Comment entity defined as follows
/** Defines a Comment entity */ /** Defines which fields comments can be queried by*/
The User entity defined as follows
/** Defines a User entity */ /** Defines how a User can be queried */
The decorators can be broken down as follows:
root
- the container store for the data model. The structure of the model should be flat with no nested data models. All references should be made through foreign keys.queryBy(QUERY_CLASS)
- defines how the entity set can be queried. The class parameter should contain the set of fields that are used as query arguments in GraphQL.hasMany
- decorator applied to a function in an entity class. The return type of this function defines a one-to-many relationship.hasOne(FOREIGN_ENTITY, fn)
- decorator applied to a property that acts as a foreign key to another entity.integer
- simple decator applied to numeric fields to indicate that the field is an integer and not floating point
Command Line
The full set of command line parameters can be found using -h
or --help
:
refluxion -h
For help on no command line arguments or -h
. Also use -o
or --output
to direct output to a specified path.
Generated Artifacts
Any of the below can be used to generate fragments of boilerplate code based on the model.
GraphQL
The option -g
or --graphql
can be used to generate a set of server-side node.js functions
that define a GraphQL model.
The output assumes the use of graphql-sequelize, which provides an easy implementation of GraphQL on top of a SQL based relational database.
The tool generates the GraphQL type representing each entity, this function is exposed as simple getGraphQL
. For example using the test-model:
The resolver functions here are using the graphql-sequelize library, alongwith the Sequelize associations defined in the Sequelize model.
Refluxion also generates the arguments type used by GraphQL (the query structure). For example this is defined and exported as follows:
;
Note that this includes the standard collection arguments order
, offset
and limit
. In addition a equivalent TypeScript interface
is generated representing this same structure:
Normalizr
The normalizr library converts deeply nested JSON structures into flatter entity-relational type structures, more useful in a Redux or Flux app.
The -n
or --normalizr
option outputs the following code for the test model:
;;;;article.define; comment.define; user.define;
This defines the key relationships in the model so that normalizr has knowledge of the returned structure. This can now be used on the client or server side to flatten query responses.
Sequelize
The Sequelize generator is generated using the -s
or --sequelize
option. It will output a file
called MODEL-FILE.sequelize.ts. There are three parts to this output artifact.
The first part is a function that creates the set of Sequelize types (the models in Sequelize terminology). An example output is as follows:
The next part of the output is a function defines the associations for the entities:
The final part is a set of interfaces that describe these types and associations to ensure type safe access to the Sequelize model and associations:
Interfaces
The -i
or --interfaces
option generates a set of interfaces that correspond to the definitions in the model.
This also includes a read-only version of the entity definition, useful for up-coming immutability support in
TypeScript.
A separate file MY-MODEL.optional-interfaces.ts is also generated where each field in the interface
is defined as being optional. This can be usedful for describing interfaces that require a partial Object
(e.g. Object.assign
or React setState
).
GraphQL Consumption - Type Safe Queries
The -c
or --client-ql
option generates a set of helper classes that can be used to create GraphQL nested
queries that follow the definition of the model.
refluxion defines a string literal type array that contains all the fields in the class. This helps provide compile-time checks that the referenced fields in a GraphQL query match the model:
;;
In addition, an interface is created that represents the Query arguments:
And a Query
derived class is created that represents the query structure for GraphQL:
Creating an instance of the above ArticleQuery
class provides a type safe method of creating a GraphQL
query, along with nested sub-queries. The class supports a toGraphQL
function that creates the
string representation of the query.
What Else?
Additional output for following is being worked on:
- Non-Sequelize GraphQL support using Graph support in some databases. Useful for queries that are hard to execute using Sequelize
- Propagated comments from the code into Sequelize and GraphQL
- Typescript 2 Readonly interface for immutable model support