fakeit-facet
Version:
Command-line utility that generates fake data which can be output as JSON, YAML, CSON, or CSV formats based on models defined in YAML.
692 lines (534 loc) • 26.6 kB
JavaScript
"use strict";
var _Reflect$construct = require("@babel/runtime-corejs3/core-js-stable/reflect/construct");
var _sliceInstanceProperty = require("@babel/runtime-corejs3/core-js-stable/instance/slice");
var _Array$from = require("@babel/runtime-corejs3/core-js-stable/array/from");
var _Symbol = require("@babel/runtime-corejs3/core-js-stable/symbol");
var _getIteratorMethod = require("@babel/runtime-corejs3/core-js/get-iterator-method");
var _Array$isArray = require("@babel/runtime-corejs3/core-js-stable/array/is-array");
var _Object$defineProperty2 = require("@babel/runtime-corejs3/core-js-stable/object/define-property");
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
_Object$defineProperty2(exports, "__esModule", {
value: true
});
exports["default"] = exports.DocumentBuilder = void 0;
exports.getPaths = getPaths;
exports.transformValueToType = transformValueToType;
exports.typeToValue = typeToValue;
var _concat = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/concat"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _defineProperty = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/object/define-property"));
var _entries = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/entries"));
var _includes = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/includes"));
var _parseInt2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/parse-int"));
var _parseFloat2 = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/parse-float"));
var _filter = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/instance/filter"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/slicedToArray"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/inherits"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/getPrototypeOf"));
var _faker = _interopRequireDefault(require("faker"));
var _chance = _interopRequireDefault(require("chance"));
var _lodash = require("lodash");
var _toJs = _interopRequireDefault(require("to-js"));
var _stream = require("stream");
var _utils = require("./utils");
var _base = _interopRequireDefault(require("./base"));
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof _Symbol !== "undefined" && _getIteratorMethod(o) || o["@@iterator"]; if (!it) { if (_Array$isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { var _context12; if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = _sliceInstanceProperty(_context12 = Object.prototype.toString.call(o)).call(_context12, 8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return _Array$from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = _Reflect$construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !_Reflect$construct) return false; if (_Reflect$construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
var exit = false;
process.on('SIGINT', function () {
exit = true;
process.exit(2);
}); /// /
/// @name Documents
/// @page api/documents
/// /
/// @name DocumentsStream
/// @description This class is used to generate all the documents for the passed models
var DocumentsStream = /*#__PURE__*/function (_Base) {
(0, _inherits2["default"])(DocumentsStream, _Base);
var _super = _createSuper(DocumentsStream);
function DocumentsStream() {
var _this;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var globals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var inputs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var output = arguments.length > 3 ? arguments[3] : undefined;
(0, _classCallCheck2["default"])(this, DocumentsStream);
// eslint-disable-line max-params
_this = _super.call(this, options);
_this.options = _toJs["default"].extend({
count: 0
}, _this.options);
_this.globals = globals;
_this.inputs = inputs;
_this.total = 0; // set a reference to the output, really all we're going to use this for is
// it's handle to the bucket
_this.output = output;
return _this;
} /// # @name build
/// # @description
/// # This takes an array of models and builds them
/// # @arg {array} models - Array of models
/// # @returns {array} of documents
/// # @async
(0, _createClass2["default"])(DocumentsStream, [{
key: "build",
value: function build(models) {
var _this2 = this;
models = _toJs["default"].clone(models);
var self = this; // hold reference to the instance
var spinners = {};
var _iterator = _createForOfIteratorHelper(models),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var name = _step.value.name;
spinners[name] = this.spinner(name);
} // get the next document to build and generate
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
var current_model = models.pop();
var current_model_document;
var next = function next() {
// if the current model is undefined, we've successfully processed all of the items in the array
if (!current_model) {
return null;
} // if we've generated all of the documents for a model, set the next model
if (current_model_document && current_model_document.generated === current_model.data.count) {
current_model_document.runData(current_model.data.post_run, current_model); // call the post run function
spinners[current_model.name].stop(); // stop the spinner
current_model = models.pop(); // get the next model
current_model_document = null; // reset the model document builder
} // if we have a model build it's next document and return it
if (current_model) {
// make sure we have a DocumentBuilder
if (!current_model_document) {
current_model_document = new DocumentBuilder(_this2.options, _this2.globals, _this2.inputs); // get a model document builder
spinners[current_model.name].start();
current_model_document.runData(current_model.data.pre_run, current_model); // call the pre_run function
}
return current_model_document.buildDocument(current_model); // return a built document
}
}; // setup reader
var reader = new _stream.Readable({
read: function read() {
// if we should exist, just stop
if (exit) {
this.push(null);
return;
}
this.push(next());
if (current_model) {
var _context, _context2;
// eslint-disable-next-line max-len
spinners[current_model.name].text = (0, _concat["default"])(_context = (0, _concat["default"])(_context2 = "".concat(current_model.name, " documents (")).call(_context2, commaify(current_model_document.generated), " / ")).call(_context, commaify(current_model.data.count), ")");
self.total += 1;
}
},
objectMode: true
}); // helper to properly display large #
var commaify = function commaify(value) {
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}; // setup writer
var writer = new _stream.Writable({
write: function write(chunk, encoding, callback) {
// if we were passed more data than we actually needed, chunk is undefined, just call the callback
if (!chunk) {
callback();
return;
}
self.output.outputter.bucket.upsert(chunk.__key, chunk, function (err) {
// eslint-disable-line no-underscore-dangle
callback(err); // notify that the data was processed regardlress of succes / failure
});
},
objectMode: true,
highWaterMark: 16 // default for object mode is 16
}); // pipe the streams together
reader.pipe(writer); // return a process that will resolve when the writer has finished
return new _promise["default"](function (resolve, reject) {
// if either the reader or the writer errors reject the promise
reader.on('error', function (err) {
console.error('Reader Error:', err);
reader.destroy(err);
reject(err);
});
writer.on('error', function (err) {
console.error('Writer Error:', err);
reader.destroy(err);
reject(err);
}); // resolve ones the writer finishes
writer.on('finish', function () {
self.inputs = {};
self.globals = {};
resolve([]);
});
});
}
}]);
return DocumentsStream;
}(_base["default"]);
exports["default"] = DocumentsStream;
var DocumentBuilder = /*#__PURE__*/function (_Base2) {
(0, _inherits2["default"])(DocumentBuilder, _Base2);
var _super2 = _createSuper(DocumentBuilder);
function DocumentBuilder() {
var _this3;
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var globals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var inputs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
(0, _classCallCheck2["default"])(this, DocumentBuilder);
_this3 = _super2.call(this, options);
_this3.options = _toJs["default"].extend({
count: 0
}, _this3.options);
_this3.globals = globals;
_this3.inputs = inputs; // set chance and faker without a seed by default
// so it can still be used
_this3.chance = new _chance["default"]();
_this3.faker = _faker["default"];
_this3.generated = 0; // hold the number of generated documents
_this3.updateFakers(_this3.options.seed);
return _this3;
} /// # @name updateFakers
/// # @description
/// # If a usable seed is passed then this updates the instances of chance and faker to use a seed version
/// # @arg {number, null} seed - The seed to use for this instance
/// # @arg {number} modifier
(0, _createClass2["default"])(DocumentBuilder, [{
key: "updateFakers",
value: function updateFakers(seed) {
// if the seed is a number then update
// the instance of chance and faker
if (typeof seed === 'number') {
this.chance = new _chance["default"](seed);
this.faker.seed(seed);
}
} /// # @name runData
/// # @description used to run the different functions that the users can pass in into the data object
/// # @arg {function} fn - The function to run
/// # @arg {*} context - The `this` context
/// # @arg {number} index [0] - The current index of the generated items
/// # @returns {*} - What ever the function that runs returns
}, {
key: "runData",
value: function runData(fn, context) {
var index = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
if (_toJs["default"].type(fn) === 'function') {
try {
return fn.call(context, null, this.globals, this.inputs, this.faker, this.chance, index, require);
} catch (e) {
var _context3;
e.message = (0, _concat["default"])(_context3 = "".concat(fn.name, " failed, ")).call(_context3, e.message);
this.log('error', e);
}
}
} /// # @name buildDocument
/// # @description
/// # builds a document
/// # @arg {object} model - The model to build the document from
/// # @arg {number} index [0] - The place in the list this item is being run from
}, {
key: "buildDocument",
value: function buildDocument(model) {
var _context4;
var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var sub_doc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
index = index || this.generated || 0;
var key_type = _toJs["default"].type(model.key);
var paths = getPaths(model); // generate the initial values
var doc = this.initializeDocument(model, paths); // if there is a pre_build function for the document call it
this.runData(model.data.pre_build, doc, index);
doc = this.buildObject(model, doc, paths, index);
doc = this.postProcess(model, doc, paths, index); // if there is a post_build function for the document call it
this.runData(model.data.post_build, doc, index); // build the key for the document
var key;
if (key_type === 'object') {
model.key.data = model.key.data || {};
key = this.buildValue(model.key, typeToValue(model.key.type), doc, index);
} else if (key_type === 'string') {
key = (0, _lodash.get)(doc, model.key);
}
key = key || (0, _concat["default"])(_context4 = "".concat(model.name, "_")).call(_context4, index); // TODO: update this to use `new Map`, or `new WeakMap`;
(0, _defineProperty["default"])(doc, '__key', {
value: key
});
(0, _defineProperty["default"])(doc, '__name', {
value: model.name
}); // only increment the generated if we're dealing with the parent document, not nested objects
if (!sub_doc) {
this.generated += 1;
}
return doc;
} /// # @name initializeDocument
/// # @description initializes a documents default values
/// # @arg {object} model - The model to parse
/// # @arg {object} paths - The paths to loop over
/// # @returns {object} - The document with the defaults
}, {
key: "initializeDocument",
value: function initializeDocument(model, paths) {
if (!paths || !paths.model || !paths.document) {
paths = getPaths(model);
}
var doc = {};
var _iterator2 = _createForOfIteratorHelper((0, _entries["default"])(_toJs["default"]).call(_toJs["default"], paths.model)),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var _step2$value = (0, _slicedToArray2["default"])(_step2.value, 2),
i = _step2$value[0],
str = _step2$value[1];
var key = paths.document[i]; // set a key for error messaging
try {
(0, _lodash.set)(doc, key, typeToValue((0, _lodash.get)(model, str).type));
} catch (e) {
var _context5;
this.log('error', (0, _concat["default"])(_context5 = "Initializing Properties in Model: \"".concat(model.name, "\" for Key: \"")).call(_context5, key, "\"\n"), e);
}
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
return doc;
} /// # @name buildObject
/// # @description builds an object based on a model
/// # @arg {object} model - The model to parse
/// # @arg {object} doc - The document to update
/// # @arg {object} paths - The paths to loop over
/// # @arg {number} index - The current index
/// # @returns {object} - The document with the defaults
}, {
key: "buildObject",
value: function buildObject(model, doc, paths, index) {
var _iterator3 = _createForOfIteratorHelper((0, _entries["default"])(_toJs["default"]).call(_toJs["default"], paths.model)),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var _step3$value = (0, _slicedToArray2["default"])(_step3.value, 2),
i = _step3$value[0],
str = _step3$value[1];
var key = paths.document[i]; // set a key for error messaging
try {
var value = this.buildValue((0, _lodash.get)(model, str), (0, _lodash.get)(doc, key), doc, index);
(0, _lodash.set)(doc, key, value);
} catch (e) {
var _context6;
this.log('error', (0, _concat["default"])(_context6 = "Building Properties in Model: \"".concat(model.name, "\" for Key: \"")).call(_context6, key, "\"\n"), e);
}
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
return doc;
} /// # @name buildValue
/// # @description builds a single value based on a property definition
/// # @arg {object} property - The property to run
/// # To build a normal value
/// # ```js
/// # {
/// # type: '', // 'object', 'structure', 'string', 'number', 'float', 'integer', etc..
/// # data: {
/// # pre_build() {}, // optional
/// # value: '', // optional
/// # build() {}, // optional
/// # fake: '', // optional
/// # }
/// # }
/// # ```
/// # To build an array
/// # ```js
/// # {
/// # type: 'array',
/// # items: {
/// # type: '', // 'object', 'structure', 'string', 'number', 'float', 'integer', etc..
/// # data: {
/// # pre_build() {}, // optional
/// # value: '', // optional
/// # build() {}, // optional
/// # fake: '', // optional
/// # }
/// # }
/// # }
/// # ```
/// # @arg {*} value - The default value
/// # @arg {object} doc [{}] - The current document
/// # @arg {number} index [0] - The place in the list this item is being run from
/// # @return {*} - The result
}, {
key: "buildValue",
value: function buildValue(property, value) {
var doc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
if (property.data) {
if (property.data.pre_build) {
value = this.runData(property.data.pre_build, doc, index);
}
if (property.data.value) {
return property.data.value;
}
if (property.data.build) {
return this.runData(property.data.build, doc, index);
}
if (property.data.fake) {
return this.faker.fake(property.data.fake);
}
} else if (property.type === 'array' && property.items) {
var count = property.items.data.count;
var _property$items$data = property.items.data,
min = _property$items$data.min,
max = _property$items$data.max;
if (count <= 0 && !!max) {
count = this.chance.integer({
min: min,
max: max
});
} // builds a complex array
if (property.items.type === 'object') {
for (var i = 0; i < count; i++) {
value[i] = this.buildDocument(property.items, index, true);
}
return value;
} // builds a simple array
for (var _i = 0; _i < count; _i++) {
var result = this.buildValue(property.items, typeToValue(property.items.type), doc, index);
if (result !== undefined) {
// eslint-disable-line no-undefined
value.push(result);
}
}
return value;
}
return value;
} /// # @name postProcess
/// # @description Post process a document after generation
/// # @arg {object} model - The model to parse
/// # @arg {object} doc - The document to update
/// # @arg {object} paths - The paths to loop over
/// # @arg {number} index [0] - The current index
/// # @returns {object} - The updated document
}, {
key: "postProcess",
value: function postProcess(model, doc, paths) {
var index = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
var _iterator4 = _createForOfIteratorHelper((0, _entries["default"])(_toJs["default"]).call(_toJs["default"], paths.model)),
_step4;
try {
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var _step4$value = (0, _slicedToArray2["default"])(_step4.value, 2),
i = _step4$value[0],
str = _step4$value[1];
var key = paths.document[i]; // set a key for error messaging
try {
var _get = (0, _lodash.get)(model, str),
_get$data = _get.data,
data = _get$data === void 0 ? {} : _get$data,
_get$items = _get.items,
items = _get$items === void 0 ? {} : _get$items,
type = _get.type;
var value = (0, _lodash.get)(doc, key); // if there is a post_build block
if (data.post_build) {
var temp = this.runData(data.post_build, doc, index);
if (temp != null) {
value = temp;
}
} else if ((items.data || {}).post_build && items.type !== 'object' // if the type is an object it will run each item through this function already
) {
for (var a = 0; a < value.length; a++) {
var _temp = transformValueToType(items.type, this.runData(items.data.post_build, doc[key][a], index));
if (_temp != null) {
value[a] = _temp;
}
}
}
(0, _lodash.set)(doc, key, transformValueToType(type, value));
} catch (e) {
var _context7;
this.log('error', (0, _concat["default"])(_context7 = "Transforming Properties in Model: \"".concat(model.name, "\" for Key: \"")).call(_context7, key, "\"\n"), e);
}
}
} catch (err) {
_iterator4.e(err);
} finally {
_iterator4.f();
}
return doc;
}
}]);
return DocumentBuilder;
}(_base["default"]); /// @name transformValueToType
/// @description This will transform a value to the correct type
/// @arg {string} type - The type to convert the value to
/// @arg {*} value - The actual value
/// @returns {*} - The converted value
exports.DocumentBuilder = DocumentBuilder;
function transformValueToType(type, value) {
var _context8, _context9, _context10;
if (type == null || value == null || type === 'array') {
return value;
} // if it is an integer make sure it is treated as such
if ((0, _includes["default"])(_context8 = 'number,integer,long').call(_context8, type)) {
return (0, _parseInt2["default"])(value);
} // if it is a double / float make sure it is treated as such
if ((0, _includes["default"])(_context9 = 'double,float').call(_context9, type)) {
return (0, _parseFloat2["default"])(value);
} // if it is a string make sure it is treated as such
if (type === 'string') {
return value.toString();
} // if it is a string make sure it is treated as such
if ((0, _includes["default"])(_context10 = 'boolean,bool').call(_context10, type)) {
// if the value is a string that is 'false', '0', 'undefined', or 'null' as a string set a boolean false
if (typeof value === 'string' && (value === 'false' || value === '0' || value === 'undefined' || value === 'null')) {
return false;
}
return Boolean(value);
}
return value;
} /// @name getPaths
/// @description finds all the paths to be used
/// @arg {object} obj - The object to be searched
/// @returns {object}
/// ```js
/// {
/// model: [],
/// document: [],
/// }
/// ```
function getPaths(obj) {
var _context11;
// finds all of the properties paths in a model
var model = (0, _filter["default"])(_context11 = (0, _utils.objectSearch)(obj, /^properties\.([^.]+|(?!items\.).+properties\.[^.]+)$/)).call(_context11, function (str) {
return !(0, _includes["default"])(str).call(str, 'items.properties');
});
return {
model: model,
// finds all of the paths that will be used by a rendered document
document: model.join(',').replace(/properties\./g, '').split(',')
};
} /// @name typeToValue
/// @description generates the initial value for a variable based on the data type
/// @arg {string} type
/// @returns {*} - What ever the value is that matches it.
/// @raw-code
function typeToValue(type) {
var types = {};
types.string = '';
types.object = types.structure = {};
types.number = types.integer = types["double"] = types["long"] = types["float"] = 0;
types.array = [];
types["boolean"] = types.bool = false;
return types[type] != null ? types[type] : null;
}