ForgeTotal.js 5
es

Total.js 5

Reglas de negocio como texto con NEWMACRO()

Total.js 5 · total5@0.0.19 macrosrulesscripting Ejecutada con Total.js 5

NEWMACRO(text, [nocompile], [isasync]) convierte unas pocas líneas de un lenguaje pequeño en una función function(model, helpers). Está pensado para reglas que cambian más a menudo que el código: un descuento, un precio, una comprobación de permisos, una validación que alguien que no escribe JavaScript mantiene en un archivo o en una base de datos.

El lenguaje, tal como se comprobó:

  • IF condition, ELSE IF condition, ELSE y FI (el final de la condición) funcionan como en una regla basada en texto. Las condiciones usan >, <, >=, <=, = y AND / OR.
  • RETURN value termina la macro con un valor. YES y NO (también TRUE, FALSE, OK) son booleanos, el texto entre comillas es texto y los números son números.
  • Una palabra es una propiedad del modelo, escrita en minúsculas: age lee model.age, y total = price * qty escribe model.total.
  • NAME(arguments) llama a un helper, la función con ese nombre (en minúsculas) en el objeto helpers que pasas; dentro de ella this es el modelo. Con isasync en true la macro es una función async y espera a los helpers.
  • #name es una variable temporal de la ejecución: #temp = num + 1 y RETURN #temp.
  • Usa nombres de varias letras: una propiedad o una variable de una letra no se reconoció en las comprobaciones.

NEWMACRO() compila el texto con new Function, así que una macro es código: compila solo texto que hayas escrito tú o que mantenga una persona de confianza, nunca texto que envió un visitante. Con nocompile en true devuelve el JavaScript que genera en lugar de la función, lo que es útil para ver qué hace una regla.

controllers/rules.js
const helpers = {
	percent: function(value, rate) {
		return value * rate / 100;
	}
};

const age = NEWMACRO("IF age >= 18\n\tRETURN 'adult'\nELSE\n\tRETURN 'minor'\nFI");

const level = NEWMACRO("IF points > 100\n\tRETURN 'gold'\nELSE IF points > 50\n\tRETURN 'silver'\nELSE\n\tRETURN 'bronze'\nFI");

const access = NEWMACRO("IF active AND score >= 5\n\tRETURN yes\nFI\nRETURN no");

const total = NEWMACRO("total = price * qty\nIF qty >= 10\n\ttotal = total - PERCENT(total, 10)\nFI\nRETURN total");

const temporary = NEWMACRO("#next = count + 1\nRETURN #next");

const slow = NEWMACRO("RETURN LOOKUP(code)", false, true);

exports.install = function() {
	ROUTE('POST /rules/', rules);
	ROUTE('GET /source/', source);
};

async function rules($) {
	const model = $.body;
	$.json({
		age: age(model, helpers),
		level: level(model, helpers),
		access: access(model, helpers),
		total: total(model, helpers),
		model_total: model.total,
		temporary: temporary(model, helpers),
		slow: await slow(model, { lookup: async code => 'found ' + code })
	});
}

function source($) {
	$.plain(NEWMACRO("IF points > 100\n\tRETURN 'gold'\nFI", true));
}