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

528 lines (516 loc) 14.8 kB
// 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 import RandExp from "randexp"; var Regex = class { constructor(pattern) { /** * Quick alias for the build function */ this.b = this.build; this.pattern = pattern; } build(buildOptions, extraInfo) { return new RandExp(this.pattern).gen(); } test(value) { return this.pattern.test(value); } }; // src/options/Builder.ts import { writeFile } from "fs"; import kleur from "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); writeFile(`${filePath}`, JSON.stringify(result), "utf8", noop); console.log(kleur.yellow("Wrote file to: "), kleur.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 import { existsSync, mkdirSync } from "fs"; import path from "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 = path.join(dir, str); const fullPath2 = fullPath.split("\\").join("/"); const dirPath = fullPath.split("\\").slice(0, -1).join("/"); if (!existsSync(dirPath)) { 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; }; export { ALPHA, ALPHA_1, ALPHA_NUMERIC, ALPHA_PHRASE, Builder, Custom as 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 };