@allnulled/flexible-db
v5.0.16
Published
Base de datos basada en JavaScript
Maintainers
Readme
flexible-db
Base de datos basada en JavaScript.
Índice
- flexible-db
- Índice
- Links
- Instalación
- Features
- API
- Database API
db = FlexibleDB.create(options:Object): Objectdb.getSchema(): Objectdb.getRelationsSchema(): Objectdb.setSchema(schema:Object): voiddb.dehydrate(): Stringdb.hydrate(stringifiedDatabase:String): Promise<void>db.selectMany(table:String, filter:Function|Array = SELECT_ALL_FILTER, withTableType:Boolean|String = false): Promise<Array>db.selectOne(table:String, id:Number, withTableType:Boolean|String = false): Promise<Object>db.selectByUid(uid:String): Promise<Object>db.selectByLabel(table:String, label:String): Promise<Array>db.selectByLabels(table:String, labels:Array<String>): Promise<Array>db.insertOne(table:String, value:Object): Promise<Integer>db.insertMany(table:String, values:Array<Object>): Promise<Array<Integer>>db.updateOne(table:String, id:Integer, properties:Object): Promise<Boolean>db.updateMany(table:String, filter:Function, properties:Object): Promise<Array<Integer>>db.deleteOne(table:String, id:Integer): Promise<Boolean>db.deleteMany(table:String, filter:Function): Promise<Array<Integer>>db.renameTable(table:String, newName:String): Promise<Boolean>db.renameColumn(table:String, column:String, newName:String): Promise<Boolean>db.addTable(table:String): Promise<Boolean>db.addColumn(table:String, column:String, metadata:Object): Promise<Boolean>db.dropTable(table:String): Promise<Boolean>db.dropColumn(table:String, column:String): Promise<Boolean>db.modifyAll(table:String, modifier:Function, errorHandler:Function = console.log): Promise<Array>db.expandRecords(sourceTable:String, dataset:Array, expandSpec:Object): Promise<Array>db.attachRecords(sourceTable:String, newColumn:String, referredTable:String, referredColumn:String, dataset:Array): Promise<Array>
- Server API
server = db.createServer(port:Integer):BasicServerserver.start(port:Integer = this.$port):Promiseserver.login(alias:String|null, email:String|null, password:String):Promise<String>server.logout(token:String):Promise<Boolean>server.authenticateRequest(request:ExpressRequest):Promise<Object|Boolean>server.getFirewall():AsyncFunctionserver.setFirewall(firewallCode:String):BasicServerserver.triggerFirewall(operation:String, args:Array, authenticationToken:String, request:ExpressRequest, response:ExpressResponse)server.stop():BasicServerserver.clone():BasicServerserver.generateSessionToken():Stringserver.onAuthenticate(opcode:String, args:Array, authenticationToken:String = null, request:ExpressRequest = null, response:ExpressResponse = null):Stringserver.operation(opcode:String, args:Array):Promise<any>
- Dataset API
proxy = db.createDataset(dataset:Array, table:String = null)proxy.$dataset:Arrayproxy.$database:Objectproxy.$table:Stringproxy.findBySelector(selectorList:Array = []):BasicDatasetproxy.setDataset(dataset:Array):BasicDatasetproxy.setTable(table:String):BasicDatasetproxy.setDatabase(database:Object):BasicDatasetproxy.getDataset():anyproxy.copy():BasicDatasetproxy.clone():BasicDatasetproxy.deduplicate():BasicDatasetproxy.filterById(id:String):BasicDatasetproxy.mapById(id:String):BasicDatasetproxy.flat():BasicDatasetproxy.hasAnyOf(list:Array):BasicDatasetBasicDataset.hasAnyOf(list1:Array, list2:Array):BasicDatasetasync proxy.filter(callback:Function):Promise<BasicDataset>async proxy.map(callback:Function):Promise<BasicDataset>async proxy.reduce(callback:Function, original:any = []):Promise<BasicDataset>async proxy.modify(callback:Function):Promise<BasicDataset>async proxy.each(callback:Function):Promise<BasicDataset>proxy.filterSync(callback:Function):BasicDatasetproxy.mapSync(callback:Function):BasicDatasetproxy.reduceSync(callback:Function, original:any = []):BasicDatasetproxy.eachSync(callback:Function):BasicDatasetproxy.modifySync(callback:Function):BasicDatasetproxy.debug(message:String):BasicDatasetproxy.filterByEval(callbackSource:String):Promise<BasicDataset>proxy.mapByEval(callbackSource:String):Promise<BasicDataset>proxy.reduceByEval(callbackSource:String, original:any = []):Promise<BasicDataset>proxy.eachByEval(callbackSource:String):Promise<BasicDataset>proxy.modifyByEval(callbackSource:String):Promise<BasicDataset>proxy.pipeByMatrix(signatures:Array):Promise<BasicDataset>async proxy.groupByColumn(column:String):BasicDatasetasync proxy.groupByColumns(columns:Array<String>):BasicDatasetasync proxy.groupByCallback(callback:Function):Promise<BasicDataset>async proxy.groupByCallbacks(callbacks:Array<Function>):Promise<BasicDataset>async proxy.groupByEval(evalSource:String):Promise<BasicDataset>async proxy.groupByEvals(evalsList:Array<String>):Promise<BasicDataset>proxy.sortByColumn(column:String):BasicDatasetproxy.sortByColumns(columns:Array<String>):BasicDatasetproxy.sortByCallback(callback:Function):BasicDatasetproxy.sortByEval(source:String):BasicDatasetproxy.extendBy(overrider:Object = {}):BasicDatasetasync proxy.expandRecords(sourceTable:String, expandSpec:Object = {}):Promise<BasicDataset>async proxy.attachRecords(sourceTable:String, newColumn:String, referredTable:String, referredColumn:String):Promise<BasicDataset>
- Query API
- Tree API
- Tests
- Ejemplo práctico
Links
Instalación
npm i -s @allnulled/flexible-dbFeatures
- Soporta esquema estricto (obligatorio, no es tan flexible)
- Soporta referencias de objetos y de listas externas
- Soporta integridad de estas referencias
- Soporta semáforos automáticamente en
node.js- Para que se pueda usar en servidores
- Soporta los tipos:
"boolean""integer""float""string""object""array""object-reference"(con integridad referencial)"array-reference"(con integridad referencial)
- Soporta métodos de esquema
- Soporta métodos de persistencia
- Soporta métodos de CRUD
- Soporta una API para proxificar datasets
- Soporta una API para desplegar servidores HTTP
- Soporta un lenguaje DSL para concentrar el control de la autentificación y autorización
- Con un sistema de eventos básicos de esquema y CRUD a disposición
- Con gestión automática para
loginylogout - Con opción de autentificar usuarios y gestionar sesiones con
authenticateRequest- Comprometiendo parcialmente el esquema con tablas específicas en español
API
La API se descompone en varias:
Database API
db = FlexibleDB.create(options:Object): Object
Crea una instancia de base de datos.
Dentro contiene:
db.$idsdb.$datadb.$schema
Acepta opciones mediante options:Object donde puedes especificar:
trace = truesi quieres activar el traceo de los métodosonPersist:Functionsi quieres sobreescribir el método automático de persistencia- La función recibe el objeto
dbque tiene el métododb.dehydrate()al uso de persistencia fácil - Puedes ver el
test-of-persistence.jspara ver un ejemplo de uso
- La función recibe el objeto
onTrigger:Functionsi quieres establecer un método automático de trigger- La función recibe los 3 parámetros:
event:String, parameters:Array, db:Object
- Puedes ver el
test-of-trigger.jspara ver un ejemplo de uso
- La función recibe los 3 parámetros:
onLock:AsyncFunctionsi quieres sobreescribir el método de bloqueo de persistencia por defecto.onUnlock:AsyncFunctionsi quieres sobreescribir el método de desbloqueo de persistencia por defecto.lockingFile:Stringsi quieres sobreescribir el fichero de bloqueo de persistencia por defecto. Si no, seráprocess.cwd() + "/db-locker.txt"lockingCheckIntervalsi quieres sobreescribir el intervalo de espera muerta en bloqueo de persistencia. Si no, será0.
db.getSchema(): Object
Devuelve el esquema de datos, en this.$schema.
db.getRelationsSchema(): Object
Devuelve el esquema de relaciones de datos, en runtime, se construye al vuelo.
Se espera un objeto con:
<table>.<column>.(actives|passives)[*].quantity: puede ser1oN.<table>.<column>.(actives|passives)[*].referredTable: puede ser una tabla deldb.$schema.
En el * se accede a una de dos:
- o a la
<tableId><columnId>en relaciones activas. - o a la
<referredTable>.<columnId>en relaciones pasivas.
db.setSchema(schema:Object): void
El schema debe ser un objeto válido que represente al esquema de datos.
Se pondrá en this.$schema.
Se espera un objeto que:
- contenga:
<table>.<column>.type ="boolean""integer""float""string""object""array""object-reference"- con
<table>.<column>.referredTable = <una tabla conocida por schema>
- con
"array-reference"- con
<table>.<column>.referredTable = <una tabla conocida por schema>
- con
- contenga opcionalmente:
<table>.<column>.nullable = true- Provocará error si ese campo se pasa como
null
- Provocará error si ese campo se pasa como
- contenga opcionalmente:
<table>.<column>.unique = true- Provocará error si ese campo está repetido en otra row
- No provocará error si ese campo se pasa como
null
- contenga opcionalmente:
<table>.<column>.defaultType = 'js'- Provocará que el valor
defautespecificado sirva como código fuente de una función que será:- evaluada en runtime
- para sacar el valor
default.
- Provocará que el valor
- contenga opcionalmente:
<table>.<column>.default = ?- Provocará que al llegar como
undefinedel valor, aplicará eldefautespecificado. - Si el
typeofdafunction, evaluará la función y tomará su retorno - Si el
<table>.<column>.defaultType === 'js', tomarádefaultcomo código de una función en JavaScript.- En este caso se espera una sentencia
"return *";explícita en eldefault.
- En este caso se espera una sentencia
- Provocará que al llegar como
db.dehydrate(): String
Devuelve un String representando la base de datos.
db.hydrate(stringifiedDatabase:String): Promise<void>
Sobreescribe los $ids, $data y $schema de la base de datos con el stringifiedDatabase proporcionado.
En stringifiedDatabase se espera un String como el que devuelve db.dehydrate().
db.selectMany(table:String, filter:Function|Array = SELECT_ALL_FILTER, withTableType:Boolean|String = false): Promise<Array>
Devuelve un Array con los registros donde la función filter ha devuelto true.
El filter también puede ser un array de operaciones booleanas, que aceptan los 3 parámetros típicos:
columna: nombre de la columna que queremos evaluar.operador: operación lógica que queremos hacer, donde cabe poner:=: igual a!=: diferente de<: menor que<=: menor o igual que>: mayor que>=: mayor o igual queis null: es nulo- No usa parámetro objeto
is not null: no es nulo- No usa parámetro objeto
has: cuando la columna es un array, buscará el elemento especificado dentro- Debe usarse con columnas de tipo array
has not: igual pero a la inversa- Debe usarse con columnas de tipo array
in: cuando el parámetro objeto es un array, buscará que la columna no esté dentro- Debe usarse con un array
not in: igual pero a la inversa- Debe usarse con un array
objeto: el parámetro con el que se compara.
El flag withTableType, si true, adjunta el campo type = <table> en las rows.
Si el flag withTableType es un string, utilizará ese string como propiedad para indicar la tabla.
db.selectOne(table:String, id:Number, withTableType:Boolean|String = false): Promise<Object>
Devuelve un Object con el registro que tiene el id especificado o lanza error.
El flag withTableType, si true, adjunta el campo type = <table> en la row.
Si el flag withTableType es un string, utilizará ese string como propiedad para indicar la tabla.
db.selectByUid(uid:String): Promise<Object>
Devuelve un Object con el registro que tiene el uid especificado o lanza error.
El flag withTableType se sobreentiende como true.
No es necesario especificar la table porque uid es universal.
db.selectByLabel(table:String, label:String): Promise<Array>
Devuelve un Array con los registros en los que aparezca label en las columnas con label:true.
Buscará en todas las columnas donde label:true aparezca.
Si la columna es tipo string, buscará con ===.
Si la columna es tipo array, buscará con .indexOf.
db.selectByLabels(table:String, labels:Array<String>): Promise<Array>
Variante del método anterior donde se le pasa una lista de labels para buscar.
Si la columna es tipo string, buscará con labels.indexOf.
Si la columna es tipo array, buscará con BasicDataset.hasAnyOf.
db.insertOne(table:String, value:Object): Promise<Integer>
Inserta un registro y devuelve su id.
db.insertMany(table:String, values:Array<Object>): Promise<Array<Integer>>
Inserta múltiples registros y devuelve sus ids.
db.updateOne(table:String, id:Integer, properties:Object): Promise<Boolean>
Actualiza un registro concreto y devuelve true.
db.updateMany(table:String, filter:Function, properties:Object): Promise<Array<Integer>>
Actualiza múltiples registros y devuelve sus ids en un Array.
db.deleteOne(table:String, id:Integer): Promise<Boolean>
Elimina un registro concreto y devuelve sus true.
db.deleteMany(table:String, filter:Function): Promise<Array<Integer>>
Elimina múltiples registros y devuelve sus ids en un Array.
db.renameTable(table:String, newName:String): Promise<Boolean>
Renombra una tabla a otro nombre y devuelve true.
db.renameColumn(table:String, column:String, newName:String): Promise<Boolean>
Renombra una columna a otro nombre y devuelve true.
db.addTable(table:String): Promise<Boolean>
Añade una tabla en el db.$schema y devuelve true.
db.addColumn(table:String, column:String, metadata:Object): Promise<Boolean>
Añade una columna en el db.$schema y devuelve true.
db.dropTable(table:String): Promise<Boolean>
Elimina una tabla en del db.$schema y devuelve true. Comprueba integridad referencial antes.
db.dropColumn(table:String, column:String): Promise<Boolean>
Elimina una columna del db.$schema y devuelve true. No necesita comprobación de integridad referencial.
db.modifyAll(table:String, modifier:Function, errorHandler:Function = console.log): Promise<Array>
Aplica una función, que debe devolver las propiedades que se cambian con respecto al original, a todas las rows de la tabla especificada. La función recibe:
it: la row que se va a modificar.- Es un objeto copia del original, para que si se hacen cambios, no repercuta a
db.$data[table][id].
- Es un objeto copia del original, para que si se hacen cambios, no repercuta a
id: elidde la row en la que se está iterando. Puede usarse para acceder al modelo real (y no solamente la copia anterior).
Devuelve los ids donde ha saltado un error y no se ha aplicado el cambio.
En caso de error, se ejecutará el errorHandler que por defecto es un console.log. Recibe:
error: el error.originalValue: el valor original sin elid.id: elidde la row que ha fallado.counter: un contador de la iteración en la que ha fallado.
db.expandRecords(sourceTable:String, dataset:Array, expandSpec:Object): Promise<Array>
Sirve para adjuntar datos de esta tabla que se refieren a otras tablas, de forma recursiva.
- El parámetro
sourceTablese refiere a ladb.$schema[sourceTable]correspondiente aldataset. - El parámetro
datasetse refiere al conjunto de datos en sí. - El parámetro
expandSpeccumple con la función de saber qué campos se tienen que expandir solamente, y lo consigue:- Al poner un objeto con la clave
personaentiendes que endb.$schema[sourceTable].personahay unaarray-referenceoobject-reference. - Al poner otro objeto bajo
personaen lugar de un simpletrue, añades quepersonatiene otros tipos que queremos expandir. - De esta forma puedes recursivamente, por ejemplo:
- Al poner un objeto con la clave
db.expandRecords("Usuario", dataset, { persona: { pais: true } });Con este simple ejemplo, arrastramos Usuario.persona » Persona.pais » Pais.* automáticamente.
En este ejemplo estamos suponiendo un schema al uso.
Pero puedes ver los tests en test-of-complex-query.js.
db.attachRecords(sourceTable:String, newColumn:String, referredTable:String, referredColumn:String, dataset:Array): Promise<Array>
Sirve para adjuntar datos de otras tablas que se refieren al tipo de esta tabla, sea como array-reference o como object-reference.
Amplía el dataset con una columna newColumn con todos los matches referidos en db.$schema[referredTable][referredColumn] del dataset.
Requiere de relaciones array-reference o object-reference, concretamente que db.$schema[sourceTable] sea la referredTable de db.$schema[referredTable][referredColumn].
- El parámetro
sourceTableindica el tipo dedb.$schemaque se considera adataset. - El parámetro
newColumnindica la nueva columna paradatasetque se va a utilizar para adjuntar los datos. - El parámetro
referredTableindica la tabla deldb.$schemaque contiene una columna que apunta conarray-referenceoobject-referencea la tabla desourceTable. - El parámetro
referredColumnindica la columna dedb.$schema[referredTable]que apunta conarray-referenceoobject-referencea la tabla desourceTable.
Server API
server = db.createServer(port:Integer):BasicServer
Crea una instancia de new FlexibleDB.BasicServer si está encuentra global, que sobreentiende entorno node.js.
A continuación se expone la interfaz de FlexibleDB.BasicServer mediante la instancia server.
server.start(port:Integer = this.$port):Promise
Inicia un servidor en el puerto especificado. Si había alguno corriendo, lo sobreescribirá sin importarle su estado.
El servidor va a esperar 2 parámetros:
request.body.opcode:String = "unknown"
request.body.parameters:Array = []server.login(alias:String|null, email:String|null, password:String):Promise<String>
Permite iniciar o recuperar una sesión del sistema. Utiliza o Usuario.alias o Usuario.email, y luego Usuario.password.
server.logout(token:String):Promise<Boolean>
Permite finalizar una sesión del sistema. Utiliza un Sesion.token.
server.authenticateRequest(request:ExpressRequest):Promise<Object|Boolean>
Permite autentificar una request:ExpressRequest.
Esto inflará el campo request.authentication con un Object.
Utiliza los campos Sesion.token, Sesion.usuario, Usuario.id, Grupo.id, Permiso.id.
Este método se deja de libre uso, no se llama en el código explícitamente, y se reserva para utilizar en el firewall en función de los requisitos de la aplicación servidor.
El procedimiento consiste en:
- Ir a buscar en
request.body.authenticationelSesion.token - De ahí arrastrar
usuario:Usuario,grupos:Array<Grupo>ypermisos:Array<Permiso>
La vuelta es un objeto con:
sesion:Sesionusuario:Usuariogrupos:Array<Grupo>permisos:Array<Permiso>
Se devuelve una Promise porque es asíncrono ya que usa varios selectMany/selectOne.
Puede devolver false:Boolean si no encuentra ninguna sesión por el token proporcionado.
server.getFirewall():AsyncFunction
Permite acceder a la función compilada del firewall.
server.setFirewall(firewallCode:String):BasicServer
Permite establecer la lógica del firewall que se aplicará en las operaciones server.operation.
Este método está pensado para node.js y usa require para importar el parser del lenguaje del firewall.
Es necesario dominar un pequeño lenguaje intermedio para poder establecer reglas del firewall bien.
El fuente tiene como core esta sintaxis:
Controller_sentence = Event_sentence
/ Start_process_sentence
/ Break_process_sentence
/ Create_sentence
/ Assign_sentence
/ Always_sentence
/ Define_block_sentence
/ Follow_block_sentence
/ Throw_sentence
/ If_sentence
/ Native_expressionLuego para ver ejemplo completo está el fuente de las reglas del firewall básicas para cubrir las operaciones de la base de datos.
define block authentication {
start process authentication {
always {{ /* }}
always {{ console.log("url", request.originalUrl); }}
always {{ console.log("operation", operation); }}
always {{ console.log("model", model); }}
always {{ console.log("args", args); }}
always {{ console.log("authenticationToken", authenticationToken); }}
always {{ console.log("authentication", authentication); }}
always {{ */ }}
if not authentication then throw {{ new Error("The request requires authentication at this sensible point [52684251]") }}
}
}
define block authorization {
start process authorization {
create permissions as {{ authentication.permisos.map(it => it.operacion) }}
always {{ // console.log("permissions", permissions) }}
create hasOperationPermission as {{ permissions.indexOf("server." + operation) !== -1 }}
if hasOperationPermission then break process authorization
throw {{ new Error("The request requires specific privilege «server." + operation + "» at this sensible point [12324688282]") }}
}
}
define block basic_auth_steps {
create authentication as {{ await this.authenticateRequest(request) }}
follow block authentication
follow block authorization
}
event on
operation
"addTable"
"addColumn"
"renameTable"
"renameColumn"
"dropTable"
"dropColumn"
"setSchema"
then follow block basic_auth_steps
event on
model
"Usuario"
"Grupo"
"Sesion"
operation
"insertOne"
"insertMany"
"updateOne"
"updateMany"
"deleteOne"
"deleteMany"
then follow block basic_auth_stepsEsto producirá un javascript bastante a bajo nivel que permitirá todo el juego de eventos y lógica cruzada.
Pero es muy importante aprender este lenguaje para meterse en la lógica del firewall.
El ejemplo todavía no es muy completo, pero el lenguaje permite:
- crear variables con
create it as {{ 5000 + 5 }} - asignar variables con
assign it to {{ 2000 }} - llamar a código libre con
always {{ console.log("printing things") }} - condicionales con
if {{ val }} then { ... }- profundos con
else if {{ val }} then { ... } - por defecto con
else then { ... }
- profundos con
- los
event onsonifen realidad - lanzar errores con
throw {{ new Error("Whatever") }} - iniciar un proceso con
start process Process_id { ... } - romper un proceso con
break process Process_id - definir un bloque con
define block Block_id { ... }- esto sirve para definir macros en memoria
- no afecta al código de salida
- imprimir un bloque con
follow block Block_id- esto sirve para imprimir un bloque
- afecta al código de salida en tanto que se va a imprimir tal cual el bloque
- agrupar variables para expresiones booleanas
- tiene parámetros
(~)para agrupar variables y/o expresiones - tiene parámetros
not ~para negar variables y/o expresiones - tiene parámetros
~ and ~para emplear conjunción lógica entre variables y/o expresiones - tiene parámetros
~ or ~para emplear disyunción lógica entre variables y/o expresiones
- tiene parámetros
Este lenguaje busca la alta legibilidad en las lógicas del control de negocio.
server.triggerFirewall(operation:String, args:Array, authenticationToken:String, request:ExpressRequest, response:ExpressResponse)
Permite hacer la llamada a la función compilada de server.setFirewall(firewallCode:String).
La función inyecta los siguientes parámetros:
operation:String: el método que se va a utilizar en esta llamadaargs:Array: los parámetros que se le pasan al método deoperationen formatometodo(...parametros), por eso siempre es array.authenticationToken:String: el token de sesiónrequest:ExpressRequest: la petición deexpressresponse:ExpressResponse: la response deexpressmodel:String: en principio esargs[0]que coincide en muchas con elmodelpero dependerá deoperationsi este argumento tiene sentido o es un valor no relevante, porque no siempreargs[0]es el modelo.
Esto significa que en el script que ponemos en server.setFirewall(source) existen por lo menos estos parametros inyectados en el espacio de nombres de la AsyncFunction.
server.stop():BasicServer
Permite parar el servidor que estuviera corriendo y encadenar otros métodos.
server.clone():BasicServer
Devuelve otra instancia de BasicServer.from(...server).
server.generateSessionToken():String
Método utilitario para generar tokens de sesión.
server.onAuthenticate(opcode:String, args:Array, authenticationToken:String = null, request:ExpressRequest = null, response:ExpressResponse = null):String
Método usado internamente en server.operation para autentificar las peticiones.
Principalmente, lo que este método hace es llamar al firewall con server.triggerFirewall(...).
Este método es llamado nada más iniciar server.operation si se han validado los parámetros.
Está aparte para que se pueda sobreescribir con una clase nueva si se quiere.
server.operation(opcode:String, args:Array):Promise<any>
Ejecuta una acción contemplada en la API de operation pasándole los parámetros especificados.
Los opcode válidos actualmente solo son los nombres finales de los métodos de la API. Aquí el switch cerrado de operaciones del BasicServer.operation:
async operation(opcode, args = [], authenticationToken = null, request = null, response = null) {
assertion(typeof opcode === "string", `Parameter «opcode» must be a string on «operation» specifically «${opcode}»`);
assertion(Array.isArray(args), `Parameter «args» must be an array on «operation» specifically «${opcode}»`);
let output = null;
switch(opcode) {
case "login": {
return await this.login(...args);
}
}
assertion(typeof authenticationToken === "string", `Parameter «authenticationToken» must be a string on «operation» specifically «${opcode}»`);
assertion(typeof request === "object", `Parameter «request» must be an object on «operation» specifically «${opcode}»`);
assertion(typeof response === "object", `Parameter «response» must be an object on «operation» specifically «${opcode}»`);
await this.onAuthenticate(opcode, args, authenticationToken, request, response);
switch (opcode) {
case "logout": {
output = await this.logout(...args);
break;
}
case "selectOne": {
output = await this.$database.selectOne(...args);
break;
}
case "selectOne": {
output = await this.$database.selectOne(...args);
break;
}
case "selectMany": {
output = await this.$database.selectMany(...args);
break;
}
case "insertOne": {
output = await this.$database.insertOne(...args);
break;
}
case "insertMany": {
output = await this.$database.insertMany(...args);
break;
}
case "updateOne": {
output = await this.$database.updateOne(...args);
break;
}
case "updateMany": {
output = await this.$database.updateMany(...args);
break;
}
case "deleteOne": {
output = await this.$database.deleteOne(...args);
break;
}
case "deleteMany": {
output = await this.$database.deleteMany(...args);
break;
}
case "addTable": {
output = await this.$database.addTable(...args);
break;
}
case "addColumn": {
output = await this.$database.addColumn(...args);
break;
}
case "renameTable": {
output = await this.$database.renameTable(...args);
break;
}
case "renameColumn": {
output = await this.$database.renameColumn(...args);
break;
}
case "dropTable": {
output = await this.$database.dropTable(...args);
break;
}
case "dropColumn": {
output = await this.$database.dropColumn(...args);
break;
}
case "getSchema": {
output = await this.$database.getSchema(...args);
break;
}
case "setSchema": {
output = await this.$database.setSchema(...args);
break;
}
default: {
throw new Error(`Parameter «opcode» must be a known opcode on «BasicServer.operation»`);
}
}
return output;
}Las que usan Function pueden funcionar con una serie de reglas:
await server.operation("selectMany", ["Grupos", [
["id", "=", 1]
]]);Este subset para selectores permite operaciones por servidor que sean seguras.
Se permiten los siguientes operadores, formando sentencias encadenadas por && lógico.
Aquí es donde se transforman en operaciones:
switch(op) {
case "=": {
isValid = isValid && row[column] === target;
break;
}
case "!=": {
isValid = isValid && (row[column] !== target);
break;
}
case "<": {
isValid = isValid && (row[column] < target);
break;
}
case ">": {
isValid = isValid && (row[column] > target);
break;
}
case "<=": {
isValid = isValid && (row[column] <= target);
break;
}
case ">=": {
isValid = isValid && (row[column] >= target);
break;
}
case "is null": {
isValid = isValid && (row[column] === null);
break;
}
case "is not null": {
isValid = isValid && (row[column] !== null);
break;
}
case "is in": {
isValid = isValid && (target.indexOf(row[column]) !== -1);
break;
}
case "is not in": {
isValid = isValid && (target.indexOf(row[column]) === -1);
break;
}
case "has": {
isValid = isValid && (row[column].indexOf(target) !== -1);
break;
}
case "has not": {
isValid = isValid && (row[column].indexOf(target) === -1);
break;
}
}Dataset API
proxy = db.createDataset(dataset:Array, table:String = null)
Crea una instancia de new FlexibleDB.BasicDataset(dataset, table, db) sobreentendiendo la db propia.
A continuación se expone la interfaz de FlexibleDB.BasicDataset mediante la instancia proxy.
proxy.$dataset:Array
El dataset sobre el que se está iterando.
proxy.$database:Object
La database que se está usando para tipos.
proxy.$table:String
La table que se sobreentiende como tipo del dataset.
proxy.findBySelector(selectorList:Array = []):BasicDataset
Permite cambiar el dataset con una subselección interna y encadenar otros métodos.
El juego de selectores permite:
["id"], donde:- si el
$datasetes unarraycogerá la propiedadidde todos los items, la flateneará y la deduplicará. - si el
$datasetes unobjectcogerá la propiedadiddel objeto
- si el
["*", "*", "ids"], donde:- si el
$datasetes unarraycogerá las propiedadidsde todos los items, pero no las flateneará ni deduplicará. - si el
$datasetes unobjectpasará el objeto de rows a columnas, y luego seleccionará elids, pero no lo flateneará ni deduplicará.
- si el
El doble asterisco "*", "*" permite pasar la vista de rows a columnas, entonces sirve para agrupar los valores por columnas.
En el test-of-select-by-uid-and-others.js está incluida una pequeña prueba.
proxy.setDataset(dataset:Array):BasicDataset
Permite cambiar el dataset y encadenar otros métodos.
proxy.setTable(table:String):BasicDataset
Permite cambiar la tabla del dataset y encadenar otros métodos.
proxy.setDatabase(database:Object):BasicDataset
Permite cambiar la base de datos del dataset y encadenar otros métodos.
proxy.getDataset():any
Permite obtener el dataset propiamente.
proxy.copy():BasicDataset
Permite hacer una copia JSON (con stringify y parse) del dataset y encadenar otros métodos.
proxy.clone():BasicDataset
Permite hacer un clon del BasicDataset y encadenar otros métodos.
Es útil para iterar datos de un subset o al menos una copia diferente de proxy.
proxy.deduplicate():BasicDataset
Permite desduplicar un conjunto de datos. Utiliza el row.id y si no lo encuentra, el row directamente.
proxy.filterById(id:String):BasicDataset
Permite cambiar el this.$dataset aplicando un filter por una columna concreta especificada y encadenar otros métodos.
proxy.mapById(id:String):BasicDataset
Permite cambiar el this.$dataset aplicando un map por una columna concreta especificada y encadenar otros métodos.
proxy.flat():BasicDataset
Permite cambiar el this.$dataset aplicando un flat que es que si una row es 1 array, la junta como ítems no como array con las otras rows, y encadenar otros métodos.
proxy.hasAnyOf(list:Array):BasicDataset
Permite saber si proxy.$dataset:Array contiene alguno de los ítems de list:Array.
BasicDataset.hasAnyOf(list1:Array, list2:Array):BasicDataset
Lo mismo que la anterior pero sin sobreentender this.$dataset como list1.
async proxy.filter(callback:Function):Promise<BasicDataset>
Permite hacer filter asíncronamente para operar sobre el dataset.
Conviene usarlo con una línea aparte que iterará sobre el dataset interno, porque es asíncrono.
La firma contractual de la función es la típica de Array.prototype.filter.
async proxy.map(callback:Function):Promise<BasicDataset>
Permite hacer map asíncronamente para operar sobre el dataset.
Conviene usarlo con una línea aparte que iterará sobre el dataset interno, porque es asíncrono.
La firma contractual de la función es la típica de Array.prototype.map.
async proxy.reduce(callback:Function, original:any = []):Promise<BasicDataset>
Permite hacer reduce asíncronamente para operar sobre el dataset.
Conviene usarlo con una línea aparte que iterará sobre el dataset interno, porque es asíncrono.
La firma contractual de la función es la típica de Array.prototype.reduce.
async proxy.modify(callback:Function):Promise<BasicDataset>
Permite hacer modify asíncronamente para operar sobre el dataset.
Conviene usarlo con una línea aparte que iterará sobre el dataset interno, porque es asíncrono.
La firma del método incluye a:
thiscomo elBasicDatasetarguments[0]como elthis.$datasetdelBasicDataset
async proxy.each(callback:Function):Promise<BasicDataset>
Permite hacer each asíncronamente para operar sobre el dataset.
Conviene usarlo con una línea aparte que iterará sobre el dataset interno, porque es asíncrono.
La firma contractual de la función es la típica de Array.prototype.each.
proxy.filterSync(callback:Function):BasicDataset
Versión síncrona del mismo método. Es la misma firma contractual que Array.prototype.filter excepto porque permite encadenar otros métodos.
proxy.mapSync(callback:Function):BasicDataset
Versión síncrona del mismo método. Es la misma firma contractual que Array.prototype.map excepto porque permite encadenar otros métodos.
proxy.reduceSync(callback:Function, original:any = []):BasicDataset
Versión síncrona del mismo método. Es la misma firma contractual que Array.prototype.reduce excepto porque permite encadenar otros métodos.
proxy.eachSync(callback:Function):BasicDataset
Versión síncrona del mismo método. Es la misma firma contractual que Array.prototype.each excepto porque permite encadenar otros métodos.
proxy.modifySync(callback:Function):BasicDataset
Versión síncrona del mismo método.
proxy.debug(message:String):BasicDataset
Permite imprimir el proxy.$dataset adjuntando algún mensaje y encadenar otros métodos.
proxy.filterByEval(callbackSource:String):Promise<BasicDataset>
Versión evaluativa de código, del mismo método. Tiene inyección de parámetros con:
it:Object: la row delArray.i:Integer: el índice de la row.
Se espera código asíncrono directamente en un string para la función del método equivalente asíncrono.
Se devuelve a sí misma pero en una Promise porque es código asíncrono.
proxy.mapByEval(callbackSource:String):Promise<BasicDataset>
Versión evaluativa de código, del mismo método. Tiene inyección de parámetros con:
it:Object: la row delArray.i:Integer: el índice de la row.
Se espera código asíncrono directamente en un string para la función del método equivalente asíncrono.
Se devuelve a sí misma pero en una Promise porque es código asíncrono.
proxy.reduceByEval(callbackSource:String, original:any = []):Promise<BasicDataset>
Versión evaluativa de código, del mismo método. Tiene inyección de parámetros con:
output:Array: elArray.it:Object: la row delArray.i:Integer: el índice de la row.
Se espera código asíncrono directamente en un string para la función del método equivalente asíncrono.
Se devuelve a sí misma pero en una Promise porque es código asíncrono.
proxy.eachByEval(callbackSource:String):Promise<BasicDataset>
Versión evaluativa de código, del mismo método. Tiene inyección de parámetros con:
it:Object: la row delArray.i:Integer: el índice de la row.
Se espera código asíncrono directamente en un string para la función del método equivalente asíncrono.
Se devuelve a sí misma pero en una Promise porque es código asíncrono.
proxy.modifyByEval(callbackSource:String):Promise<BasicDataset>
Versión evaluativa de código, del mismo método. Tiene inyección de parámetros con:
input:any: elproxy.$dataset.dataset:BasicDataset: elproxy.
Se espera código asíncrono directamente en un string para la función del método equivalente asíncrono.
Se devuelve a sí misma pero en una Promise porque es código asíncrono.
proxy.pipeByMatrix(signatures:Array):Promise<BasicDataset>
Permite procesar el proxy.$dataset por diferentes métodos del proxy, de golpe.
En signatures:Array se espera un conjunto de reglas donde:
signatures[i][0]:String: nombre del método del mismoproxy.signatures[i][1]:Array: parámetros de la llamada al método.
De esta forma, puede usarse así:
await proxy.pipeByMatrix([
["mapByEval", "return await this.$database.selectOne('Permiso', it)"]
["mapByEval", "return it.operacion"]
]);async proxy.groupByColumn(column:String):BasicDataset
Permite mutar el proxy.$dataset a un objeto con propiedades los valores de proxy.$dataset[*][column].
Esas propiedades serán Array con todos los ítems que tienen, en el campo column, el valor de esa propiedad.
El resultado será proxy.$dataset[propiedad]:Array<Object>.
El problema de este método es que el corte es, estrictamente, el valor del campo. A menudo querremos hacer cortes de rango, y para eso, este método no nos servirá.
async proxy.groupByColumns(columns:Array<String>):BasicDataset
Permite lo mismo que el anterior, pero estableciendo más de 1 nivel de agrupación.
De esta forma, podemos hacer proxy.$dataset[column1][column2][column3]... y seguir las agrupaciones.
El problema de este método es que el corte es, estrictamente, el valor del campo. A menudo querremos hacer cortes de rango, y para eso, este método no nos servirá.
async proxy.groupByCallback(callback:Function):Promise<BasicDataset>
Permite crear grupos de 1 nivel utilizando una función para saber en qué grupo cae cada row.
La callback:Function recibirá it:Object e i:Integer con la row y el índice de row.
La callback:Function devolverá un label:String con el nombre de la propiedad donde esta row cae.
El proxy.$dataset entonces mutará para un objeto.
Por tanto, este método requiere de proxy.$dataset:Array al principio, y terminará con proxy.$dataset:Object.
Solo permite agrupaciones de 1 nivel.
async proxy.groupByCallbacks(callbacks:Array<Function>):Promise<BasicDataset>
Mismo método, pero permitiendo agrupaciones multinivel, al aceptar no 1 Function sino un Array<Function>.
Las funciones pueden ser asíncronas.
Un simple ejemplo de uso sería este:
const proxy = await flexdb.createDataset([
{ name: "Ana", age: 10, active: true },
{ name: "Luis", age: 10, active: false },
{ name: "Eva", age: 20, active: true },
]).groupByCallbacks([
it => it.active ? "activos" : "inactivos",
it => it.age < 18 ? "menores" : "adultos"
]);
proxy.debug();Esto nos imprime:
{
"activos": {
"menores": [{
"name": "Ana",
"age": 10,
"active": true
}],
"adultos": [{
"name": "Eva",
"age": 20,
"active": true
}]
},
"inactivos": {
"menores": [{
"name": "Luis",
"age": 10,
"active": false
}]
}
}Con este método sí puedes hacer:
- cortes de rango y otras condiciones más complejas.
- agrupaciones multinivel con diferentes condiciones y campos.
Es el más completo de la saga de métodos groupBy.
También se puede devolver un Array<String> para clasificar a 1 row en varias categorías a la vez:
const proxy = await flexdb.createDataset([
{ name: "Ana", age: 10, active: true },
{ name: "Luis", age: 10, active: false },
{ name: "Eva", age: 20, active: true },
]).groupByCallbacks([
it => it.active ? "activos" : "inactivos",
it => it.age < 18 ? "menores" : (it.age >= 18) && (it.age < 35) ? ["adultos", "jovenes"] : it.age < 65 ? ["adultos"] : "veteranos",
])
proxy.debug();Este ejemplo, en cambio, nos daría:
{
"activos": {
"menores": [{
"name": "Ana",
"age": 10,
"active": true
}],
"adultos": [{
"name": "Eva",
"age": 20,
"active": true
}],
"jovenes": [{
"name": "Eva",
"age": 20,
"active": true
}]
},
"inactivos": {
"menores": [{
"name": "Luis",
"age": 10,
"active": false
}]
}
}Como se ve, Eva aparece en 2 categorías a la vez: adultos y jovenes. Y solo hemos devuelto un Array<String> con las categorías en las que queríamos que apareciera, según la lógica que quisiéramos.
async proxy.groupByEval(evalSource:String):Promise<BasicDataset>
Permite llamar a proxy.groupByCallback pero sin usar una función, solo string con código asíncrono.
async proxy.groupByEvals(evalsList:Array<String>):Promise<BasicDataset>
Permite llamar a proxy.groupByCallbacks pero sin usar una función, solo string con código asíncrono.
El ejemplo anterior se haría con:
const proxy6 = await flexdb.createDataset([
{ name: "Ana", age: 10, active: true },
{ name: "Luis", age: 10, active: false },
{ name: "Eva", age: 20, active: true },
]).groupByEvals([
`return it.active ? "activos" : "inactivos";`,
`return it.age < 18 ? "menores" : (it.age >= 18) && (it.age < 35) ? ["adultos", "jovenes"] : it.age < 65 ? ["adultos"] : "veteranos";`,
]);Este método es también el más completo de la saga groupBy, permite lo mismo que groupByCallbacks.
proxy.sortByColumn(column:String):BasicDataset
Permite reordenar un proxy.$dataset:Array según el valor de una columna.
proxy.sortByColumns(columns:Array<String>):BasicDataset
Permite reordenar un proxy.$dataset:Array según el valor de varias columnas, donde las columnas aparecen por orden de prioridad.
proxy.sortByCallback(callback:Function):BasicDataset
Permite reordenar un proxy.$dataset:Array mediante una función síncrona sort típica.
Se le inyectan a, b.
proxy.sortByEval(source:String):BasicDataset
Versión del mismo método pero usando código síncrono en formato string.
Se le inyectan a, b.
Por ejemplo:
flexdb.createDataset([
{ name: "Ana", surname: "Dac", active: true },
{ name: "Luis", surname: "Bac", active: false },
{ name: "Eva", surname: "Cac", active: true },
]).sortByEval("return a.surname > b.surname ? 1 : -1;").debug();Nos dará a Luis, Eva y Ana por este orden. Es un ejemplo.
proxy.extendBy(overrider:Object = {}):BasicDataset
Permite extender/sobreescribir cualquier propiedad o método del dataset.
Generalmente, interesará sobreescribir propiedades instumentales para el proceso de formateo del dataset.
async proxy.expandRecords(sourceTable:String, expandSpec:Object = {}):Promise<BasicDataset>
Permite expandir registros del dataset con la database.
Sigue el mismo contrato de tipos que el homónimo db.expandRecords(sourceTable, dataset, expandSpec) dando por sobreentendido el dataset.
Conviene usarlo con una línea aparte que iterará sobre el dataset interno, porque es asíncrono.
async proxy.attachRecords(sourceTable:String, newColumn:String, referredTable:String, referredColumn:String):Promise<BasicDataset>
Permite adjuntar registros del dataset con la database.
Sigue el mismo contrato de tipos que el homónimo db.attachRecords(sourceTable, newColumn, referredTable, referredColumn, dataset) dando por sobreentendido el dataset.
Conviene usarlo con una línea aparte que iterará sobre el dataset interno, porque es asíncrono.
Query API
La Query API es una API muy sencilla y abierta pero que permite gestionar los pasos de una consulta de datos en sentido amplio, mediante código asíncrono en function o string.
Esto es para facilitar la conversión de APIs de interfaz gráfica a código y hacer de una BasicQuery, un objeto persistible fácilmente.
query = FlexibleDB.BasicQuery.from(overrider:Object = {})
Permite crear una BasicQuery.
Por defecto, BasicQuery introduce estos query.steps por defecto, que pueden sobreescribirse con overrider fácilmente:
steps = [
"onStart",
"onReset",
"onFetch",
"onValidate",
"onPrepare",
"onQuery",
"onConfirm",
"onTransform",
"onCommit",
"onEnd",
];Puedes sobreescribir cualquiera con una function o con un string con código asíncrono, que query.run() funcionará correctamente.
async query.run():Promise
Permite llamar a todos los query[query.steps[i]].call(query) y funcionar tanto si son function como string con código asíncrono.
query.steps:Array<String>
Lista de pasos que sobreentiende query.run que se tienen que suceder entre sí.
Sobreescribir para cambiar proceso en query.run().
query.onError:Function|String
Permite inyectar una función que controle los errores.
Por defecto va a imprimir console.error("Error in step «" + step + "»", error); y volverá a lanzar el error con throw error;.
Sobreescribir para mejor gestión del error.
La función recibe 2 parámetros:
error:Error: el error que ha saltado.step:String: el método en el que ha saltado.
Se puede poner en forma tanto de function como de string que gestionará el error con código plano también.
query.wrapAsAsyncFunction(code:String, parameters:Array<String>)
Método utilitario para fabricar AsyncFunction y definir los nombres de los parámetros de la función de una vez.
Se utiliza en query.run() para evaluar a todos los query.steps que sean string y no function.
También lo usa query.run() para gestionar los onError, que puede ser llamado aunque no aparezca en query.steps.
Tree API
La Tree API puede ir bien para gestionar estructuras tipo árbol cuando una columna es tree: true, que forzadamente tiene que ser referredTable: $self.
Es un conjunto de métodos muy reducido de momento que permite gestionar el uso clásico de un árbol suponiendo que la columna donde metemos tree: true es el padre del nodo.
A continuación se transmiten los métodos y clases.
tree = db.createTree(table:String, column:String):BasicTree
Permite crear un árbol desde la base de datos, especificando table y column del schema.
Se comprobará que el db.$schema esté apoyando esto.
Se pondrán en tree.$table y tree.$column.
async tree.addBranchOf(data:Object, parent:Integer):Promise<Integer>
Permite añadir una subbranca a una branca padre.
Sería lo mismo que sobreescribir la columna del tree.$column en el data.
Por debajo hace un db.insertOne y lo devuelve.
async tree.getBranchesOf(parent:Integer):Promise<Array<Object>>
Permite acceder a las subbrancas de la branca especificada.
Para obtener las brancas raíces, pasar null en parent es también correcto.
async tree.dropBranch(id:Integer):Promise<Boolean>
Permite eliminar una branca, si es que la tesis de integridad lo permite.
No lo permitirá si hay alguna branca en la base de datos que tiene a ésta como padre, mediante la propiedad tree.$column, entre otras posibles comprobaciones.
En otras palabras, no hará eliminación recursiva, de momento.
Tests
Estos son los tests actualmente:
test-of-complex-query.jstest-of-controller-language.jstest-of-dataset-proxy.jstest-of-default.jstest-of-modify-all.jstest-of-performance.jstest-of-persistence.jstest-of-relations-schema.jstest-of-renaming.jstest-of-rescheming.jstest-of-select-by-uid-and-others.jstest-of-server.jstest-of-trigger.jstest-of-uniqueness.jstest-on-readme.jstest.js
Ejemplo práctico
require(__dirname + "/flexible-db.js");
const main = async function () {
const flexdb = FlexibleDB.create({
});
await flexdb.setSchema({
Sesion: {
token: { type: "string", nullable: false },
usuario: { type: "object-reference", referredTable: "Usuario", nullable: false },
},
Pais: {
nombre: { type: "string" },
presidentes: { type: "array-reference", referredTable: "Persona" }
},
Persona: {
nombre: { type: "string", label: true, },
edad: { type: "integer", },
pais: { type: "object-reference", referredTable: "Pais" },
tags: { type: "array", default: [], label: true }
},
Usuario: {
persona: { type: "object-reference", referredTable: "Persona" },
alias: { type: "string", unique: true },
email: { type: "string", unique: true },
password: { type: "string" }
},
Grupo: {
nombre: { type: "string", unique: true },
usuarios: { type: "array-reference", referredTable: "Usuario" },
permisos: { type: "array-reference", referredTable: "Permiso" },
presidente: { type: "object-reference", referredTable: "Usuario", nullable: true },
legislaciones: { type: "array-reference", referredTable: "Legislacion", nullable: true },
},
Permiso: {
nombre: { type: "string", unique: true },
operacion: { type: "string" },
modelo: { type: "string" },
descripcion: { type: "string" },
},
Legislacion: {
titulo: { type: "string" },
contenido: { type: "string" },
creador: { type: "object-reference", referredTable: "Persona" }
},
});
await flexdb.insertOne("Persona", { nombre: "Carlos", edad: 20, pais: 1, tags: ["uat"] });
await flexdb.insertOne("Persona", { nombre: "user2", edad: 30, pais: 1, tags: ["cal"] });
await flexdb.insertOne("Persona", { nombre: "user3", edad: 40, pais: 1, tags: ["nic"] });
await flexdb.insertOne("Persona", { nombre: "user4", edad: 50, pais: 1 });
await flexdb.insertOne("Persona", { nombre: "user5", edad: 60, pais: 1 });
await flexdb.insertOne("Persona", { nombre: "user6", edad: 70, pais: 1 });
const legislacion1 = await flexdb.insertOne("Legislacion", { titulo: "Carta de derechos 1", contenido: "tal", creador: 1 });
const legislacion2 = await flexdb.insertOne("Legislacion", { titulo: "Carta de derechos 2", contenido: "tal", creador: 2 });
const legislacion3 = await flexdb.insertOne("Legislacion", { titulo: "Carta de derechos 3", contenido: "tal", creador: 1 });
const legislacion4 = await flexdb.insertOne("Legislacion", { titulo: "Carta de derechos 4", contenido: "tal", creador: 2 });
const legislacion5 = await flexdb.insertOne("Legislacion", { titulo: "Carta de derechos 5", contenido: "tal", creador: 1 });
const legislacion6 = await flexdb.insertOne("Legislacion", { titulo: "Carta de derechos 6", contenido: "tal", creador: 4 });
await flexdb.insertOne("Pais", { nombre: "España", presidentes: [1] });
await flexdb.insertOne("Pais", { nombre: "Andorra", presidentes: [2] });
await flexdb.insertOne("Pais", { nombre: "Francia", presidentes: [3] });
await flexdb.insertOne("Pais", { nombre: "Portugal", presidentes: [4] });
const usuario1 = await flexdb.insertOne("Usuario", { persona: 1, alias: "usuario1", email: "[email protected]", password: "123456.1" });
const usuario2 = await flexdb.insertOne("Usuario", { persona: 2, alias: "usuario2", email: "[email protected]", password: "123456.2" });
const usuario3 = await flexdb.insertOne("Usuario", { persona: 3, alias: "usuario3", email: "[email protected]", password: "123456.3" });
const permisoAdministrar = await flexdb.insertOne("Permiso", { nombre: "administrar", operacion: "app.administrate" });
const permisoMoverCosas = await flexdb.insertOne("Permiso", { nombre: "mover cosas", operacion: "app.move things" });
const permisoSelectOne = await flexdb.insertOne("Permiso", { operacion: "server.selectOne" });
const permisoSelectMany = await flexdb.insertOne("Permiso", { operacion: "server.selectMany" });
const permisoInsertOne = await flexdb.insertOne("Permiso", { operacion: "server.insertOne" });
const permisoInsertMany = await flexdb.insertOne("Permiso", { operacion: "server.insertMany" });
const permisoUpdateOne = await flexdb.insertOne("Permiso", { operacion: "server.updateOne" });
const permisoUpdateMany = await flexdb.insertOne("Permiso", { operacion: "server.updateMany" });
const permisoDeleteOne = await flexdb.insertOne("Permiso", { operacion: "server.deleteOne" });
const permisoDeleteMany = await flexdb.insertOne("Permiso", { operacion: "server.deleteMany" });
const permisoAddTable = await flexdb.insertOne("Permiso", { operacion: "server.addTable" });
const permisoAddColumn = await flexdb.insertOne("Permiso", { operacion: "server.addColumn" });
const permisoRenameTable = await flexdb.insertOne("Permiso", { operacion: "server.renameTable" });
const permisoRenameColumn = await flexdb.insertOne("Permiso", { operacion: "server.renameColumn" });
const permisoDropTable = await flexdb.insertOne("Permiso", { operacion: "server.dropTable" });
const permisoDropColumn = await flexdb.insertOne("Permiso", { operacion: "server.dropColumn" });
const permisoSetSchema = await flexdb.insertOne("Permiso", { operacion: "server.setSchema" });
const permisoGetSchema = await flexdb.insertOne("Permiso", { operacion: "server.getSchema" });
await flexdb.insertOne("Grupo", {
nombre: "administración",
usuarios: [usuario1],
permisos: [
permisoAdministrar,
permisoSelectOne,
permisoSelectMany,
permisoInsertOne,
permisoInsertMany,
permisoUpdateOne,
permisoUpdateMany,
permisoDeleteOne,
permisoDeleteMany,
permisoAddTable,
permisoAddColumn,
permisoRenameTable,
permisoRenameColumn,
permisoDropTable,
permisoDropColumn,
permisoSetSchema,
permisoGetSchema
],
legislaciones: [legislacion1, legislacion2]
});
await flexdb.insertOne("Grupo", {
nombre: "logística",
usuarios: [usuario2, usuario3],
permisos: [permisoMoverCosas],
legislaciones: [legislacion1, legislacion2, legislacion3]
});
const persona1 = await flexdb.selectByUid(1)
const persona2 = await flexdb.selectByUid(2)
const persona3 = await flexdb.selectByUid(3)
const objeto4 = await flexdb.selectByUid(flexdb.$ids.uid);
FlexibleDB.assertion(typeof persona1.uid === "number", `Parameter «persona1.uid» must be an integer here`);
FlexibleDB.assertion(typeof persona2.uid === "number", `Parameter «persona2.uid» must be an integer here`);
FlexibleDB.assertion(typeof persona3.uid === "number", `Parameter «persona3.uid» must be an integer here`);
FlexibleDB.assertion(typeof objeto4.uid === "number", `Parameter «objeto4.uid» must be an integer here`);
const personasPorLabel1 = await flexdb.selectByLabel("Persona", "Carlos");
const personasPorLabel2 = await flexdb.selectByLabels("Persona", ["Carlos", "user2"]);
const personasPorLabel3 = await flexdb.selectByLabels("Persona", ["uat", "cal"]);
FlexibleDB.assertion(personasPorLabel1.length === 1, `Parameter «personasPorLabel1.length» must be 1 here`);
FlexibleDB.assertion(personasPorLabel2.length === 2, `Parameter «personasPorLabel2.length» must be 2 here`);
FlexibleDB.assertion(personasPorLabel3.length === 2, `Parameter «personasPorLabel3.length» must be 2 here`);
const d1 = await flexdb.createDataset(await flexdb.selectMany("Grupo"), "Grupo");
FlexibleDB.assertion(d1.getDataset()[0].nombre === "administración", "Parameter «d1.getDataset()[0].nombre» must be «administración»");
const d2 = d1.clone().findByS