UNPKG

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
"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; }