@udraft/core
Version:
uDraft is a language and stack agnostic code-generation tool that simplifies full-stack development by converting a single YAML file into code for rapid development.
681 lines • 31.5 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UDraft = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const lodash_1 = __importDefault(require("lodash"));
const process_1 = require("process");
const module_1 = require("./module");
const renderer_required_error_1 = require("../errors/renderer-required-error");
const terminal_kit_1 = require("terminal-kit");
const udraft_error_1 = require("../errors/udraft-error");
const yaml_1 = require("yaml");
const model_1 = require("./model");
const feature_1 = require("./feature");
const attribute_1 = require("./attribute");
const attributes_1 = require("../shortcuts/attributes");
const field_1 = require("./field");
const parsing_error_1 = require("../errors/parsing-error");
const queries_1 = require("../shortcuts/queries");
class UDraft {
constructor() {
this._modules = [];
this._attributes = [];
this._renderers = [];
}
$modules() {
return [...this._modules];
}
$workingDir() {
var _a;
return (_a = this._workingDir) !== null && _a !== void 0 ? _a : "";
}
$renderers() {
return this._renderers;
}
$renderer(name) {
return this.$renderers().find((r) => r.$name() == name) || null;
}
$requireRenderer(fromRendererClass, name) {
const renderer = this.$renderer(name);
if (renderer)
return renderer;
throw new renderer_required_error_1.RendererRequiredError(fromRendererClass.$name(), name);
}
$attributes() {
return [...this._attributes];
}
attributes(attributes) {
this.removeAttributes(attributes);
this._attributes = this._attributes.concat(attributes);
return this;
}
removeAttributes(attributes) {
this._attributes = this._attributes.filter((attribute) => !attributes.some((a) => a.$name() == attribute.$name()));
return this;
}
static load(filePath) {
const ext = path.extname(filePath);
const content = fs.readFileSync(filePath, "utf-8");
switch (ext) {
case ".yaml":
return UDraft.yaml(content);
case ".json":
return UDraft.json(content);
default:
throw new udraft_error_1.UDraftError(`Unsupported file extension: ${ext}`);
}
}
static yaml(yamlDraft) {
try {
const rawDraft = (0, yaml_1.parseDocument)(yamlDraft).toJSON();
return UDraft._parse(rawDraft);
}
catch (e) {
if (e instanceof parsing_error_1.ParsingError)
terminal_kit_1.terminal.red(`[uDraft] Parsing Error: `).red.bold(e.message + "\n");
else if (["YAMLParseError", "YAMLWarning"].includes(e.name) ||
e instanceof ReferenceError)
terminal_kit_1.terminal
.red(`[uDraft] Error in YAML file: `)
.red.bold(`${e.message}\n`);
else
throw e;
return null;
}
}
static json(jsonDraft) {
try {
const rawDraft = JSON.parse(jsonDraft);
return UDraft._parse(rawDraft);
}
catch (e) {
if (e instanceof parsing_error_1.ParsingError)
terminal_kit_1.terminal.red(`[uDraft] Parsing Error: `).red.bold(e.message + "\n");
else if (e instanceof SyntaxError)
terminal_kit_1.terminal.red(`[uDraft] Error in JSON file: `).red.bold(`${e.stack}\n`);
else
throw e;
return null;
}
}
static _parse(rawDraft) {
if (!(rawDraft === null || rawDraft === void 0 ? void 0 : rawDraft.draft))
throw new parsing_error_1.ParsingError(`No draft found in the file`);
const draft = new UDraft();
const simpleTypes = ["string", "number", "int", "float", "boolean", "date"];
const modelTriggers = {};
const models = {};
const addModelTrigger = (modelName, trigger) => {
if (models[modelName]) {
trigger(models[modelName]);
return;
}
if (!modelTriggers[modelName])
modelTriggers[modelName] = [];
modelTriggers[modelName].push(trigger);
};
const emitModelUpdate = (modelName, model) => {
if (modelTriggers[modelName])
modelTriggers[modelName].forEach((trigger) => trigger(model));
};
const parseCallSignature = (signature) => {
const match = signature.trim().match(/^\$([^\(]+)(?:\(*([^\)]*)\))*$/);
if (!match)
return null;
return {
fn: match[1].trim(),
args: (match[2] || "")
.split(",")
.map((arg) => arg.trim())
.filter((v) => !!v),
};
};
const parseFieldSignature = (signature) => {
const match = signature.trim().match(/^([^\(]+)\[([^\)]+)\]$/);
if (!match)
return null;
return { name: match[1].trim(), type: match[2].trim() };
};
const parseFieldAttributeSignature = (signature) => {
const match = signature.trim().match(/^([^\(]+)(?:\(*(.+)\))*$/);
if (!match)
return null;
if (match[1].match(/[\(\)]/g))
return null;
return {
name: match[1].trim(),
value: (match[2] || "").trim().replace(/\)$/, ""),
};
};
const parseModelSignature = (signature) => {
const match = signature.match(/[\~\+]([^\(]+)\(*([^\)]*)\)*/);
if (!match)
return null;
return {
name: match[1].trim(),
extends: (match[2] || "")
.split(",")
.map((arg) => arg.trim())
.filter((arg) => !!arg),
};
};
const parseAttribute = (attributeKey, rawAttribute) => {
const match = attributeKey.match(/\/([^\(]+)\(*([^\)]*)\)*/);
if (!match)
return null;
const attributeName = match[1].trim();
const extendsAttributes = (match[2] || "")
.split(",")
.map((arg) => arg.trim())
.filter((arg) => !!arg)
.map((arg) => {
const source = models[arg];
if (!source)
throw new parsing_error_1.ParsingError(`Source model ${arg} not found when extending the ${attributeName} attribute`);
const extended = source.$attribute(attributeName);
if (!extended)
throw new parsing_error_1.ParsingError(`Attribute ${attributeName} not found in source model ${arg} when extending the ${attributeName} attribute`);
return extended;
});
let finalRawAttribute = typeof rawAttribute == "object" ? {} : rawAttribute;
const mergeAttributeValue = (value) => {
return lodash_1.default.mergeWith(finalRawAttribute, value, (objValue, srcValue) => {
if (lodash_1.default.isArray(objValue)) {
return objValue.concat(srcValue);
}
});
};
if (extendsAttributes.length > 0) {
extendsAttributes.forEach((extended) => {
const extendedValue = extended.$value();
if (typeof extendedValue == "object" &&
typeof rawAttribute == "object") {
mergeAttributeValue(extendedValue);
}
else
finalRawAttribute = extendedValue;
});
if (typeof rawAttribute == "object")
mergeAttributeValue(rawAttribute);
return new attribute_1.UAttribute(attributeName, finalRawAttribute);
}
else
return new attribute_1.UAttribute(attributeName, rawAttribute);
};
const parseModel = (modelKey, rawModel) => {
const isModel = modelKey[0] == "+";
const isEnum = modelKey[0] == "~";
if (!isModel && !isEnum)
return null;
const modelSignature = parseModelSignature(modelKey);
if (!modelSignature)
throw new parsing_error_1.ParsingError(`Invalid model declaration: ${modelKey}`);
const modelName = modelSignature.name;
const model = new model_1.UModel(modelName);
if (isEnum)
model.attributes([new attribute_1.UAttribute("enum", rawModel)]);
else {
const initialFieldNames = Object.keys(rawModel || {})
.map((fieldName) => {
const fieldSignature = parseFieldSignature(fieldName);
return fieldSignature === null || fieldSignature === void 0 ? void 0 : fieldSignature.name;
})
.filter((name) => !!name);
Object.keys(rawModel || {}).forEach((subModelKey) => {
const subModelData = rawModel[subModelKey];
const attr = parseAttribute(subModelKey, subModelData);
if (attr)
return model.attributes([attr]);
const call = parseCallSignature(subModelKey);
if (call) {
switch (call.fn) {
case "pick":
const srcModelName = call.args[0];
let didPick = false;
addModelTrigger(srcModelName, (srcModel) => {
if (!srcModel)
throw new parsing_error_1.ParsingError(`Source model ${srcModelName} not found when pick fields to ${modelName} model: ${subModelKey}}`);
const fieldsToPick = subModelData
.map((fieldName) => {
const renameField = fieldName.match(/([^>]+)\>([^>]*)/);
if (renameField)
return {
from: renameField[1].trim(),
to: renameField[2].trim(),
};
return { from: fieldName, to: fieldName };
})
.filter((fieldToPick) => !initialFieldNames.includes(fieldToPick.to));
const pickField = ({ from, to, }) => {
const srcField = srcModel.$field(from);
if (!srcField)
throw new parsing_error_1.ParsingError(`Field ${from} not found in source model ${srcModelName} when picking fields to ${modelName} model: ${subModelKey}}`);
if (from === to)
model.fields([srcField]);
else
model.fields([srcField.$clone(to)]);
};
if (!didPick) {
fieldsToPick.forEach(pickField);
didPick = true;
}
else {
// Refresh picked fields that were not removed
fieldsToPick.forEach(({ from, to }) => {
if (model.$field(to))
pickField({ from, to });
});
}
});
break;
case "remove":
addModelTrigger(modelName, (updatedModel) => {
if (updatedModel)
updatedModel.remove(subModelData.filter((fieldToRemove) => !initialFieldNames.includes(fieldToRemove)));
});
break;
default:
throw new parsing_error_1.ParsingError(`Invalid call inside Model ${modelName}: ${subModelKey}`);
}
return;
}
const signature = parseFieldSignature(subModelKey);
if (!signature)
throw new parsing_error_1.ParsingError(`Invalid field signature inside Model ${modelName} : ${subModelKey}`);
let refModelName = "";
if (!simpleTypes.includes(signature.type)) {
refModelName = signature.type;
if (signature.type[0] == "&") {
refModelName = signature.type.slice(1);
signature.type = "reference";
}
else
signature.type = "nested";
}
const field = new field_1.UField(signature.name, signature.type);
(subModelData || []).forEach((attrKey) => {
const attrSignature = parseFieldAttributeSignature(attrKey);
if (!attrSignature)
throw new parsing_error_1.ParsingError(`Invalid field attribute inside field ${subModelKey} from ${modelName} model: ${attrKey}`);
if (attrSignature.value)
attrSignature.value = eval(attrSignature.value);
else
attrSignature.value = null;
field.attributes([
new attribute_1.UAttribute(attrSignature.name, attrSignature.value),
]);
});
if (refModelName) {
addModelTrigger(refModelName, (refModel) => {
if (!refModel)
throw new parsing_error_1.ParsingError(`Model ${refModelName} not found to reference inside Model ${modelName}: ${subModelKey}`);
field.attributes([new attribute_1.UAttribute("ref", refModel)]);
});
}
model.fields([field]);
});
modelSignature.extends.forEach((baseModelName) => {
let didExtend = false;
addModelTrigger(baseModelName, (baseModel) => {
if (!baseModel)
throw new parsing_error_1.ParsingError(`Base model ${baseModelName} not found when extending the ${modelName} model: ${modelKey}}`);
if (!didExtend) {
model.fields(baseModel
.$fields()
.filter((f) => !initialFieldNames.includes(f.$name())));
didExtend = true;
}
else {
// Refresh extended fields that were not removed
model.fields(baseModel.$fields().filter((field) => baseModel.$field(field)));
}
emitModelUpdate(modelName, model);
});
});
}
models[modelName] = model;
emitModelUpdate(modelName, model);
return model;
};
const parseModule = (moduleKey, rawModule) => {
const mod = new module_1.UModule(moduleKey);
Object.keys(rawModule || {}).forEach((subModKey) => {
const subModData = rawModule[subModKey];
const attr = parseAttribute(subModKey, subModData);
if (attr)
return mod.attributes([attr]);
const model = parseModel(subModKey, subModData);
if (model)
return mod.models([model]);
const feature = new feature_1.UFeature(subModKey);
mod.features([feature]);
Object.keys(subModData || {}).forEach((subFeatKey) => {
const subFeatData = subModData[subFeatKey];
const featAttr = parseAttribute(subFeatKey, subFeatData);
if (featAttr)
return feature.attributes([featAttr]);
if (subFeatKey == "input") {
let didSetInput = false;
Object.keys(subFeatData || {}).forEach((subInputKey) => {
if (didSetInput)
return;
const subInputData = subFeatData[subInputKey];
const inputModel = parseModel(subInputKey, subInputData);
if (inputModel) {
feature.input(inputModel);
didSetInput = true;
}
});
if (!didSetInput)
addModelTrigger(subFeatData, (inputModel) => {
if (!inputModel)
throw new parsing_error_1.ParsingError(`Model ${subFeatData} not found when setting input for ${feature.$name()} feature`);
feature.input(inputModel);
});
}
if (subFeatKey == "output") {
let didSetOutput = false;
Object.keys(subFeatData || {}).forEach((subOutputKey) => {
if (didSetOutput)
return;
const subOutputData = subFeatData[subOutputKey];
const outputModel = parseModel(subOutputKey, subOutputData);
if (outputModel) {
feature.output(outputModel);
didSetOutput = true;
}
});
if (!didSetOutput)
addModelTrigger(subFeatData, (outputModel) => {
if (!outputModel)
throw new parsing_error_1.ParsingError(`Model ${subFeatData} not found when setting output for ${feature.$name()} feature`);
feature.output(outputModel);
});
}
});
});
return mod;
};
Object.keys(rawDraft.draft || {}).forEach((rootKey) => {
const rootData = rawDraft.draft[rootKey];
const rootAttr = parseAttribute(rootKey, rootData);
if (rootAttr)
return draft.attributes([rootAttr]);
const module = parseModule(rootKey, rootData);
if (module)
return draft.modules([module]);
});
Object.keys(modelTriggers || {}).forEach((modelName) => {
if (models[modelName])
return;
modelTriggers[modelName].forEach((trigger) => {
trigger(null);
});
});
return draft;
}
extends(seed) {
return this.modules(seed.$modules());
}
modules(modules) {
this.remove(modules);
this._modules = this._modules.concat(modules);
return this;
}
remove(modules) {
this._modules = this._modules.filter((module) => !modules.some((m) => m.$name() == module.$name()));
return this;
}
_goTo(workingDir) {
terminal_kit_1.terminal.blue(`[uDraft] Working Directory: `).bold.magenta(`${workingDir}\n`);
this._workingDir = workingDir;
return this;
}
_clear() {
terminal_kit_1.terminal.blue(`[uDraft] Clear Renderers\n`);
this._renderers.forEach((renderer) => {
renderer.clear();
});
this._renderers = [];
return this;
}
begin(workingDir) {
return this._pipeline([]).goTo(workingDir);
}
_pipeline(renderers, _controls) {
if (!_controls) {
_controls = {
start: () => { },
error: (err) => { },
waitFor: Promise.resolve(),
executionError: Promise.resolve(),
};
_controls.waitFor = new Promise((resolve, reject) => {
_controls.start = resolve;
});
_controls.executionError = new Promise((resolve, reject) => {
_controls.error = reject;
});
}
const execution = _controls.waitFor
.then(() => new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
for (const renderer of renderers) {
yield this.render(renderer);
}
resolve();
}
catch (err) {
reject(err);
}
})))
.catch((err) => {
if (!_controls)
throw err;
if (err instanceof udraft_error_1.UDraftError) {
terminal_kit_1.terminal.red(`[uDraft] Pipeline Error: `).black.bold(err.message + "\n");
}
else
_controls.error(err);
const halt = new Promise(() => { });
return halt;
});
const cursor = {
goTo: (workingDir) => {
execution.then(() => this._goTo(workingDir));
return cursor;
},
clear: () => {
execution.then(() => this._clear());
return cursor;
},
pipeline: (renderers) => {
return this._pipeline(renderers, Object.assign(Object.assign({}, _controls), { waitFor: execution }));
},
exec: () => {
terminal_kit_1.terminal.blue(`[uDraft] uDraft: `).bold.green(`${(0, queries_1.$attr)(this, "name")}\n`);
_controls.start();
return Promise.race([execution, _controls.executionError]).then(() => {
terminal_kit_1.terminal.bold.green(`\n[uDraft] Done!\n\n`);
});
},
};
return cursor;
}
render(renderer) {
return __awaiter(this, void 0, void 0, function* () {
terminal_kit_1.terminal.blue(`[uDraft] Rendering: `).bold.yellow(`${renderer.$name()}\n`);
yield renderer.init(this);
const paths = renderer.$paths();
const contents = [];
for (const renderPath of paths) {
renderPath.path = renderPath.path.startsWith("/")
? renderPath.path
: path.join((0, process_1.cwd)(), this.$workingDir(), renderPath.path);
const renderDir = path.dirname(renderPath.path);
if (!fs.existsSync(renderDir))
fs.mkdirSync(renderDir, { recursive: true });
let content = "";
if (fs.existsSync(renderPath.path))
content = fs.readFileSync(renderPath.path, "utf-8");
contents.push({
key: renderPath.key,
content,
meta: renderPath.meta,
});
}
yield renderer.run(contents);
const modules = renderer.$selection().modules || [];
const models = renderer.$selection().models || [];
const features = renderer.$selection().features || [];
if (modules.length) {
terminal_kit_1.terminal.white(`[uDraft] Selected Modules: `);
terminal_kit_1.terminal.white.bold(modules.map((module) => module.$name()).join(", ") + "\n");
}
if (models.length) {
terminal_kit_1.terminal.white(`[uDraft] Selected Models: `);
terminal_kit_1.terminal.white.bold(models.map((model) => model.$name()).join(", ") + "\n");
}
if (features.length) {
terminal_kit_1.terminal.white(`[uDraft] Selected Features: `);
terminal_kit_1.terminal.white.bold(features.map((feature) => feature.$name()).join(", ") + "\n");
}
for (const renderPath of paths) {
const output = renderer.$output(renderPath.key);
if (output === null)
continue;
const dir = path.dirname(renderPath.path);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(renderPath.path, output.content, "utf-8");
terminal_kit_1.terminal
.white(`[uDraft] Output: `)
.bold.white(`${renderPath.key} `)
.black(`${renderer
.$resolveRelativePath((0, process_1.cwd)() + "/index.js", renderPath.path)
.replace(/^\.\//, "")}\n`);
}
this._renderers.push(renderer);
return this;
});
}
$json() {
const json = {
attributes: [],
models: {},
modules: {},
};
const attrToJson = (attr) => ({
name: attr.$name(),
value: attr.$value(),
});
const fieldToJson = (field) => {
var _a;
return ({
name: field.$name(),
type: field.$type(),
isArray: !!(0, queries_1.$attr)(field, (0, attributes_1._array)()),
ref: (_a = (0, queries_1.$attr)(field, (0, attributes_1._ref)())) === null || _a === void 0 ? void 0 : _a.$name(),
attributes: field
.$attributes()
.filter((attr) => !["ref", "array"].includes(attr.$name()))
.map(attrToJson),
});
};
const modelToJson = (model) => {
var _a, _b;
const enumData = (0, queries_1.$attr)(model, (0, attributes_1._enum)());
const jsonModel = {
name: model.$name(),
module: (_b = (_a = (0, queries_1.$attr)(model, (0, attributes_1._rootModule)())) === null || _a === void 0 ? void 0 : _a.$name()) !== null && _b !== void 0 ? _b : "",
enum: enumData !== null && enumData !== void 0 ? enumData : undefined,
fields: !enumData ? model.$fields().map(fieldToJson) : undefined,
attributes: model
.$attributes()
.filter((attr) => !["enum", "rootModule"].includes(attr.$name()))
.map(attrToJson),
};
if (!json.models[model.$name()])
json.models[model.$name()] = jsonModel;
return jsonModel;
};
this.$modules().forEach((mod) => {
const models = {};
const features = {};
mod.$models().forEach((model) => {
if ((0, queries_1.$attr)(mod, (0, attributes_1._rootModule)()) != mod || !!models[model.$name()])
return;
models[model.$name()] = modelToJson(model);
});
mod.$features().forEach((feature) => {
var _a, _b;
const input = feature.$input();
const output = feature.$output();
features[feature.$name()] = {
name: feature.$name(),
attributes: feature.$attributes().map(attrToJson),
module: (_b = (_a = (0, queries_1.$attr)(feature, (0, attributes_1._rootModule)())) === null || _a === void 0 ? void 0 : _a.$name()) !== null && _b !== void 0 ? _b : "",
input: input ? modelToJson(input) : undefined,
output: output ? modelToJson(output) : undefined,
};
});
const jsonModule = {
name: mod.$name(),
attributes: mod.$attributes().map(attrToJson),
features,
models,
};
json.modules[mod.$name()] = jsonModule;
});
return json;
}
}
exports.UDraft = UDraft;
//# sourceMappingURL=draft.js.map
;