UNPKG

json-response-gen

Version:

Repo for some utility functions to generate random data when you know the shape of data samples but cant get it

597 lines (583 loc) 17.7 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { ALPHA: () => ALPHA, ALPHA_1: () => ALPHA_1, ALPHA_NUMERIC: () => ALPHA_NUMERIC, ALPHA_PHRASE: () => ALPHA_PHRASE, Builder: () => Builder, C: () => Custom, Custom: () => Custom, DAY_RANGE: () => DAY_RANGE, DEFAULT_MOCKS_DIR: () => DEFAULT_MOCKS_DIR, DateRange: () => DateRange, EMAIL: () => EMAIL, LOWER_ALPHA: () => LOWER_ALPHA, LOWER_ALPHA_NUMERIC: () => LOWER_ALPHA_NUMERIC, NUMERIC: () => NUMERIC, Option: () => Option, Regex: () => Regex, Repetition: () => Repetition, SELECTION_TYPES: () => SELECTION_TYPES, Shared: () => Shared, UPPER_ALPHA: () => UPPER_ALPHA, UPPER_ALPHA_NUMERIC: () => UPPER_ALPHA_NUMERIC, conditionalDatePrefix: () => conditionalDatePrefix, get: () => get, getDay: () => getDay, handleObject: () => handleObject, handleValue: () => handleValue, hasTestFunc: () => hasTestFunc, implementsOptions: () => implementsOptions, isLeapYear: () => isLeapYear, noop: () => noop, prepDirs: () => prepDirs, shouldGetFromExtraDetails: () => shouldGetFromExtraDetails, toReverseLookupMap: () => toReverseLookupMap }); module.exports = __toCommonJS(src_exports); // src/utils/regexOptions.ts var ALPHA_PHRASE = (wordCount) => { return new Custom(() => { let phrase = ALPHA_1(4, 10)?.build({}); for (let i = 1; i < wordCount; i++) { const next = LOWER_ALPHA(4, 10).build({}); phrase += ` ${next}`; } return phrase; }); }; var UPPER_ALPHA = (min, max = min) => new Regex(new RegExp(`^[A-Z]{${min},${max}}$`)); var LOWER_ALPHA = (min, max = min) => new Regex(new RegExp(`^[a-z]{${min},${max}}$`)); var ALPHA = (min, max = min) => new Regex(new RegExp(`^[a-zA-Z]{${min},${max}}$`)); var ALPHA_1 = (min, max = min) => { if (max == 1) return new Regex(new RegExp(`^[A-Z]$`)); return new Regex(new RegExp(`^[A-Z][a-z]{${min - 1},${max - 1}}$`)); }; var LOWER_ALPHA_NUMERIC = (min, max = min) => new Regex(new RegExp(`^[a-z0-9]{${min},${max}}$`)); var UPPER_ALPHA_NUMERIC = (min, max = min) => new Regex(new RegExp(`^[A-Z0-9]{${min},${max}}$`)); var ALPHA_NUMERIC = (min, max = min) => new Regex(new RegExp(`^[a-zA-Z0-9]{${min},${max}}$`)); var NUMERIC = (min, max = min) => new Regex(new RegExp(`^[0-9]{${min},${max}}$`)); var EMAIL = (length = 9, domain) => { let eCount; let dCount; if (!!domain) { const dLen = domain.length; eCount = Math.max(1, length - (dLen + 1)); dCount = 0; } else { const usableLen = Math.max(4, length - 5); eCount = Math.ceil(Math.max(1, usableLen * 2 / 3)); dCount = usableLen - eCount; } const dom = domain || `${ALPHA_NUMERIC(dCount).build({})}.${LOWER_ALPHA(3).build({})}`; return new Custom(() => `${ALPHA_NUMERIC(eCount).build({})}@${dom}`); }; // src/options/DateRange.ts var DateRange = class { /** * @param range +/- from current year */ constructor(range, maxToday = true) { /** * Quick alias for the build function */ this.b = this.build; this.range = range; this.maxToday = maxToday; } /** * @returns date in MM/DD/YYY */ build(buildOptions, extraInfo) { const month = Number(NUMERIC(2).build()) % 12 + 1; const mult = [-1, 1][this.maxToday ? 0 : Math.floor(Math.random() * 2)]; const year = (/* @__PURE__ */ new Date()).getFullYear() + mult * Math.round(Math.random() * this.range); const day = Math.floor(Math.random() * getDay(month, year)) + 1; return `${conditionalDatePrefix(month)}/${conditionalDatePrefix(day)}/${year}`; } test(value) { const reg = /^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/; const year = value.slice(value.length - 4); const withinRange = Number(year) >= (/* @__PURE__ */ new Date()).getFullYear() - this.range; return reg.test(value) && withinRange; } }; // src/constants/constants.ts var SELECTION_TYPES = Object.freeze({ IN_ORDER: "IN_ORDER", RANDOM: "RANDOM" }); var DAY_RANGE = Object.freeze({ 1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31, LEAP: 29 }); var DEFAULT_MOCKS_DIR = "__jgen__/mocks"; // src/options/Option.ts var Option = class { /** * * @param array - Array of options to select, if filled with Regex it returns a random instance of the pattern * @param selectionType - How to select the item, check out our SELECTION_TYPES */ constructor(array, options) { /** * Quick alias for the build function */ this.b = this.build; this.array = array; this.nextIdx = 0; this.selectionType = options?.selectionType; this.shouldSpread = options?.shouldSpread; } /** * @returns an item from the given array depending on the selection type */ build(buildOptions, extraInfo) { const selectionType = this.selectionType || buildOptions?.selectionType; let idx; if (selectionType === SELECTION_TYPES.RANDOM) { idx = Math.floor(Math.random() * this.array.length); } else { if (this.nextIdx >= this.array.length) { this.nextIdx = 0; } idx = this.nextIdx++; } const value = this.array[idx]; return handleValue(value, buildOptions); } test(value) { const result = this.array.some((option) => { if (hasTestFunc(option)) { return option.test(value); } else { return option === value; } }); return result; } }; // src/options/Repetition.ts var Repetition = class { /** * * @param shape Object of the array element * @param repetitions Amount of elements in the array */ constructor(shape, options) { /** * Quick alias for the build function */ this.b = this.build; this.shape = shape; this.options = options; this.sharedOptions = !!options?.baseArrayPath; } /** * @returns Array filled with randomly generated elements of described shape */ build(buildOptions, extraInfo) { if (this.sharedOptions) { return this.buildShared(buildOptions, extraInfo); } const repetitions = this.options?.repetitions || buildOptions?.repetitions; return Array(repetitions).fill(0).map(() => handleValue(this.shape, buildOptions)); } buildShared(buildOptions, extraInfo = {}) { const { getFromGeneration } = buildOptions; const { baseArrayPath, sharedKeysMap = {} } = this.options; const [shouldGetFromExtra, newPath] = shouldGetFromExtraDetails( baseArrayPath ); let baseArray; if (shouldGetFromExtra) { const relativeValue = get(extraInfo, newPath, void 0); if (Array.isArray(relativeValue)) { baseArray = relativeValue; } else { return []; } } else { baseArray = getFromGeneration?.(baseArrayPath); } if (!Array.isArray(baseArray)) return []; const { toShare, toRename } = sharedKeysMap; const reducedShare = toShare?.reduce((prev, currString) => { return { ...prev, [currString]: currString }; }, {}); const reducedRename = toReverseLookupMap(toRename); const reducedMap = { ...reducedShare, ...reducedRename }; return baseArray.map((currObj) => { const newGen = handleValue(this.shape, buildOptions, { ...extraInfo, ...currObj }); if (Array.isArray(newGen)) { return newGen; } if (!(newGen instanceof Object)) { return newGen; } if (!(currObj instanceof Object)) { return currObj; } const sharedGen = Object.keys(reducedMap).reduce((prev, currKey) => { const oldKey = reducedMap[currKey]; const sharedValue = currObj[oldKey]; return { ...prev, [currKey]: sharedValue }; }, {}); return { ...newGen, ...sharedGen }; }); } /** * TODO: * what if: * Object: should we iterate and verify each object? its for testing so probly * Option: call its test function for generated value * Repetition: call test function for generated value i guess? * DateRange: call its test function for generated value * Regex: call its test function for generated value */ test(value) { const isLength = value.length === this.options?.repetitions; return isLength; } }; // src/options/Regex.ts var import_randexp = __toESM(require("randexp")); var Regex = class { constructor(pattern) { /** * Quick alias for the build function */ this.b = this.build; this.pattern = pattern; } build(buildOptions, extraInfo) { return new import_randexp.default(this.pattern).gen(); } test(value) { return this.pattern.test(value); } }; // src/options/Builder.ts var import_fs = require("fs"); var import_kleur = __toESM(require("kleur")); var defaultOptions = { repetitions: 3, selectionType: SELECTION_TYPES.IN_ORDER, addSharedValue: noop, getSharedValue: noop, getFromGeneration: noop }; var Builder = class { /** * * @param path Path in which to save the files under * @param {BuildProps} options Build options to configure the builder * */ constructor(options) { /** * @param result Result which was built, probly only used internally */ this.addResult = (result) => { const newResults = [...this.results, result]; this.results = newResults; }; /** * @returns Get result at index, defaults to 0, or first generated result */ this.getResult = (index = 0) => { return this.results[index]; }; this.getResults = () => { return this.results; }; this.addSharedValue = (key, value) => { this.valueMap.set(key, value); }; this.getSharedValue = (key) => { return this.valueMap.get(key); }; /** * * @param key Key to get an item from the base generation, use '/' to get the root of the object, same as getResult() * @returns */ this.getFromGeneration = (key, genNumber = 0) => { if (key === "/") return this.results[genNumber]; return get(this.results[genNumber], key, void 0); }; /** * @param shape Shape of the object to build * @returns Same Builder object with baseGeneration populated for future builds, saved if save option is enabled */ this.build = (shape, fileName) => { const result = handleValue(shape, this.options); this.addResult(result); const shouldSave = !!fileName; if (shouldSave) { const filePath = prepDirs(fileName, this.options?.writeDir); (0, import_fs.writeFile)(`${filePath}`, JSON.stringify(result), "utf8", noop); console.log(import_kleur.default.yellow("Wrote file to: "), import_kleur.default.green(filePath)); } return this; }; /** * Quick alias for the build function */ this.b = this.build; this.valueMap = /* @__PURE__ */ new Map(); this.results = new Array(); this.options = { ...defaultOptions, ...options, addSharedValue: this.addSharedValue, getSharedValue: this.getSharedValue, getFromGeneration: this.getFromGeneration }; } }; // src/options/Shared.ts var Shared = class { /** * @param key Key is string to be used to save/access data * @param options Object containing value to use/build if saving i.e. shape or any of our other utility classes, i.e. Option, Repetition, DateRange, Regex. * Also if it returns an object, if it should spread that. */ constructor(key, options) { /** * Quick alias for the build function */ this.b = this.build; this.key = key; this.value = options?.value; this.shouldSpread = options?.shouldSpread; } // Probably call a separate build for when it is a repetition being called back build(buildOptions, extraInfo) { const { addSharedValue, getSharedValue } = buildOptions; const currentValue = getSharedValue?.(this.key); if (!currentValue) { const genValue = handleValue(this.value, buildOptions); addSharedValue?.(this.key, genValue); return genValue; } return currentValue; } // TODO: test somehow or we can remove the test thing test(value) { return true; } }; // src/options/Custom.ts var Custom = class { constructor(callback, shouldSpread = false) { /** * Quick alias for the build function */ this.b = this.build; this.callback = callback; this.shouldSpread = shouldSpread; } build(buildOptions, extraInfo) { return this.callback(); } test(value) { return true; } }; // src/utils/utils.ts var import_fs2 = require("fs"); var import_path = __toESM(require("path")); function noop() { } function implementsOptions(object) { return !!object?.build; } var hasTestFunc = (value) => { return implementsOptions(value); }; var isLeapYear = (year) => { return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; }; var getDay = (month, year) => { const dayKey = month === 2 && isLeapYear(year) ? "LEAP" : month; return DAY_RANGE[dayKey]; }; var conditionalDatePrefix = (dateNumber, prefix = "0") => { if (dateNumber < 10) { return `${prefix}${dateNumber}`; } return `${dateNumber}`; }; var toReverseLookupMap = (lookupMap) => { if (!lookupMap) return {}; const reverseLookup = Object.keys(lookupMap).reduce((prevMap, currKey) => { const stringArr = lookupMap[currKey]; let keyMap; if (stringArr.length === 0) { const lastKey = currKey.split(".").at(-1); keyMap = { [lastKey]: lastKey }; } else { keyMap = stringArr.reduce((prev, currValue) => { return { ...prev, [currValue]: currKey }; }, {}); } return { ...prevMap, ...keyMap }; }, {}); return reverseLookup; }; var shouldGetFromExtraDetails = (path2) => { const pathArr = path2.split("."); const idx = pathArr.findIndex((str) => str === "?"); if (idx === -1) return [false, path2]; return [true, pathArr.slice(idx + 1).join("")]; }; var prepDirs = (str, writeDir) => { const dir = writeDir || DEFAULT_MOCKS_DIR; const fullPath = import_path.default.join(dir, str); const fullPath2 = fullPath.split("\\").join("/"); const dirPath = fullPath.split("\\").slice(0, -1).join("/"); if (!(0, import_fs2.existsSync)(dirPath)) { (0, import_fs2.mkdirSync)(dirPath, { recursive: true }); } return fullPath2; }; // src/utils/generation.ts var handleValue = (value, buildOptions, extraInfo) => { if (implementsOptions(value)) { return value.build(buildOptions, extraInfo); } else if (Array.isArray(value)) { return new Repetition(value[0]).build(buildOptions, extraInfo); } else if (value instanceof Object) { return handleObject(value, buildOptions, extraInfo); } return value; }; var handleObject = (shape, buildOptions, extraInfo) => { const keyArr = Object.keys(shape); if (keyArr.length === 0) { return {}; } const result = keyArr.reduce((prev, currKey) => { const currVal = shape[currKey]; const currResult = handleValue(currVal, buildOptions, extraInfo); let _currResult; if (currVal?.shouldSpread && currResult instanceof Object) { _currResult = currResult; } else { _currResult = { [currKey]: currResult }; } return { ...prev, ..._currResult }; }, {}); return result; }; // src/utils/selectors.ts var get = (obj, path2, defaultValue) => { if (!path2) return void 0; const pathArray = Array.isArray(path2) ? path2 : path2.match(/([^.[\]])+/g); const result = pathArray?.reduce( (prevObj, key) => prevObj && prevObj[key], obj ); return result === void 0 ? defaultValue : result; }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ALPHA, ALPHA_1, ALPHA_NUMERIC, ALPHA_PHRASE, Builder, C, Custom, DAY_RANGE, DEFAULT_MOCKS_DIR, DateRange, EMAIL, LOWER_ALPHA, LOWER_ALPHA_NUMERIC, NUMERIC, Option, Regex, Repetition, SELECTION_TYPES, Shared, UPPER_ALPHA, UPPER_ALPHA_NUMERIC, conditionalDatePrefix, get, getDay, handleObject, handleValue, hasTestFunc, implementsOptions, isLeapYear, noop, prepDirs, shouldGetFromExtraDetails, toReverseLookupMap });