├── .env.development ├── .github └── workflows │ ├── promote-npm.yml │ └── test-coveralls.yml ├── .gitignore ├── .npmignore ├── README.es-MX.md ├── README.md ├── babel.config.json ├── coveralls.yml ├── jest.config.js ├── modules ├── cli │ ├── index.mjs │ ├── make │ │ ├── connection │ │ │ ├── index.js │ │ │ └── templates │ │ │ │ └── config.template │ │ ├── migration │ │ │ ├── index.js │ │ │ └── templates │ │ │ │ ├── import.template │ │ │ │ └── require.template │ │ ├── model │ │ │ ├── index.js │ │ │ └── templates │ │ │ │ ├── import.template │ │ │ │ └── require.template │ │ └── seeder │ │ │ ├── index.js │ │ │ └── templates │ │ │ ├── container │ │ │ ├── import.template │ │ │ └── require.template │ │ │ └── seeder │ │ │ ├── import.template │ │ │ └── require.template │ ├── migrate │ │ ├── fresh │ │ │ └── index.js │ │ ├── index.js │ │ ├── refresh │ │ │ └── index.js │ │ ├── reset │ │ │ └── index.js │ │ └── rollback │ │ │ └── index.js │ └── seed │ │ └── index.js ├── config │ ├── cli │ │ └── questions.cli.js │ ├── index.js │ ├── languages │ │ ├── en.js │ │ ├── es.js │ │ └── index.js │ ├── prototypes.config.js │ └── utils.js ├── global │ └── get-name.js ├── index.js └── orm │ ├── adapters │ ├── index.js │ └── mysql │ │ ├── builders │ │ ├── faker.builder.js │ │ ├── query.builder.js │ │ └── schema.builder.js │ │ ├── connection.js │ │ └── index.js │ ├── database.js │ ├── migration.js │ ├── model.js │ ├── schema.js │ └── seeder.js ├── obremap.config.js ├── package.json ├── tests └── orm │ ├── model │ ├── index.test.js │ ├── properties.test.js │ └── queryBuilder │ │ ├── select.test.js │ │ ├── setCreatedAt.test.js │ │ ├── setTimestamps.test.js │ │ ├── setUpdatedAt.test.js │ │ ├── table.test.js │ │ └── where.test.js │ └── setup │ ├── user.model.js │ └── usuario.model.js └── yarn.lock /.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=test 2 | 3 | DATABASE_URL=mysql://root:root@127.0.0.1/node_obremap 4 | DATABASE_URL_OTHER=mysql://root:root@127.0.0.1/node_obremap 5 | 6 | DB_DRIVER=mysql 7 | DB_HOST=127.0.0.1 8 | DB_USERNAME=root 9 | DB_PASSWORD=root 10 | DB_NAME=node_obremap 11 | 12 | DB_TEST_DRIVER=mysql 13 | DB_TEST_HOST=127.0.0.1 14 | DB_TEST_USERNAME=root 15 | DB_TEST_PASSWORD=root 16 | DB_TEST_NAME=node_obremap 17 | 18 | # TZ="America/Monterrey" -------------------------------------------------------------------------------- /.github/workflows/promote-npm.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | npm-publish: 9 | name: npm-publish 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@master 15 | 16 | - name: Set up Node.js 17 | uses: actions/setup-node@master 18 | with: 19 | node-version: 16 20 | 21 | - name: Install Dependencies 22 | run: npm install 23 | 24 | - name: Build CLI Applicatoin 25 | run: npm run build 26 | 27 | - name: Publish if version has been updated 28 | uses: pascalgn/npm-publish-action@1.3.9 29 | with: # All of theses inputs are optional 30 | tag_name: "v%s" 31 | tag_message: "v%s" 32 | commit_pattern: "Release (\\S+)$" 33 | workspace: "." 34 | publish_command: "yarn" 35 | publish_args: "--non-interactive" 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} # You need to set this in your repo settings 39 | -------------------------------------------------------------------------------- /.github/workflows/test-coveralls.yml: -------------------------------------------------------------------------------- 1 | name: Run Tests and Coveralls 2 | on: 3 | push: 4 | branches-ignore: 5 | master 6 | pull_request: 7 | branches: [ master, dev ] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Install all dependencies 17 | run: npm install 18 | 19 | - name: Run tests and coveralls 20 | run: npm run test 21 | env: 22 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 23 | DATABASE_URL: ${{ secrets.DATABASE_URL }} 24 | DATABASE_URL_OTHER: ${{ secrets.DATABASE_URL_OTHER }} 25 | DB_HOST: ${{ secrets.DB_HOST }} 26 | DB_USERNAME: ${{ secrets.DB_USERNAME }} 27 | DB_PASSWORD: ${{ secrets.DB_PASSWORD }} 28 | DB_NAME: ${{ secrets.DB_NAME }} 29 | DB_PORT: ${{ secrets.DB_PORT }} 30 | DB_DRIVER: ${{ secrets.DB_DRIVER }} 31 | DB_TEST_DRIVER: ${{ secrets.DB_TEST_DRIVER }} 32 | DB_TEST_HOST: ${{ secrets.DB_TEST_HOST }} 33 | DB_TEST_USERNAME: ${{ secrets.DB_TEST_USERNAME }} 34 | DB_TEST_PASSWORD: ${{ secrets.DB_TEST_PASSWORD }} 35 | DB_TEST_NAME: ${{ secrets.DB_TEST_NAME }} 36 | 37 | - name: Coveralls 38 | uses: coverallsapp/github-action@master 39 | with: 40 | github-token: ${{ secrets.GITHUB_TOKEN }} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | migrations 4 | node_modules 5 | .env 6 | .nyc_output 7 | _* 8 | models 9 | app 10 | seeds 11 | .DS_Store 12 | 13 | ## EXCEPTS 14 | !tests/setup/models 15 | !modules/cli/migrations 16 | !tests/unit/migrations 17 | !tests/setup/migrations -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests 2 | modules 3 | node_modules 4 | .babelrc 5 | .gitignore 6 | circle.yml 7 | coveralls.yml 8 | coverage 9 | migrations 10 | .env* 11 | .nyc_output 12 | .git 13 | _* 14 | models 15 | obremap.config.js 16 | .github 17 | .github/* 18 | 19 | ## EXCEPTS 20 | !dist/cli/migrations 21 | !dist/cli/models -------------------------------------------------------------------------------- /README.es-MX.md: -------------------------------------------------------------------------------- 1 | # OBREMAP - Node ORM 2 | 3 | ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) 4 | [![made-with-Go](https://img.shields.io/badge/Made%20with-NPM-6cc24a.svg)](https://nodejs.org) 5 | ![NPM Version](https://badge.fury.io/js/%40moccacoders%2Fnode-obremap.svg) 6 | ![Testing Status](https://github.com/moccacoders/node-obremap/workflows/Run%20Tests%20and%20Coveralls/badge.svg?branch=master) 7 | ![Coverage Status](https://coveralls.io/repos/github/moccacoders/node-obremap/badge.svg?branch=dev) 8 | 9 | **OBREMAP Node ORM** es una herramienta de Mapeo Objeto-Relacional para Node JS basada en el famoso ORM de [Laravel](https://laravel.com/), [**Eloquent**](https://laravel.com/docs/eloquent). 10 | OBREMAP proporciona una implementación de ActiveRecord hermosa y simple para trabajar con su base de datos. Cada tabla de base de datos tiene un **"Modelo OBREMAP"** correspondiente que se utiliza para interactuar con esa tabla. 11 | Los modelos le permiten consultar datos en sus tablas, así como insertar nuevos registros en la tabla. 12 | 13 | Antes de comenzar, asegurate de configurar tus bases de datos correctamente. para más información sobre la configuración de la base de datos, consulta la [configuración de base de datos](#database-configuration). 14 | 15 | ## ¿CÓMO INSTALAR? 16 | 17 | ``` 18 | $ npm install @moccacoders/node-obremap --save 19 | or 20 | $ yarn add @moccacoders/node-obremap 21 | 22 | //if using mysql driver this is peer dependency anyway 23 | npm install mysql --save 24 | ``` 25 | 26 | ## CONFIGURACIÓN DE BASE DE DATOS 27 | 28 | Con **OBREMAP Node ORM** tienes dos formas con las que puedes configurar tus bases de datos. La primera es agregando tus bases de datos a las variables de entorno de tu app y la segunda (más recomendable) utilizando el archivo de configuración `obremap.config.js`. 29 | 30 | #### -> Variables de Entorno 31 | 32 | Para configurar tus bases de datos deberás definir las siguientes variables: 33 | ``` 34 | DB_DRIVER=mysql 35 | DB_HOST=localhost 36 | DB_USERNAME=user 37 | DB_PASSWORD=pass 38 | DB_NAME=database_name 39 | ``` 40 | 41 | #### -> Archivo de Configuración OBREMAP 42 | 43 | Si bien es muy sencillo configurar tus bases de datos solo agregando las variables de entorno necesarias, te recomendamos que utilices el **archivo de configuración OBREMAP**, este te permitirá configurar tus bases de datos de manera independiente. 44 | Lo que deberás hacer es crear un archivo con el nombre `obremap.config.js` en la carpeta raiz de tu aplicación y agregar la siguiente configuración básica. 45 | 46 | * Es importante agregar la llave `default` a la configuración de bases de datos, ya que esta será la información principal que se utilizará para la conexión 47 | 48 | ```js 49 | module.exports = { 50 | databases : { 51 | default : { // IMPORTANTE AGREGAR "DEFAULT". ESTA SERÁ LA INFORMACIÓN DE CONEXIÓN PRINCIPAL 52 | host: "localhost", 53 | user: "user", 54 | password: "pass", 55 | database: "database_name", 56 | port: 3306, 57 | driver: "mysql" 58 | } 59 | } 60 | } 61 | ``` 62 | 63 | ### Configuración usando URL's 64 | Comunmente, la configuración de las bases de datos utilizan multiples valores de configuración como `host`, `database`, `username`, `password`, etc. Cada uno de ellos corresponde a una variable de entorno. Esto quiere decir que cuando configuras tus bases de datos en tu servidor de producción, deberás manejar muchas variables de entorno. 65 | 66 | Muchos de los proveedores de bases de datos proveen una única "URL" de conexión a base de datos, la cual contiene toda la información necesaria para la conexión en una simple cadena. Un ejemplo de URL de dase de datos se ve muy similiar a esto: 67 | 68 | ``` 69 | mysql://root:password@127.0.0.1/forge?charset=UTF-8 70 | ``` 71 | 72 | Estas URLs suelen seguir una convención de esquema estándar: 73 | 74 | ``` 75 | driver://username:password@host:port/database?options 76 | ``` 77 | 78 | Para tu conveniencia, **OBREMAP Node ORM** admite estas URLs como una alternativa a la configuración de sus bases de datos con múltiples opciones de configuración. Es decir, si se encuentra presenta una URL de configuración de base de datos (o su correspondiente variable de entorno `DATABASE_URL`) se utilizará para extraer la conexión de la base de datos y la información de credenciales. 79 | 80 | ##### -> VARIABLES DE ENTORNO 81 | ``` 82 | DATABASE_URL=mysql://root:password@127.0.0.1/database_name 83 | ``` 84 | 85 | ##### -> ARCHIVO DE CONFIGURACIÓN OBREMAP 86 | La configuración es similar a la anterior. Solo que en lugar de guardar un objeto dentro de la configuración default, deberás agregar la URL como cadena. 87 | ```js 88 | module.exports = { 89 | databases : { 90 | default : "mysql://root:password@127.0.0.1/database_name" 91 | } 92 | } 93 | ```` 94 | 95 | ### Configuración de multiples bases de datos 96 | 97 | **OBREMAP Node ORM** provee la posibilidad de realizar multiples conexiones a multiples bases de datos. No tienes límite en cuanto a conexiones, puedes configurar todas las conexiones que necesites, todas las bases de datos que requieras. 98 | La configuración es similar a lo visto anteriormente. 99 | 100 | #### -> VARIABLES DE ENTORNO 101 | 102 | Para configurar multiples conexiones por medio de las variables de entorno **OBREMAP Node ORM** toma todas las varibles que tengan como prefijo `DB_` y asigna el valor siguiente como variable de configuración. Por lo que si colocas `DB_HOST` esta será la variable de configuración principal (`default`) que contiene el hostname de la base de datos. Sin embargo, si colocas un identificador despues del prefijo `DB_`, este será tomado como nombre de conexión, a este debe seguir el nombre de la variable de conexión, ejemplo: `DB_LOCAL_HOST` para este caso el nombre de la conexión será `local` y la variable de conexión será `host`. 103 | 104 | ```json 105 | // CONEXIÓN PRINCIPAL 106 | DB_DRIVER=mysql 107 | DB_HOST=127.0.0.1 108 | DB_USERNAME=user 109 | DB_PASSWORD=pass 110 | DB_NAME=database_name 111 | 112 | // CONEXIÓN SECUNDARIA [LOCAL] 113 | DB_LOCAL_DRIVER=mysql 114 | DB_LOCAL_HOST=localhost 115 | DB_LOCAL_USERNAME=loca_user 116 | DB_LOCAL_PASSWORD=local_pass 117 | DB_LOCAL_NAME=other_database 118 | 119 | ``` 120 | 121 | #### -> ARCHIVO DE CONFIGURACIÓN OBREMAP 122 | 123 | Así como se puede configurar multiples conexión a la base de datos con una pequeña modificación en las varibles de entorno también lo puedes hacer mediante el **archivo de configuración OBREMAP** lo unico que tendrás que hacer es agregar un elemento más a tu objecto `databases`, tomando en consideración que el nombre que le des al objeto nuevo, será el nombre de tu conexión. 124 | 125 | ```js 126 | module.exports = { 127 | databases : { 128 | // CONEXIÓN PRINCIPAL 129 | default : "mysql://root:password@127.0.0.1/database_name", 130 | // CONEXIÓN SECUNDARIA [LOCAL] 131 | local : "mysql://loca_user:local_pass@localhost/other_database" 132 | } 133 | } 134 | ``` 135 | 136 | #### SELECCIONAR BASE DE DATOS 137 | 138 | Una vez que ya haz configurados tus multiples bases de datos lo que deberás hacer es indicar al model que conexión debe utilizar. Y esto se hace solamente agregando un methodo estatico dentro de tu modelo. Recuerda que el nombre que coloques aquí es el nombre que le diste a tu conexión en la configuración de conexión de bases de datos multiples. 139 | 140 | ```js 141 | import { Model } from 'node-obremap' 142 | 143 | export default class Chat extends Model { 144 | static conexion = "local"; 145 | } 146 | ``` 147 | 148 | ### DEFINIR UN MODELO 149 | 150 | Para comenzar, crearemos un **Modelo Obremap**. Por lo general los modelos se encuentran en la carpeta raiz dentro de la carpeta **MODELS** sin embargo puedes colocarlos donde prefieras, siempre y cuando puedas acceder a ellos. Todos los **Modelos OBREMAP** deben extender de la clase `Model` dentro de **node-obremap**. 151 | La forma más sencilla para crear tus modelos es utilizando el **Obremap CLI** con la funcion `obremap make:model` 152 | 153 | `chat.js` 154 | 155 | ```js 156 | import { Model } from 'node-obremap' 157 | 158 | export default class Chat extends Model { 159 | /* 160 | overwrite table name, this is optional 161 | static tableName = 'dashboard_chats'; 162 | */ 163 | } 164 | 165 | ``` 166 | 167 | ### USANDO EL MODELO 168 | 169 | Una vez creado el **Modelo OBREMAP** (y asociada la tabla de base de datos correctamente), estará listo para obtener datos de sus bases de datos. Piensa en los Modelos OBREMAP como generadores de consultas eficientes que te ayudarán a realizar tus consultas con rápidez a la base de datos asociada a tus Modelos. 170 | Ejemplo: 171 | 172 | ```js 173 | import Chat from './chat' 174 | 175 | async function getChats { 176 | let chats = await Chat.all() 177 | console.log(chats) 178 | } 179 | ``` 180 | 181 | #### Metodos Soportados 182 | 183 | - `.all()` Regresa todo en la table 184 | - `.count()` Regresa un valor numerico correspondiente al total de registros extraidos 185 | - `.create({ field: 'value'})` Crear un nuevo registro 186 | - `.delete(where||)` Elimina el registro 187 | - `.find()` Encuentra y devuelve una relación actualmente solo `id` ya que la clave primaria es compatible pero la clave primaria dinámica llegará pronto 188 | - `.first()` Regresa el primer registro 189 | - `.join(tableName, , )` Crear uniones entre tablas 190 | - `.limit(5)` Limita el total de resultados 191 | - `.offset(10)` Define el inicio de la búsqueda 192 | - `.orderBy('fieldName' || {fieldName : "asc"})` Regresa los resulstados ordenados 193 | - `.select('column', 'column2')` Define las columnas de la base de datos a extraer 194 | - `.update({ field: 'value' }, where)` Actualiza un registro 195 | - `.where({ fieldName: 'value' })` Regresa los resultados que coincidan con la expresión 196 | 197 | ### Query Building 198 | 199 | ```js 200 | 201 | Chat.all() 202 | 203 | Chunk.count(); 204 | 205 | Chat.where({ messages : "blah" }).count(); 206 | 207 | Chat.create({ user_id: 23 }) 208 | 209 | User.find(1) 210 | 211 | Chat.select('messages', 'id').where({ messages: 'blah' }).get() 212 | 213 | Chat.where({ messages: 'blah' }).get() 214 | 215 | Chat.select('messages').first() 216 | 217 | Chat.where({ messages: 'blah' }).limit(2).get() 218 | 219 | Chat.update({ name: 'Raymundo' }, { id : 1 }) 220 | 221 | Chat.delete(1) 222 | 223 | ``` 224 | 225 | ### RELACIONES 226 | 227 | Esta es una gran WIP, no dude en contribuir 228 | 229 | Soporta: 230 | - Uno to Uno 231 | - Uno a muchos 232 | 233 | Por Hacer: 234 | - Muchos a Muchos 235 | - Tiene muchos a través 236 | - Relaciones polimórficas 237 | - Relaciones polimórficas de muchos a muchos 238 | 239 | #### Uno to Uno - Ejemplo 240 | 241 | ```js 242 | import { Model } from 'node-obremap' 243 | 244 | 245 | export default class User extends Model { 246 | 247 | } 248 | 249 | export default class Chat extends Model { 250 | user() { 251 | return this.hasOne(User) 252 | } 253 | } 254 | 255 | let chat = await Chat.first() 256 | 257 | //any relationship will return a promise with the result 258 | let user = await chat.user 259 | 260 | expect(user.name).to.be.equal('Bob') 261 | 262 | ``` 263 | 264 | #### Uno a muchos - Ejemplo 265 | 266 | ```js 267 | import { Model } from 'node-obremap' 268 | 269 | export default class User extends Model { 270 | chats() { 271 | return this.hasMany(Chat) 272 | } 273 | } 274 | 275 | /* ============================================= */ 276 | 277 | export default class Chat extends Model { } 278 | 279 | let user = await User.first() 280 | //has many results return a query builder instance 281 | let chats = await user.chats.first() 282 | 283 | 284 | ``` 285 | 286 | ### CLI 287 | 288 | Si instala **node-obremap globalmente** (`npm install @moccacoders/node-obremap -g`) puede acceder a los métodos CLI para ayudar a crear modelos, conexiones, etc. y utilizar los diferentes asistentes de creación. 289 | Actualmente se encuentran en **español** e **ingles**. 290 | 291 | #### Models 292 | 293 | `obremap make:model` 294 | 295 | Inicializará el asistente de creación de **Modelos** el cual te guiará en la creación de tu modelo solicitandote información necesaria para ello. Adicionalemnte, y solo si es necesario, te desplegará el asistente de creación de conexiones. 296 | Creates a file in your current directory `/models/user.js` with a default model 297 | 298 | #### Conexiones 299 | 300 | `obremap make:connection` 301 | 302 | Inicializará le asistente de creación de **Conexiones** el cual te solicitará la información necesaria para le creación de conexiones; así mismo te permitirá seleccionar entre utilizar el archivo de configuración OBREMAP o bien tus Variables de Entorno. 303 | 304 | 305 | ### Todo 306 | - Agregar más controladores de Bases de Datos 307 | - CLI 308 | - Agregar más funciones 309 | - Agregar más lenguajes 310 | - Migraciones 311 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OBREMAP - Node ORM 2 | 3 | 4 | ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg) 5 | [![made-with-Go](https://img.shields.io/badge/Made%20with-NPM-6cc24a.svg)](https://nodejs.org) 6 | ![NPM Version](https://badge.fury.io/js/%40moccacoders%2Fnode-obremap.svg) 7 | ![Testing Status](https://github.com/moccacoders/node-obremap/workflows/Run%20Tests%20and%20Coveralls/badge.svg?branch=master) 8 | ![Coverage Status](https://coveralls.io/repos/github/moccacoders/node-obremap/badge.svg?branch=dev) 9 | 10 | **OBREMAP Node ORM** is an Object-Relational Mapping tool for Node JS based on the famous ORM of [Laravel](https://laravel.com/), [**Eloquent ORM**](https://laravel.com/docs/eloquent). 11 | OBREMAP provides a beautiful and simple ActiveRecord implementation to work with your database. Each database table has a corresponding **"OBREMAP Model"** that is used to interact with that table. 12 | Models allow you to query data in your tables, as well as insert new records into the table. 13 | 14 | Before starting, make sure you configure your databases correctly. For more information on database configuration, see the [database configuration](#database-configuration). 15 | 16 | ## HOW TO INSTALL? 17 | 18 | ``` 19 | $ npm install @ moccacoders / node-obremap --save 20 | or 21 | $ yarn add @ moccacoders / node-obremap 22 | 23 | // if using mysql driver this is peer dependency anyway 24 | npm install mysql --save 25 | ``` 26 | 27 | ## DATABASE CONFIGURATION 28 | 29 | With **OBREMAP Node ORM** you have two ways with which you can configure your databases. The first is by adding your databases to the environment variables of your app and the second *(most recommended)* using the configuration file `obremap.config.js`. 30 | 31 | #### -> Environment Variables 32 | 33 | To configure your databases you must define the following variables: 34 | ``` 35 | DB_DRIVER = mysql 36 | DB_HOST = localhost 37 | DB_USERNAME = user 38 | DB_PASSWORD = pass 39 | DB_NAME = database_name 40 | ``` 41 | 42 | #### -> OBREMAP Configuration File 43 | 44 | Although it is very easy to configure your databases just by adding the necessary environment variables, we recommend that you use the **OBREMAP configuration file**, this will allow you to configure your databases independently. 45 | What you should do is create a file with the name `obremap.config.js` in the root folder of your application and add the following basic configuration. 46 | 47 | * It is important to add the `default` key to the database configuration, as this will be the main information that will be used for the connection 48 | 49 | ``` js 50 | module.exports = { 51 | databases: { 52 | default: {// IMPORTANT ADD "DEFAULT". THIS WILL BE THE MAIN CONNECTION INFORMATION 53 | host: "localhost", 54 | user: "user", 55 | password: "pass", 56 | database: "database_name", 57 | port: 3306, 58 | driver: "mysql" 59 | } 60 | } 61 | } 62 | ``` 63 | 64 | ### Configuration using URL's 65 | Commonly, database settings use multiple settings such as `host`,` database`, `username`,` password`, etc. Each of them corresponds to an environment variable. This means that when you configure your databases on your production server, you will have to handle many environment variables. 66 | 67 | Many of the database providers provide a single database connection "URL", which contains all the necessary information for the connection in a single string. An example of data-giving URLs looks very similar to this: 68 | 69 | ``` php 70 | mysql://root:password@127.0.0.1/forge?charset=UTF-8 71 | ``` 72 | 73 | These URLs usually follow a standard schema convention: 74 | 75 | ``` php 76 | driver://username:password@host:port/database?options 77 | ``` 78 | 79 | For your convenience, **OBREMAP Node ORM** supports these URLs as an alternative to configuring their databases with multiple configuration options. That is, if a database configuration URL is found (or its corresponding `DATABASE_URL` environment variable) it will be used to extract the database connection and credential information. 80 | 81 | ##### -> ENVIRONMENT VARIABLES 82 | ``` 83 | DATABASE_URL = mysql://root:password@127.0.0.1/database_name 84 | ``` 85 | 86 | ##### -> OBREMAP CONFIGURATION FILE 87 | The configuration is similar to the previous one. Only instead of saving an object within the default configuration, you should add the URL as a string. 88 | ``` js 89 | module.exports = { 90 | databases: { 91 | default: "mysql://root:password@127.0.0.1/database_name" 92 | } 93 | } 94 | ``` 95 | 96 | ### Configuration of multiple databases 97 | 98 | **OBREMAP Node ORM** provides the possibility of making multiple connections to multiple databases. You have no limit in terms of connections, you can configure all the connections you need, all the databases you require. 99 | The configuration is similar to what was seen previously. 100 | 101 | #### -> ENVIRONMENT VARIABLES 102 | 103 | To configure multiple connections using the environment variables **OBREMAP Node ORM** take all the variables with the prefix `DB_` and assign the following value as the configuration variable. So if you put `DB_HOST` this will be the main configuration variable (` default`) that contains the database hostname. However, if you put an identifier after the `DB_` prefix, it will be taken as the connection name, it must be followed by the name of the connection variable, example:` DB_LOCAL_HOST` in this case the connection name will be `local `and the connection variable will be` host`. 104 | 105 | ```json 106 | // MAIN CONNECTION 107 | DB_DRIVER = mysql 108 | DB_HOST = 127.0.0.1 109 | DB_USERNAME = user 110 | DB_PASSWORD = pass 111 | DB_NAME=database_name 112 | 113 | // SECONDARY CONNECTION [LOCAL] 114 | DB_LOCAL_DRIVER=mysql 115 | DB_LOCAL_HOST=localhost 116 | DB_LOCAL_USERNAME=loca_user 117 | DB_LOCAL_PASSWORD=local_pass 118 | DB_LOCAL_NAME=other_database 119 | 120 | ``` 121 | 122 | #### -> OBREMAP CONFIGURATION FILE 123 | 124 | Just as you can configure multiple connections to the database with a small modification in the environment variables, you can also do it using the **OBREMAP configuration file** the only thing you will have to do is add one more element to your object ` databases`, taking into account that the name you give to the new object will be the name of your connection. 125 | 126 | ``` js 127 | module.exports = { 128 | databases: { 129 | // MAIN CONNECTION 130 | default: "mysql://root:password@127.0.0.1/database_name", 131 | // SECONDARY CONNECTION [LOCAL] 132 | local: "mysql://loca_user:local_pass@localhost/other_database" 133 | } 134 | } 135 | ``` 136 | 137 | #### SELECT DATABASE 138 | 139 | Once you have configured your multiple databases, what you should do is indicate to the model which connection to use. And this is only done by adding a static method within your model. Remember that the name you put here is the name you gave your connection in the multiple database connection settings. 140 | 141 | ``` js 142 | import {Model} from 'node-obremap' 143 | 144 | export default class Chat extends Model { 145 | static connection = "local"; 146 | } 147 | ``` 148 | 149 | ### Create a Model 150 | 151 | To begin, we will create a **Obremap Model**. Models are usually found in the root folder inside the **MODELS** folder, however you can place them where you prefer, as long as you can access them. All **OBREMAP Models** must extend from the `Model` class within **node-obremap**. 152 | The easiest way to create your models is using the **Obremap CLI** with the `obremap make: model` function 153 | 154 | `chat.js` 155 | 156 | ``` js 157 | import {Model} from 'node-obremap' 158 | 159 | export default class Chat extends Model { 160 | /* 161 | overwrite table name, this is optional 162 | static tableName = 'dashboard_chats'; 163 | */ 164 | } 165 | 166 | ``` 167 | 168 | ### Using the Model 169 | 170 | Once you have created the **OBREMAP Model** (and associated the database table correctly), you are ready to get data from your databases. Think of OBREMAP Models as efficient query generators that will help you quickly make your queries to the database associated with your Models. 171 | Example: 172 | 173 | ```js 174 | import Chat from './chat' 175 | 176 | async function getChats { 177 | let chats = await Chat.all() 178 | console.log(chats) 179 | } 180 | ``` 181 | 182 | #### Supported methods 183 | 184 | - `.all()` Returns everything in the table 185 | - `.count()` Returns a numerical value corresponding to the total number of records extracted 186 | - `.create({field: 'value'})` Create a new record 187 | - `.delete(where || )` Unregister 188 | - `.find()` Find and return a relation currently only `id` since primary key is supported but dynamic primary key will be coming soon 189 | - `.first()` Returns the first record 190 | - `.join(tableName, , )` Create joins between tables 191 | - `.limit(5)` Limit the total results 192 | - `.offset(10)` Defines the start of the search 193 | - `.orderBy('fieldName' || {fieldName:" asc "})` Returns the ordered results 194 | - `.select('column', 'column2')` Defines the columns of the database to extract 195 | - `.update({field: 'value'}, where)` Update a record 196 | - `.where({fieldName: 'value'})` Returns the results that match the expression 197 | 198 | ### Query Building 199 | 200 | ```js 201 | 202 | Chat.all() 203 | 204 | Chunk.count(); 205 | 206 | Chat.where({ messages : "blah" }).count(); 207 | 208 | Chat.create({ user_id: 23 }) 209 | 210 | User.find(1) 211 | 212 | Chat.select('messages', 'id').where({ messages: 'blah' }).get() 213 | 214 | Chat.where({ messages: 'blah' }).get() 215 | 216 | Chat.select('messages').first() 217 | 218 | Chat.where({ messages: 'blah' }).limit(2).get() 219 | 220 | Chat.update({ name: 'Raymundo' }, { id : 1 }) 221 | 222 | Chat.delete(1) 223 | 224 | ``` 225 | 226 | ### Relationships 227 | 228 | This is a huge WIP, feel free to contribute :) 229 | 230 | Supported: 231 | - One To One 232 | - One To Many 233 | 234 | Todo: 235 | - Many To Many 236 | - Has Many Through 237 | - Polymorphic Relations 238 | - Many To Many Polymorphic Relations 239 | 240 | #### One to One Example 241 | 242 | ```js 243 | import { Model } from 'node-obremap' 244 | 245 | 246 | export default class User extends Model { 247 | 248 | } 249 | 250 | export default class Chat extends Model { 251 | user() { 252 | return this.hasOne(User) 253 | } 254 | } 255 | 256 | let chat = await Chat.first() 257 | 258 | //any relationship will return a promise with the result 259 | let user = await chat.user 260 | 261 | expect(user.name).to.be.equal('Bob') 262 | 263 | ``` 264 | 265 | #### One to Many Example 266 | 267 | ```js 268 | import { Model } from 'node-obremap' 269 | 270 | 271 | export default class User extends Model { 272 | chats() { 273 | return this.hasMany(Chat) 274 | } 275 | } 276 | 277 | export default class Chat extends Model { 278 | 279 | } 280 | 281 | let user = await User.first() 282 | 283 | //has many results return a query builder instance 284 | let chats = await user.chats.first() 285 | 286 | 287 | ``` 288 | 289 | ### CLI 290 | 291 | By installing **node-obremap globally** (`npm install @moccacoders/node-obremap -g`) you can access CLI methods to help create models, connections, etc. and use the different creation wizards. 292 | They are currently in **Spanish** and **English**. 293 | 294 | #### Methods 295 | - `obremap make:model [options]` Create a new Obremap Model Class. 296 | - `obremap make:connection [options]` Create a new Database Connection 297 | - `obremap make:migration [options]` Create a new migration file 298 | - `obremap migrate [options]` Execute all migrations 299 | - `obremap migrate:reset [options]` Rollback all database migrations 300 | - `obremap migrate:refresh [options]` Reset and re-run all migrations 301 | - `obremap migrate:rollback [options]` Rollback the last database migration 302 | - `obremap migrate:refresh [options]` Drop all tables and re-run all migrations 303 | - `obremap make:seeder [options]` Create a new seeder file 304 | - `obremap seed [options]` 305 | 306 | #### Models 307 | 308 | `obremap make:model` 309 | 310 | It will initialize the **Models** creation wizard which will guide you in creating your model, requesting the necessary information for it. Additionally, and only if necessary, it will display the connection creation wizard. 311 | Creates a file in your current directory `/models/user.js` with a default model 312 | 313 | #### Connections 314 | 315 | `obremap make:connection` 316 | 317 | It will initialize the **Connections** creation wizard which will ask you for the necessary information to create connections; It will also allow you to select between using the OBREMAP configuration file or your Environment Variables. 318 | 319 | #### Migrations 320 | 321 | `obremap make:migration` Create a migration file. 322 | 323 | 324 | #### Seeders 325 | `obremap make:seeder` Create a Seeder file. 326 | 327 | 328 | ### Everything 329 | - Add more Database drivers 330 | - CLI 331 | - Add more functions 332 | - Add more languages 333 | - Migrations 334 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env" 4 | ], 5 | "plugins" : [ 6 | "@babel/plugin-proposal-class-properties", 7 | [ 8 | "module-resolver", { 9 | "root": ["./modules"], 10 | "alias" : { 11 | "tests" : "./tests" 12 | } 13 | } 14 | ] 15 | 16 | ] 17 | } -------------------------------------------------------------------------------- /coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: ${{ secret.COVERALLS_REPO_TOKEN }} 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('jest').Config} */ 2 | const config = { 3 | testMatch: ["**/tests/**/*.test.js"], 4 | }; 5 | 6 | module.exports = config; 7 | -------------------------------------------------------------------------------- /modules/cli/index.mjs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | import fs from "fs"; 3 | import yargs from "yargs"; 4 | import chalk from "chalk"; 5 | import path from "path"; 6 | import { toCase, regex } from "../config/utils"; 7 | // import "core-js/stable"; 8 | // import "regenerator-runtime/runtime"; 9 | 10 | const argsToOptions = yargs 11 | .scriptName("obremap") 12 | .usage("Usage: $0 [args]") 13 | .command("make:model ", "Create a new Obremap Model Class", (yargs) => { 14 | yargs 15 | .usage("Usage: $0 make:model [args]") 16 | .positional("name", { 17 | describe: "Defines the name of the Obremap Model Class to create.", 18 | type: "string", 19 | }) 20 | .option({ 21 | a: { 22 | alias: "incrementing", 23 | type: "boolean", 24 | describe: 25 | "Set this option if your primary key field is auto incrementing.", 26 | }, 27 | c: { 28 | alias: "snake-case", 29 | type: "boolean", 30 | describe: 31 | "Set this option to use Snake Case mode (test_name). Default: Camel Case (CamelCase)", 32 | }, 33 | d: { 34 | alias: "driver", 35 | choices: ["mysql"], 36 | describe: "Select the driver you want to use.", 37 | default: "mysql", 38 | }, 39 | i: { 40 | alias: "created-at-name", 41 | type: "string", 42 | describe: "Defines the custom name of the 'created-at' field.", 43 | }, 44 | k: { 45 | alias: "key-type", 46 | type: "string", 47 | describe: "Defines the primary key field type.", 48 | }, 49 | n: { 50 | alias: "connection", 51 | type: "string", 52 | describe: "Defines the custom connection name.", 53 | }, 54 | p: { 55 | alias: "primary-key", 56 | type: "string", 57 | describe: "Defines the custom primary key field name.", 58 | }, 59 | s: { 60 | alias: "timestamps", 61 | type: "boolean", 62 | describe: 63 | "Set this option if you want to add created and updated dates.", 64 | }, 65 | t: { 66 | alias: "table-name", 67 | type: "string", 68 | describe: "Defines the custom table name.", 69 | }, 70 | u: { 71 | alias: "updated-at-name", 72 | type: "string", 73 | describe: "Defines the custom name of the 'updated-at' field.", 74 | }, 75 | w: { 76 | alias: "wizard", 77 | type: "boolean", 78 | describe: 79 | "Set this option if you want to use the Obremap Model Class Creation Wizard.", 80 | }, 81 | x: { 82 | alias: "date-format", 83 | type: "string", 84 | describe: 85 | "Define the format of your dates. See: https://momentjs.com/docs/#/displaying/format/.", 86 | }, 87 | }) 88 | .version(false) 89 | .help("h") 90 | .example( 91 | "$0 make:model ", 92 | '"Simple way to create an Obremap Model Class"' 93 | ) 94 | .example( 95 | "$0 make:model -w", 96 | '"Create a model with the Obremap Model Class Creation Wizard."' 97 | ); 98 | }) 99 | .command( 100 | "make:connection ", 101 | "Create a new Database Connection", 102 | (yargs) => { 103 | yargs 104 | .positional("connection-name", { 105 | describe: "Defines the name of the Obremap Model Class to create.", 106 | type: "string", 107 | default: "default", 108 | }) 109 | .option({ 110 | f: { 111 | alias: "config-file", 112 | describe: 113 | "Set this option to use the Obremap Config File (Recommended)", 114 | type: "boolean", 115 | default: true, 116 | }, 117 | x: { 118 | alias: "connection-url", 119 | describe: 120 | "Define you connection url (mysql://user:pass@hostname:port/database)", 121 | type: "string", 122 | }, 123 | d: { 124 | alias: "driver", 125 | choices: ["mysql"], 126 | describe: "Select the driver you want to use.", 127 | default: "mysql", 128 | }, 129 | i: { 130 | alias: "hostname", 131 | describe: "", 132 | type: "string", 133 | }, 134 | u: { 135 | alias: "username", 136 | describe: "", 137 | type: "string", 138 | }, 139 | p: { 140 | alias: "password", 141 | describe: "", 142 | type: "string", 143 | }, 144 | s: { 145 | alias: "database", 146 | describe: "", 147 | type: "string", 148 | }, 149 | t: { 150 | alias: "port", 151 | describe: "", 152 | type: "string", 153 | }, 154 | w: { 155 | alias: "wizard", 156 | type: "boolean", 157 | describe: 158 | "Set this option if you want to use the Obremap Model Class Creation Wizard.", 159 | }, 160 | }) 161 | .help("h") 162 | .version(false) 163 | .check((args, opt) => { 164 | if ( 165 | (args["connection-url"] && 166 | regex.url.test(args["connection-url"])) || 167 | (args["driver"] && 168 | args["hostname"] && 169 | args["username"] && 170 | args["password"] && 171 | args["database"]) || 172 | args["wizard"] 173 | ) 174 | return true; 175 | throw new Error("A connection url or database info is required."); 176 | }); 177 | } 178 | ) 179 | .command( 180 | "make:migration [options]", 181 | "Create a new migration file", 182 | (yargs) => { 183 | yargs 184 | .positional("name", { 185 | describe: "Defines the name of the Obremap Migration.", 186 | type: "string", 187 | }) 188 | .option({ 189 | t: { 190 | alias: "table-name", 191 | type: "string", 192 | describe: "Defines the table name.", 193 | }, 194 | d: { 195 | alias: "driver", 196 | choices: ["mysql"], 197 | describe: "Select the driver you want to use.", 198 | default: "mysql", 199 | }, 200 | m: { 201 | alias: "model", 202 | type: "boolean", 203 | describe: 204 | "Set this option if you want to create an Obremap Model Class.", 205 | }, 206 | fields: { 207 | type: "array", 208 | describe: "Defines the migrations fields.", 209 | }, 210 | s: { 211 | alias: "seeder", 212 | type: "boolean", 213 | describe: "Set this option if you want to create a Seeder Class.", 214 | }, 215 | }) 216 | .version(false) 217 | .help("h"); 218 | } 219 | ) 220 | .command( 221 | "make:seeder [options]", 222 | "Create a new migration file", 223 | (yargs) => { 224 | yargs 225 | .positional("name", { 226 | describe: "Defines the name of the Obremap Migration.", 227 | type: "string", 228 | }) 229 | .option({ 230 | t: { 231 | alias: "table-name", 232 | type: "string", 233 | describe: "Defines the table name.", 234 | }, 235 | }) 236 | .version(false) 237 | .help("h"); 238 | } 239 | ) 240 | .command("migrate", "Execute all migrations", (yargs) => { 241 | yargs 242 | .option({ 243 | p: { 244 | alias: "path", 245 | type: "array", 246 | describe: "The path(s) to the migrations files to be executed.", 247 | }, 248 | pretend: { 249 | type: "boolean", 250 | describe: "Dump the SQL queries that would be run.", 251 | }, 252 | }) 253 | .version(false) 254 | .help("h"); 255 | }) 256 | .command("migrate:reset", "Rollback all database migrations", (yargs) => { 257 | yargs.option({}).version(false).help("h"); 258 | }) 259 | .command("migrate:refresh", "Reset and re-run all migrations", (yargs) => { 260 | yargs.option({}).version(false).help("h"); 261 | }) 262 | .command( 263 | "migrate:rollback", 264 | "Rollback the last database migration", 265 | (yargs) => { 266 | yargs 267 | .option({ 268 | s: { 269 | alias: "step", 270 | type: "number", 271 | describe: "The number of migrations to be reverted", 272 | }, 273 | }) 274 | .version(false) 275 | .help("h"); 276 | } 277 | ) 278 | .command( 279 | "migrate:fresh", 280 | "Drop all tables and re-run all migrations", 281 | (yargs) => { 282 | let demand = process.env.NODE_ENV == "production" ? ["f", "force"] : []; 283 | yargs 284 | .option({ 285 | f: { 286 | alias: "force", 287 | type: "boolean", 288 | describe: "Force the operation to run when in production", 289 | }, 290 | s: { 291 | alias: "seed", 292 | type: "boolean", 293 | describe: "Indicates if the seed task should be re-run.", 294 | }, 295 | }) 296 | .version(false) 297 | .help("h"); 298 | } 299 | ) 300 | .command("seed", "Execute all migrations", (yargs) => { 301 | yargs.option({}).version(false).help("h"); 302 | }) 303 | .demandCommand(1, "You need at least one command before moving on.") 304 | .wrap(yargs.terminalWidth()) 305 | .alias("v", "version") 306 | .alias("h", "help") 307 | .option({ 308 | verbose: { 309 | type: "boolean", 310 | default: false, 311 | describe: "Set this to get complete error trace", 312 | }, 313 | o: { 314 | alias: "how-import", 315 | type: "string", 316 | describe: "How do you want to import the 'Node OBREMAP' module?", 317 | choices: ["import", "require"], 318 | }, 319 | folder: { 320 | type: "string", 321 | describe: "Defines the custom folder path.", 322 | }, 323 | }) 324 | .example("$0 make:model User", "- Use custom config") 325 | .strict().argv; 326 | 327 | const start = async (args) => { 328 | let newArgs = {}; 329 | let obremapConfig = null; 330 | let cwd = process.cwd(); 331 | let config = require("../config/index"); 332 | try { 333 | obremapConfig = require(path.join(cwd, "/obremap.config.js")); 334 | } catch (err) {} 335 | 336 | Object.entries(args).map((obj) => { 337 | if (obj[0].length > 2) 338 | newArgs[`--${toCase(obj[0], true).replace("_", "-")}`] = obj[1]; 339 | }); 340 | global.dev = newArgs["--verbose"]; 341 | const [cmd, type] = args._[0].split(":"); 342 | newArgs["_type"] = type; 343 | let command = await import(`./${cmd}/${type || "index.js"}`); 344 | let options = { 345 | args: newArgs, 346 | cwd, 347 | fs, 348 | obremapConfig, 349 | }; 350 | 351 | if (command.default) command = command.default; 352 | if (command.default) command = command.default; 353 | 354 | if (!newArgs["--how-import"] || newArgs["--how-import"] === null) 355 | newArgs["--how-import"] = 356 | obremapConfig && obremapConfig.howImport 357 | ? obremapConfig.howImport 358 | : config.howImport; 359 | 360 | try { 361 | command(options); 362 | } catch (err) { 363 | console.log(chalk.red("Error:"), err.message); 364 | if (global.dev) console.log(err); 365 | } 366 | }; 367 | 368 | start(argsToOptions); 369 | -------------------------------------------------------------------------------- /modules/cli/make/connection/index.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const inquirer = require("inquirer"); 3 | const path = require("path"); 4 | 5 | const lang = require("config/languages"); 6 | const questions = require("config/cli/questions.cli"); 7 | 8 | module.exports = ({ args, cwd, fs }) => { 9 | let answers; 10 | if (!args["--wizard"]) { 11 | delete args["_type"]; 12 | delete args["--config-file"]; 13 | args = { 14 | "__set-connection": true, 15 | "--config-file": true, 16 | connections: [args], 17 | }; 18 | return setConnetion(args, cwd); 19 | } 20 | if (!args["_createConnection"]) 21 | connectionConfig(args).then((ans) => { 22 | answers = ans; 23 | setConnetion(answers, cwd); 24 | }); 25 | else 26 | return new Promise((done) => { 27 | connectionConfig(args).then((ans) => { 28 | answers = ans; 29 | if (setConnetion(answers, cwd)) { 30 | done(answers); 31 | } 32 | }); 33 | }); 34 | }; 35 | 36 | const connectionConfig = (args) => { 37 | return new Promise((done, error) => { 38 | let answers = { ...args }; 39 | if (args["_createConnection"]) 40 | console.log( 41 | `\n------------------------------------------------------------------------\n${ 42 | lang[answers["_lang"]].connectionConfig 43 | }\n------------------------------------------------------------------------\n` 44 | ); 45 | inquirer 46 | .prompt([ 47 | ...questions(answers).general, 48 | ...questions(answers).configConnection, 49 | ]) 50 | .then((ans) => { 51 | if (ans["__url-config"] && !ans["__url"]) 52 | console.log( 53 | `\n------------------------------------------------------------------------\n${ 54 | lang[answers["_lang"]].wizard 55 | }\n------------------------------------------------------------------------\n` 56 | ); 57 | answers = { 58 | ...answers, 59 | ...ans, 60 | }; 61 | 62 | createConnection(answers).then((ans) => { 63 | done(ans); 64 | }); 65 | }); 66 | }); 67 | }; 68 | 69 | const createConnection = (answers) => { 70 | return new Promise((done, error) => { 71 | inquirer.prompt(questions(answers).connection).then((connection) => { 72 | answers = { 73 | ...answers, 74 | }; 75 | 76 | if (!answers["connections"]) answers["connections"] = []; 77 | answers["connections"].push(connection); 78 | 79 | if (connection["__moreConnections"] === true) { 80 | createConnection(answers).then((ans) => { 81 | done(ans); 82 | }); 83 | } else { 84 | done(answers); 85 | } 86 | }); 87 | }); 88 | }; 89 | 90 | const setConnetion = (args, cwd) => { 91 | if (!args["__set-connection"] || !args["connections"]) return true; 92 | if (!args["--config-file"]) { 93 | console.log( 94 | `\n------------------------------------------------------------------------\n\n${ 95 | lang[args["_lang"]].connectionEnv 96 | }\n${ 97 | lang[args["_lang"]].connectionEnvMsg 98 | }\n\n------------------------------------------------------------------------\n` 99 | ); 100 | 101 | args["connections"].map((connection) => { 102 | let dbName = 103 | connection["--connection-name"] != "default" 104 | ? `_${connection["--connection-name"].toUpperCase()}` 105 | : ""; 106 | console.log( 107 | `${ 108 | connection["__url-config"] 109 | ? `DB${dbName}_URL="${ 110 | connection["__url"] 111 | ? `${connection["--connection-url"]}` 112 | : `${connection["--driver"]}://${connection["--username"]}:${ 113 | connection["--password"] 114 | }@${connection["--hostname"]}${ 115 | connection["--port"] ? `:${connection["--port"]}` : `` 116 | }/${connection["--database"]}` 117 | }"` 118 | : `DB${dbName}_DRIVER="${connection["--driver"]}" 119 | DB${dbName}_HOST="${connection["--hostname"]}" 120 | DB${dbName}_USER="${connection["--username"]}" 121 | DB${dbName}_PASSWORD="${connection["--passwor"]}" 122 | DB${dbName}_DATABSE="${connection["--database"]}" 123 | DB${dbName}_PORT="${connection["--port"]}"` 124 | }` 125 | ); 126 | }); 127 | 128 | console.log( 129 | `\n------------------------------------------------------------------------\n` 130 | ); 131 | return true; 132 | } 133 | 134 | const configPath = path.join(cwd, "/obremap.config.js"); 135 | let config = { databases: {} }; 136 | let configFile = null; 137 | try { 138 | config = require(configPath); 139 | } catch (err) {} 140 | 141 | let databases = config && config.databases ? config.databases : {}; 142 | 143 | args["connections"].map((connection) => { 144 | args["--how-import"] = connection["--how-import"]; 145 | if (connection["__url-config"] && !connection["__url"]) 146 | connection["--connection-url"] = `${connection["--driver"]}://${ 147 | connection["--username"] 148 | }:${connection["--password"]}@${connection["--hostname"]}${ 149 | connection["--port"] ? `:${connection["--port"]}` : `` 150 | }/${connection["--database"]}`; 151 | 152 | databases[connection["--connection-name"]] = connection["--connection-url"] 153 | ? connection["--connection-url"] 154 | : { 155 | driver: connection["--driver"], 156 | host: connection["--hostname"], 157 | user: connection["--username"], 158 | password: connection["--password"], 159 | database: connection["--database"], 160 | port: connection["--port"], 161 | }; 162 | }); 163 | 164 | if (config.empty) 165 | config = { 166 | databases, 167 | }; 168 | 169 | const template = fs 170 | .readFileSync(path.join(__dirname, `/templates/config.template`), "utf8") 171 | .replace("#__CONFIGURATION__#", JSON.stringify(config, null, "\t")); 172 | 173 | try { 174 | fs.writeFileSync(configPath, template, "utf8"); 175 | console.log( 176 | `> `, 177 | lang[args["_lang"] || "english"].configFile[ 178 | configFile ? "updated" : "created" 179 | ], 180 | configPath 181 | ); 182 | } catch (err) { 183 | console.log(err); 184 | } 185 | return true; 186 | }; 187 | -------------------------------------------------------------------------------- /modules/cli/make/connection/templates/config.template: -------------------------------------------------------------------------------- 1 | // THIS CONFIG FILE WAS CREATED BY OBREMAP CLI 2 | module.exports = #__CONFIGURATION__# -------------------------------------------------------------------------------- /modules/cli/make/migration/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const moment = require("moment"); 3 | const path = require("path"); 4 | 5 | const config = require("config"); 6 | const { getTableName } = require("global/get-name"); 7 | const Model = require("cli/make/model"); 8 | const utils = require("config/utils"); 9 | const Seeder = require("cli/make/seeder"); 10 | 11 | module.exports = ({ args, cwd, fs, obremapConfig }) => { 12 | let name = args["--name"]; 13 | let type = ""; 14 | let fields = []; 15 | let column, 16 | from, 17 | to, 18 | match, 19 | moveType, 20 | tableName = null; 21 | if (!args["--folder"]) 22 | args["--folder"] = 23 | obremapConfig && obremapConfig.folders 24 | ? obremapConfig.folders.migrations 25 | : config.folders.migrations; 26 | if (!/^\//.test(args["--folder"])) args["--folder"] = `/${args["--folder"]}`; 27 | let folderPath = path.join(cwd, args["--folder"]); 28 | try { 29 | let access = fs.accessSync(folderPath, fs.F_OK); 30 | } catch (e) { 31 | fs.mkdirSync(folderPath, { recursive: true }); 32 | } 33 | 34 | let fileName = name; 35 | name = name.split("_"); 36 | if (["create", "add", "rename", "move"].includes(name[0])) { 37 | type = name[0]; 38 | delete name[0]; 39 | } 40 | 41 | if (args["--fields"] && args["--fields"].length > 0) { 42 | args["--fields"].map((field) => { 43 | let options = field.split(","); 44 | let opts = 1; 45 | field = {}; 46 | 47 | let names = ["type", "name"]; 48 | Object.entries(options).map((obj) => { 49 | let [ind, value] = obj; 50 | let modifiers; 51 | let modLen = !field["modifiers"] ? 0 : field["modifiers"].length; 52 | let name = names[ind] ? names[ind] : `opt${opts}`; 53 | if (value.search(/\./) >= 0) { 54 | [value, ...modifiers] = value.split(/\./); 55 | modifiers.map((modifier) => { 56 | let val; 57 | if (modifier.search(/^(.+)-(.+)/) >= 0) 58 | [match, modifier, val] = modifier.match(/(.+)-(.+)/); 59 | 60 | if (!field["modifiers"]) field["modifiers"] = []; 61 | if (!field["modifiers"][modLen]) field["modifiers"][modLen] = {}; 62 | field["modifiers"][modLen]["name"] = modifier; 63 | if (val) field["modifiers"][modLen]["value"] = val; 64 | modLen++; 65 | }); 66 | } 67 | field[name] = value; 68 | if (field[`opt${opts}`]) opts++; 69 | }); 70 | fields.push(field); 71 | }); 72 | } 73 | 74 | // create_[tableName]_table [CREATE NEW TABLE] 75 | // add_[column]_to_[tableName]_table [ADD A NEW COLUMN TO TABLE] 76 | // move_[column]_after_[existingColumn]_on_[tableName]_table [MOVE COLUMN POSITION ON TABLE] 77 | // rename_[column]_to_[columnTo]_on_[tableName]_table [RENAME COLUMN ON TABLE] 78 | // rename_[tableName]_to_[tableNameTo]_table [RENAME TABLE] 79 | 80 | if (name[name.length - 1] == "table") { 81 | if (["rename", "move"].includes(type)) { 82 | if (name.includes("on")) type += "-column"; 83 | else type += "-table"; 84 | } 85 | delete name[name.length - 1]; 86 | } 87 | 88 | name = name.filter((e) => e).join("_"); 89 | if (name.search(/(_to_|_after_)/) >= 0) { 90 | [match, from, moveType, to] = name.match(/(.+)(_to_|_after_)(.+)/); 91 | moveType = moveType.replaceAll(/_/g, ""); 92 | if (to.search(/_on_/) >= 0) { 93 | [match, tableName] = to.match(/_on_(.+)/); 94 | to = to.replace(/_on_(.+)/, ""); 95 | } 96 | 97 | if (type == "add") { 98 | column = from; 99 | name = to; 100 | } 101 | if (tableName) name = tableName; 102 | } 103 | 104 | const moduleName = 105 | process.env.NODE_ENV == "test" ? "../dist" : "@moccacoders/node-obremap"; 106 | tableName = args["--table-name"] || getTableName(name); 107 | const template = fs 108 | .readFileSync( 109 | path.join(__dirname, `templates/${args["--how-import"]}.template`), 110 | "utf8" 111 | ) 112 | .replaceAll("#__MODEL_NAME__#", utils.toCase(fileName, false, true)) 113 | .replaceAll("#__TABLE_NAME__#", "tableName") 114 | .replaceAll("#__MODULE_NAME__#", moduleName) 115 | .replace( 116 | "#__UP__#", 117 | processUp({ type, tableName, column, from, to, fields }) 118 | ) 119 | .replace( 120 | "#__DOWN__#", 121 | processDown({ type, tableName, column, from, to, fields }) 122 | ); 123 | 124 | let filePath = path.join( 125 | folderPath, 126 | `/${moment().format("YYYY_MM_DD_HHmmss")}_${utils.toCase( 127 | fileName 128 | )}.migration.js` 129 | ); 130 | try { 131 | fs.writeFileSync(filePath, template); 132 | console.log(chalk.green("Migration created: "), filePath); 133 | 134 | args["--name"] = name; 135 | if (args["--seeder"]) { 136 | delete args["--folder"]; 137 | Seeder({ args, cwd, fs, obremapConfig }); 138 | } 139 | 140 | if (args["--model"] === true) { 141 | args["_type"] = "model"; 142 | delete args["--folder"]; 143 | delete args["--model"]; 144 | delete args["--seeder"]; 145 | Model({ args, cwd, obremapConfig }); 146 | } 147 | } catch (error) {} 148 | }; 149 | 150 | const processUp = ({ type, tableName, column, from, to, fields }) => { 151 | let template = `//`; 152 | switch (type) { 153 | case "create": 154 | template = `Schema.create('${tableName}', table => {${ 155 | fields.length > 0 156 | ? `${processFields(fields)} 157 | ` 158 | : ` 159 | table.id(); 160 | table.string('name'); 161 | table.timestamps(); 162 | ` 163 | }})`; 164 | break; 165 | 166 | case "add": 167 | template = `Schema.table('${tableName}', table => { 168 | table.string('${column}'); 169 | })`; 170 | break; 171 | 172 | case "rename-table": 173 | template = `Schema.rename('${from}', '${to}')`; 174 | break; 175 | 176 | case "rename-column": 177 | template = `Schema.table('${tableName}', table => { 178 | table.renameColumn('string', '${from}', '${to}'); 179 | })`; 180 | break; 181 | 182 | case "move-column": 183 | if (from && to) 184 | template = `Schema.table('${tableName}', table => { 185 | table.moveColumn('string', '${from}', '${to}'); 186 | })`; 187 | break; 188 | } 189 | return template; 190 | }; 191 | 192 | const processDown = ({ type, tableName, column, from, to, fields }) => { 193 | let template = `//`; 194 | switch (type) { 195 | case "create": 196 | template = `Schema.dropIfExists('${tableName}')`; 197 | break; 198 | 199 | case "add": 200 | template = `Schema.table('${tableName}', table => { 201 | table.dropColumn('${column}'); 202 | })`; 203 | break; 204 | 205 | case "rename-table": 206 | template = `Schema.rename('${to}', '${from}')`; 207 | break; 208 | 209 | case "rename-column": 210 | template = `Schema.table('${tableName}', table => { 211 | table.renameColumn('string', '${to}', '${from}'); 212 | })`; 213 | break; 214 | case "move-column": 215 | if (from && to) { 216 | template = `Schema.table('${tableName}', table => { 217 | // Replace #__EXISTING_COLUMN_NAME__# for correct column name to roll back 218 | table.moveColumn('string', '${to}', '#__EXISTING_COLUMN_NAME__#'); 219 | })`; 220 | } 221 | break; 222 | } 223 | 224 | return template; 225 | }; 226 | 227 | const processFields = (fields) => { 228 | let str = ``; 229 | fields.map((field) => { 230 | let { type, name, modifiers, ...opts } = field; 231 | str += ` 232 | table.${type}(${name ? `'${name}'` : ""}${ 233 | Object.entries(opts).length > 0 234 | ? Object.entries(opts) 235 | .map((opt, ind) => { 236 | return `${ind == 0 ? ", " : ""}${ 237 | !isNaN(opt[1]) ? opt[1] : `'${opt[1]}'` 238 | }`; 239 | }) 240 | .join(", ") 241 | : "" 242 | })${ 243 | modifiers 244 | ? modifiers 245 | .map((mod, ind) => { 246 | return `${ind == 0 ? "." : ""}${mod.name}(${ 247 | mod.value 248 | ? `${ 249 | !isNaN(mod.value) || 250 | mod.value == "true" || 251 | mod.value == "false" 252 | ? mod.value 253 | : `'${mod.value}'` 254 | }` 255 | : "" 256 | })`; 257 | }) 258 | .join(".") 259 | : "" 260 | };`; 261 | }); 262 | return str; 263 | }; 264 | -------------------------------------------------------------------------------- /modules/cli/make/migration/templates/import.template: -------------------------------------------------------------------------------- 1 | // THIS MIGRATION FILE WAS CREATED BY OBREMAP CLI 2 | import { Schema } from "#__MODULE_NAME__#"; 3 | /* 4 | Model Name: `#__MODEL_NAME__#` 5 | Database Table: `#__TABLE_NAME__#` 6 | */ 7 | 8 | export default class #__MODEL_NAME__# { 9 | /** 10 | * Run the migrations. 11 | * @return void 12 | */ 13 | static up() { 14 | return #__UP__# 15 | } 16 | 17 | /** 18 | * Reverse the migrations. 19 | * @return void 20 | */ 21 | static down() { 22 | return #__DOWN__# 23 | } 24 | } -------------------------------------------------------------------------------- /modules/cli/make/migration/templates/require.template: -------------------------------------------------------------------------------- 1 | // THIS MIGRATION FILE WAS CREATED BY OBREMAP CLI 2 | const { Schema } = require("#__MODULE_NAME__#") 3 | /* 4 | Model Name: `#__MODEL_NAME__#` 5 | Database Table: `#__TABLE_NAME__#` 6 | */ 7 | 8 | module.exports = class #__MODEL_NAME__# { 9 | /** 10 | * Run the migrations. 11 | * @return void 12 | */ 13 | static up() { 14 | return #__UP__# 15 | } 16 | 17 | /** 18 | * Reverse the migrations. 19 | * @return void 20 | */ 21 | static down() { 22 | return #__DOWN__# 23 | } 24 | } -------------------------------------------------------------------------------- /modules/cli/make/model/index.js: -------------------------------------------------------------------------------- 1 | const inquirer = require("inquirer"); 2 | const pluralize = require("pluralize"); 3 | const path = require("path"); 4 | const fs = require("fs"); 5 | 6 | const config = require("config"); 7 | const connection = require("cli/make/connection"); 8 | const lang = require("config/languages"); 9 | const questions = require("config/cli/questions.cli"); 10 | const utils = require("config/utils"); 11 | const chalk = require("chalk"); 12 | 13 | /** 14 | * @typedef {Object} ConfigurationData 15 | * @property {object} args - User-captured arguments 16 | * @property {string} cwd - Full path of the project 17 | * @property {object} obremapConfig - Obrempa configuration object 18 | */ 19 | 20 | /** 21 | * Create obremap model with options that users entered 22 | * @param {ConfigurationData} confgiData me as you like 23 | */ 24 | module.exports = ({ args, cwd, obremapConfig }) => { 25 | configuration(args).then((args) => { 26 | let modelName = utils.toCase(args["--name"], false, true); 27 | let options = ""; 28 | Object.entries(args).map((arg, ind) => { 29 | let [key, val] = arg; 30 | if ( 31 | /^_(.+)?/.test(key) || 32 | [ 33 | "--name", 34 | "--driver", 35 | "--how-import", 36 | "--wizard", 37 | "--folder", 38 | "--verbose", 39 | ].includes(key) 40 | ) 41 | return true; 42 | key = utils.toCase(key.replace("--", "").replace("-", "_"), false); 43 | if ( 44 | val == config.default[args["--driver"]][key] || 45 | val == 46 | utils.toCase( 47 | pluralize(args["--name"] || "default"), 48 | args["--snake-case"] 49 | ) 50 | ) 51 | return true; 52 | options += `static ${key} = ${typeof val == "string" ? `'${val}'` : val}; 53 | `; 54 | }); 55 | 56 | const moduleName = 57 | process.env.NODE_ENV == "test" ? "../dist" : "@moccacoders/node-obremap"; 58 | const template = fs 59 | .readFileSync( 60 | path.join(__dirname, `/templates/${args["--how-import"]}.template`), 61 | "utf8" 62 | ) 63 | .replace("#__MODEL_NAME__#", modelName) 64 | .replace("#__MODULE_NAME__#", moduleName) 65 | .replace("#__CONFIGURATION__#", options); 66 | 67 | if (!args["--folder"]) 68 | args["--folder"] = 69 | obremapConfig && obremapConfig.folders 70 | ? obremapConfig.folders.models 71 | : config.folders.models; 72 | if (!/^\//.test(args["--folder"])) 73 | args["--folder"] = `/${args["--folder"]}`; 74 | try { 75 | fs.accessSync(`${cwd}${args["--folder"]}`, fs.F_OK); 76 | } catch (e) { 77 | fs.mkdirSync(`${cwd}${args["--folder"]}`, { recursive: true }); 78 | } 79 | 80 | let filePath = `${cwd}${args["--folder"]}/${utils.toCase( 81 | args["--name"] 82 | )}.model.js`; 83 | fs.writeFile(filePath, template, (err) => { 84 | if (err) throw err; 85 | console.log(chalk.green("Model created: "), filePath); 86 | }); 87 | }); 88 | }; 89 | 90 | /** 91 | * Validate if user select wizard configuration or doesn't. If user doesn't select wizard configuration only done with args. 92 | * @param args - Receive all arguments of CLI 93 | * @returns {Promise} Promise object represents all args if user doesn't select wizard configuration or start the wizard configuration mode and return all user answers. 94 | */ 95 | const configuration = (args) => { 96 | return new Promise((done, error) => { 97 | if (!args["--wizard"]) return done(args); 98 | let answers = { ...args }; 99 | let errorConnection = false; 100 | 101 | inquirer 102 | .prompt([...questions(answers).general, ...questions(answers).model]) 103 | .then((ans) => { 104 | if (ans["--snake-case"]) 105 | ans["--snake-case"] = 106 | ans["--snake-case"] == "snake_case" ? true : false; 107 | if (ans["--name"]) 108 | ans["--name"] = utils.toCase(ans["--name"], false, true); 109 | if (ans["--table-name"]) 110 | ans["--table-name"] = utils.toCase( 111 | ans["--table-name"], 112 | answers["--snake-case"] 113 | ); 114 | if (ans["--connection"] != "default") { 115 | if ( 116 | !process.env[`DB_${ans["--connection"].toUpperCase()}_HOST`] && 117 | !process.env[`DB_${ans["--connection"].toUpperCase()}_URL`] 118 | ) 119 | errorConnection = true; 120 | else errorConnection = false; 121 | 122 | try { 123 | if (!obremapConfig.databases[ans["--connection"]]) 124 | errorConnection = true; 125 | else errorConnection = false; 126 | } catch (err) { 127 | errorConnection = true; 128 | } 129 | } 130 | 131 | answers = { 132 | ...answers, 133 | ...ans, 134 | }; 135 | if (errorConnection) { 136 | answers["_createConnection"] = true; 137 | connection({ args: answers }).then((answers) => { 138 | done(answers); 139 | }); 140 | } else done(answers); 141 | }) 142 | .catch((err) => console.log(err)); 143 | }); 144 | }; 145 | -------------------------------------------------------------------------------- /modules/cli/make/model/templates/import.template: -------------------------------------------------------------------------------- 1 | // THIS MODEL FILE WAS CREATED BY OBREMAP CLI 2 | import { Model } from "#__MODULE_NAME__#"; 3 | class #__MODEL_NAME__# extends Model { 4 | /* 5 | overwrite table name, this action is optional 6 | static tableName = "table_name"; 7 | */ 8 | 9 | #__CONFIGURATION__# 10 | } 11 | export default new #__MODEL_NAME__#(); -------------------------------------------------------------------------------- /modules/cli/make/model/templates/require.template: -------------------------------------------------------------------------------- 1 | // THIS MODEL FILE WAS CREATED BY OBREMAP CLI 2 | const { Model } = require("#__MODULE_NAME__#"); 3 | class #__MODEL_NAME__# extends Model { 4 | /* 5 | overwrite table name, this action is optional 6 | static tableName = "table_name"; 7 | */ 8 | 9 | #__CONFIGURATION__# 10 | } 11 | module.exports = new #__MODEL_NAME__#(); -------------------------------------------------------------------------------- /modules/cli/make/seeder/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const path = require("path"); 3 | 4 | const config = require("config"); 5 | const { getTableName } = require("global/get-name"); 6 | const model = require("cli/make/model"); 7 | const utils = require("config/utils"); 8 | 9 | module.exports = ({ args, cwd, fs, obremapConfig }) => { 10 | let name = args["--name"].replace( 11 | /_?(table|create|add|rename-table|rename-column)_?/g, 12 | "" 13 | ); 14 | let fields = []; 15 | if (!args["--folder"]) 16 | args["--folder"] = 17 | obremapConfig && obremapConfig.folders 18 | ? obremapConfig.folders.seeders 19 | : config.folders.seeders; 20 | if (!/^\//.test(args["--folder"])) args["--folder"] = `/${args["--folder"]}`; 21 | let folderPath = path.join(cwd, args["--folder"]); 22 | const fileName = utils.toCase(name.replace(/(_|\-|\.)?seeder/i, "")); 23 | const filePath = path.join(folderPath, `/${fileName}.seeder.js`); 24 | 25 | if (args["--fields"] && args["--fields"].length > 0) { 26 | args["--fields"].map((field) => { 27 | let options = field.split(","); 28 | let opts = 1; 29 | field = {}; 30 | 31 | let names = ["type", "name"]; 32 | Object.entries(options).map((obj) => { 33 | let [ind, value] = obj; 34 | let modifiers; 35 | let modLen = !field["modifiers"] ? 0 : field["modifiers"].length; 36 | let name = names[ind] ? names[ind] : `opt${opts}`; 37 | if (value.search(/\./) >= 0) { 38 | [value, ...modifiers] = value.split(/\./); 39 | modifiers.map((modifier) => { 40 | let val, match; 41 | if (modifier.search(/(.+)-(.+)/) >= 0) 42 | [match, modifier, val] = modifier.match(/(.+)-(.+)/); 43 | 44 | if (!field["modifiers"]) field["modifiers"] = []; 45 | if (!field["modifiers"][modLen]) field["modifiers"][modLen] = {}; 46 | field["modifiers"][modLen]["name"] = modifier; 47 | if (val) field["modifiers"][modLen]["value"] = val; 48 | modLen++; 49 | }); 50 | } 51 | field[name] = value; 52 | if (field[`opt${opts}`]) opts++; 53 | }); 54 | fields.push(field); 55 | }); 56 | } 57 | 58 | try { 59 | if (fs.existsSync(filePath)) { 60 | console.log( 61 | chalk.yellow("Warning."), 62 | `Seeder ('${fileName}.seeder.js') already exists.`, 63 | chalk.bold("Seeder not created.") 64 | ); 65 | return false; 66 | } 67 | } catch (e) {} 68 | 69 | if (!createSeederContainer({ args, cwd, fs })) return false; 70 | 71 | const moduleName = 72 | process.env.NODE_ENV == "test" ? "../dist" : "@moccacoders/node-obremap"; 73 | const className = utils.toCase(name, false, true); 74 | const tableName = 75 | args["--table-name"] || getTableName(name.replace(/(_|\-|\.)?seeder/i, "")); 76 | const template = fs 77 | .readFileSync( 78 | path.join(__dirname, `templates/seeder/${args["--how-import"]}.template`), 79 | "utf8" 80 | ) 81 | .replaceAll("#__MODULE_NAME__#", moduleName) 82 | .replaceAll("#__SEEDER_NAME__#", className) 83 | .replaceAll("#__TABLE_NAME__#", tableName) 84 | .replaceAll( 85 | "#__TIMESTAMPS__#", 86 | fields.filter((field) => field.type.search("timestamp") >= 0).length > 0 87 | ) 88 | .replaceAll( 89 | "#__FIELDS__#", 90 | fields.length > 0 91 | ? processFields(fields) 92 | : `name : 'John', 93 | last_name : 'Doe', 94 | email : 'john.doe@example.com'` 95 | ); 96 | 97 | fs.writeFile(filePath, template, (err) => { 98 | if (err) throw err; 99 | console.log(chalk.green("Seeder created: "), filePath); 100 | }); 101 | 102 | if (args["--model"] === true) { 103 | model({ args, cwd, fs }); 104 | } 105 | }; 106 | 107 | const createSeederContainer = ({ args, cwd, fs }) => { 108 | let folderPath = path.join(cwd, args["--folder"]); 109 | let containerPath = path.join(folderPath, "/container.seeder.js"); 110 | let name = args["--name"]; 111 | let fileName = utils.toCase(name.replace(/seeder/i, "")); 112 | let className = [utils.toCase(name, false, true)]; 113 | let importClass = [ 114 | `${ 115 | args["--how-import"] == "import" 116 | ? `import ${className[0]} from './${fileName}.seeder'` 117 | : `const ${className[0]} = require('./${fileName}.seeder')` 118 | };`, 119 | ]; 120 | 121 | try { 122 | try { 123 | fs.accessSync(folderPath, fs.F_OK); 124 | } catch (e) { 125 | fs.mkdirSync(folderPath, { recursive: true }); 126 | } 127 | 128 | if (fs.existsSync(containerPath)) { 129 | const container = fs.readFileSync(containerPath, "utf8"); 130 | [ 131 | ...container.matchAll( 132 | /import ((?!\{(\s+)?Seeder(\s+)?\}).+) from (('|")(.+)('|"))|(const|var|let) ((?!\{(\s+)?Seeder(\s+)?\}).+) = require(\s+)?\((('|")(.+)('|")\));?/gi 133 | ), 134 | ].map((match) => { 135 | if (!importClass.find((ele) => ele === match[0])) 136 | importClass.push(match[0]); 137 | if (!className.find((ele) => ele === match[1])) 138 | className.push(match[1]); 139 | return { import: match[0], name: match[1] }; 140 | }); 141 | } 142 | const moduleName = 143 | process.env.NODE_ENV == "test" ? "../dist" : "@moccacoders/node-obremap"; 144 | const template = fs 145 | .readFileSync( 146 | path.join( 147 | __dirname, 148 | `templates/container/${args["--how-import"]}.template` 149 | ), 150 | "utf8" 151 | ) 152 | .replace("#__IMPORTS__#", importClass.sort().join("\r")) 153 | .replaceAll("#__MODULE_NAME__#", moduleName) 154 | .replace("#__CALLS__#", className.sort().join(",\r\t\t\t")); 155 | fs.writeFileSync(containerPath, template, { recursive: true }); 156 | console.log(chalk.green("Container created:"), containerPath); 157 | return true; 158 | } catch (err) { 159 | console.log(chalk.yellow("Error: "), err.message); 160 | return false; 161 | } 162 | }; 163 | 164 | const processFields = (fields) => { 165 | let str = ``; 166 | fields.map((field) => { 167 | let { type, name, modifiers, ...opts } = field; 168 | if (name) 169 | str += ` 170 | ${name} : ${type.search(/(id|int)/) >= 0 ? 0 : `''`}`; 171 | }); 172 | return str; 173 | }; 174 | -------------------------------------------------------------------------------- /modules/cli/make/seeder/templates/container/import.template: -------------------------------------------------------------------------------- 1 | // THIS SEED CONTAINER FILE WAS CREATED BY OBREMAP CLI 2 | import { Seeder } from "#__MODULE_NAME__#"; 3 | #__IMPORTS__# 4 | 5 | export default class SeedContainer extends Seeder { 6 | /** 7 | * Set seed container. 8 | * @return void 9 | */ 10 | static run() { 11 | this.call([ 12 | #__CALLS__# 13 | ]) 14 | } 15 | } -------------------------------------------------------------------------------- /modules/cli/make/seeder/templates/container/require.template: -------------------------------------------------------------------------------- 1 | // THIS SEED CONTAINER FILE WAS CREATED BY OBREMAP CLI 2 | const { Seeder } = require("#__MODULE_NAME__#"); 3 | #__IMPORTS__# 4 | 5 | module.exports = class SeedContainer extends Seeder { 6 | /** 7 | * Set seed container. 8 | * @return void 9 | */ 10 | static run() { 11 | this.call([ 12 | #__CALLS__# 13 | ]) 14 | } 15 | } -------------------------------------------------------------------------------- /modules/cli/make/seeder/templates/seeder/import.template: -------------------------------------------------------------------------------- 1 | // THIS SEEDER FILE WAS CREATED BY OBREMAP CLI 2 | import { Seeder, DB } from "#__MODULE_NAME__#"; 3 | export default class #__SEEDER_NAME__# extends Seeder{ 4 | /** 5 | * Run seeder #__SEEDER_NAME__#. 6 | * @return void 7 | */ 8 | static run() { 9 | /* 10 | DB.table('#__TABLE_NAME__#').truncate(); 11 | return DB.table('#__TABLE_NAME__#') 12 | .setTimestamps(#__TIMESTAMPS__#) 13 | .set([ 14 | { 15 | #__FIELDS__# 16 | } 17 | ]) 18 | .create(); 19 | */ 20 | } 21 | } -------------------------------------------------------------------------------- /modules/cli/make/seeder/templates/seeder/require.template: -------------------------------------------------------------------------------- 1 | // THIS SEEDER FILE WAS CREATED BY OBREMAP CLI 2 | const { Seeder, DB } = require("#__MODULE_NAME__#"); 3 | module.exports = class #__SEEDER_NAME__# extends Seeder{ 4 | /** 5 | * Run seeder #__SEEDER_NAME__#. 6 | * @return void 7 | */ 8 | static run() { 9 | /* 10 | DB.table('#__TABLE_NAME__#').truncate(); 11 | return DB.table('#__TABLE_NAME__#') 12 | .setTimestamps(#__TIMESTAMPS__#) 13 | .set([ 14 | { 15 | #__FIELDS__# 16 | } 17 | ]) 18 | .insert(); 19 | */ 20 | } 21 | } -------------------------------------------------------------------------------- /modules/cli/migrate/fresh/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const path = require("path"); 3 | const moment = require("moment"); 4 | const inquirer = require("inquirer"); 5 | const Migrate = require("./index"); 6 | const Seed = require("../seed/index"); 7 | const { DB, Schema } = require("../../index"); 8 | const config = require("../../config"); 9 | const questions = require("../../config/cli/questions.cli.js"); 10 | const { emit } = require("process"); 11 | 12 | exports.default = ({ args, cwd, fs, obremapConfig }) => { 13 | if (!args["--force"] && process.env.NODE_ENV == "production") 14 | inquirer 15 | .prompt(questions(args).migrations) 16 | .then((args) => { 17 | if (args["--force"] == true) { 18 | exports.start({ args, cwd, fs, obremapConfig }); 19 | } else { 20 | return console.log( 21 | "Fresh process canceled by user", 22 | chalk.green(`successfully`) 23 | ); 24 | } 25 | }) 26 | .catch((err) => console.log(chalk.red("Error:"), err.message)); 27 | else exports.start({ args, cwd, fs, obremapConfig }); 28 | }; 29 | 30 | exports.start = async ({ args, cwd, fs, obremapConfig }) => { 31 | let dropTables = await exports.dropTables(obremapConfig); 32 | if (dropTables) { 33 | await Migrate.default({ 34 | args, 35 | cwd, 36 | fs, 37 | obremapConfig, 38 | exit: !args["--seed"], 39 | }); 40 | if (args["--seed"]) { 41 | delete args["--folder"]; 42 | Seed.default({ args, cwd, fs, obremapConfig }); 43 | } 44 | } 45 | }; 46 | 47 | exports.dropTables = async (obremapConfig) => { 48 | let errors = []; 49 | let connection = DB.connection != "default" ? DB.connection : "default"; 50 | let database = null; 51 | if (obremapConfig && obremapConfig.databases) { 52 | database = obremapConfig.databases[DB.connection]; 53 | if (database.database) database = database.database; 54 | else { 55 | database = new URL(database); 56 | database = database.pathname.slice(1); 57 | } 58 | } else if ( 59 | process.env[`DATABASE_URL_${connection}`] || 60 | process.env[`DATABASE_URL`] 61 | ) { 62 | database = new URL( 63 | process.env[`DATABASE_URL_${connection}`] || process.env[`DATABASE_URL`] 64 | ); 65 | database = database.pathname.slice(1); 66 | } else if (process.env[`DB_${connection}_NAME`]) { 67 | database = process.env[`DB_${connection}_NAME`]; 68 | } 69 | 70 | let tables = await DB.table("information_schema.tables") 71 | .select("table_name") 72 | .where("table_schema", database) 73 | .get(); 74 | 75 | tables = tables.map((table) => 76 | table.TABLE_NAME ? table.TABLE_NAME : table.table_name 77 | ); 78 | 79 | if (tables.length == 0) { 80 | console.log(chalk.yellow("There are no tables to drop")); 81 | return true; 82 | } 83 | 84 | for (const table of tables) { 85 | try { 86 | let drop = await Schema.dropIfExists(table); 87 | if (drop.constructor.name === "OkPacket") 88 | tables = tables.filter((ele) => ele != table); 89 | } catch (err) { 90 | errors.push(err); 91 | } 92 | } 93 | 94 | if (errors.length > 0) throw errors; 95 | if (tables.length === 0) { 96 | console.log("Droped all tables", chalk.green("successfully.")); 97 | return true; 98 | } else return false; 99 | }; 100 | -------------------------------------------------------------------------------- /modules/cli/migrate/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const path = require("path"); 3 | 4 | const config = require("config"); 5 | const { DB, Schema } = require("/"); 6 | const { microtime } = require("config/utils.js"); 7 | 8 | exports.default = async ({ args, cwd, fs, exit = true, obremapConfig }) => { 9 | if (!args["--folder"]) 10 | args["--folder"] = 11 | obremapConfig && obremapConfig.folders 12 | ? obremapConfig.folders.migrations 13 | : config.folders.migrations; 14 | try { 15 | let files = fs.readdirSync(path.join(cwd, args["--folder"])).sort(); 16 | if (args["--path"]) files = args["--path"]; 17 | let batch = 0; 18 | 19 | const migrations = await exports.migrations({ files, obremapConfig }); 20 | files = migrations.files; 21 | batch = migrations.batch; 22 | if (files.length == 0) { 23 | console.log(chalk.green(`Nothing to migrate on "${args["--folder"]}"`)); 24 | } 25 | 26 | for (const file of files) { 27 | console.log(chalk.yellow("Migrating:"), file); 28 | let startTime = microtime(true); 29 | let filePath = path.join( 30 | cwd, 31 | file.search(args["--folder"]) >= 0 ? "" : args["--folder"], 32 | file 33 | ); 34 | 35 | let Migration = require(filePath); 36 | Migration = await Migration.up(); 37 | 38 | const insert = await DB.table("migrations") 39 | .setTimestamps(false) 40 | .set("migration", file) 41 | .set("batch", batch + 1) 42 | .insert(); 43 | 44 | let runTime = parseFloat(microtime(true) - startTime).toFixed(2); 45 | console.log(chalk.green("Migrated:"), file, `(${runTime} seconds)`); 46 | } 47 | } catch (err) { 48 | if (err.message.search("no such file or directory") >= 0) 49 | return console.log( 50 | chalk.green(`Nothing to migrate on "${args["--folder"]}"`) 51 | ); 52 | 53 | console.log(chalk.red("Errores: "), err); 54 | if (global.dev) console.log(err); 55 | } 56 | }; 57 | 58 | const migrations = async ({ 59 | files, 60 | reset = false, 61 | batch = 0, 62 | step = 0, 63 | obremapConfig, 64 | }) => { 65 | let connection = DB.connection != "default" ? DB.connection : ""; 66 | let database = null; 67 | if (!files) files = []; 68 | if (obremapConfig && obremapConfig.databases) { 69 | database = obremapConfig.databases[DB.connection]; 70 | if (database.database) database = database.database; 71 | else { 72 | database = new URL(database); 73 | database = database.pathname.slice(1); 74 | } 75 | } else if (process.env[`DATABASE_URL_${connection}`]) { 76 | database = new URL(process.env[`DATABASE_URL_${connection}`]); 77 | database = database.pathname.slice(1); 78 | } else if (process.env[`DB_${connection}_NAME`]) { 79 | database = process.env[`DB_${connection}_NAME`]; 80 | } 81 | 82 | let migrationTable = await DB.table("information_schema.tables") 83 | .select("table_name") 84 | .where("table_schema", database) 85 | .where("table_name", "migrations") 86 | .exists(); 87 | 88 | if (!migrationTable) { 89 | try { 90 | await Schema.create("migrations", (table) => { 91 | table.id(); 92 | table.string("migration", 255); 93 | table.integer("batch", 11); 94 | }); 95 | console.log("Migration table created", chalk.green("successfully.")); 96 | } catch (err) { 97 | console.log("There was an error migrating.", err.message); 98 | } 99 | } else { 100 | console.log("Migration table already", chalk.green("exists.")); 101 | } 102 | 103 | let all = DB.table("migrations"); 104 | if (batch > 0) all = all.where({ batch }); 105 | if (step > 0) all = all.limit(step); 106 | files.map((file, ind) => { 107 | if (ind == 0) all = all.where(`migration`, file); 108 | else all = all.orWhere(`migration`, file); 109 | }); 110 | if (reset) all = all.orderBy("id", "desc"); 111 | all = await all.get(); 112 | 113 | if (!reset) { 114 | if (all.length > 0) 115 | files = files.filter((ele) => !all.map((e) => e.migration).includes(ele)); 116 | batch = 117 | all.length > 0 118 | ? Math.max.apply( 119 | 0, 120 | all.map((ele) => ele.batch) 121 | ) 122 | : 0; 123 | } else files = all; 124 | return { files: files.filter((i) => i), batch }; 125 | }; 126 | 127 | exports.migrations = migrations; 128 | -------------------------------------------------------------------------------- /modules/cli/migrate/refresh/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const Migrate = require("cli/migrate"); 3 | const Reset = require("cli/migrate/reset"); 4 | 5 | exports.default = async ({ args, cwd, fs, obremapConfig }) => { 6 | console.log(chalk.bold("==== ROLLING BACK MIGRATIONS ====")); 7 | await Reset.default({ args, cwd, fs, obremapConfig, exit: false }); 8 | 9 | console.log(chalk.bold("==== RUN MIGRATIONS ====")); 10 | await Migrate.default({ args, cwd, fs, obremapConfig }); 11 | }; 12 | -------------------------------------------------------------------------------- /modules/cli/migrate/reset/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const path = require("path"); 3 | const moment = require("moment"); 4 | 5 | const config = require("config"); 6 | const { DB } = require("/"); 7 | const Migrate = require("cli/migrate"); 8 | 9 | exports.default = async ({ args, cwd, fs, exit = true, obremapConfig }) => { 10 | if (!args["--folder"]) 11 | args["--folder"] = 12 | obremapConfig && obremapConfig.folders 13 | ? obremapConfig.folders.migrations 14 | : config.folders.migrations; 15 | if (!args["--batch"]) args["--batch"] = 0; 16 | if (!args["--step"]) args["--step"] = 0; 17 | try { 18 | let { files, batch } = await Migrate.migrations({ 19 | files: [], 20 | reset: true, 21 | batch: args["--batch"], 22 | step: args["--step"], 23 | obremapConfig, 24 | }); 25 | if (files.length == 0) { 26 | console.log( 27 | chalk.green(`Nothing to Rolling Back on "${args["--folder"]}"`) 28 | ); 29 | } 30 | 31 | for (const file of files) { 32 | try { 33 | let filePath = path.join( 34 | cwd, 35 | file.migration.search(args["--folder"]) >= 0 ? "" : args["--folder"], 36 | file.migration 37 | ); 38 | console.log(chalk.yellow("Rolling Back:"), file.migration); 39 | let start = moment(new Date()); 40 | 41 | let Migration = require(filePath); 42 | Migration = await Migration.down(); 43 | 44 | const deleted = await DB.table("migrations") 45 | .where({ id: file.id }) 46 | .delete(); 47 | let end = moment(new Date()); 48 | let diff = end.diff(start, "seconds"); 49 | 50 | console.log( 51 | chalk.green("Rolled Back:"), 52 | file.migration, 53 | `(${diff} seconds)` 54 | ); 55 | } catch (err) { 56 | if (err.message.search("Cannot find module") >= 0) 57 | console.log(chalk.red("Migration not found:"), file.migration); 58 | else console.log(chalk.red("Error:"), err.message); 59 | if (global.dev) console.log(err); 60 | } 61 | } 62 | } catch (err) { 63 | if (err.message.search("no such file or directory") >= 0) 64 | return console.log( 65 | chalk.green(`Nothing to migrate on "${args["--folder"]}"`) 66 | ); 67 | console.log(chalk.red("Error:"), err.message); 68 | console.log(err); 69 | if (global.dev) console.log(err); 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /modules/cli/migrate/rollback/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | 3 | const config = require("config"); 4 | const Reset = require("cli/migrate/reset"); 5 | const { DB } = require("/"); 6 | 7 | exports.default = async ({ args, cwd, fs, obremapConfig, exit = true }) => { 8 | if (!args["--folder"]) 9 | args["--folder"] = 10 | obremapConfig && obremapConfig.folders 11 | ? obremapConfig.folders.migrations 12 | : config.folders.migrations; 13 | args["--rollback"] = true; 14 | if (!args["--step"]) { 15 | let migration = await DB.table("migrations").orderBy("id", "desc").first(); 16 | if (!migration) { 17 | console.log( 18 | chalk.green(`Nothing to Rolling Back on "${args["--folder"]}"`) 19 | ); 20 | process.exit(); 21 | } 22 | args["--batch"] = migration.batch; 23 | } 24 | 25 | await Reset.default({ args, cwd, fs, obremapConfig }); 26 | if (exit) process.exit(); 27 | }; 28 | -------------------------------------------------------------------------------- /modules/cli/seed/index.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | const path = require("path"); 3 | 4 | const config = require("config"); 5 | 6 | export default async ({ args, cwd, fs, exit = true, obremapConfig }) => { 7 | if (!args["--folder"]) 8 | args["--folder"] = 9 | obremapConfig && obremapConfig.folders 10 | ? obremapConfig.folders.seeders 11 | : config.folders.seeders; 12 | 13 | let folderPath = path.join(cwd, args["--folder"]); 14 | let containerPath = path.join(folderPath, "container.seeder.js"); 15 | fs.readFileSync(containerPath).toString(); 16 | 17 | let Container = require(containerPath); 18 | await Container.run(); 19 | 20 | process.exit(1); 21 | Container = new Container(); 22 | console.log(chalk.green("Database seeding completed successfully.")); 23 | if (exit) process.exit(); 24 | }; 25 | -------------------------------------------------------------------------------- /modules/config/cli/questions.cli.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const pluralize = require("pluralize"); 4 | const config = require('../index.js'); 5 | const configPath = path.join(process.cwd(), "/obremap.config.js"); 6 | let lang = require('../languages'); 7 | const utils = require('../utils'); 8 | let configFile = {}; 9 | try{ 10 | configFile = require(configPath) 11 | }catch(err){} 12 | 13 | module.exports = answers => { 14 | return { 15 | general : [ 16 | { 17 | name : "_lang", 18 | message: "What language do you prefer? | ¿Cuál idioma prefieres?", 19 | type : "list", 20 | choices: [ 21 | "english", 22 | "español" 23 | ], 24 | default: "english", 25 | when: (ans) => { return !answers["_lang"] }, 26 | }, 27 | { 28 | name : "--folder", 29 | message: (ans) => { return lang[ans["_lang"]].questions.folder }, 30 | type : "input", 31 | default : () => { 32 | let customFolder = configFile.folders ? configFile.folders.models : null; 33 | return customFolder || config.folders.models 34 | }, 35 | when: (ans) => { return answers["_type"] == "model" }, 36 | }, 37 | { 38 | name : "--how-import", 39 | message: (ans) => { return lang[ans["_lang"]].questions.howImport }, 40 | type : "list", 41 | choices: [ 42 | { 43 | name : `import { Model } from '@moccacoders/obremap' | export default class ${answers["--name"] || "Test"} extends Model { }`, 44 | value : "import" 45 | }, 46 | { 47 | name : `const Model = require("@moccacoders/node-obremap").Model | module.exports = class ${answers["--name"] || "Test"} extends Model { }`, 48 | value : "require" 49 | } 50 | ], 51 | default: "import", 52 | when: (ans) => { return answers["_type"] == "model" }, 53 | } 54 | ], 55 | model : [ 56 | { 57 | name : "--driver", 58 | message: (ans) => { return lang[ans["_lang"]].questions.driver }, 59 | type : "list", 60 | choices: config.drivers, 61 | default: config.default.driver 62 | },{ 63 | name : "--snake-case", 64 | message: (ans) => { return lang[ans["_lang"]].questions.snakeCase }, 65 | type : "list", 66 | choices: [ 67 | "snake_case", 68 | "CamelCase" 69 | ], 70 | default: "snake_case" 71 | }, 72 | { 73 | name : "--name", 74 | message: (ans) => { return lang[ans["_lang"]].questions.name }, 75 | type : "input", 76 | default: utils.toCase(answers["--name"] || "default", false, true), 77 | filter: (input) => { return utils.toCase(input || "", false, true) } 78 | }, 79 | { 80 | name : "--table-name", 81 | message: (ans) => { return lang[ans["_lang"]].questions.tableName }, 82 | type : "input", 83 | default: (ans) => { 84 | return utils.toCase(pluralize(ans["--name"] || "default"), true) 85 | } 86 | }, 87 | { 88 | name : "--primary-key", 89 | message: (ans) => { return lang[ans["_lang"]].questions.primaryKey }, 90 | type : "input", 91 | default: (ans) => { 92 | return config.default[ans["--driver"]].primaryKey; 93 | } 94 | }, 95 | { 96 | name : "--incrementing", 97 | message: (ans) => { return lang[ans["_lang"]].questions.incrementing }, 98 | type : "confirm", 99 | default: (ans) => { 100 | return config.default[ans["--driver"]].incrementing; 101 | } 102 | }, 103 | { 104 | name : "--key-type", 105 | message: (ans) => { return lang[ans["_lang"]].questions.keyType }, 106 | type : "list", 107 | choices: (ans) => { 108 | return config.choices[ans["--driver"]].keyType 109 | }, 110 | default: (ans) => { 111 | return config.default[ans["--driver"]].keyType; 112 | } 113 | }, 114 | { 115 | name : "--timestamps", 116 | message: (ans) => { return lang[ans["_lang"]].questions.timestamps }, 117 | type : "confirm", 118 | default: (ans) => { 119 | return config.default[ans["--driver"]].timestamps; 120 | } 121 | }, 122 | { 123 | name : "--date-format", 124 | message: (ans) => { return lang[ans["_lang"]].questions.dateFormat }, 125 | type : "input", 126 | when: (ans) => { return ans["--timestamps"] == true }, 127 | default: (ans) => { 128 | return config.default[ans["--driver"]].dateFormat; 129 | } 130 | }, 131 | { 132 | name : "--created-at", 133 | message: (ans) => { return lang[ans["_lang"]].questions.createdAt }, 134 | type : "input", 135 | when: (ans) => { return ans["--timestamps"] == true }, 136 | default: (ans) => { 137 | return config.default[ans["--driver"]].createdAt; 138 | } 139 | }, 140 | { 141 | name : "--updated-at", 142 | message: (ans) => { return lang[ans["_lang"]].questions.updatedAt }, 143 | type : "input", 144 | when: (ans) => { return ans["--timestamps"] == true }, 145 | default: (ans) => { 146 | return config.default[ans["--driver"]].updatedAt; 147 | } 148 | }, 149 | { 150 | name : "--connection", 151 | message: (ans) => { return lang[ans["_lang"]].questions.connection }, 152 | type : "input", 153 | default: (ans) => { 154 | return config.default[ans["--driver"]].connection; 155 | } 156 | } 157 | ], 158 | configConnection : [ 159 | { 160 | name : "__set-connection", 161 | message: (ans) => { 162 | if(answers["--connection"]) 163 | return lang[ans["_lang"] || answers["_lang"]].questions.setConnection.replace("#_CONNECTION_#", answers["--connection"]) 164 | else 165 | return lang[ans["_lang"] || answers["_lang"]].questions.setDefaultConnection 166 | }, 167 | type : "confirm", 168 | default: config.default.connection.setConnection, 169 | }, 170 | { 171 | name : "--config-file", 172 | message: (ans) => { return lang[ans["_lang"] || answers["_lang"]].questions.configFile }, 173 | type : "confirm", 174 | default: config.default.connection.configFile, 175 | when : (ans) => { 176 | let configFile; 177 | try{ 178 | configFile = fs.readFileSync(configPath); 179 | ans["--config-file"] = true; 180 | }catch(e){ configFile = null; } 181 | 182 | return ans["__set-connection"] && !configFile; 183 | } 184 | }, 185 | { 186 | name : "__multi-connections", 187 | message: (ans) => { return lang[ans["_lang"] || answers["_lang"]].questions.multiConnections }, 188 | type : "confirm", 189 | default: config.default.connection.multiConnections, 190 | when : (ans) => { 191 | return ans["__set-connection"]; 192 | } 193 | } 194 | ], 195 | connection : [ 196 | { 197 | name : "__url-config", 198 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.urlConfig}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 199 | type : "confirm", 200 | default: config.default.connection.urlConfig, 201 | when : (ans) => { 202 | return answers["__set-connection"]; 203 | } 204 | }, 205 | { 206 | name : "__url", 207 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.url}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 208 | type : "confirm", 209 | default: config.default.connection.url, 210 | when : (ans) => { 211 | return answers["__set-connection"] && ans["__url-config"]; 212 | } 213 | }, 214 | { 215 | name : "--connection-url", 216 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.connectionUrl}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 217 | type : "input", 218 | validate : (input) => { 219 | return utils.regex.url.test(input) || lang[answers["_lang"]].error.url; 220 | }, 221 | when : (ans) => { 222 | return answers["__set-connection"] && ans["__url"]; 223 | } 224 | }, 225 | { 226 | name : "--driver", 227 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.driver}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 228 | type : "list", 229 | choices: config.drivers, 230 | default: config.default.driver, 231 | when : (ans) => { 232 | if(ans["--connection-url"]){ 233 | let url = new URL(ans["--connection-url"]); 234 | ans["--driver"] = url.protocol.replace(":", ""); 235 | } 236 | return !answers["--driver"] && !ans["--connection-url"] && answers["__set-connection"] 237 | } 238 | }, 239 | { 240 | name : "--hostname", 241 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.hostname}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 242 | type : "input", 243 | default: config.default.connection.hostname, 244 | when : (ans) => { 245 | return answers["__set-connection"] && !ans["__url"]; 246 | } 247 | }, 248 | { 249 | name : "--username", 250 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.username}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 251 | type : "input", 252 | default: config.default.connection.username, 253 | when : (ans) => { 254 | return answers["__set-connection"] && !ans["__url"]; 255 | } 256 | }, 257 | { 258 | name : "--password", 259 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.password}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 260 | type : "input", 261 | default: config.default.connection.password, 262 | when : (ans) => { 263 | return answers["__set-connection"] && !ans["__url"]; 264 | } 265 | }, 266 | { 267 | name : "--database", 268 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.database}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 269 | type : "input", 270 | default: config.default.connection.database, 271 | when : (ans) => { 272 | return answers["__set-connection"] && !ans["__url"]; 273 | } 274 | }, 275 | { 276 | name : "--port", 277 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.port}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 278 | type : "input", 279 | default: ans => { return config.default[ans["--driver"] || answers["--driver"]].port }, 280 | when : (ans) => { 281 | return answers["__set-connection"] && !ans["__url"]; 282 | } 283 | }, 284 | { 285 | name : "--connection-name", 286 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.connectionName}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 287 | type : "input", 288 | default: ans => { return answers["--connection"] || config.default.connection.connectionName; }, 289 | validate: (input) => { 290 | let file = null; 291 | if(answers["connections"] && answers["connections"].findByValue("--connection-name", input)) 292 | return lang[answers["_lang"]].error.connectionName.replace("#_CONNECTION_NAME_#", input); 293 | 294 | try{ 295 | file = fs.readFileSync(configPath); 296 | if(config.databases[input]) return lang[answers["_lang"]].error.connectionName.replace("#_CONNECTION_NAME_#", input); 297 | }catch(e){ } 298 | 299 | return true; 300 | }, 301 | when : (ans) => { 302 | return answers["__set-connection"]; 303 | } 304 | }, 305 | { 306 | name : "__moreConnections", 307 | message: (ans) => { return `${lang[ans["_lang"] || answers["_lang"]].questions.moreConnections}${(answers["connections"] && answers["connections"].length > 0) ? ` (${answers["connections"].length + 1})` : ""}` }, 308 | type : "confirm", 309 | default: config.default.connection.moreConnections, 310 | when : (ans) => { 311 | return answers["__set-connection"] && answers["__multi-connections"]; 312 | } 313 | }, 314 | ], 315 | migrations : [ 316 | { 317 | name : "--force", 318 | message: (ans) => { return lang[ans["_lang"]??"english"].questions.forceFresh }, 319 | type : "confirm", 320 | default: args => { return args["--force"] } 321 | }, 322 | ] 323 | } 324 | } 325 | 326 | require("../prototypes.config.js"); -------------------------------------------------------------------------------- /modules/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | drivers : [ 3 | "mysql" 4 | ], 5 | folders : { 6 | models : "/models", 7 | migrations : "/migrations", 8 | seeders : "/seeds" 9 | }, 10 | howImport : "import", 11 | default : { 12 | driver : "mysql", 13 | mysql : { 14 | snakeCase : true, 15 | tableName : null, 16 | primaryKey : "id", 17 | incrementing : true, 18 | keyType : "int", 19 | timestamps : true, 20 | dateFormat : "TIMESTAMP", 21 | createdAt : "created_at", 22 | updatedAt : "updated_at", 23 | connection : "default", 24 | port : 3306 25 | }, 26 | connection : { 27 | setConnection : true, 28 | configFile : true, 29 | multiConnections : false, 30 | urlConfig : true, 31 | url : true, 32 | hostname : "localhost", 33 | username : "root", 34 | password : "root", 35 | database : "database", 36 | connectionName : "default", 37 | moreConnections : true 38 | } 39 | }, 40 | choices : { 41 | mysql : { 42 | keyType: [ 43 | "tinyint", 44 | "smallint", 45 | "mediumint", 46 | "int", 47 | "bigint", 48 | "decimal", 49 | "float", 50 | "double", 51 | "bit", 52 | "char", 53 | "varchar", 54 | "binary", 55 | "varbinary", 56 | "tinyblob", 57 | "blob", 58 | "mediumblob", 59 | "longblob", 60 | "tinytext", 61 | "text", 62 | "mediumtext", 63 | "longtext", 64 | "enum", 65 | "set", 66 | "date", 67 | "time", 68 | "datetime", 69 | "timestamp", 70 | "year", 71 | "geometry", 72 | "point", 73 | "linestring", 74 | "polygon", 75 | "geometrycollection", 76 | "multilinestring", 77 | "multipoint", 78 | "multipolygon", 79 | "json", 80 | "boolean / bool" 81 | ] 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /modules/config/languages/en.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | module.exports = { 3 | questions : { 4 | howImport: "How do you want to import the 'Node OBREMAP' module?", 5 | folder : "In which folder do you want to save your models?", 6 | name : "What name will the model have?", 7 | snakeCase : `Are the names of your tables and columns in 'snake case' ${chalk.bold.underline("(snake_case)")} or in 'camel case' ${chalk.bold.underline("(camelCase)")}?`, 8 | tableName : "What is the name of the table?", 9 | primaryKey : "What is the Primary Key of this table?", 10 | incrementing : "Is it self-increasing?", 11 | keyType : "What is the field type of your Primary Key '?", 12 | timestamps : `Do you want the created at and updated at dates ${chalk.bold.underline("(Timestamps)")} to be added automatically?`, 13 | dateFormat : `What format do you want for the created at and updated at dates ${chalk.bold.underline("(Check: https://momentjs.com/docs/#/displaying/format/)")}?`, 14 | createdAt : "What is the name of your created at field?", 15 | updatedAt : "What is the name of your updated at field?", 16 | connection : "What is the name of the database connection you want to use?", 17 | // CREATE CONNECTION 18 | setConnection : `The mentioned connection ${chalk.bold.underline("(#_CONNECTION_#)")} was not found or no database configuration was found. Do you want to configure it now? `, 19 | setDefaultConnection : `Do you want to configure database connection now?`, 20 | driver : "What driver do you want to work with? ", 21 | configFile : "Do you want to use the OBREMAP Configuration file? [RECOMMENDED]", 22 | multiConnections : "Will you configure multiple database connections? ", 23 | urlConfig : "Do you want to configure your connections using a connection URL? Note. You can use the connection URL creation wizard to create your URLs.", 24 | url : "Do you have a connection URL? If you select NO, the connection URL creation wizard will start.", 25 | connectionUrl : "Write your URL:", 26 | hostname : `Defines the server name or IP. ${chalk.bold.underline("(Ex. 127.0.0.1 or moccacoders.com)")} `, 27 | username : `Defines the connection username. ${chalk.bold.underline("(Eg root)")} `, 28 | password : "Define the database password ", 29 | database : "Defines the name of the database ", 30 | port : "Defines the port.", 31 | connectionName : "Define a name for this connection. ", 32 | moreConnections : "Do you need one more connection? ", 33 | forceFresh: "Do you really want to drop all tables?" 34 | }, 35 | wizard : "CONNECTION URL CREATION WIZARD", 36 | connectionConfig : "DATABASE CONNECTION CONFIG", 37 | connectionEnv : "ENVIRONMENTAL VARIABLES FOR THE CONNECTION", 38 | connectionEnvMsg : "Add these variables to your environment file (.env) or to the environment variables of your cloud environment.", 39 | configFile: { 40 | created : `The ${chalk.bold("OBREMAP config file")} was ${chalk.green("CREATED")}:`, 41 | updated : `The ${chalk.bold("OBREMAP config file")} was ${chalk.keyword("orange")("UPDATED")}:` 42 | }, 43 | model: { 44 | created : `The ${chalk.bold("OBREMAP model file")} was ${chalk.green("CREATED")}:`, 45 | updated : `The ${chalk.bold("OBREMAP model file")} was ${chalk.keyword("orange")("UPDATED")}:` 46 | }, 47 | error : { 48 | url : `Your URLs must match the following format.${chalk.bold.underline("(driver://username:password@host:port/database?options)")}`, 49 | connectionName : `A connection configuration with the name ${chalk.bold.underline.keyword("orange")("(#_CONNECTION_NAME_#)")} already exists. Please use a different name.`, 50 | } 51 | } -------------------------------------------------------------------------------- /modules/config/languages/es.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"); 2 | module.exports = { 3 | questions : { 4 | howImport: "¿De que manera quieres importar el módulo 'Node OBREMAP'?", 5 | folder : "En que carpeta deseas guardar tus modelos?", 6 | name : "¿Qué nombre tendrá el modelo?", 7 | snakeCase : `¿Los nombres de tus tablas y columnas estan en 'snake case' ${chalk.bold.underline("(snake_case)")} o en 'camel case' ${chalk.bold.underline("(camelCase)")}?`, 8 | tableName : "¿Cuál es el nombre de la tabla?", 9 | primaryKey : "¿Cuál es la Llave Primaria 'Primary Key' de esta tabla?", 10 | incrementing : "¿Es autoincrementable?", 11 | keyType : "¿Cuál es el tipo de campo de tu Llave Primaria 'Primary Key'?", 12 | timestamps : `¿Deseas que se agrege en automático las fechas de creación y edición ${chalk.bold.underline("(Timestamps)")}?`, 13 | dateFormat : `¿Qué formato deseas para las fechas de creación y edición ${chalk.bold.underline("(Revisar: https://momentjs.com/docs/#/displaying/format/)")}?`, 14 | createdAt : "¿Cuál es el nombre de tu campo de fecha de creación?", 15 | updatedAt : "¿Cuál es el nombre de tu campo de fecha de edición?", 16 | connection : "¿Cuál es el nombre de la conexión a base de datos que deseas utilizar?", 17 | // CREATE CONNECTION 18 | setConnection : `No se encontro la conexión mencionada ${chalk.bold.underline("(#_CONNECTION_#)")} o no se encontro configuración de base de datos. ¿Desea configurarla ahora?`, 19 | setDefaultConnection : `¿Desea configurar la conexión a base de datos ahora?`, 20 | configFile : "¿Deseas utilizar el archivo de Configuración OBREMAP? [RECOMENDADO]", 21 | multiConnections : "¿Configurarás multiples conexiones de base de datos?", 22 | urlConfig : "¿Deseas configurar tus conexiones mediante una URL de conexión? Nota. Puedes utilizar el asistente de creación de URL de conexión para crear tus URLs.", 23 | url : "¿Tienes una URL de conexión? Si seleccionas NO, se iniciará el asistente de creación de URL de conexión.", 24 | connectionUrl : "Escribe tu URL:", 25 | driver : "¿Con qué driver deseas trabajar?", 26 | hostname : `Define el nombre o IP de servidor. ${chalk.bold.underline("(Ej. 127.0.0.1 ó moccacoders.com)")}`, 27 | username : `Define el nombre de usuario de conexión. ${chalk.bold.underline("(Ej. root)")}`, 28 | password : "Define la contraseña de la base de datos", 29 | database : "Define el nombre de la base de datos", 30 | port : "Define el puerto.", 31 | connectionName : "Define un nombre para esta conexión.", 32 | moreConnections : "¿Necesitas una conexión más?", 33 | forceFresh: "Realmente deseas eliminar todas las tablas?" 34 | }, 35 | wizard : "ASISTENTE DE CREACIÓN DE URL DE CONEXIÓN", 36 | connectionConfig : "CONFIGURACIÓN DE CONEXIÓN DE BASE DE DATOS", 37 | connectionEnv : "VARIABLES DE ENTORNO PARA LA CONEXIÓN", 38 | connectionEnvMsg : "Agrega estas variables a tu archivo de entorno (.env) o a las variables de entorno de tu ambiente en la nube.", 39 | configFile: { 40 | created : `El ${chalk.bold("archivo de Configuración OBREMAP")} ha sido ${chalk.green("CREADO")}:`, 41 | updated : `El ${chalk.bold("archivo de Configuración OBREMAP")} ha sido ${chalk.keyword("orange")("ACTUALIZADO")}:`, 42 | }, 43 | model: { 44 | created : `El ${chalk.bold("archivo de Modelo OBREMAP")} ha sido ${chalk.green("CREADO")}:`, 45 | updated : `El ${chalk.bold("archivo de Modelo OBREMAP")} ha sido ${chalk.keyword("orange")("ACTUALIZADO")}:`, 46 | }, 47 | error : { 48 | connectionName : `Ya existe una configuración de conexión con el nombre ${chalk.bold.underline.keyword("orange")("(#_CONNECTION_NAME_#)")}. Por favor, utiliza otro nombre.`, 49 | url : `Tus URLs deben coincidir con el siguiente formato. ${chalk.bold.underline("(driver://username:password@host:port/database?options)")}` 50 | } 51 | } -------------------------------------------------------------------------------- /modules/config/languages/index.js: -------------------------------------------------------------------------------- 1 | exports.english = require("./en"); 2 | exports.español = require("./es"); -------------------------------------------------------------------------------- /modules/config/prototypes.config.js: -------------------------------------------------------------------------------- 1 | if(!Array.prototype.hasOwnProperty("findByValue")){ 2 | Object.defineProperty(Array.prototype, 'findByValue', { 3 | value: function(key, val) { 4 | let resp = false; 5 | this.map(obj => { 6 | if (obj[key] && obj[key] == val) 7 | resp = true; 8 | }) 9 | return resp; 10 | } 11 | }); 12 | } 13 | 14 | if(!Object.prototype.hasOwnProperty("empty")){ 15 | Object.defineProperty(Object.prototype, 'empty', { 16 | get: function() { 17 | return Object.entries(this).length === 0 18 | } 19 | }); 20 | } 21 | 22 | if(!Array.prototype.hasOwnProperty("wrap")){ 23 | Object.defineProperty(Array.prototype, 'wrap', { 24 | value: function(key, val) { 25 | return this.filter(arr => arr); 26 | } 27 | }); 28 | } 29 | 30 | -------------------------------------------------------------------------------- /modules/config/utils.js: -------------------------------------------------------------------------------- 1 | const uncapitalize = (str) => { 2 | let string = str.split(" ").map(s => { 3 | return s.charAt(0).toLowerCase() + s.slice(1); 4 | }); 5 | return string.join(" "); 6 | } 7 | 8 | const capitalize = (str) => { 9 | let string = str.split(" ").map(s => { 10 | return s.charAt(0).toUpperCase() + s.slice(1); 11 | }); 12 | return string.join(" "); 13 | } 14 | 15 | const toCase = (str, toSnakeCase, cap) => { 16 | switch(toSnakeCase){ 17 | case false: 18 | str = str.replace(/[\w]([A-Z])|(_|\ )(\w)/g, function(m){ 19 | return (`${/(_|\ )/.test(m[0]) ? "" : m[0]}${m[1].toUpperCase()}`); 20 | }).trim(); 21 | break; 22 | default: 23 | str = str.replace(/[\w]([A-Z])/g, function(m) { 24 | return m[0] + "_" + m[1]; 25 | }).trim().replace(/\s+/g, "_").toLowerCase(); 26 | } 27 | if(!cap) str = uncapitalize(str); 28 | else str = capitalize(str); 29 | return str; 30 | } 31 | 32 | exports.microtime = (getAsFloat) => { 33 | var s; 34 | var now = (Date.now ? Date.now() : new Date().getTime()) / 1000; 35 | if(getAsFloat) return now; 36 | s = now | 0; 37 | return (Math.round((now - s) * 1000) / 1000) + ' ' + s 38 | } 39 | 40 | 41 | exports.regex = { 42 | url : /^([a-z\+]+):\/\/([\w\d]+):([\w\d\.\?\!\#\/\\\*\-\%]+)@(((\d{1,3}):(\d{1,3}):(\d{1,3}))|([\w\d\.]+))(:([\d]{2,}))?(\/([\w\d\-\.]+))(\?(.+))?$/ 43 | } 44 | 45 | exports.uncapitalize = uncapitalize; 46 | exports.capitalize = capitalize; 47 | exports.toCase = toCase; 48 | -------------------------------------------------------------------------------- /modules/global/get-name.js: -------------------------------------------------------------------------------- 1 | import pluralize from 'pluralize' 2 | import Utils from "../config/utils"; 3 | 4 | /* 5 | Get plural name for table 6 | ex Chat -> chats 7 | 8 | Get field name for relationships 9 | ex Chat -> chat_id 10 | */ 11 | module.exports = { 12 | getTableName: (model, toSnakeCase) => typeof model === 'string' ? Utils.toCase(pluralize(model), toSnakeCase) : null, 13 | getFieldName: (model, toSnakeCase) => typeof model === 'string' ? `${Utils.toCase(model, toSnakeCase)}_id` : null 14 | } -------------------------------------------------------------------------------- /modules/index.js: -------------------------------------------------------------------------------- 1 | import Model from "./orm/model"; 2 | import Schema from "./orm/schema"; 3 | import Seeder from "./orm/seeder"; 4 | import DB from "./orm/database"; 5 | import Migration from "./orm/migration"; 6 | export { Model, Schema, Seeder, DB, Migration }; 7 | -------------------------------------------------------------------------------- /modules/orm/adapters/index.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const path = require("path"); 3 | 4 | export default (model) => { 5 | try { 6 | global.dbConn = dbConfig(model.model.connection); 7 | const adapter = require(`./${global.dbConn.driver}`).default; 8 | adapter.setModel(model); 9 | return adapter; 10 | } catch (e) { 11 | console.log("debug", e); 12 | throw Error( 13 | "You must specify process.env.DB_DRIVER before creating a model." 14 | ); 15 | } 16 | }; 17 | 18 | export const dbConfig = (connection) => { 19 | let databases = {}; 20 | let configFile = null; 21 | 22 | try { 23 | configFile = require(path.join(process.cwd(), "/obremap.config.js")); 24 | } catch (e) {} 25 | 26 | if (configFile && configFile.databases) { 27 | databases = configFile.databases; 28 | global.TZ = configFile.timezone || null; 29 | } 30 | 31 | if (!configFile || !configFile.databases) 32 | Object.entries(process.env).map((elem, ind) => { 33 | const [key, value] = elem; 34 | 35 | // DATABASE URL 36 | const url = key.match(/DATABASE_URL([\w]+)?/i); 37 | if (url) { 38 | let params = new URL(value); 39 | let name = url[1] ? url[1].slice(1).toLowerCase() : "default"; 40 | 41 | databases[name] = { 42 | host: params.hostname, 43 | user: params.username, 44 | password: params.password, 45 | database: params.pathname.slice(1), 46 | port: params.port || 3306, 47 | driver: params.protocol.replace(":", ""), 48 | }; 49 | } 50 | 51 | // DB 52 | const db = key.match(/DB([\w]+)?_([\w]+)/i); 53 | if (db) { 54 | let name = db[1] ? db[1].slice(1).toLowerCase() : "default"; 55 | let prop = ( 56 | db[2] == "NAME" ? "database" : db[2] == "USERNAME" ? "user" : db[2] 57 | ).toLowerCase(); 58 | if (!databases[name]) databases[name] = {}; 59 | databases[name][prop] = value; 60 | } 61 | }); 62 | 63 | if (typeof databases[connection] == "string") { 64 | let params = new URL(databases[connection]); 65 | return { 66 | host: params.hostname, 67 | user: params.username, 68 | password: params.password, 69 | database: params.pathname.slice(1), 70 | port: params.port || 3306, 71 | driver: params.protocol.replace(":", ""), 72 | }; 73 | } 74 | return databases[connection]; 75 | }; 76 | -------------------------------------------------------------------------------- /modules/orm/adapters/mysql/builders/faker.builder.js: -------------------------------------------------------------------------------- 1 | import faker from "faker"; 2 | import adapter from '../index' 3 | import "../../../../config/prototypes.config.js" 4 | 5 | export default class Faker { 6 | faker = faker; 7 | data = []; 8 | 9 | constructor(options){ 10 | this.model = options.model 11 | delete options.model; 12 | this.options = options 13 | } 14 | 15 | fixed (...data) { 16 | return this.createFake("fixed", data) 17 | } 18 | 19 | times (times) { 20 | this.times = times; 21 | return this; 22 | } 23 | 24 | locale (locale) { 25 | this.faker.locale(locale) 26 | return this; 27 | } 28 | 29 | /* METHODS */ 30 | address (type=false) { 31 | return this.createFake("address", type) 32 | } 33 | 34 | commerce (...type) { 35 | return this.createFake("commerce", type) 36 | } 37 | 38 | company (...type) { 39 | return this.createFake("company", type) 40 | } 41 | 42 | database (...type) { 43 | return this.createFake("database", type) 44 | } 45 | 46 | date (...type) { 47 | return this.createFake("date", type) 48 | } 49 | 50 | fake (str) { 51 | return this.faker.fake(str) 52 | } 53 | 54 | finance (...type) { 55 | return this.createFake("finance", type) 56 | } 57 | 58 | git (...type) { 59 | return this.createFake("git", type) 60 | } 61 | 62 | hacker (...type) { 63 | return this.createFake("hacker", type) 64 | } 65 | 66 | helpers (...type) { 67 | return this.createFake("helpers", type) 68 | } 69 | 70 | image (...type) { 71 | return this.createFake("image", type) 72 | } 73 | 74 | internet (...type) { 75 | return this.createFake("internet", type) 76 | } 77 | 78 | lorem (...type) { 79 | return this.createFake("lorem", type) 80 | } 81 | 82 | name (...type) { 83 | return this.createFake("name", type) 84 | } 85 | 86 | number (...type) { 87 | return this.createFake("number", type) 88 | } 89 | 90 | phone (...type) { 91 | return this.createFake("phone", type) 92 | } 93 | 94 | random (...type) { 95 | return this.createFake("random", type) 96 | } 97 | 98 | randomOptions (...data) { 99 | return this.createFake("randomOptions", data) 100 | } 101 | 102 | system (...type) { 103 | return this.createFake("system", type) 104 | } 105 | 106 | time (...type) { 107 | return this.createFake("time", type) 108 | } 109 | 110 | unique (...type) { 111 | return this.createFake("date", type) 112 | } 113 | 114 | vehicle (...type) { 115 | return this.createFake("date", type) 116 | } 117 | 118 | /* GENERALS */ 119 | createFake (method, type) { 120 | let other = null; 121 | type = type.map(item => { 122 | let type = null; 123 | if(typeof item == "object"){ 124 | let {type, ...others} = item; 125 | item.type = type; 126 | other = others; 127 | } 128 | return item.type; 129 | }) 130 | if(type) type = type.wrap(); 131 | this.data.push({ method, type, ...other }); 132 | return this; 133 | } 134 | 135 | /* MAKE */ 136 | make () { 137 | let fake = []; 138 | let consecutive = 1; 139 | let times = this.times; 140 | while(this.times > 0){ 141 | this.data.map(item => { 142 | let data = null; 143 | if(!fake[this.times]) fake[this.times] = {}; 144 | if(item.type.length > 0){ 145 | item.type.map(type => { 146 | if(this.faker[item.method] && typeof this.faker[item.method][type] != "undefined"){ 147 | fake[this.times][item.name || item.method] = this.faker[item.method][type](); 148 | } 149 | 150 | if(item.method == "number"){ 151 | if(type == "consecutive"){ 152 | data = this.times; 153 | } 154 | data = Math.floor(Math.random() * (((item.max+1)||times) - (item.min||1)) + (item.min||1)); 155 | } 156 | }) 157 | } 158 | 159 | if(item.method == "randomOptions"){ 160 | data = item.options[Math.floor(Math.random() * item.options.length)] 161 | } 162 | if(item.method == "fixed") 163 | data = item.value 164 | 165 | if(data) 166 | fake[this.times][item.name || item.method] = data 167 | }) 168 | this.times--; 169 | } 170 | fake = fake.wrap() 171 | return fake; 172 | } 173 | } -------------------------------------------------------------------------------- /modules/orm/adapters/mysql/builders/query.builder.js: -------------------------------------------------------------------------------- 1 | import adapter from "orm/adapters"; 2 | import { getTableName } from "global/get-name"; 3 | import _ from "lodash"; 4 | import url from "url"; 5 | 6 | export default class QueryBuilder { 7 | options = {}; 8 | operators = [ 9 | "=", 10 | "<", 11 | ">", 12 | "<=", 13 | ">=", 14 | "<>", 15 | "!=", 16 | "<=>", 17 | "like", 18 | "like binary", 19 | "not like", 20 | "ilike", 21 | "&", 22 | "|", 23 | "^", 24 | "<<", 25 | ">>", 26 | "rlike", 27 | "not rlike", 28 | "regexp", 29 | "not regexp", 30 | "~", 31 | "~*", 32 | "!~", 33 | "!~*", 34 | "similar to", 35 | "not similar to", 36 | "not ilike", 37 | "~~*", 38 | "!~~*", 39 | "in", 40 | "not in", 41 | "is null", 42 | "is not null", 43 | "between", 44 | "not between", 45 | "json_contains", 46 | "not json_contains", 47 | "json_length", 48 | "not json_length", 49 | null, 50 | ]; 51 | 52 | constructor(model) { 53 | this.setModel(model); 54 | } 55 | 56 | adapter() { 57 | return adapter(this); 58 | } 59 | 60 | /** 61 | * Execute a SQL query. In order to avoid SQL Injection attacks, you should always escape any user provided data before using it inside a SQL query. 62 | * @param {string} sql - SQL query, use "?" instead of values to use escaping method. 63 | * @param {array} values - These values will be used to be replaced in the escaping method. 64 | * @returns 65 | */ 66 | sql(sql, values = []) { 67 | return this.adapter().sql({ sql, values, direct: true }); 68 | } 69 | 70 | /** 71 | * The Obremap all() method will return all of the results in the model's table. Since each Obremap model serves as a query builder, 72 | * you may also add constraints to queries, and then use the get method to retrieve the results. 73 | * @returns Obremap Collection 74 | */ 75 | all() { 76 | return this.adapter().all(); 77 | } 78 | 79 | /** 80 | * If you just need to retrieve a single row from the database table, you may use the first() method. 81 | * This method will return a single Obremap object 82 | * @returns Obremap object 83 | */ 84 | first() { 85 | return this.adapter().first(); 86 | } 87 | 88 | /** 89 | * If you just need to retrieve a last row from the database table, you may use the last() method. 90 | * This method will return a single Obremap object 91 | * @returns Obremap object 92 | */ 93 | last() { 94 | return this.adapter().last(); 95 | } 96 | 97 | /** 98 | * Get the count of seleced rows 99 | * @returns Number 100 | */ 101 | count() { 102 | return this.adapter().count(); 103 | } 104 | 105 | max(column) { 106 | return this.adapter().max(column); 107 | } 108 | 109 | min(column) { 110 | return this.adapter().min(column); 111 | } 112 | 113 | sum(column) { 114 | return this.adapter().sum(column); 115 | } 116 | 117 | avg(column) { 118 | return this.adapter().avg(column); 119 | } 120 | 121 | average(column) { 122 | return this.avg(column); 123 | } 124 | 125 | get() { 126 | return this.adapter().get(); 127 | } 128 | 129 | toSql(values = false, type = "select") { 130 | return this.adapter().toSql(values, type); 131 | } 132 | 133 | insertToSql(values = false) { 134 | return this.toSql(values, "insert"); 135 | } 136 | 137 | updateToSql(values = false) { 138 | return this.toSql(values, "update"); 139 | } 140 | 141 | deleteToSql(values = false) { 142 | return this.toSql(values, "delete"); 143 | } 144 | 145 | truncateToSql(values = false) { 146 | return this.toSql(values, "truncate"); 147 | } 148 | 149 | find(id, ...columns) { 150 | return this.where((this.options.model ?? this).primaryKey, id) 151 | .select(...columns) 152 | .first(); 153 | } 154 | 155 | value(column) { 156 | this.select(column); 157 | return this.adapter().value(column); 158 | } 159 | 160 | paginate(perPage = 15, page = 1, pageName = "page") { 161 | let from = (page - 1) * perPage; 162 | from = from > 0 ? from : 1; 163 | from = page > 1 ? from + 1 : from; 164 | let to = page * perPage; 165 | return this.select(...columns) 166 | .forPage(page, perPage) 167 | .get() 168 | .then((res) => { 169 | return this.limit(undefined) 170 | .offset(undefined) 171 | .count() 172 | .then((count) => { 173 | to = to > count ? count : to; 174 | let lastPage = Math.ceil(count / perPage); 175 | if (page > lastPage) { 176 | from = null; 177 | to = null; 178 | count = null; 179 | } 180 | return { 181 | currentPage: page, 182 | data: res, 183 | from, 184 | to, 185 | lastPage, 186 | perPage, 187 | total: count, 188 | }; 189 | }); 190 | }); 191 | } 192 | 193 | implode(column, glue = "") { 194 | return this.select(column) 195 | .get() 196 | .then((res) => { 197 | return res.map((e) => e[column]).join(glue); 198 | }); 199 | } 200 | 201 | exists() { 202 | return this.count().then((count) => count > 0); 203 | } 204 | 205 | doesntExist() { 206 | return this.exists().then((res) => !res); 207 | } 208 | 209 | update(values) { 210 | if (values) this.set(values, undefined, true); 211 | return this.adapter().update(); 212 | } 213 | 214 | insert(values) { 215 | if (values) this.set(values, undefined, true); 216 | return this.adapter().insert(); 217 | } 218 | 219 | delete(id, user_id) { 220 | if (id) this.where(this.options.model.primaryKey, id); 221 | if (this.getLogicalDeleted) { 222 | let set = { 223 | [this.options.model.deleted]: true, 224 | [this.options.model.deletedAt]: this.options.model.currentDate, 225 | }; 226 | if (user_id) set[this.options.model.deletedBy] = user_id; 227 | return this.set(set).setTimestamps(false).update(); 228 | } 229 | return this.adapter().delete(); 230 | } 231 | 232 | truncate() { 233 | return this.adapter().truncate(); 234 | } 235 | 236 | // ### QUERYBUILDER ## // 237 | table(tableName) { 238 | if (!tableName) return this; 239 | tableName = tableName.replaceAll(/\`/gi, "").split(".").join("`.`"); 240 | this.options.tableName = `\`${tableName}\``; 241 | return this; 242 | } 243 | 244 | setTimestamps(timestamps) { 245 | this.options.timestamps = timestamps; 246 | return this; 247 | } 248 | 249 | setCreatedAt(createdAt) { 250 | this.options.createdAt = createdAt; 251 | return this; 252 | } 253 | 254 | setUpdatedAt(updatedAt) { 255 | this.options.updatedAt = updatedAt; 256 | return this; 257 | } 258 | 259 | select(...select) { 260 | this.options.select = select; 261 | return this; 262 | } 263 | 264 | where( 265 | column, 266 | operator = "=", 267 | value, 268 | separator = ", ", 269 | parenthesis = true, 270 | isFunction = false 271 | ) { 272 | let where = this.options.where ? this.options.where : []; 273 | if (!this.operators.includes(operator)) { 274 | value = operator; 275 | operator = "="; 276 | } 277 | 278 | if (column.constructor.name === "Array") { 279 | where = [ 280 | ...where, 281 | column.map((ele) => { 282 | let [column, operator, value] = ele; 283 | if ( 284 | typeof column == "object" || 285 | typeof operator == "object" || 286 | typeof value == "object" 287 | ) 288 | throw new Error( 289 | "Each element of the array should be an array containing the three arguments typically passed to the where method. [column, operator, value]" 290 | ); 291 | 292 | if (!this.operators.includes(operator)) { 293 | value = operator; 294 | operator = "="; 295 | } 296 | return { 297 | column, 298 | operator, 299 | value, 300 | orWhere: false, 301 | separator, 302 | parenthesis, 303 | isFunction, 304 | }; 305 | }), 306 | ]; 307 | } 308 | 309 | if (column.constructor.name == "String") { 310 | where.push({ 311 | column, 312 | operator, 313 | value, 314 | orWhere: false, 315 | separator, 316 | parenthesis, 317 | isFunction, 318 | }); 319 | } 320 | 321 | this.options.where = where; 322 | return this; 323 | } 324 | 325 | orWhere( 326 | column, 327 | operator = "=", 328 | value, 329 | separator = ", ", 330 | parenthesis = true, 331 | isFunction = false 332 | ) { 333 | let orWhere = this.options.orWhere ? this.options.orWhere : []; 334 | if (!this.operators.includes(operator)) { 335 | value = operator; 336 | operator = "="; 337 | } 338 | 339 | if (column.constructor.name === "Array") { 340 | orWhere = [ 341 | ...orWhere, 342 | column.map((ele) => { 343 | let [column, operator, value] = ele; 344 | if ( 345 | typeof column == "object" || 346 | typeof operator == "object" || 347 | typeof value == "object" 348 | ) 349 | throw new Error( 350 | "Each element of the array should be an array containing the three arguments typically passed to the orWhere method. [column, operator, value]" 351 | ); 352 | 353 | if (!this.operators.includes(operator)) { 354 | value = operator; 355 | operator = "="; 356 | } 357 | return { 358 | column, 359 | operator, 360 | value, 361 | orWhere: true, 362 | separator, 363 | parenthesis, 364 | isFunction, 365 | }; 366 | }), 367 | ]; 368 | } 369 | 370 | if (column.constructor.name == "String") { 371 | orWhere.push({ 372 | column, 373 | operator, 374 | value, 375 | orWhere: true, 376 | separator, 377 | parenthesis, 378 | isFunction, 379 | }); 380 | } 381 | 382 | this.options.where = [...this.options.where, ...orWhere]; 383 | return this; 384 | } 385 | 386 | whereIn(column, values) { 387 | return this.where(column, "in", values); 388 | } 389 | 390 | orWhereIn(column, values) { 391 | return this.orWhere(column, "in", values); 392 | } 393 | 394 | whereNotIn(column, values) { 395 | return this.where(column, "not in", values); 396 | } 397 | 398 | orWhereNotIn(column, values) { 399 | return this.orWhere(column, "not in", values); 400 | } 401 | 402 | whereNull(column) { 403 | return this.where(column, "is null"); 404 | } 405 | 406 | orWhereNull(column) { 407 | return this.orWhere(column, "is null"); 408 | } 409 | 410 | whereNotNull(column) { 411 | return this.where(column, "is not null"); 412 | } 413 | 414 | orWhereNotNull(column) { 415 | return this.orWhere(column, "is not null"); 416 | } 417 | 418 | whereBetween(column, values) { 419 | return this.where(column, "between", values, " AND ", false); 420 | } 421 | 422 | orWhereBetween(column, values) { 423 | return this.orWhere(column, "between", values, " AND ", false); 424 | } 425 | 426 | whereNotBetween(column, values) { 427 | return this.where(column, "not between", values, " AND ", false); 428 | } 429 | 430 | orWhereNotBetween(column, values) { 431 | return this.orWhere(column, "not between", values, " AND ", false); 432 | } 433 | 434 | whereJsonContains(column, value) { 435 | return this.where(column, null, value, "", true, "json_contains"); 436 | } 437 | 438 | orWhereJsonContains(column, value) { 439 | return this.orWhere(column, null, value, "", true, "json_contains"); 440 | } 441 | 442 | whereJsonDoesntContains(column, value) { 443 | return this.where(column, null, value, "", true, "not json_contains"); 444 | } 445 | 446 | orWhereJsonDoesntContains(column, value) { 447 | return this.orWhere(column, null, value, "", true, "not json_contains"); 448 | } 449 | 450 | whereJsonLength(column, operator = null, value) { 451 | return this.where(column, operator, value, "", true, "json_length"); 452 | } 453 | 454 | orWhereJsonLength(column, operator = null, value) { 455 | return this.orWhere(column, operator, value, "", true, "json_length"); 456 | } 457 | 458 | orderBy(column, direction = "ASC") { 459 | if (!this.options.orderBy) this.options.orderBy = []; 460 | this.options.orderBy = [...this.options.orderBy, [column, direction]]; 461 | return this; 462 | } 463 | 464 | latest(column) { 465 | return this.orderBy(column ? column : this.options.model.createdAt, "desc"); 466 | } 467 | 468 | oldest(column) { 469 | return this.orderBy(column ?? this.options.model.createdAt, "asc"); 470 | } 471 | 472 | reorder(column = null, direction = "ASC") { 473 | if (column !== null) this.orderBy(column, direction); 474 | this.options.orderBy = undefined; 475 | return this; 476 | } 477 | 478 | groupBy(...columns) { 479 | if (!this.options.groupBy) this.options.groupBy = []; 480 | this.options.groupBy = [...this.options.groupBy, ...columns]; 481 | return this; 482 | } 483 | 484 | offset(offset) { 485 | this.options.offset = offset; 486 | return this; 487 | } 488 | 489 | skip(value) { 490 | return this.offset(value); 491 | } 492 | 493 | limit(limit) { 494 | this.options.limit = limit; 495 | return this; 496 | } 497 | 498 | take(value) { 499 | return this.limit(value); 500 | } 501 | 502 | forPage(page, perPage = 15) { 503 | return this.offset((page - 1) * perPage).limit(perPage); 504 | } 505 | 506 | set(column, value, override = false) { 507 | let set = override ? {} : { ...this.options.set }; 508 | if (typeof column === "string") { 509 | set[column] = value; 510 | } else if (typeof column === "object" && !Array.isArray(column)) { 511 | set = { ...set, ...column }; 512 | } else if (typeof column === "object" && Array.isArray(column)) { 513 | set = column; 514 | } 515 | 516 | this.options.set = set; 517 | return this; 518 | } 519 | 520 | logicalDeleted(logicalDelete = true) { 521 | this.options.logicalDelete = logicalDelete; 522 | return this; 523 | } 524 | 525 | join( 526 | table, 527 | first, 528 | operator = null, 529 | second = null, 530 | type = "inner", 531 | where = false 532 | ) { 533 | if (!this.options.joins) this.options.joins = []; 534 | let joins = [...this.options.joins]; 535 | 536 | if (!["inner", "left", "right", "cross"].includes(type)) { 537 | where = type; 538 | } 539 | 540 | if (["inner", "left", "right", "cross"].includes(second)) { 541 | type = second; 542 | } 543 | 544 | if (!this.operators.includes(operator)) { 545 | second = operator; 546 | operator = "="; 547 | } 548 | 549 | joins.push({ table, first, operator, second, type, where }); 550 | this.options.joins = joins; 551 | return this; 552 | } 553 | 554 | leftJoin(table, first, operator = null, second = null) { 555 | return this.join(table, first, operator, second, "left"); 556 | } 557 | 558 | rightJoin(table, first, operator = null, second = null) { 559 | return this.join(table, first, operator, second, "right"); 560 | } 561 | 562 | crossJoin(table, first, operator = null, second = null) { 563 | return this.join(table, first, operator, second, "cross"); 564 | } 565 | 566 | queryBuilder() { 567 | return true; 568 | } 569 | 570 | setModel(model) { 571 | if (!model.builder) { 572 | this.model = model; 573 | this.init(); 574 | this.builder = true; 575 | } 576 | } 577 | 578 | init() { 579 | this.options = {}; 580 | QueryBuilder.options = {}; 581 | delete this.builder; 582 | } 583 | 584 | get getTableName() { 585 | let tableName = 586 | this.options.tableName ?? 587 | this.model.tableName ?? 588 | getTableName(this.model.name, this.model.snakeCase); 589 | tableName = tableName.replaceAll(/\`/gi, "").split(".").join("`.`"); 590 | return `\`${tableName}\``; 591 | } 592 | 593 | get getTimestamps() { 594 | return this.options.timestamps ?? this.model.timestamps; 595 | } 596 | 597 | get getCreatedAt() { 598 | return this.options.createdAt ?? this.model.createdAt; 599 | } 600 | 601 | get getUpdatedAt() { 602 | return this.options.updatedAt ?? this.model.updatedAt; 603 | } 604 | 605 | get getLogicalDeleted() { 606 | return this.options.logicalDelete ?? this.model.logicalDelete; 607 | } 608 | } 609 | -------------------------------------------------------------------------------- /modules/orm/adapters/mysql/builders/schema.builder.js: -------------------------------------------------------------------------------- 1 | import adapter from "../index"; 2 | 3 | export default class SchemaBuilder { 4 | static _engine = "InnoDB"; 5 | static _charset = "utf8mb4"; 6 | static _collation = "utf8mb4_unicode_ci"; 7 | 8 | static options = {}; 9 | fields = []; 10 | alterTables = []; 11 | FIELD_INDEX = 0; 12 | #OTHER_FIELD_INDEX = 0; 13 | 14 | static id() { 15 | this.increments("id"); 16 | this.options.fields[this.options.FIELD_INDEX].type = "bigint"; 17 | return SchemaBuilder; 18 | } 19 | 20 | static foreignId(name) { 21 | this.createField({ 22 | name, 23 | type: "bigint", 24 | unsigned: true, 25 | }); 26 | return SchemaBuilder; 27 | } 28 | 29 | static bigIncrements(name) { 30 | this.increments(name); 31 | this.options.fields[this.options.FIELD_INDEX].type = "bigint"; 32 | return SchemaBuilder; 33 | } 34 | 35 | static bigInteger(name) { 36 | this.createField({ 37 | name, 38 | type: "bigint", 39 | }); 40 | return SchemaBuilder; 41 | } 42 | 43 | static binary(name) { 44 | this.createField({ 45 | name, 46 | type: "blob", 47 | }); 48 | return SchemaBuilder; 49 | } 50 | 51 | static boolean(name) { 52 | this.createField({ 53 | name, 54 | type: "tinyint", 55 | length: 1, 56 | }); 57 | return SchemaBuilder; 58 | } 59 | 60 | static char(name, length = 255) { 61 | this.createField({ 62 | name, 63 | type: "char", 64 | length, 65 | }); 66 | return SchemaBuilder; 67 | } 68 | 69 | static date(name) { 70 | this.createField({ 71 | name, 72 | type: "date", 73 | nullable: false, 74 | }); 75 | return SchemaBuilder; 76 | } 77 | 78 | static dateTime(name, length = 0) { 79 | this.date(name); 80 | this.options.fields[this.options.FIELD_INDEX].length = length; 81 | this.options.fields[this.options.FIELD_INDEX].type = "datetime"; 82 | return SchemaBuilder; 83 | } 84 | 85 | static dateTimeTz(name, length = 0) { 86 | return this.dateTime(name, length); 87 | } 88 | 89 | static decimal(name, digits = 8, decimal = 2) { 90 | this.createField({ 91 | name, 92 | type: "decimal", 93 | digits, 94 | decimal, 95 | }); 96 | return SchemaBuilder; 97 | } 98 | 99 | static double(name, digits = 8, decimal = 2) { 100 | this.decimal(name, digits, decimal); 101 | this.options.fields[this.options.FIELD_INDEX].type = "double"; 102 | return SchemaBuilder; 103 | } 104 | 105 | /** 106 | * @param {string} name Column name 107 | * @param {array} values Enum values 108 | */ 109 | static enum(name, values) { 110 | if (!values) throw Error("Values is required. Ex. ['easy', 'hard']"); 111 | if (values.constructor !== Array) 112 | throw Error("Values must be an array Ex. ['easy', 'hard']"); 113 | 114 | this.createField({ 115 | name, 116 | type: "enum", 117 | values, 118 | }); 119 | return SchemaBuilder; 120 | } 121 | 122 | static float(name, digits = 8, decimal = 2) { 123 | return this.decimal(name, digits, decimal); 124 | } 125 | 126 | static geometry(name) { 127 | this.createField({ 128 | name, 129 | type: "geometry", 130 | nullable: false, 131 | }); 132 | return SchemaBuilder; 133 | } 134 | 135 | static geometryCollection(name) { 136 | this.geometry(name); 137 | this.options.fields[this.options.FIELD_INDEX].type = "geometrycollection"; 138 | return SchemaBuilder; 139 | } 140 | 141 | static increments(name) { 142 | this.createField({ 143 | name, 144 | type: "int", 145 | unsigned: true, 146 | auto_increment: true, 147 | primary: true, 148 | }); 149 | return SchemaBuilder; 150 | } 151 | 152 | static integer(name, length = 11) { 153 | this.createField({ 154 | name, 155 | type: "int", 156 | length, 157 | }); 158 | return SchemaBuilder; 159 | } 160 | 161 | static ipAddress(name) { 162 | return this.string(name); 163 | } 164 | 165 | static json(name) { 166 | this.createField({ 167 | name, 168 | type: "json", 169 | }); 170 | return SchemaBuilder; 171 | } 172 | 173 | static jsonb(name) { 174 | return this.json(name); 175 | } 176 | 177 | static lineString(name) { 178 | this.createField({ 179 | name, 180 | type: "linestring", 181 | }); 182 | return SchemaBuilder; 183 | } 184 | 185 | static longText(name) { 186 | this.createField({ 187 | name, 188 | type: "longtext", 189 | }); 190 | return SchemaBuilder; 191 | } 192 | 193 | static macAddress(name) { 194 | return this.string(name, 17); 195 | } 196 | 197 | static mediumIncrements(name) { 198 | this.increments(name); 199 | this.options.fields[this.options.FIELD_INDEX].type = "mediumint"; 200 | return SchemaBuilder; 201 | } 202 | 203 | static mediumInteger(name) { 204 | this.createField({ 205 | name, 206 | type: "mediumint", 207 | }); 208 | return SchemaBuilder; 209 | } 210 | 211 | static mediumText(name) { 212 | this.createField({ 213 | name, 214 | type: "mediumtext", 215 | }); 216 | return SchemaBuilder; 217 | } 218 | 219 | static morphs(name, nullable = false) { 220 | let id = `${name}_id`; 221 | let type = `${name}_type`; 222 | this.foreignId(id).nullable(nullable); 223 | this.string(type, 255).nullable(nullable); 224 | this.options.alterTables.push({ 225 | name: `${this.getTableName}_${id}_${type}_index`, 226 | type, 227 | id, 228 | }); 229 | return SchemaBuilder; 230 | } 231 | 232 | static uuidMorphs(name, nullable = false) { 233 | let id = `${name}_id`; 234 | let type = `${name}_type`; 235 | this.char(id, 36).nullable(nullable); 236 | this.string(type, 255).nullable(nullable); 237 | this.options.alterTables.push({ 238 | name: `${this.getTableName}_${id}_${type}_index`, 239 | type, 240 | id, 241 | }); 242 | return SchemaBuilder; 243 | } 244 | 245 | static multiLineString(name) { 246 | this.createField({ 247 | name, 248 | type: "multilinestring", 249 | }); 250 | return SchemaBuilder; 251 | } 252 | 253 | static multiPoint(name) { 254 | this.createField({ 255 | name, 256 | type: "multipoint", 257 | }); 258 | return SchemaBuilder; 259 | } 260 | 261 | static multiPolygon(name) { 262 | this.createField({ 263 | name, 264 | type: "multipolygon", 265 | }); 266 | return SchemaBuilder; 267 | } 268 | 269 | static nullableMorphs(name) { 270 | return this.morphs(name, true); 271 | } 272 | 273 | static nullableUuidMorphs(name) { 274 | return this.uuidMorphs(name, true); 275 | } 276 | 277 | static nullableTimestamps(length = 0) { 278 | return this.timestamps(length, true); 279 | } 280 | 281 | static timestamps(length = 0, nullable = false) { 282 | this.timestamp("created_at", length).nullable(nullable); 283 | this.timestamp("updated_at", length).nullable(true); 284 | return SchemaBuilder; 285 | } 286 | 287 | static point(name) { 288 | this.createField({ 289 | name, 290 | type: "point", 291 | }); 292 | return SchemaBuilder; 293 | } 294 | 295 | static polygon(name) { 296 | this.createField({ 297 | name, 298 | type: "polygon", 299 | }); 300 | return SchemaBuilder; 301 | } 302 | 303 | static rememberToken() { 304 | return this.string("remember_token", 100).nullable(); 305 | } 306 | 307 | static set(name, values) { 308 | if (!values) throw Error("Values is required. Ex. ['easy', 'hard']"); 309 | if (values.constructor !== Array) 310 | throw Error("Values must be an array Ex. ['easy', 'hard']"); 311 | 312 | this.createField({ 313 | name, 314 | type: "set", 315 | values, 316 | }); 317 | return SchemaBuilder; 318 | } 319 | 320 | static smallIncrements(name) { 321 | this.increments(name); 322 | this.options.fields[this.options.FIELD_INDEX].type = "smallint"; 323 | return SchemaBuilder; 324 | } 325 | 326 | static smallInteger(name) { 327 | this.createField({ 328 | name, 329 | type: "smallint", 330 | }); 331 | return SchemaBuilder; 332 | } 333 | 334 | static softDeletes(name, length = 0) { 335 | return this.timestamp(name, length); 336 | } 337 | 338 | static softDeletesTz(name, length = 0) { 339 | return this.timestamp(name, length); 340 | } 341 | 342 | static string(name, length = 45) { 343 | this.createField({ 344 | name, 345 | type: "varchar", 346 | length, 347 | }); 348 | return SchemaBuilder; 349 | } 350 | 351 | static text(name) { 352 | this.createField({ 353 | name, 354 | type: "text", 355 | }); 356 | return SchemaBuilder; 357 | } 358 | 359 | static timestampTz(name, length = 0) { 360 | return this.timestamp(name, length); 361 | } 362 | 363 | static tinyIncrements(name) { 364 | this.increments(name); 365 | this.options.fields[this.options.FIELD_INDEX].type = "tinyint"; 366 | return SchemaBuilder; 367 | } 368 | 369 | static tinyInteger(name) { 370 | this.createField({ 371 | name, 372 | type: "tinyint", 373 | }); 374 | return SchemaBuilder; 375 | } 376 | 377 | static timestamp(name, length = 0) { 378 | this.createField({ 379 | name, 380 | type: "timestamp", 381 | length, 382 | }); 383 | return SchemaBuilder; 384 | } 385 | 386 | static unsignedBigInteger(name) { 387 | return this.bigIncrements(name).unsigned(); 388 | } 389 | 390 | static unsignedDecimal(name, digits = 8, decimal = 2) { 391 | return this.decimal(name, digits, decimal).unsigned(); 392 | } 393 | 394 | static unsignedInteger(name, length = 11) { 395 | return this.integer(name, length).unsigned(); 396 | } 397 | 398 | static unsignedMediumInteger(name) { 399 | return this.mediumInteger(name).unsigned(); 400 | } 401 | 402 | static unsignedSmallInteger(name) { 403 | return this.smallInteger(name).unsigned(); 404 | } 405 | 406 | static unsignedTinyInteger(name) { 407 | return this.tinyInteger(name).unsigned(); 408 | } 409 | 410 | static uuid(name) { 411 | return this.char(name, 36); 412 | } 413 | 414 | static year(name) { 415 | this.createField({ 416 | name, 417 | type: "year", 418 | }); 419 | return SchemaBuilder; 420 | } 421 | 422 | /** 423 | * Change the column position on table after or before exisiting column 424 | * @param {string} type Set the type of column use the schema builder method name 425 | * @param {string} column Set the name of column to move 426 | * @param {string} existingColumn Set the name of reference column 427 | * @param {string} moveType Set the move type. Options [after, first]. Default: after 428 | * @param {...any} options Set the custom options from schema builder method 429 | * @returns SchemaBuilder 430 | */ 431 | static moveColumn(type, column, existingColumn, ...options) { 432 | this[type](column, ...options) 433 | .after(existingColumn) 434 | .change(); 435 | return SchemaBuilder; 436 | } 437 | 438 | static renameColumn(type, currentName, name, ...options) { 439 | this[type](currentName, ...options) 440 | .rename(name) 441 | .change(); 442 | return SchemaBuilder; 443 | } 444 | 445 | // MODIFIERS 446 | static rename(name) { 447 | this.setModel(this); 448 | this.options.fields[this.options.FIELD_INDEX].rename = name; 449 | return SchemaBuilder; 450 | } 451 | 452 | static after(column) { 453 | this.setModel(this); 454 | this.options.fields[this.options.FIELD_INDEX].after = column; 455 | return SchemaBuilder; 456 | } 457 | 458 | static autoIncrement() { 459 | this.setModel(this); 460 | this.options.fields[this.options.FIELD_INDEX].auto_increment = true; 461 | return SchemaBuilder; 462 | } 463 | 464 | static charset(charset) { 465 | this.setModel(this); 466 | this.options.charset = charset; 467 | return SchemaBuilder; 468 | } 469 | 470 | static engine(engine) { 471 | this.setModel(this); 472 | this.options.engine = engine; 473 | return SchemaBuilder; 474 | } 475 | 476 | static collation(collation) { 477 | this.setModel(this); 478 | this.options.collation = collation; 479 | return SchemaBuilder; 480 | } 481 | 482 | static comment(comment) { 483 | this.setModel(this); 484 | this.options.fields[this.options.FIELD_INDEX].comment = comment; 485 | return SchemaBuilder; 486 | } 487 | 488 | static constrained(table) { 489 | this.setModel(this); 490 | let field = this.options.fields[this.options.FIELD_INDEX].name; 491 | this.options.alterTables.push({ 492 | name: `${this.getTableName}_${field}_foreign`, 493 | type, 494 | id, 495 | }); 496 | } 497 | 498 | static default(value) { 499 | this.setModel(this); 500 | this.options.fields[this.options.FIELD_INDEX].default = value; 501 | return SchemaBuilder; 502 | } 503 | 504 | static first() { 505 | this.setModel(this); 506 | this.options.fields[this.options.FIELD_INDEX].first = true; 507 | return SchemaBuilder; 508 | } 509 | 510 | static nullable(nullable = true) { 511 | this.setModel(this); 512 | this.options.fields[this.options.FIELD_INDEX].nullable = nullable; 513 | return SchemaBuilder; 514 | } 515 | 516 | static unsigned() { 517 | this.setModel(this); 518 | this.options.fields[this.options.FIELD_INDEX].unsigned = true; 519 | return SchemaBuilder; 520 | } 521 | 522 | static unique() { 523 | this.setModel(this); 524 | this.options.fields[this.options.FIELD_INDEX].unique = true; 525 | return SchemaBuilder; 526 | } 527 | 528 | static useCurrent() { 529 | this.setModel(this); 530 | this.options.fields[this.options.FIELD_INDEX].default = "CURRENT_TIMESTAMP"; 531 | return SchemaBuilder; 532 | } 533 | 534 | static primaryKey() { 535 | this.setModel(this); 536 | this.options.fields[this.options.FIELD_INDEX].primary = true; 537 | return SchemaBuilder; 538 | } 539 | 540 | // GENERAL UTILS 541 | static createField(options) { 542 | if (!options.name) throw Error("Column Name is required."); 543 | this.checkName(options.name); 544 | this.options.FIELD_INDEX = this.options.fields 545 | ? this.options.fields.length 546 | : 0; 547 | if (!this.options.fields) this.options.fields = []; 548 | this.options.fields[this.options.FIELD_INDEX] = { 549 | nullable: false, 550 | ...options, 551 | }; 552 | return SchemaBuilder; 553 | } 554 | 555 | static checkName(name) { 556 | this.setModel(this); 557 | if (!this.options.fields || this.options.fields.length === 0) return true; 558 | this.options.fields.filter((obj) => { 559 | if (obj.name == name) 560 | throw Error(`This field name ("${name}") already exists`); 561 | }); 562 | } 563 | 564 | static dropColumn(column) { 565 | this.setModel(this); 566 | this.options.column = column; 567 | this.options.drop = true; 568 | return SchemaBuilder; 569 | } 570 | 571 | static change() { 572 | this.setModel(this); 573 | this.options.fields[this.options.FIELD_INDEX].modify = true; 574 | return SchemaBuilder; 575 | } 576 | 577 | static setModel(model) { 578 | if (!model.builder) { 579 | this.init(); 580 | this.builder = true; 581 | } 582 | } 583 | 584 | static init() { 585 | this.options = {}; 586 | SchemaBuilder.options = {}; 587 | delete this.builder; 588 | } 589 | 590 | static get getTableName() { 591 | let tableName = 592 | this.options.tableName ?? 593 | this.tableName ?? 594 | getTableName(this.name, this.snakeCase); 595 | tableName = tableName.replaceAll(/\`/gi, "").split(".").join("`.`"); 596 | return `\`${tableName}\``; 597 | } 598 | 599 | static get getTimestamps() { 600 | return this.options.timestamps ?? this.timestamps; 601 | } 602 | 603 | static get getCreatedAt() { 604 | return this.options.createdAt ?? this.createdAt; 605 | } 606 | 607 | static get getUpdatedAt() { 608 | return this.options.updatedAt ?? this.updatedAt; 609 | } 610 | 611 | static get getLogicalDeleted() { 612 | return this.options.logicalDelete ?? this.logicalDelete; 613 | } 614 | 615 | static get getCharset() { 616 | return this.options.charset ?? this._charset; 617 | } 618 | 619 | static get getCollation() { 620 | return this.options.collation ?? this._collation; 621 | } 622 | 623 | static get getEngine() { 624 | return this.options.engine ?? this._engine; 625 | } 626 | } 627 | -------------------------------------------------------------------------------- /modules/orm/adapters/mysql/connection.js: -------------------------------------------------------------------------------- 1 | import mysql from "mysql"; 2 | 3 | function handleDisconnect() { 4 | let connection = mysql.createConnection(global.dbConn); 5 | return connection; 6 | } 7 | 8 | const connect = () => handleDisconnect(); 9 | const format = (...options) => mysql.format(...options); 10 | 11 | export { connect, format }; 12 | -------------------------------------------------------------------------------- /modules/orm/adapters/mysql/index.js: -------------------------------------------------------------------------------- 1 | import mysql from "mysql"; 2 | import _ from "lodash"; 3 | import { connect, format as formatSQL } from "./connection"; 4 | import SchemaBuilder from "./builders/schema.builder"; 5 | import FakerBuilder from "./builders/faker.builder"; 6 | import QueryBuilder from "./builders/query.builder"; 7 | import { DB } from "/"; 8 | 9 | class MysqlAdapter { 10 | model = null; 11 | joins = null; 12 | supportedCastTypes = { 13 | array: (val) => val.split(","), 14 | boolean: (val) => Boolean(val), 15 | date: (val) => this.model.model.formatDate(val), 16 | datetime: (val) => this.model.model.formatDate(val), 17 | decimal: (val) => parseFloat(val), 18 | double: (val) => parseFloat(val), 19 | float: (val) => parseFloat(val), 20 | integer: (val) => parseInt(val), 21 | object: (val) => JSON.parse(val), 22 | string: (val) => String(val), 23 | timestamp: (val) => this.model.model.formatDate(val), 24 | }; 25 | log = { 26 | sql: null, 27 | bindings: [], 28 | time: { start: null, end: null }, 29 | }; 30 | 31 | setModel(model) { 32 | this.log.time.start = model.model.formatDate(new Date(), true); 33 | this.model = model; 34 | this.options = model.options; 35 | this.init = model.init; 36 | } 37 | 38 | /** 39 | * 40 | * @param {Object} SqlOptions 41 | * @param { String } SqlOptions.sql 42 | * @param { Array } SqlOptions.values 43 | * @returns MySQL Collection 44 | * @throws Promise rejection 45 | */ 46 | sql( 47 | { sql, values, nestTables = true, format = false, direct = false }, 48 | formatResponse 49 | ) { 50 | return new Promise((resolve, reject) => { 51 | const _reject = (err) => { 52 | this.init(); 53 | return reject(err); 54 | }; 55 | 56 | const _resolve = (res) => { 57 | this.init(); 58 | return resolve(res); 59 | }; 60 | 61 | const connection = connect(); 62 | this.log.sql = sql; 63 | this.log.bindings = values; 64 | if (format) sql = formatSQL(sql); 65 | connection.query( 66 | { 67 | sql, 68 | nestTables, 69 | values, 70 | }, 71 | (err, res, fields) => { 72 | if (direct && this.model.tableName != fields[0].table) 73 | this.model.table(fields[0].table); 74 | connection.end(); 75 | this.log.time.end = this.model.model.formatDate(new Date(), true); 76 | DB.logQuery( 77 | this.log.sql, 78 | this.log.bindings, 79 | DB.getDifferenceDate(this.log.time.start, this.log.time.end) 80 | ); 81 | if (err) _reject(err); 82 | try { 83 | if (nestTables) res = this.processNestTables(res); 84 | if (formatResponse) res = formatResponse(res); 85 | if ( 86 | this.model.model.casts && 87 | Object.entries(this.model.model.casts).length > 0 88 | ) 89 | res = this.processCasts(res); 90 | _resolve(res); 91 | } catch (err) { 92 | _reject(err); 93 | } 94 | } 95 | ); 96 | }); 97 | } 98 | 99 | all() { 100 | const { sql } = this.processSQL(); 101 | return this.sql({ sql }); 102 | } 103 | 104 | first(formatResponse = undefined) { 105 | this.options.limit = 1; 106 | const { sql, values } = this.processSQL(); 107 | return this.sql( 108 | { 109 | sql, 110 | values, 111 | nestTables: this.options.joins && this.options.joins.length > 0, 112 | }, 113 | formatResponse ?? ((res) => res[0]) 114 | ); 115 | } 116 | 117 | last() { 118 | this.options = { orderBy: [this.model.model.primaryKey, "DESC"], limit: 1 }; 119 | const { sql } = this.processSQL(); 120 | return this.sql({ sql }); 121 | } 122 | 123 | count() { 124 | this.options.select = [`COUNT(*) as count`]; 125 | const { sql, values } = this.processSQL(); 126 | return this.sql({ sql, values, nestTables: false }, (res) => { 127 | return res[0].count; 128 | }); 129 | } 130 | 131 | max(column) { 132 | this.options.select = [`MAX(${column}) as max`]; 133 | const { sql } = this.processSQL(); 134 | return this.sql({ sql, nestTables: false }, (res) => { 135 | return res[0].max; 136 | }); 137 | } 138 | 139 | min(column) { 140 | this.options.select = [`MIN(${column}) as min`]; 141 | const { sql } = this.processSQL(); 142 | return this.sql({ sql, nestTables: false }, (res) => { 143 | return res[0].min; 144 | }); 145 | } 146 | 147 | sum(column) { 148 | this.options.select = [`SUM(${column}) as sum`]; 149 | const { sql } = this.processSQL(); 150 | return this.sql({ sql, nestTables: false }, (res) => { 151 | return res[0].sum; 152 | }); 153 | } 154 | 155 | avg(column) { 156 | this.options.select = [`AVG(${column}) as avg`]; 157 | const { sql } = this.processSQL(); 158 | return this.sql({ sql, nestTables: false }, (res) => { 159 | return res[0].avg; 160 | }); 161 | } 162 | 163 | get() { 164 | const { sql, values } = this.processSQL(); 165 | return this.sql({ sql, values }); 166 | } 167 | 168 | toSql(showValues = false, type = "select") { 169 | return new Promise((resolve, reject) => { 170 | try { 171 | let { sql, values } = this.processSQL(type); 172 | if (showValues) sql = mysql.format(sql, values); 173 | this.init(); 174 | resolve(sql); 175 | } catch (error) { 176 | reject(error); 177 | } 178 | }); 179 | } 180 | 181 | value() { 182 | return this.first((res) => { 183 | return res[0].username; 184 | }); 185 | } 186 | 187 | update() { 188 | const { sql, values } = this.processSQL("update"); 189 | return this.sql({ sql, values, nestTables: false }); 190 | } 191 | 192 | insert() { 193 | const { sql, values } = this.processSQL("insert"); 194 | return this.sql({ sql, values, nestTables: false }); 195 | } 196 | 197 | delete() { 198 | const { sql, values } = this.processSQL("delete"); 199 | return this.sql({ sql, values, nestTables: false }); 200 | } 201 | 202 | truncate() { 203 | const { sql } = this.processSQL("truncate"); 204 | return this.sql({ sql, nestTables: false }); 205 | } 206 | 207 | createTable({ 208 | options: { fields }, 209 | getCharset: charset, 210 | getEngine: engine, 211 | getCollation: collation, 212 | }) { 213 | fields = this.processFields(fields).join(", "); 214 | const sql = `CREATE TABLE ${this.model.getTableName} (${fields}) DEFAULT CHARACTER SET ${charset} COLLATE '${collation}' ENGINE = ${engine}`; 215 | return this.sql({ sql, format: true }); 216 | } 217 | 218 | alterTable({ options: { fields, drop, column, ...options } }) { 219 | fields = this.processFields(fields).join(", "); 220 | let modify = /(modify|rename) column/i.test(fields); 221 | if (drop) fields = `\`${column}\``; 222 | const sql = `ALTER TABLE ${this.model.getTableName} ${ 223 | modify ? "" : `${drop ? "DROP" : "ADD"} COLUMN ` 224 | }${fields}`; 225 | return this.sql({ sql, format: true }); 226 | } 227 | 228 | dropTable({ options: { ifExists } }) { 229 | let sql = `DROP TABLE ${ifExists === true ? "IF EXISTS " : ""}${ 230 | this.model.getTableName 231 | }`; 232 | return this.sql({ sql, format: true }); 233 | } 234 | 235 | /* 236 | returns a new schema builder instance 237 | */ 238 | 239 | queryBuilder(options = {}) { 240 | return new QueryBuilder(options); 241 | } 242 | 243 | schemaBuilder(options = {}) { 244 | return new SchemaBuilder(options); 245 | } 246 | 247 | /* 248 | returns a new faker builder instance 249 | */ 250 | fakerBuilder(options) { 251 | return new FakerBuilder(options); 252 | } 253 | 254 | processSQL(type = "select") { 255 | const model = this.model; 256 | let sql = ""; 257 | let { joins, values: joinValues } = this.processJoins(); 258 | let { where, values } = this.processWhere(model.options?.where); 259 | const limit = `${ 260 | model.options?.limit ? ` LIMIT ${model.options.limit}` : "" 261 | }`; 262 | const offset = `${ 263 | model.options?.offset ? ` OFFSET ${model.options.offset}` : "" 264 | }`; 265 | const orderBy = this.processOrderBy(); 266 | const groupBy = this.processGroupBy(); 267 | 268 | let { columns, values: updateValues } = ["update", "insert"].includes(type) 269 | ? this.processColumns(type) 270 | : { columns: "", values: [] }; 271 | switch (type) { 272 | case "update": 273 | values = [...updateValues, ...values]; 274 | sql = `UPDATE ${model.getTableName} SET ${columns}${joins}${where}${orderBy}${limit}${offset}`; 275 | break; 276 | case "insert": 277 | values = [...updateValues, ...values]; 278 | sql = `INSERT INTO ${model.getTableName} ${columns}`; 279 | break; 280 | case "delete": 281 | sql = `DELETE FROM ${model.getTableName}${joins}${where}${groupBy}${orderBy}${limit}${offset}`; 282 | break; 283 | case "truncate": 284 | values = [...updateValues, ...values]; 285 | sql = `TRUNCATE TABLE ${model.getTableName}`; 286 | break; 287 | default: 288 | sql = `SELECT ${this.processSelect(model.options?.select)} FROM ${ 289 | model.getTableName 290 | }${joins}${where}${groupBy}${orderBy}${limit}${offset}`; 291 | } 292 | return { sql, values }; 293 | } 294 | 295 | processColumns(type = "update") { 296 | let { set } = this.options; 297 | set = set ?? []; 298 | if (!Array.isArray(set)) set = [set]; 299 | let columns = _.uniq( 300 | _.flatten( 301 | set.map((item) => { 302 | let keys = Object.keys(item); 303 | if (this.model.getTimestamps) { 304 | if (type === "update") keys.push(this.model.getUpdatedAt); 305 | if (type === "insert") keys.push(this.model.getCreatedAt); 306 | } 307 | return keys; 308 | }) 309 | ) 310 | ); 311 | let values = [ 312 | set.map((item) => { 313 | let values = Object.values(item); 314 | if (this.model.getTimestamps) { 315 | values.push(this.model.model.currentDate); 316 | } 317 | return values; 318 | }), 319 | ]; 320 | if (type === "insert") columns = `(\`${columns.join("`, `")}\`) VALUES ?`; 321 | if (type === "update") columns = `SET \`${columns.join("` = ?, `")}\` = ?`; 322 | 323 | return { 324 | columns, 325 | values, 326 | }; 327 | } 328 | 329 | processSelect(select) { 330 | return !select || select.length == 0 331 | ? "*" 332 | : `${select 333 | .map((s) => { 334 | if (/ as /gi.test(s)) { 335 | s = s.split(/ as /gi); 336 | s = `${/\(/gi.test(s[0]) ? `${s[0]}` : `\`${s[0]}\``} AS \`${ 337 | s[1] 338 | }\``; 339 | } else { 340 | s = `\`${s}\``; 341 | } 342 | return s; 343 | }) 344 | .join(", ")}`; 345 | } 346 | 347 | processWhere(where) { 348 | if (!where || where.length === 0) return { where: "", values: [] }; 349 | let values = []; 350 | where = where 351 | .map((item, ind) => { 352 | let { 353 | column, 354 | operator, 355 | value, 356 | orWhere: or, 357 | separator, 358 | parenthesis, 359 | isFunction, 360 | } = item; 361 | 362 | if (typeof column === "object") { 363 | let isOr = false; 364 | item = `(${item 365 | .map((item, idx) => { 366 | let { 367 | column, 368 | operator, 369 | value, 370 | orWhere, 371 | separator, 372 | parenthesis, 373 | isFunction, 374 | } = item; 375 | 376 | isOr = orWhere; 377 | if (Array.isArray(value)) values = [...values, ...value]; 378 | else values.push(value); 379 | value = Array.isArray(value) 380 | ? `${parenthesis ? "(" : ""}${value 381 | .map(() => "?") 382 | .join(separator)}${parenthesis ? ")" : ""}` 383 | : value 384 | ? "?" 385 | : ""; 386 | column = `${column 387 | .replaceAll(/\`/gi, "") 388 | .split(".") 389 | .join("`.`")}`; 390 | if (isFunction) 391 | return `${idx !== 0 ? "AND " : ""}${isFunction}(\`${column}\`${ 392 | !operator ? `, ${value}` : "" 393 | })${operator ? ` ${operator} ${value}` : ""}`; 394 | column = `${column 395 | .replaceAll(/\`/gi, "") 396 | .split(".") 397 | .join("`.`")}`; 398 | return `${ 399 | idx !== 0 ? "AND " : "" 400 | }\`${column}\` ${operator} ${value}`; 401 | }) 402 | .join(" ")})`; 403 | return `${ind !== 0 ? (isOr ? "OR " : "AND ") : ""}${item}`; 404 | } else { 405 | if (Array.isArray(value)) values = [...values, ...value]; 406 | else values.push(value); 407 | value = Array.isArray(value) 408 | ? `${parenthesis ? "(" : ""}${value 409 | .map(() => "?") 410 | .join(separator)}${parenthesis ? ")" : ""}` 411 | : value 412 | ? "?" 413 | : ""; 414 | column = `${column.replaceAll(/\`/gi, "").split(".").join("`.`")}`; 415 | if (isFunction) 416 | return `${ 417 | ind !== 0 ? (or ? "OR " : "AND ") : "" 418 | }${isFunction}(\`${column}\`${!operator ? `, ${value}` : ""})${ 419 | operator ? ` ${operator} ${value}` : "" 420 | }`; 421 | column = `${column.replaceAll(/\`/gi, "").split(".").join("`.`")}`; 422 | return `${ 423 | ind !== 0 ? (or ? "OR " : "AND ") : "" 424 | }\`${column}\` ${operator} ${ 425 | Array.isArray(value) 426 | ? `${parenthesis ? "(" : ""}${value 427 | .map(() => "?") 428 | .join(separator)}${parenthesis ? ")" : ""}` 429 | : value 430 | ? "?" 431 | : "" 432 | }`; 433 | } 434 | }) 435 | .join(" "); 436 | where = ` WHERE ${where}`; 437 | values = values.filter((ele) => ele); 438 | return { where, values }; 439 | } 440 | 441 | processJoins() { 442 | const values = []; 443 | if (!this.options?.joins) return { joins: "", values }; 444 | let joins = this.options.joins.map( 445 | ({ table, first, operator, second, type, where }) => { 446 | table = `${table.replaceAll(/\`/gi, "").split(".").join("`.`")}`; 447 | if (/ as /gi.test(table)) { 448 | table = table.split(/ as /gi); 449 | table = `${ 450 | /\(/gi.test(table[0]) ? `${table[0]}` : `\`${table[0]}\`` 451 | } AS \`${table[1]}\``; 452 | } else { 453 | table = `\`${table}\``; 454 | } 455 | first = `${first.replaceAll(/\`/gi, "").split(".").join("`.`")}`; 456 | second = `${second.replaceAll(/\`/gi, "").split(".").join("`.`")}`; 457 | if (where) values.push(where); 458 | return `${type.toUpperCase()} JOIN ${table} ON \`${first}\` ${operator} ${ 459 | where ? "?" : `\`${second}\`` 460 | }`; 461 | } 462 | ); 463 | if (joins.length > 0) joins = ` ${joins.join(" ")}`; 464 | return { joins, values }; 465 | } 466 | 467 | processGroupBy() { 468 | let { groupBy } = this.options; 469 | if (!groupBy || groupBy.length === 0) return ""; 470 | groupBy = groupBy.map((group) => { 471 | return `${/\`/.test(group) ? group : `\`${group}\``}`; 472 | }); 473 | return ` GROUP BY ${groupBy.join(", ")}`; 474 | } 475 | 476 | processOrderBy() { 477 | let { orderBy } = this.options; 478 | if (!orderBy || orderBy.length === 0) return ""; 479 | orderBy = orderBy.map((order) => { 480 | return `${/\`/.test(order[0]) ? order[0] : `\`${order[0]}\``} ${ 481 | order[1] 482 | }`; 483 | }); 484 | return ` ORDER BY ${orderBy.join(", ")}`; 485 | } 486 | 487 | processNestTables(response) { 488 | if ( 489 | response.constructor.name == "OkPacket" || 490 | (Array.isArray(response) && response.length === 0) 491 | ) 492 | return response; 493 | if ( 494 | response[0] && 495 | Object.keys(response[0]).findIndex((ele) => 496 | ["max", "min", "sum", "avg", "count"].includes(ele) 497 | ) >= 0 498 | ) 499 | return response; 500 | 501 | let tableName = this.model.getTableName.replaceAll(/\`/gi, "").split("."); 502 | tableName = tableName[tableName.length - 1]; 503 | const newResponse = []; 504 | response.map((res) => { 505 | Object.entries(res).map((obj) => { 506 | const [key, val] = obj; 507 | let idx; 508 | if (key === tableName) { 509 | idx = newResponse.findIndex((ele) => 510 | ele[this.model.model.primaryKey] 511 | ? ele[this.model.model.primaryKey] === 512 | val[this.model.model.primaryKey] 513 | : false 514 | ); 515 | if (idx < 0) return newResponse.push(val); 516 | else return; 517 | } 518 | const ind = idx >= 0 ? idx : newResponse.length - 1; 519 | if (ind >= 0) { 520 | if (!newResponse[ind][key]) newResponse[ind][key] = []; 521 | if (Object.entries(val).filter((obj) => obj[1]).length === 0) return; 522 | newResponse[ind][key].push(val); 523 | } 524 | }); 525 | }); 526 | return newResponse; 527 | } 528 | 529 | processCasts(response) { 530 | const { casts } = this.model; 531 | if (typeof response !== "object" || !Array.isArray(response) || !casts) 532 | return response; 533 | response.map((res) => { 534 | Object.entries(casts).map((cast) => { 535 | if (res[cast[0]]) 536 | res[cast[0]] = this.supportedCastTypes[cast[1]](res[cast[0]]); 537 | }); 538 | }); 539 | return response; 540 | } 541 | 542 | processFields(fields) { 543 | let uniques = []; 544 | let primaries = []; 545 | if (!fields) return []; 546 | fields = fields.map((field) => { 547 | if (field.primary) primaries.push(`\`${field.name}\``); 548 | if (field.unique) uniques.push(`\`${field.name}\``); 549 | 550 | let str = []; 551 | str.push( 552 | `${ 553 | field.modify ? `${field.rename ? "RENAME" : "MODIFY"} COLUMN ` : "" 554 | }\`${field.name}\`${ 555 | field.type && !field.rename ? ` ${field.type}` : "" 556 | }` 557 | ); 558 | if (!field.rename) { 559 | if (field.length && field.length > 0) str.push(`(${field.length})`); 560 | if (field.unsigned) str.push("UNSIGNED"); 561 | if (field.nullable) str.push("NULL"); 562 | else str.push("NOT NULL"); 563 | if (field.auto_increment) str.push("AUTO_INCREMENT"); 564 | if (field.default) 565 | str.push( 566 | `DEFAULT ${ 567 | ["CURRENT_TIMESTAMP", "GETDATE()"].includes(field.default) 568 | ? field.default 569 | : `'${field.default}'` 570 | }` 571 | ); 572 | if (field.comment) str.push(`COMMENT '${field.comment}'`); 573 | if (field.after) str.push(`AFTER \`${field.after}\``); 574 | } 575 | 576 | if (field.rename) str.push(`TO \`${field.rename}\``); 577 | 578 | return str.join(" ").trim(); 579 | }); 580 | 581 | if (uniques.length > 0) fields.push(`UNIQUE (${uniques.join(", ")})`); 582 | if (primaries.length > 0) 583 | fields.push(`PRIMARY KEY (${primaries.join(", ")})`); 584 | return fields; 585 | } 586 | } 587 | 588 | export default new MysqlAdapter(); 589 | -------------------------------------------------------------------------------- /modules/orm/database.js: -------------------------------------------------------------------------------- 1 | import Model from "./model"; 2 | import adapter from "./adapters"; 3 | import moment from "moment"; 4 | 5 | export default class DB extends Model { 6 | static timestamps = true; 7 | static connection = "default"; 8 | static sync = true; 9 | static queryLog = []; 10 | static loggingQueries = false; 11 | 12 | // static table(tableName) { 13 | // this.tableName = tableName; 14 | // return adapter(this).queryBuilder({ 15 | // model: this, 16 | // sync: this.sync, 17 | // }); 18 | // } 19 | 20 | static connection(connection) { 21 | this.connection = connection; 22 | return adapter(this).queryBuilder({ 23 | model: this, 24 | sync: this.sync, 25 | }); 26 | } 27 | 28 | // static truncate(tableName) { 29 | // this.tableName = tableName; 30 | // return adapter(this).truncateTable(this); 31 | // } 32 | 33 | static dropIfExists(tableName) { 34 | return this.drop(tableName, true); 35 | } 36 | 37 | static drop(tableName, ifExists = false) { 38 | this.tableName = tableName; 39 | this.schemaBuilder = SchemaBuilder; 40 | this.schemaBuilder.options.ifExists = ifExists; 41 | 42 | try { 43 | let dropTable = adapter(this).dropTable(this.schemaBuilder); 44 | this.schemaBuilder.init(); 45 | return dropTable; 46 | } catch (err) { 47 | return err; 48 | } 49 | } 50 | 51 | static enableQueryLog() { 52 | this.loggingQueries = true; 53 | } 54 | 55 | static logQuery(query, bindings, time = null) { 56 | if (this.loggingQueries) this.queryLog.push({ query, bindings, time }); 57 | } 58 | 59 | static getQueryLog() { 60 | return this.queryLog; 61 | } 62 | 63 | static getDifferenceDate(start, end) { 64 | const duration = moment.duration(end.diff(start)); 65 | return duration.milliseconds() + " ms"; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /modules/orm/migration.js: -------------------------------------------------------------------------------- 1 | import "../config/prototypes.config"; 2 | import chalk from "chalk"; 3 | import moment from "moment-timezone"; 4 | import adapter from "./adapters"; 5 | import { getTableName, getFieldName } from "../global/get-name"; 6 | import { microtime } from "../config/utils"; 7 | 8 | export default class Migration { 9 | constructor() { 10 | console.log("this", this); 11 | if (typeof this.constructor.up === "undefined") 12 | throw Error(`Method [up] missing from ${this.constructor.name}`); 13 | if (typeof this.constructor.down === "undefined") 14 | throw Error(`Method [down] missing from ${this.constructor.name}`); 15 | 16 | return this.container 17 | ? this.container.call([this, "run"]) 18 | : this.constructor.run(); 19 | } 20 | 21 | static call(classes, silent = false) { 22 | classes = classes.wrap(); 23 | classes.map(($class) => { 24 | let seeder = this.resolve($class); 25 | let name = seeder.name; 26 | let startTime = microtime(true); 27 | 28 | if (silent === false) console.log(chalk.yellow("Seeding:"), name); 29 | 30 | try { 31 | seeder = new seeder(); 32 | let runTime = parseFloat(microtime(true) - startTime).toFixed(2); 33 | if (silent === false) 34 | console.log(chalk.green("Seeded:"), name, `${runTime} Seconds`); 35 | } catch (err) { 36 | console.log(err.message); 37 | process.exit(); 38 | } 39 | }); 40 | } 41 | 42 | static resolve(Class) { 43 | let instance; 44 | if (typeof Class == "function") instance = Class; 45 | 46 | return instance; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /modules/orm/model.js: -------------------------------------------------------------------------------- 1 | import moment from "moment-timezone"; 2 | import _ from "lodash"; 3 | import QueryBuilder from "./adapters/mysql/builders/query.builder"; 4 | 5 | export default class Model { 6 | static snakeCase = true; 7 | static tableName = null; 8 | 9 | // PRIMARY KEYS 10 | static primaryKey = "id"; 11 | static incrementing = true; 12 | static keyType = "int"; 13 | 14 | //TIMESTAMPS 15 | static timestamps = true; 16 | static dateFormat = "TIMESTAMP"; // [Use Moment JS Format](https://momentjs.com/docs/#/displaying/format/) 17 | static createdAt = "created_at"; // If null not set on query 18 | static updatedAt = "updated_at"; // If null not set on query 19 | static timezone = null; 20 | 21 | // DB CONNECTION 22 | static connection = "default"; 23 | 24 | // Logical Delete 25 | static logicalDelete = false; 26 | static deleted = "deleted"; 27 | static deletedAt = "deleted_at"; 28 | static deletedBy = "deleted_by"; 29 | 30 | static casts = {}; 31 | 32 | options = {} 33 | 34 | constructor() { 35 | this.model = this.constructor; 36 | return new Proxy(this, { 37 | get: function get(_class, method) { 38 | if (method in _class) return _class[method]; 39 | return new QueryBuilder(_class.model)[method] 40 | }, 41 | }); 42 | } 43 | 44 | /** 45 | * Execute a SQL query. In order to avoid SQL Injection attacks, you should always escape any user provided data before using it inside a SQL query. 46 | * @param {string} sql - SQL query, use "?" instead of values to use escaping method. 47 | * @param {array} values - These values will be used to be replaced in the escaping method. 48 | * @returns 49 | */ 50 | static sql(sql, values = []) { 51 | return new this().sql(sql, values); 52 | } 53 | 54 | static get getTimezone() { 55 | return ( 56 | this.timezone || global.TZ || process.env.TZ || "America/Los_Angeles" 57 | ); 58 | } 59 | 60 | static get currentDate() { 61 | return this.formatDate(); 62 | } 63 | 64 | static formatDate(date, notFormat = false) { 65 | date = moment(date).tz(this.getTimezone); 66 | if (!notFormat) 67 | date = date.format( 68 | this.dateFormat != "TIMESTAMP" ? this.dateFormat : "YYYY-MM-DD HH:mm:ss" 69 | ); 70 | return date; 71 | } 72 | 73 | static getDateFormat() { 74 | return this.dateFormat != "TIMESTAMP" 75 | ? this.dateFormat 76 | : "YYYY-MM-DD HH:mm:ss"; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /modules/orm/schema.js: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | import adapter from "./adapters"; 3 | import SchemaBuilder from "./adapters/mysql/builders/schema.builder"; 4 | import { dbConfig } from "./adapters"; 5 | // import { getTableName, getFieldName } from "../global/get-name"; 6 | 7 | export default class Schema extends SchemaBuilder { 8 | static tableName = null; 9 | static connection = "default"; 10 | static timestamps = false; 11 | 12 | constructor() { 13 | if (typeof this.constructor.run === "undefined") 14 | throw Error(`Method [run] missing from ${this.constructor.name}`); 15 | 16 | return this.container 17 | ? this.container.call([this, "run"]) 18 | : this.constructor.run(); 19 | } 20 | 21 | static get getConnection() { 22 | return dbConfig(this.connection); 23 | } 24 | 25 | static create(tableName, action) { 26 | this.tableName = tableName; 27 | this.schemaBuilder = SchemaBuilder; 28 | action(this.schemaBuilder); 29 | 30 | try { 31 | const create = adapter(this).createTable(this.schemaBuilder); 32 | this.schemaBuilder.init(); 33 | return create; 34 | } catch (err) { 35 | throw err; 36 | } 37 | } 38 | 39 | static dropIfExists(tableName) { 40 | return this.drop(tableName, true); 41 | } 42 | 43 | static drop(tableName, ifExists = false) { 44 | this.tableName = tableName; 45 | this.schemaBuilder = SchemaBuilder; 46 | this.schemaBuilder.options.ifExists = ifExists; 47 | 48 | try { 49 | let dropTable = adapter(this).dropTable(this.schemaBuilder); 50 | this.schemaBuilder.init(); 51 | return dropTable; 52 | } catch (err) { 53 | return err; 54 | } 55 | } 56 | 57 | static table(tableName, action) { 58 | this.tableName = tableName; 59 | this.schemaBuilder = SchemaBuilder; 60 | action(this.schemaBuilder); 61 | 62 | try { 63 | const alterTable = adapter(this).alterTable(this.schemaBuilder); 64 | this.schemaBuilder.init(); 65 | return alterTable; 66 | } catch (err) { 67 | throw err; 68 | } 69 | } 70 | 71 | static get getTimezone() { 72 | return ( 73 | this.timezone || global.TZ || process.env.TZ || "America/Los_Angeles" 74 | ); 75 | } 76 | 77 | static get currentDate() { 78 | return this.formatDate(); 79 | } 80 | 81 | static formatDate(date, notFormat = false) { 82 | date = moment(date).tz(this.getTimezone); 83 | if (!notFormat) 84 | date = date.format( 85 | notFormat 86 | ? "" 87 | : this.dateFormat != "TIMESTAMP" 88 | ? this.dateFormat 89 | : "YYYY-MM-DD HH:mm:ss" 90 | ); 91 | return date; 92 | } 93 | 94 | static getDateFormat() { 95 | return this.dateFormat != "TIMESTAMP" 96 | ? this.dateFormat 97 | : "YYYY-MM-DD HH:mm:ss"; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /modules/orm/seeder.js: -------------------------------------------------------------------------------- 1 | import "../config/prototypes.config"; 2 | import chalk from "chalk"; 3 | import moment from "moment-timezone"; 4 | import adapter from "./adapters"; 5 | import { getTableName, getFieldName } from "../global/get-name"; 6 | import { microtime } from "../config/utils"; 7 | 8 | export default class Seeder { 9 | static container; 10 | static command; 11 | 12 | constructor() { 13 | if (typeof this.constructor.run === "undefined") 14 | throw Error(`Method [run] missing from ${this.constructor.name}`); 15 | 16 | return this.container 17 | ? this.container.call([this, "run"]) 18 | : this.constructor.run(); 19 | } 20 | 21 | static async call(classes, silent = false) { 22 | classes = classes.wrap(); 23 | for (const Seeder of classes) { 24 | if (!Seeder.prototype instanceof Seeder) return false; 25 | let name = Seeder.name; 26 | let startTime = microtime(true); 27 | 28 | if (silent === false) console.log(chalk.yellow("Seeding:"), name); 29 | try { 30 | Seeder = await Seeder.run(); 31 | let runTime = parseFloat(microtime(true) - startTime).toFixed(2); 32 | 33 | if (silent === false) 34 | console.log(chalk.green("Seeded:"), name, `${runTime} Seconds`); 35 | } catch (err) { 36 | console.log(chalk.red("Error:"), err.message); 37 | if (global.dev) console.log(err); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /obremap.config.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | module.exports = { 4 | config: "test", 5 | timezone: "America/Mexico_City", 6 | howImport: "require", 7 | databases: { 8 | default: process.env.DATABASE_URL_OTHER, 9 | test: { 10 | host: process.env.DB_HOST, 11 | user: process.env.DB_USERNAME, 12 | password: process.env.DB_PASSWORD, 13 | database: process.env.DB_NAME, 14 | driver: process.env.DB_DRIVER, 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@moccacoders/node-obremap", 3 | "version": "5.1.11", 4 | "description": "OBREMAP Node ORM is an Object-Relational Mapping tool for Node JS. OBREMAP provides a beautiful and simple ActiveRecord implementation to work with your database.", 5 | "homepage": "https://github.com/moccacoders/node-obremap#readme", 6 | "bin": { 7 | "obremap": "dist/cli/index.js" 8 | }, 9 | "main": "dist/index.js", 10 | "scripts": { 11 | "start": "yarn build && yarn local-cli && yarn watch", 12 | "watch": "babel modules --watch --out-dir dist --copy-files", 13 | "local-cli": "npm install -g .", 14 | "local-test": "mocha --timeout 0 --exit --require @babel/register --require tests/setup/local-setup tests/unit/*/*.test.js tests/unit/*/*/*.test.js --recursive", 15 | "build": "babel modules --out-dir dist --no-comments --copy-files", 16 | "local-cover": "nyc --reporter=text mocha --timeout 0 -- --require @babel/register --require tests/setup/local-setup tests/unit/*/*.test.js tests/unit/*/*/*.test.js --recursive --exit", 17 | "test": "jest --unhandled-rejections=strict", 18 | "test-file": "mocha --timeout 0 --exit --require @babel/register --require tests/setup/local-setup " 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/moccacoders/node-obremap.git" 23 | }, 24 | "keywords": [ 25 | "orm", 26 | "node-eloquent", 27 | "node-orm", 28 | "eloquent", 29 | "node", 30 | "laravel", 31 | "database", 32 | "mysql", 33 | "cli", 34 | "database-models", 35 | "models", 36 | "query-builder", 37 | "relationship" 38 | ], 39 | "author": "Raymundo Salazar ", 40 | "license": "MIT", 41 | "dependencies": { 42 | "arg": "4.1.3", 43 | "chalk": "4.1.0", 44 | "dotenv": "^8.2.0", 45 | "faker": "^5.1.0", 46 | "inquirer": "^7.3.3", 47 | "moment": "^2.29.1", 48 | "moment-timezone": "^0.5.32", 49 | "mysql": "2.18.1", 50 | "pluralize": "^8.0.0", 51 | "yargs": "^16.1.1" 52 | }, 53 | "devDependencies": { 54 | "@babel/cli": "^7.15.7", 55 | "@babel/core": "^7.15.8", 56 | "@babel/plugin-proposal-class-properties": "^7.12.1", 57 | "@babel/preset-env": "^7.18.6", 58 | "@moccacoders/node-obremap": "./", 59 | "babel-plugin-module-resolver": "^4.1.0", 60 | "concat-stream": "2.0.0", 61 | "inquirer-test": "2.0.1", 62 | "jest": "^28.1.3" 63 | }, 64 | "bugs": { 65 | "url": "https://github.com/moccacoders/node-obremap/issues", 66 | "email": "obremap@moccacoders.com" 67 | }, 68 | "directories": { 69 | "test": "tests" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tests/orm/model/index.test.js: -------------------------------------------------------------------------------- 1 | const Model = require("orm/model").default; 2 | const Schema = require("orm/schema").default; 3 | 4 | describe("Validate model structure", () => { 5 | test("Model is a Model Object", () => { 6 | expect(Model.constructor.name).toBe("Function"); 7 | expect(Model.name).toBe("Model"); 8 | }); 9 | 10 | test("Model has sql method", async () => { 11 | expect(Model.sql.name).toBe("sql"); 12 | const { database } = Schema.getConnection; 13 | const response = await Model.sql( 14 | `SELECT * FROM information_schema.tables WHERE table_schema = ?`, 15 | [database] 16 | ); 17 | expect(response.length).toBeGreaterThan(0); 18 | expect(response[0].TABLE_SCHEMA).toBe(database); 19 | }); 20 | 21 | test("Model has getTimezone", async () => { 22 | expect(Model.getTimezone.constructor.name).toBe("String"); 23 | const timezone = await Model.getTimezone; 24 | const config = require(`${process.cwd()}/obremap.config.js`); 25 | expect([process.env.TZ, config.timezone, "America/Los_Angeles"]).toContain( 26 | timezone 27 | ); 28 | }); 29 | 30 | test("Model has formatDate", async () => { 31 | expect(Model.formatDate.name).toBe("formatDate"); 32 | expect(Model.formatDate()).toMatch( 33 | /([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/ 34 | ); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/orm/model/properties.test.js: -------------------------------------------------------------------------------- 1 | const User = require("tests/orm/setup/user.model"); 2 | const Usuario = require("tests/orm/setup/usuario.model"); 3 | 4 | describe("Default properties", () => { 5 | test("Validating the properties configured by default", () => { 6 | expect(User.getTableName).toBe("`users`"); 7 | expect(User.primaryKey).toBe("id"); 8 | expect(User.incrementing).toBe(true); 9 | expect(User.keyType).toBe("int"); 10 | expect(User.timestamps).toBe(true); 11 | expect(User.dateFormat).toBe("TIMESTAMP"); 12 | expect(User.createdAt).toBe("created_at"); 13 | expect(User.updatedAt).toBe("updated_at"); 14 | expect(User.timezone).toBe(null); 15 | expect(User.connection).toBe("default"); 16 | expect(User.logicalDelete).toBe(false); 17 | expect(User.deleted).toBe("deleted"); 18 | expect(User.deletedAt).toBe("deleted_at"); 19 | expect(User.deletedBy).toBe("deleted_by"); 20 | expect(typeof User.casts).toBe("object"); 21 | expect(Object.entries(User.casts).length).toBe(3); 22 | }); 23 | 24 | test("Validating the custom properties", () => { 25 | expect(Usuario.getTableName).toBe("`usuarios`"); 26 | expect(Usuario.primaryKey).toBe("user_id"); 27 | expect(Usuario.incrementing).toBe(false); 28 | expect(Usuario.keyType).toBe("string"); 29 | expect(Usuario.timestamps).toBe(false); 30 | expect(Usuario.dateFormat).toBe("DD-MM-YYYY HH:mm:ss"); 31 | expect(Usuario.createdAt).toBe("creado"); 32 | expect(Usuario.updatedAt).toBe("actualizado"); 33 | expect(Usuario.timezone).toBe("America/Los_Angeles"); 34 | expect(Usuario.connection).toBe("test"); 35 | expect(Usuario.logicalDelete).toBe(true); 36 | expect(Usuario.deleted).toBe("eliminado"); 37 | expect(Usuario.deletedAt).toBe("fecha_eliminado"); 38 | expect(Usuario.deletedBy).toBe("eliminado_por"); 39 | expect(typeof Usuario.casts).toBe("object"); 40 | expect(Object.entries(Usuario.casts).length).toBe(0); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /tests/orm/model/queryBuilder/select.test.js: -------------------------------------------------------------------------------- 1 | const User = require("tests/orm/setup/user.model"); 2 | 3 | describe("Select() method", () => { 4 | test("Select method returns a query builder", () => { 5 | const users = User.select(); 6 | expect(users.builder).toBeTruthy(); 7 | expect(typeof users.options.select).toEqual("object"); 8 | expect(users.options.select.length).toEqual(0); 9 | }); 10 | 11 | test("Select method with one column", () => { 12 | const users = User.select("id"); 13 | expect(users.options.select.length).toEqual(1); 14 | expect(users.options.select[0]).toEqual("id"); 15 | }); 16 | 17 | test("Select method with two columns", () => { 18 | const users = User.select("id", "name"); 19 | expect(users.options.select.length).toEqual(2); 20 | expect(users.options.select[0]).toEqual("id"); 21 | expect(users.options.select[1]).toEqual("name"); 22 | }); 23 | 24 | test("Select method to SQL", async () => { 25 | const users = await User.select("id", "name").toSql(); 26 | expect(users).toEqual("SELECT `id`, `name` FROM `users`"); 27 | }); 28 | 29 | test("Select method to GET", async () => { 30 | const users = await User.select("id", "name").get(); 31 | expect(typeof users).toEqual("object"); 32 | expect(users.length).toBeGreaterThan(0); 33 | expect(users[0]).toHaveProperty("id"); 34 | expect(users[0]).toHaveProperty("name"); 35 | }); 36 | 37 | test("Select method with AS", async () => { 38 | const users = await User.select("id", "name AS nombre") 39 | .join( 40 | "here.catalog_length_units as length_unit", 41 | "length_unit_id", 42 | "length_unit.id", 43 | "left" 44 | ) 45 | .toSql(); 46 | expect(users).toEqual("SELECT `id`, `name` AS `nombre` FROM `users`"); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /tests/orm/model/queryBuilder/setCreatedAt.test.js: -------------------------------------------------------------------------------- 1 | const User = require("tests/orm/setup/user.model"); 2 | 3 | describe("setCreatedAt() method", () => { 4 | test("setCreatedAt method returns a query builder", () => { 5 | const users = User.setCreatedAt("created_at"); 6 | expect(users.builder).toBeTruthy(); 7 | }); 8 | 9 | test("setCreatedAt method should change created_at column name", () => { 10 | expect(User.getCreatedAt).toEqual("created_at"); 11 | const users = User.setCreatedAt("created_date"); 12 | expect(users.getCreatedAt).toEqual("created_date"); 13 | }); 14 | 15 | test("setCreatedAt method to createToSql", async () => { 16 | const users = await User.setCreatedAt("created_date") 17 | .set("name", "Raymundo") 18 | .insertToSql(); 19 | expect(users).toEqual( 20 | "INSERT INTO `users` (`name`, `created_date`) VALUES (?, ?)" 21 | ); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/orm/model/queryBuilder/setTimestamps.test.js: -------------------------------------------------------------------------------- 1 | const User = require("tests/orm/setup/user.model"); 2 | 3 | describe("setTimestamps() method", () => { 4 | test("setTimestamps method returns a query builder", () => { 5 | const users = User.setTimestamps(true); 6 | expect(users.builder).toBeTruthy(); 7 | }); 8 | 9 | test("setTimestamps method should change timestamps", () => { 10 | expect(User.timestamps).toBeTruthy(); 11 | const users = User.setTimestamps(false); 12 | expect(users.options.timestamps).toBeFalsy(); 13 | }); 14 | 15 | test("setTimestamps as TRUE method to insertToSql", async () => { 16 | const users = await User.setTimestamps(true) 17 | .set("name", "Raymundo") 18 | .insertToSql(); 19 | expect(users).toEqual( 20 | "INSERT INTO `users` (`name`, `created_at`) VALUES (?, ?)" 21 | ); 22 | }); 23 | 24 | test("setTimestamps as TRUE method to updateToSql", async () => { 25 | const users = await User.setTimestamps(true) 26 | .set("name", "Rodolfo") 27 | .updateToSql(); 28 | expect(users).toEqual("UPDATE `users` SET `name` = ?, `updated_at` = ?"); 29 | }); 30 | 31 | test("setTimestamps as FALSE method to insertToSql", async () => { 32 | const users = await User.setTimestamps(false) 33 | .set("name", "Raymundo") 34 | .insertToSql(); 35 | expect(users).toEqual("INSERT INTO `users` (`name`) VALUES (?)"); 36 | }); 37 | 38 | test("setTimestamps as FALSE method to updateToSql", async () => { 39 | const users = await User.setTimestamps(false) 40 | .set("name", "Rodolfo") 41 | .updateToSql(); 42 | expect(users).toEqual("UPDATE `users` SET `name` = ?"); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /tests/orm/model/queryBuilder/setUpdatedAt.test.js: -------------------------------------------------------------------------------- 1 | const User = require("tests/orm/setup/user.model"); 2 | 3 | describe("setUpdatedAt() method", () => { 4 | test("setUpdatedAt method returns a query builder", () => { 5 | const users = User.setUpdatedAt("updated_at"); 6 | expect(users.builder).toBeTruthy(); 7 | }); 8 | 9 | test("setUpdatedAt method should change updated_at column name", () => { 10 | expect(User.getUpdatedAt).toEqual("updated_at"); 11 | const users = User.setUpdatedAt("update_date"); 12 | expect(users.getUpdatedAt).toEqual("update_date"); 13 | }); 14 | 15 | test("setUpdatedAt method to createToSql", async () => { 16 | const users = await User.setUpdatedAt("update_date") 17 | .set("name", "Raymundo") 18 | .updateToSql(); 19 | expect(users).toEqual("UPDATE `users` SET `name` = ?, `update_date` = ?"); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /tests/orm/model/queryBuilder/table.test.js: -------------------------------------------------------------------------------- 1 | const User = require("tests/orm/setup/user.model"); 2 | 3 | describe("Table() method", () => { 4 | test("Table method returns a query builder", () => { 5 | const users = User.table(); 6 | expect(users.builder).toBeTruthy(); 7 | }); 8 | 9 | test("Table method should change tableName", () => { 10 | expect(User.getTableName).toEqual("`users`"); 11 | const users = User.table("user"); 12 | expect(typeof users.getTableName).toEqual("string"); 13 | expect(users.getTableName).toEqual("`user`"); 14 | }); 15 | 16 | test("Table method to SQL", async () => { 17 | const users = await User.table("user").toSql(); 18 | expect(users).toEqual("SELECT * FROM `user`"); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/orm/model/queryBuilder/where.test.js: -------------------------------------------------------------------------------- 1 | const User = require("tests/orm/setup/user.model"); 2 | 3 | describe("Where() method", () => { 4 | test("Where method returns a query builder", () => { 5 | const users = User.where("column", "value") 6 | .where("column2", "value2") 7 | .where("table.column2", "value2"); 8 | expect(users.options).toBeTruthy(); 9 | expect(users.options.where).toBeTruthy(); 10 | // expect(typeof users.options.where).toEqual("object"); 11 | // expect(users.options.where.length).toEqual(1); 12 | // expect(users.options.where[0]).toEqual({ 13 | // column: "column", 14 | // operator: "=", 15 | // value: "value", 16 | // orWhere: false, 17 | // separator: ", ", 18 | // parenthesis: true, 19 | // isFunction: false, 20 | // }); 21 | // }); 22 | 23 | // test("Where method with another operator", () => { 24 | // const users = User.where("column", "!=", "value"); 25 | // console.log("users", users.options.where); 26 | // expect(users.options.where[0]).toEqual({ 27 | // column: "column", 28 | // operator: "!=", 29 | // value: "value", 30 | // orWhere: false, 31 | // separator: ", ", 32 | // parenthesis: true, 33 | // isFunction: false, 34 | // }); 35 | }); 36 | 37 | // test("Where method with two columns", () => { 38 | // const users = User.where("id", "name"); 39 | // expect(users.options.where.length).toEqual(2); 40 | // expect(users.options.where[0]).toEqual("id"); 41 | // expect(users.options.where[1]).toEqual("name"); 42 | // }); 43 | 44 | // test("Where method to SQL", async () => { 45 | // const users = await User.where("id", "name").toSql(); 46 | // expect(users).toEqual("SELECT `id`, `name` FROM `users`"); 47 | // }); 48 | 49 | // test("Where method to GET", async () => { 50 | // const users = await User.where("id", "name").get(); 51 | // expect(typeof users).toEqual("object"); 52 | // expect(users.length).toBeGreaterThan(0); 53 | // expect(users[0]).toHaveProperty("id"); 54 | // expect(users[0]).toHaveProperty("name"); 55 | // }); 56 | }); 57 | -------------------------------------------------------------------------------- /tests/orm/setup/user.model.js: -------------------------------------------------------------------------------- 1 | const Model = require("orm/model").default; 2 | class User extends Model { 3 | static casts = { 4 | deleted: "boolean", 5 | created_at: "timestamp", 6 | deleted_at: "timestamp", 7 | }; 8 | } 9 | module.exports = new User(); 10 | -------------------------------------------------------------------------------- /tests/orm/setup/usuario.model.js: -------------------------------------------------------------------------------- 1 | // THIS MODEL FILE WAS CREATED BY OBREMAP CLI 2 | const Model = require("orm/model").default; 3 | module.exports = class User extends Model { 4 | /* 5 | overwrite table name, this action is optional 6 | static tableName = "table_name"; 7 | */ 8 | static tableName = "usuarios"; 9 | static primaryKey = "user_id"; 10 | static incrementing = false; 11 | static keyType = "string"; 12 | static timestamps = false; 13 | static dateFormat = "DD-MM-YYYY HH:mm:ss"; 14 | static createdAt = "creado"; 15 | static updatedAt = "actualizado"; 16 | static timezone = "America/Los_Angeles"; 17 | static connection = "test"; 18 | static logicalDelete = true; 19 | static deleted = "eliminado"; 20 | static deletedAt = "fecha_eliminado"; 21 | static deletedBy = "eliminado_por"; 22 | }; 23 | --------------------------------------------------------------------------------