0.0.2 • Published 3 years ago

@dupy/main v0.0.2

Weekly downloads
-
License
ISC
Repository
gitlab
Last release
3 years ago

Dupy is a project manager. This project allows you to build our own project in which language you want. Here is the alpha version of this project. The project will implement a lot of features in the future and add some language export in the future. Please see the #incoming(incoming features) section.

How to install it ?

The easier way it to install it globally via npm or yarn with the following command: npm install -g @dupy/main (or yarn install -g @dupy/main for yarn).

Sure, you can install the package locally, but the parser will install all the dupy dependencies into the same folder: all the dupy dependency will be shared with all the duty manager.# How does it work

@dupy/main is the main project (I called it the dupy manager) who contains the dupy parser and all the interfaces data: such as the resources, the builders, the actions, the functions, Language, the environnement variables, dependencies, controllers.

How to start a new project

Make sure to install at least one Dupy Builders into our @dupy/main project. For example, to install the nodejs Dupy Builder, you can run the following command:

dupy --install="@dupy/nodejs" 

Once this pre-requisite is completed, you can simply run the following command to start a dupy project and answer to ask question:

dupy init 

Now you can see the dupy structure to understand the global structure of your initialized project.

Dupy Structure

At project initialization, dupy will generate a project for you. Some folders will be rewritten during each dupy command, others not:

  • The dupy folder contains the different folders and files who will interpreted by the dupy manager to build our project.
  • The configuration folder contains the different folders containing the configuration of your application:
    • The configuration/certificates folder contains the certifications that will be used by your API (Most browsers require a secure connection to have a http2 connection). The whole of this folder "belongs to you", it will never alternate by re-generating the project, you can directly modify its certificates afterwards. (This section is on the TODO list: take a look at the incoming features section.)
    • The configuration/environment folder contains the environment files. Environment keys will never change or delete, but the dupy manager can add the missing keys. See the environnement variables section to read more information.
    • The configuration/lang folder contains the language files. The language translations will never be edited or deleted, but the dupy manager can add the missing keys. Each file contains the translation key in question.
  • The export and src folders generally have the same file structures but do not have the same purpose. The files contained in the src folder dependent on files contained in the export folder. The files in the src folder extend the files in export, allowing you to modify the functionality as you wish. Indeed, the regeneration of your project will not modify and will not delete the files contained in the source folder, unlike the files contained in the export folder that will be constantly overridden.

Environnement variables

As I said, on the #structure(dupy structure) section, the language file translations are stored in the configuration/environment of your project. The duty manager will never destroy your environment values or edit them, we will fill them if the key is missing. We can also notice that the files respect some nomenclatures:

  • The files who contain .dupy. in our file extension will be used in the dupy CLI only. Theses variables won't be used in our project.
  • The files who contain .secret. will be hidden. Those files should be ignored from your version control. Indeed, the dupy manager will retrieve all your environment value and inject it into a cache file into our export folder (make sure to ignore it from your version control). Then, don't forget to build our project to update our environmental values.

Dupy Dependencies

In the major case, dupy will firstly load all our project configurations, and add our dupy dependencies (the functions and the builders) into the dupy manager.

By default, the dupy manager doesn't have functions and dupy builders, you have to install it manually.Here is the command to install a nodejs builder:

dupy --install="@dupy/nodejs" 

dupy --install="..." is an alias of npm install command: you can specify multiple dupy dependencies to install, give folder or a repository, or give a specific version to install. Be careful, the options are not available. The dupy dependencies will be installed into the ~/.dupy/. You can uninstall the dependency with by following the example command:

dupy --uninstall="@dupy/nodejs" 

Ressource

In dupy, a resource represents a grouping of structured data. This grouping of data can have specific rules, who containing named value who including specific rules themselves.

You can see the resource as an excel table where each column is represented a field, and all the table represent the resource. All the field, and the resource itself apply the given rule that you will specify. Each excel line is a resource data.

For example, a user has an email address (which must be defined and respect the syntax of an email address), a first name, and a last name. In this example, the user is the resource here; on the other hand, the E-mail address, first name and last name are the values of user resources: they are the user resource's field.

Controller

A controller is librairy of utility function for your model. It allows you to use the functions easiers.

Action

An action represents an action to be performed from one or several resources.

For example, an action could be adding a person to the database. However, this user can only be added when he respects the rules that have been given in the resource and the values of the resources. If the resource respects these rules, the action will be performed, otherwise the action will not be performed.

A running action will respect the following middleware:

  • The action will build a resource model. This step is called entry.
  • The action will parse the attached request into the body. This step is called parseInputRequest.
  • The action will fulfill the prerequisites for continuing the course of the action. For example, during this step, the action will inject the values of the action into the models. This step is called inflate.
  • The action will make direct modifications to the models. This stage is called mutation.
  • The action will carry out the checks to carry out the action. This step is called check.
  • The action will then determine if the desired action can be performed. If one of the given conditions fails, the action will not execute the actions. It is from here that the action "splits" in two ways of executions: the action can fail or succeed. This step is called canContinue.
  • If the canContinue step did not find an error, then the action will execute the desired processing(s). This step is called execute.
  • If the action step went well as desired, then the action will execute the success step. This step allows you to perform actions after processing, for example, saving the model in session is done after inserting this model.
  • If the canContinue step or the action step has an error, then the action will execute the step called error.
  • If the action step fails, then the action will execute the step called fallback. After the fallback step, the action will set his state as around and execute the error step.
  • Therefore, the action will render the output of the request (if the request is linked to the action). This action is called parse output request npm.io

Dupy Builder

In @dupy/main, a dupy build is an interface who allows you to build our own dupy builder. This allows you to export our project in a lot of language (for exemple the dependency @dupy/nodejs allows you to export our project as a nodejs project.).

For exemple, your @dupy/main would like to export a specific model. Then, @model/main will call a method included in our Dupy Builder to export his specific model.

You have to run the following command to install the nodejs builder: dupy --install="@dupy/nodejs"

Dupy Functions

A Dupy functions allow you to add some functionality to our project.

For example, you would like to apply the auto increment function into a specific duty column. The @dupy/main project will fetch for the dupy function associated with the auto increment function to apply the auto increment modifier.

There are 4 types of dupy functions: a function for the actions, a function for the Resources, a function for the Actions, a function of the field and the dupy function of the project.

If a field function is declared into a resource context, the dupy manager will automatically create an empty field within the given field function. You have to install the dupy functions. Here is a command line example to install the auto increment function: dupy --install="@dupy/auto-increment"

Dupy Parser

Understand with an example

Objective of the example

In this example, we will create a simple API allows to create a blog:

  • With users containing:
    • An identifier number
    • A password who have at least 5 characters and encrypted
    • A unique email address
    • A first name who will be lowered, trimmed. The value must be defined.
    • A last name who will be lowered, trimmed. The value must be defined.
    • A slug which will be from the last name and the user's first name
    • A creation date
    • An update date
  • With articles containing:
    • An identifier
    • The user who created the article
    • The content of the article
    • The title of the article
    • A slug which will be from the title of the article
    • A creation date
    • An update date
  • With comments containing:
    • An identifier
    • The article associated with the comment
    • The user who created the comment
    • The content of the comment
    • The title of the article
    • A creation date
    • An update date

Explanation of logic

As you can see, the structure of the blog is quite simple to be identified. First of all, we can group together 3 types of data structures: user, article and order. In dupy, its data types are the resources and each of those resources contains fields. In addition, each field may have functions applied to it. Each of those resources can be inserted, modified or deleted by the actions.

Applying logic

Dupy declaration

The configuration of the modules of your project is carried out in the dupy folder (located at the root of your project): each.dupy file contained in this folder will be interpreted when the project is exported.

Resources declaration

First of all, you have to create the User,Article and Comment resources. The syntax for creating a resource is as follows:

Resource **name of the resource**{
    // resource content
}

Then, here is the code to add User,Article and Comment resources to write into a .dupy file into your dupy folder:

Resource User{
    // User's resource content
}
Resource Article{
    // Article's resource content 
}
Resource Comment{
    // Comment's resource content
}

Once the resources have been created, we are now going to add the fields for each of thus resources. The syntax to add a resource's field declaration is:

**field's name**: **fonction list**

The code above, is to be placed in the resource whose field is associated

First, we want to declare the field id of the resource User. In this example id is the name of the field to add and @id is the function that will be applied to the field id of the resource. Finally, to integrate this field id into the resource User, just write in the .dupy file:

Resource User{
    id: @id
}

You have to install the dupy dependency for the @id function. To install it, you have to run the following command: dupy --install="@dupy/auto-increment" Notice that some function automatically sets the name of your field if it's not defined. The @id function sets the field to id if the field name is not defined. Then the following code is the same as above:

resource User{
    @id
}

You an also apply multiple function to a field. That's why dupy is magic! For example, here are the functions to use for the last name and the first name of the user:

resource User{
    @id
    last_name: @string @trim @lower @required @not_empty
    first_name: @string @trim @lower @required @not_empty
}

You have to install the dupy dependencies for the @string, @trim, @lower, @required and @not_empty functions. To install it, you have to run the following command: dupy --install="@dupy/string @dupy/trim @dupy/lower @dupy/required @dupy/not-empty"

In the above example, you can simplify the representation of the code. Indeed, the @trim and the @lower functions automatically add the @string: we don't need to apply the @string function to the last_name and first_name fields in this case. Then this dupy declaration is the same as above:

resource User{
    @id
    last_name: @trim @lower @required @not_empty
    first_name: @trim @lower @required @not_empty
}

Now we are going to add the email, created_at and updated_at fields with the following code:

resource User{
    @id
    last_name: @trim @lower @required @not_empty
    first_name: @trim @lower @required @not_empty
    email: @email @unique
    password: @bcrypt @no_output @required @min(5)
    @created_at
    @updated_at
}

You have to install the dupy dependencies for the @min, @bcrypt and @no_output functions. To install it, you have to run the following command: dupy --install="@dupy/min @dupy/bcrypt @dupy/remove-output"

In the preview lines of code you can that we give a parameter value to the @min. If you take a look at the @min documentation function, you can see that function must have a parameter to the set the minimal value expected (in our example the expected value is 5). The dupy parameter can have multiple of value type:

  • A number: 42
  • A string: "hello world"
  • A null value: null
  • A list of values: [1,2,3,4,5] or {1,2,3,4,5} or (1,2,3,4,5). The value can be surrounded by [ and ] or { and } or ( and ), and the value are separated with ,.
  • An object who look like a list of values with key: {last_name: "Dupas", first_name: "Jeremie"}. Such as the list of value, the object an be surrounded by [ and ] or { and } or ( and ), and the value are separated with ,.
  • An action, a resource or a field just by specifying their name.

In Dupy, the function can ask multiple parameters such as @join who we are going to use for the slug field. (This function allows us to join the field value when the given field are all defined or one of them is updated.)

resource User{
    @id
    last_name: @trim @lower @required @not_empty
    first_name: @trim @lower @required @not_empty
    email: @email @unique
    slug: @slug @join(fields: [User.first_name, User.last_name],glue: "-")
    password: @bcrypt @no_output @required @min(5)
    @created_at
    @updated_at
}

We can simply the syntax above because the slug set the field name to slug by default, the email set the field name to email by default and bcrypt set field name to password by default. Then we can remove thus field's names to simplify our dupy configuration, but we have to keep the other field's function, then we have to let the : character to keep the functions on the same field. (otherwise, the dupy manager will create a field for each function). Then, here is the simplified code for the User resource:

resource User{
    @id
    last_name: @trim @lower @required @not_empty
    first_name: @trim @lower @required @not_empty
    : @email @unique
    : @slug @join([first_name,last_name])
    : @bcrypt @no_output @required @min(5)
    @created_at
    @updated_at
}

With the same as above User resource example, we add the Article and the Comment resources. Here is the code to add them:

resource Article{
    @id
    @link(User)
    content: @not_empty @required @trim @markdown
    title: @not_empty @required @min(5) @max(25)
    : @slug @join(title)
    @created_at
    @updated_at
}
resource Comment{
    @id
    @link(User)
    @link(Article)
    content: @not_empty @required @trim
    @created_at
    @updated_at
}

The @link function can have a field or resource in. The field will allow us to do the junction between two resources data. (Indeed, in the major case, the given value field must be unique). When a resource is given, the @link function will fetch for the optimised field: he will try to fetch the primary value of the resource, the seo field, an integer unique value field, then a field a unique field value. Then, @link(Article) is the same to @link(Article.id)

The @link function set the default with a template. Firstly, it's the name of the linked resource, then the name of the linked field. For example: @link(Article.slug) will give a field a name article_slug, and @link(User) will give a field with a default name to: user_id.

Actions declaration

One the resource created, we now have to create the actions. The dupy syntax to create an action is similar to the resource's declaration:

Action **name of the action**{
    // action's content
}

The action content is a list of dupy action function who will add a specific running hook. Here are the list a function who is going to use to build an insert action:

  • @input_json: allow to parse the json parse body and set it into the active data
  • @output_json: allow to output the request as a json content
  • @model_from_data: convert the action data to resources instances (we are going to execute the checks for an insert context)
  • @insert_check: run all the resource and field checks added with resource function and the field function (we are going to execute the mutations for an insert context)
  • @insert_mutation: run all the resource and field value mutation added with resource function and the field function
  • @no_error: run the executable action only if the action does not have errors.
  • @insert_structure: when the action will be executed, the action will insert all the models linked to the action
  • @fallback_unique: check for unique data when the action detects errors Then here is the dupy declaration for the insert action:
Action Insert{
    @input_json
    @output_json
    @model_from_data
    @insert_check
    @insert_mutation
    @no_error
    @insert_structure
    @fallback_unique
} 

Now we are going to set the First (action)#action who will attempt to fetch for a resource model. Here are the (function), who we are going to use for:

  • @no_input: Forbidden the request input body
  • @output_json: allow to output the request as a json content
  • @data_from_params: Allow to inflate the action's data from the query parameters
  • @model_from_data: convert the action data to resources instances
  • @use_check: run all the resource and field checks added with resource function and the field function (we are going to execute the mutations for an use context. This is the default context)
  • @use_mutation: run all the resource and field value mutation added with resource function and the field function (we are going to execute the checks for an use context. This is the default context)
  • @no_error: run the executable action only if the action does not have errors.
  • @load_structure: Load all the actions models. The function add an error if one the linked model do not exist. Here is the result dupy configuration for the First (action)#action:
Action First{
    @no_input
    @output_json
    @data_from_params
    @model_from_data
    @use_check
    @use_mutation
    @no_error
    @load_structure
} 

Now, with the same logic, we are going to declare the List (action)#action:

Action List{
    @no_input
    @output_json
    @request_to_query
    @query_use_check
    @query_use_mutation
    @no_error
    @load_structure
} 

Here is the explanation of the (functions)#function:

  • @request_to_query: Each given field value will be transformed to a field link to the (resource)#resource's (action)#action, and the will be added to the query structure.
  • @query_use_mutation and @query_use_check are the same to @use_mutation and @use_mutation (functions)#function, but the @query_use_mutation and @query_use_check (functions)#function will use the field and resource from the query structure.
  • @load_structure: the action do not have model in fact. But the query has been edited by the @request_to_query, @query_use_check, @query_use_mutation (functions)#function. Then, the function will add the loaded model into the action.

Now we are going to declare the more complex function: the Update (action)#action who allow use to update a (resource)#resource data. Firstly, we can copy and paste the following function from Insert action:

Action Update{
    @input_json
    @output_json
    @model_from_data
    @no_error
    @fallback_unique
} 

Now we can add the update mutations and the update checks functions (their names are @update_mutation, @update_check):

Action Update{
    @input_json
    @output_json
    @model_from_data
    @no_error
    @fallback_unique
    @update_check
    @update_mutation
} 

Afterward, we are going to add the execute's (functions)#function for our Update (action)#action: @update_structure:

Action Update{
    @input_json
    @output_json
    @model_from_data
    @no_error
    @fallback_unique
    @update_check
    @update_mutation
    @update_structure
} 

For the moment, the action won't working for security reason. Actually, we didn't specify which model we want to modify: we actually set the data to set. We are going to use our previous action First to fetch the model to edit: this action will be a child action of our Update action. To do it we are going to update the @action_to_query function:

Action Update{
    @input_json
    @output_json
    @model_from_data
    @no_error
    @fallback_unique
    @update_check
    @update_mutation
    @update_structure
    @action_to_query(First)
} 

Afterward, we are going to set the Delete (action)#action to remove specific resource data:

Action Delete{
    @no_input
    @output_json
    @no_error
    @delete_structure
    @action_to_query(First)
} 

Now, we are going to link the created actions to ours resources with the @post,@put,@delete and @get function. Here is the final code to create our blog:

Action Insert{
    @input_json
    @output_json
    @model_from_data
    @insert_check
    @insert_mutation
    @no_error
    @insert_structure
    @fallback_unique
} 
Action First{
    @no_input
    @output_json
    @data_from_params
    @model_from_data
    @use_check
    @use_mutation
    @no_error
    @load_structure
} 
Action Update{
    @input_json
    @output_json
    @model_from_data
    @no_error
    @fallback_unique
    @update_check
    @update_mutation
    @update_structure
    @action_to_query(First)
} 
Action List{
    @no_input
    @output_json
    @request_to_query
    @query_use_check
    @query_use_mutation
    @no_error
    @load_structure
} 
Action Delete{
    @no_input
    @output_json
    @no_error
    @delete_structure
    @action_to_query(First)
} 

resource User{
    @id
    last_name: @trim @lower @required @not_empty
    first_name: @trim @lower @required @not_empty
    : @email @unique
    : @slug @join([first_name,last_name])
    : @bcrypt @no_output @required @min(5)
    @created_at
    @updated_at
    @get(List,"/users")
    
    @get(First,"/user/:slug/slug/")
    @get(First,"/user/:id/id/")
    @get(First,"/user/:slug/")

    @put(Update,"/user/:slug/slug/")
    @put(Update,"/user/:id/id/")
    @put(Update,"/user/:slug/")

    @delete(Delete,"/user/:slug/slug/")
    @delete(Delete,"/user/:id/id/")
    @delete(Delete,"/user/:slug/")

    @post(Insert,"/user")
}
resource Article{
    @id
    @link(User)
    content: @not_empty @required @trim @markdown
    title: @not_empty @required @min(5) @max(25)
    : @slug @join(title)
    @created_at
    @updated_at
    
    @get(List,"/articles")
    
    @get(First,"/article/:slug/slug/")
    @get(First,"/article/:id/id/")
    @get(First,"/article/:slug/")

    @put(Update,"/article/:slug/slug/")
    @put(Update,"/article/:id/id/")
    @put(Update,"/article/:slug/")

    @delete(Delete,"/article/:slug/slug/")
    @delete(Delete,"/article/:id/id/")
    @delete(Delete,"/article/:slug/")

    @post(Insert,"/article")
}
resource Comment{
    @id
    @link(User)
    @link(Article)
    content: @not_empty @required @trim
    @created_at
    @updated_at

    @get(List,"/comments")
    
    @get(First,"/comment/:slug/slug/")
    @get(First,"/comment/:id/id/")
    @get(First,"/comment/:slug/")

    @put(Update,"/comment/:slug/slug/")
    @put(Update,"/comment/:id/id/")
    @put(Update,"/comment/:slug/")

    @delete(Delete,"/comment/:slug/slug/")
    @delete(Delete,"/comment/:id/id/")
    @delete(Delete,"/comment/:slug/")

    @post(Insert,"/comment")
}

build the project

To build our project we can simply run dupy. This command is an alias of dupy --default.

Update the missing language value into your configuration/lang folder. (do not forget to run dupy --default to update your language cache file)

Incoming features

  • manage relation model
  • manage graphql request
  • can extend action
  • can extend resource
  • add unit test to manage dupy
  • add command to begin our own dupy function from a template
  • add command to begin our own dupy builder from a template
  • allow installing local dupy function
  • link dupy dependency when we install a dupy dependency globally
  • split and update the parameter library check to allow mores features
  • split the dupy parser
  • build a dupy builder for php
  • build a dupy builder for java
  • build a dupy builder for nodejs
  • write library to allow a postgres structure
  • write library to allow a mariadb structure
  • allow creating dupy snapshot of the project
  • allow generating dupy migration for structured database
  • allow to build some front stuff: the dupy will generate the data model, the field will be transformed and checked to be included into our front software. (the value will be checked offline). Build our api plugin.
  • generate api documentation