├── .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 | 
4 | [](https://nodejs.org)
5 | 
6 | 
7 | 
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 | 
5 | [](https://nodejs.org)
6 | 
7 | 
8 | 
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 |
--------------------------------------------------------------------------------