fakelab 0.0.30 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/cli.js CHANGED
@@ -1,10 +1,10 @@
1
- import m from'path';import u from'fs-extra';import {Command}from'commander';import R from'express';import Y from'cors';import ee from'http';import te from'express-ejs-layouts';import {Project}from'ts-morph';import f from'picocolors';import {JSONFilePreset}from'lowdb/node';import {fileURLToPath}from'url';import V from'qs';import se from'figlet';import {bundleRequire}from'bundle-require';import ie from'joycon';import oe from'mitt';var U=new Intl.ListFormat("en",{style:"long",type:"unit"}),i=class{static label(e){switch(e){case "info":return f.blueBright(e.toUpperCase());case "error":return f.redBright(e.toUpperCase());case "warn":return f.yellowBright(e.toUpperCase());case "success":return f.greenBright(e.toUpperCase())}}static log(e,t){return [f.dim(`[${new Date().toISOString()}]`),this.label(e),t].join(" ")}static blue(e){return f.blueBright(e)}static red(e){return f.redBright(e)}static yellow(e){return f.yellowBright(e)}static green(e){return f.greenBright(e)}static info(e,...t){console.log(this.log("info",e),...t);}static warn(e,...t){console.log(this.log("warn",e),...t);}static error(e,...t){console.error(this.log("error",e),...t);}static success(e,...t){console.log(this.log("success",e),...t);}static debug(e,...t){typeof process>"u"||!process.env.DEBUG||console.log(this.log("info",e),...t);}static list(e){return U.format(e)}};var v=class{constructor(e){this.faker=e;}JSDOC_FAKER_FIELD="faker";FAKER_TAG_REGEX=/^([a-zA-Z0-9._]+)(?:\((.*)\))?$/;boolMapping={true:true,false:false};string(e){return this.execute(e,this.faker.word.noun)}int(e){return this.execute(e,this.faker.number.int)}bool(e){return this.execute(e,this.faker.datatype.boolean)}bigInt(e){return this.execute(e,this.faker.number.bigInt)}litbool(e){return this.boolMapping[e]}async object(e,t){let s={};return await Promise.all(e.map(async r=>{let o=r.getTypeAtLocation(r.getValueDeclarationOrThrow());s[r.getName()]=await t(o,this,this.readJSDocTags(r));})),s}async union(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}async intersection(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}evalArgs(e){if(!(!e||!e.trim()))return Function(`"use strict"; return (${e});`)()}readJSDocTags(e){let t=e.getJsDocTags().filter(s=>s.getName()===this.JSDOC_FAKER_FIELD);return t.length===0?[]:t.map(s=>{let[r]=s.getText();if(!r)return;let o=r.text.trim().match(this.FAKER_TAG_REGEX);if(!o)return;let[,a,p]=o,c=this.evalArgs(p);return {path:a,args:c}})}execute(e,t){if(!e)return t();let s=e.path.split("."),r=this.faker;for(let o of s)r=r[o],r||(i.error("Invalid faker module path: (%s)",e.path),process.exit(1));typeof r!="function"&&(i.error("Unresolvable faker function. (%s)",e.path),process.exit(1));try{return e.args?r(e.args):r()}catch(o){return i.error("Passed invalid arguments to faker function. error: %s",o),r()}}};var d=m.dirname(fileURLToPath(import.meta.url)),g=process.cwd();var k=class{constructor(e,t){this.files=e;this.database=t;let r=new Project({tsConfigFilePath:"tsconfig.json"}).addSourceFilesAtPaths(e);this.__targets=r.flatMap(o=>{let a=o.getInterfaces(),p=o.getTypeAliases(),c=o.getExportDeclarations().flatMap(l=>l.getNamedExports().flatMap(h=>h.getLocalTargetDeclarations()));return [...a,...p,...c]}),this.generateInFileEntitiyMap(this.__targets);}__targets;async run(e){return await e()}normalizePath(e){return e.split(m.sep).join(m.posix.sep)}generateInFileEntitiyMap(e){let s=`
1
+ import m from'path';import u from'fs-extra';import {Command}from'commander';import R from'express';import Z from'cors';import te from'http';import se from'express-ejs-layouts';import {Project}from'ts-morph';import f from'picocolors';import {JSONFilePreset}from'lowdb/node';import {fileURLToPath}from'url';import Q from'qs';import re from'figlet';import {bundleRequire}from'bundle-require';import oe from'joycon';import ne from'mitt';var B=new Intl.ListFormat("en",{style:"long",type:"unit"}),o=class{static label(e){switch(e){case "info":return f.blueBright(e.toUpperCase());case "error":return f.redBright(e.toUpperCase());case "warn":return f.yellowBright(e.toUpperCase());case "success":return f.greenBright(e.toUpperCase())}}static log(e,t){return [f.dim(`[${new Date().toISOString()}]`),this.label(e),t].join(" ")}static blue(e){return f.blueBright(e)}static red(e){return f.redBright(e)}static yellow(e){return f.yellowBright(e)}static green(e){return f.greenBright(e)}static info(e,...t){console.log(this.log("info",e),...t);}static warn(e,...t){console.log(this.log("warn",e),...t);}static error(e,...t){console.error(this.log("error",e),...t);}static success(e,...t){console.log(this.log("success",e),...t);}static debug(e,...t){typeof process>"u"||!process.env.DEBUG||console.log(this.log("info",e),...t);}static list(e){return B.format(e)}};var v=class{constructor(e){this.faker=e;}JSDOC_FAKER_FIELD="faker";FAKER_TAG_REGEX=/^([a-zA-Z0-9._]+)(?:\((.*)\))?$/;boolMapping={true:true,false:false};string(e){return this.execute(e,this.faker.word.noun)}int(e){return this.execute(e,this.faker.number.int)}bool(e){return this.execute(e,this.faker.datatype.boolean)}bigInt(e){return this.execute(e,this.faker.number.bigInt)}litbool(e){return this.boolMapping[e]}async object(e,t){let s={};return await Promise.all(e.map(async r=>{let i=r.getTypeAtLocation(r.getValueDeclarationOrThrow());s[r.getName()]=await t(i,this,this.readJSDocTags(r));})),s}async union(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}async intersection(e){let t=await Promise.all(e);return t[Math.floor(Math.random()*t.length)]}evalArgs(e){if(!(!e||!e.trim()))return Function(`"use strict"; return (${e});`)()}readJSDocTags(e){let t=e.getJsDocTags().filter(s=>s.getName()===this.JSDOC_FAKER_FIELD);return t.length===0?[]:t.map(s=>{let[r]=s.getText();if(!r)return;let i=r.text.trim().match(this.FAKER_TAG_REGEX);if(!i)return;let[,a,p]=i,c=this.evalArgs(p);return {path:a,args:c}})}execute(e,t){if(!e)return t();let s=e.path.split("."),r=this.faker;for(let i of s)r=r[i],r||(o.error("Invalid faker module path: (%s)",e.path),process.exit(1));typeof r!="function"&&(o.error("Unresolvable faker function. (%s)",e.path),process.exit(1));try{return e.args?r(e.args):r()}catch(i){return o.error("Passed invalid arguments to faker function. error: %s",i),r()}}};var d=m.dirname(fileURLToPath(import.meta.url)),g=process.cwd();var k=class{constructor(e,t){this.files=e;this.database=t;let r=new Project({tsConfigFilePath:"tsconfig.json"}).addSourceFilesAtPaths(e);this.__targets=r.flatMap(i=>{let a=i.getInterfaces(),p=i.getTypeAliases(),c=i.getExportDeclarations().flatMap(l=>l.getNamedExports().flatMap(h=>h.getLocalTargetDeclarations()));return [...a,...p,...c]}),this.generateInFileEntitiyMap(this.__targets);}__targets;async run(e){return await e()}normalizePath(e){return e.split(m.sep).join(m.posix.sep)}generateInFileEntitiyMap(e){let s=`
2
2
  interface Runtime$ {
3
- ${[...new Set(e.map(r=>{let o=r.getName(),a=r.getSourceFile().getFilePath();return `${o}: import("${a}").${o}`}))].join(`
3
+ ${[...new Set(e.map(r=>{let i=r.getName(),a=r.getSourceFile().getFilePath();return `${i}: import("${a}").${i}`}))].join(`
4
4
  `)}
5
- }`;u.appendFile(m.resolve(d,"runtime.d.ts"),s);}address(e,t){let s=this.normalizePath(g);return `${e.replace(s,"")}/${t}`}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let s=t.getName().toLowerCase(),r=t.getType(),o=this.normalizePath(t.getSourceFile().getDirectoryPath()),a=t.getSourceFile().getBaseName(),p=this.address(o,a),c=m.resolve(this.database.DATABASE_DIR,`${s}.json`),l=this.address(this.normalizePath(this.database.DATABASE_DIR),m.basename(c)),h=o.includes("/.fakelab/snapshots"),O=await JSONFilePreset(c,[]);return [s,{type:r,filepath:p,snapshot:h,table:O,tablepath:l}]}));return new Map(e.sort((t,s)=>Number(t[1].snapshot)-Number(s[1].snapshot)))}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};async function b(n,e,t=[],s=0){if(n.isString())return e.string(t[s]);if(n.isNumber())return e.int(t[s]);if(n.isBoolean())return e.bool(t[s]);if(n.isBigInt())return e.bigInt(t[s]);if(n.isBooleanLiteral())return e.litbool(n.getText());if(n.isLiteral())return n.getLiteralValue();if(!n.isUndefined()){if(n.isUnion()){let r=n.getUnionTypes();return await e.union(r.map((o,a)=>b(o,e,t,a)))}if(n.isIntersection()){let r=n.getIntersectionTypes();return await e.intersection(r.map((o,a)=>b(o,e,t,a)))}if(n.isArray()){let r=n.getArrayElementTypeOrThrow();return [await b(r,e,t,s)]}if(n.isObject()){let r=n.getProperties();return await e.object(r,(o,a,p)=>b(o,a,p,s))}return null}}function z({each:n}){return {resolve:async t=>await Promise.all(Array.from({length:t},n))}}async function H(n,e,t){let s=await n.files(e.source),r=new k(s,t),o=await r.entities(),a=await r.initFakerLibrary(n.options.faker(e.locale)),p=new v(a);async function c(l,h){let O=z({each:()=>b(l,p)}),j=await(h.count?O.resolve(parseInt(h.count)):b(l,p)),M=JSON.stringify(j,null,2);return {data:j,json:M}}return {entities:o,build:c}}var S=class{constructor(e,t,s){this.builder=e;this.database=t;this.pkg=s;}async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}index(){return (e,t)=>{t.render("index",{name:null,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}}preview(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,o=t.params.name,a=await this.handleQueries(t),p=V.stringify(a,{addQueryPrefix:true}),c=this.builder.entities.get(o.toLowerCase());if(c){let{json:l}=await this.builder.build(c.type,a),h=c.filepath;s.render("preview",{name:o,suffix:c.snapshot?"(snapshot)":"",filepath:h,address:r,search:p,json:l,prefix:e,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}else s.redirect("/");}}db(){return (e,t)=>{this.database.enabled()?t.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):t.redirect("/");}}table(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,o=t.params.name,a=this.builder.entities.get(o.toLowerCase());if(!this.database.enabled())s.redirect("/");else if(a){await a.table.read();let c=a.table.data.length>0,l=JSON.stringify(a.table.data,null,2),h=a.filepath;s.render("table",{name:o,filepath:h,address:r,prefix:e,json:l,hasData:c,entities:this.builder.entities,version:this.pkg.version});}else s.redirect("/database");}}};var x=class{constructor(e,t,s){this.builder=e;this.network=t;this.database=s;}SEED_MERGE_THRESHOLD=1e3;async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}async applyNetworkHandlers(e){if(this.network.offline()){let{status:t,message:s}=this.network.state("offline");return e.status(t).json({message:s}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:s}=this.network.state("error");return e.status(t).json({message:s}),true}return false}entity(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),o=this.builder.entities.get(s.toLowerCase());if(o){let{data:a}=await this.builder.build(o.type,r);t.status(200).json(a);}else t.status(400).json({message:"The entity is not exists"});}catch(s){t.status(500).send(s);}}}getTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),t.status(200).json(r.table.data)):t.status(400).json({message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}updateTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),o=this.builder.entities.get(s.toLowerCase());if(o){let{data:a}=await this.builder.build(o.type,r);await o.table.update(p=>p.push(...Array.isArray(a)?a:[a])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}insert(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.body?.count||1,r=e.body?.strategy||"reset",o=e.params.name,a=this.builder.entities.get(o.toLowerCase());if(a){let{data:p}=await this.builder.build(a.type,{count:s});if(await a.table.read(),r==="once"&&a.table.data.length>0)return t.status(200).json({message:`${o} entity was seeded once before.`});await a.table.update(c=>{r!=="merge"&&(c.length=0);let l=Array.isArray(p)?p:[p];c.length+l.length<this.SEED_MERGE_THRESHOLD&&c.push(...l);}),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){t.status(500).send(s);}}}flush(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.update(o=>o.length=0),t.status(200).json({success:!0})):t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){console.log({error:s}),t.status(500).send(s);}}}_update(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=await this.handleQueries(e),o=this.builder.entities.get(s.toLowerCase());if(o){let{data:a}=await this.builder.build(o.type,r);await o.table.update(p=>p.push(a)),t.status(301).redirect(`/database/${s.toLowerCase()}`);}else t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}_clear(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),r.table.data.length>0&&await r.table.update(o=>o.length=0),t.status(301).redirect(`/database/${s.toLowerCase()}`)):t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}};var X=u.readJSONSync(m.join(d,"../package.json")),E=class{constructor(e,t,s,r,o){this.router=e;this.config=t;this.network=s;this.database=r;this.serverCLIOptions=o;let{pathPrefix:a}=this.config.options.server(this.serverCLIOptions.pathPrefix,this.serverCLIOptions.port);this.prefix=a;}prefix;async register(){let e=await H(this.config,this.serverCLIOptions,this.database),t=new S(e,this.database,X),s=new x(e,this.network,this.database);this.router.get("/",t.index()),this.router.get("/entities/:name",t.preview(this.prefix)),this.router.get("/database",t.db()),this.router.get("/database/:name",t.table(this.prefix)),this.router.get(`/${this.prefix}/:name`,s.entity()),this.router.get(`/${this.prefix}/database/:name`,s.getTable()),this.router.post(`/${this.prefix}/database/:name`,s.updateTable()),this.router.post(`/${this.prefix}/database/insert/:name`,s.insert()),this.router.post(`/${this.prefix}/database/flush/:name`,s.flush()),this.router.post("/__update/:name",s._update()),this.router.post("/__delete/:name",s._clear());}};var T=class n{constructor(e){this.config=e;this.options=this.config.options.network(),this.timeout=this.timeout.bind(this),this.error=this.error.bind(this),this.state=this.state.bind(this),this.middleware=this.middleware.bind(this),this.wait=this.wait.bind(this),this.offline=this.offline.bind(this);}options;static initHandlers(e){return new n(e)}timeout(){let e=this.chance(this.options.timeoutRate);return e&&i.debug("Network timeout..."),e}error(){let e=this.chance(this.options.errorRate);return e&&i.debug("Network error..."),e}state(e){switch(e){case "error":{let t=this.options.errors?.statusCodes||[],s=t.length>0?t[Math.floor(Math.random()*t.length)]:500,r=this.options.errors?.messages?.[s]??"Network error";return {status:s,message:r}}case "offline":return {status:503,message:"Network offline"};default:return {status:500,message:"Server unknown error"}}}async wait(){let e=this.resolveDelay();e>0&&(i.debug("Network waiting (%d ms)...",e),await this.sleep(e));}offline(){return this.options.offline??false}middleware(e,t,s){let r=this.options.errorRate||0,o=this.options.timeoutRate||0,a=this.options.offline??false,p=`delay=${this.resolveDelay()},error=${r},timeout=${o},offline=${a}`;t.setHeader("X-Fakelab-Network",p),s();}resolveDelay(){let e=this.options.delay;return typeof e=="number"?Math.round(e):Array.isArray(e)?this.random(e):0}chance(e=0){return Math.random()<e}random([e,t]){return Math.round(Math.random()*(t-e)+e)}sleep(e){return new Promise(t=>setTimeout(t,e))}};var D=class n{constructor(e){this.config=e;this.enabled=this.enabled.bind(this),this.options=this.config.options.database(),this.options.enabled||u.rmSync(this.DATABASE_DIR,{force:true,recursive:true});}options;DATABASE_DIR=m.resolve(g,".fakelab/db");static register(e){return new n(e)}enabled(){return this.options.enabled??false}async initialize(){if(this.enabled())try{await u.ensureDir(this.DATABASE_DIR),await this.modifyGitignoreFile(".fakelab/*");}catch(e){i.error("Could not create database. error: %s",e);}}async modifyGitignoreFile(e){try{let t=m.resolve(g,".gitignore");if((await u.readFile(t,{encoding:"utf8"})).split(`
5
+ }`;u.appendFile(m.resolve(d,"runtime.d.ts"),s);}address(e,t){let s=this.normalizePath(g);return `${e.replace(s,"")}/${t}`}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let s=t.getName().toLowerCase(),r=t.getType(),i=this.normalizePath(t.getSourceFile().getDirectoryPath()),a=t.getSourceFile().getBaseName(),p=this.address(i,a),c=m.resolve(this.database.DATABASE_DIR,`${s}.json`),l=this.address(this.normalizePath(this.database.DATABASE_DIR),m.basename(c)),h=i.includes("/.fakelab/snapshots"),O=await JSONFilePreset(c,[]);return [s,{type:r,filepath:p,snapshot:h,table:O,tablepath:l}]}));return new Map(e.sort((t,s)=>Number(t[1].snapshot)-Number(s[1].snapshot)))}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};async function b(n,e,t=[],s=0){if(n.isString())return e.string(t[s]);if(n.isNumber())return e.int(t[s]);if(n.isBoolean())return e.bool(t[s]);if(n.isBigInt())return e.bigInt(t[s]);if(n.isBooleanLiteral())return e.litbool(n.getText());if(n.isLiteral())return n.getLiteralValue();if(!n.isUndefined()){if(n.isUnion()){let r=n.getUnionTypes();return await e.union(r.map((i,a)=>b(i,e,t,a)))}if(n.isIntersection()){let r=n.getIntersectionTypes();return await e.intersection(r.map((i,a)=>b(i,e,t,a)))}if(n.isArray()){let r=n.getArrayElementTypeOrThrow();return [await b(r,e,t,s)]}if(n.isObject()){let r=n.getProperties();return await e.object(r,(i,a,p)=>b(i,a,p,s))}return null}}function V({each:n}){return {resolve:async t=>await Promise.all(Array.from({length:t},n))}}async function H(n,e,t){let s=await n.files(e.source),r=new k(s,t),i=await r.entities(),a=await r.initFakerLibrary(n.options.faker(e.locale)),p=new v(a);async function c(l,h){let O=V({each:()=>b(l,p)}),j=await(h.count?O.resolve(parseInt(h.count)):b(l,p)),U=JSON.stringify(j,null,2);return {data:j,json:U}}return {entities:i,build:c}}var S=class{constructor(e,t,s){this.builder=e;this.database=t;this.pkg=s;}async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}index(){return (e,t)=>{t.render("index",{name:null,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}}preview(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,i=t.params.name,a=await this.handleQueries(t),p=Q.stringify(a,{addQueryPrefix:true}),c=this.builder.entities.get(i.toLowerCase());if(c){let{json:l}=await this.builder.build(c.type,a),h=c.filepath;s.render("preview",{name:i,suffix:c.snapshot?"(snapshot)":"",filepath:h,address:r,search:p,json:l,prefix:e,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}else s.redirect("/");}}db(){return (e,t)=>{this.database.enabled()?t.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):t.redirect("/");}}table(e){return async(t,s)=>{let r=`${t.protocol}://${t.host}/`,i=t.params.name,a=this.builder.entities.get(i.toLowerCase());if(!this.database.enabled())s.redirect("/");else if(a){await a.table.read();let c=a.table.data.length>0,l=JSON.stringify(a.table.data,null,2),h=a.filepath;s.render("table",{name:i,filepath:h,address:r,prefix:e,json:l,hasData:c,entities:this.builder.entities,version:this.pkg.version});}else s.redirect("/database");}}};var x=class{constructor(e,t,s){this.builder=e;this.network=t;this.database=s;}SEED_MERGE_THRESHOLD=1e3;async handleQueries(e){let t=e.query.count;return t?{count:t.toString()}:{}}async applyNetworkHandlers(e){if(this.network.offline()){let{status:t,message:s}=this.network.state("offline");return e.status(t).json({message:s}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:s}=this.network.state("error");return e.status(t).json({message:s}),true}return false}entity(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),i=this.builder.entities.get(s.toLowerCase());if(i){let{data:a}=await this.builder.build(i.type,r);t.status(200).json(a);}else t.status(400).json({message:"The entity is not exists"});}catch(s){t.status(500).send(s);}}}getTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),t.status(200).json(r.table.data)):t.status(400).json({message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}updateTable(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let s=e.params.name,r=await this.handleQueries(e),i=this.builder.entities.get(s.toLowerCase());if(i){let{data:a}=await this.builder.build(i.type,r);await i.table.update(p=>p.push(...Array.isArray(a)?a:[a])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:`${s} table is not exists`});}catch(s){t.status(500).send(s);}}}insert(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.body?.count||1,r=e.body?.strategy||"reset",i=e.params.name,a=this.builder.entities.get(i.toLowerCase());if(a){let{data:p}=await this.builder.build(a.type,{count:s});if(await a.table.read(),r==="once"&&a.table.data.length>0)return t.status(200).json({message:`${i} entity was seeded once before.`});await a.table.update(c=>{r!=="merge"&&(c.length=0);let l=Array.isArray(p)?p:[p];c.length+l.length<this.SEED_MERGE_THRESHOLD&&c.push(...l);}),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){t.status(500).send(s);}}}flush(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.update(i=>i.length=0),t.status(200).json({success:!0})):t.status(400).json({success:!1,message:"The table is not exists"});}catch(s){console.log({error:s}),t.status(500).send(s);}}}_update(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=await this.handleQueries(e),i=this.builder.entities.get(s.toLowerCase());if(i){let{data:a}=await this.builder.build(i.type,r);await i.table.update(p=>p.push(a)),t.status(301).redirect(`/database/${s.toLowerCase()}`);}else t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}_clear(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let s=e.params.name,r=this.builder.entities.get(s.toLowerCase());r?(await r.table.read(),r.table.data.length>0&&await r.table.update(i=>i.length=0),t.status(301).redirect(`/database/${s.toLowerCase()}`)):t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}};var Y=u.readJSONSync(m.join(d,"../package.json")),E=class{constructor(e,t,s,r,i){this.router=e;this.config=t;this.network=s;this.database=r;this.serverCLIOptions=i;let{pathPrefix:a}=this.config.options.server(this.serverCLIOptions.pathPrefix,this.serverCLIOptions.port);this.prefix=a;}prefix;async register(){let e=await H(this.config,this.serverCLIOptions,this.database),t=new S(e,this.database,Y),s=new x(e,this.network,this.database);this.router.get("/",t.index()),this.router.get("/entities/:name",t.preview(this.prefix)),this.router.get("/database",t.db()),this.router.get("/database/:name",t.table(this.prefix)),this.router.get(`/${this.prefix}/:name`,s.entity()),this.router.get(`/${this.prefix}/database/:name`,s.getTable()),this.router.post(`/${this.prefix}/database/:name`,s.updateTable()),this.router.post(`/${this.prefix}/database/insert/:name`,s.insert()),this.router.post(`/${this.prefix}/database/flush/:name`,s.flush()),this.router.post("/__update/:name",s._update()),this.router.post("/__delete/:name",s._clear());}};var T=class n{constructor(e){this.config=e;this.options=this.config.options.network(),this.timeout=this.timeout.bind(this),this.error=this.error.bind(this),this.state=this.state.bind(this),this.middleware=this.middleware.bind(this),this.wait=this.wait.bind(this),this.offline=this.offline.bind(this);}options;static initHandlers(e){return new n(e)}timeout(){let e=this.chance(this.options.timeoutRate);return e&&o.debug("Network timeout..."),e}error(){let e=this.chance(this.options.errorRate);return e&&o.debug("Network error..."),e}state(e){switch(e){case "error":{let t=this.options.errors?.statusCodes||[],s=t.length>0?t[Math.floor(Math.random()*t.length)]:500,r=this.options.errors?.messages?.[s]??"Network error";return {status:s,message:r}}case "offline":return {status:503,message:"Network offline"};default:return {status:500,message:"Server unknown error"}}}async wait(){let e=this.resolveDelay();e>0&&(o.debug("Network waiting (%d ms)...",e),await this.sleep(e));}offline(){return this.options.offline??false}middleware(e,t,s){let r=this.options.errorRate||0,i=this.options.timeoutRate||0,a=this.options.offline??false,p=`delay=${this.resolveDelay()},error=${r},timeout=${i},offline=${a}`;t.setHeader("X-Fakelab-Network",p),s();}resolveDelay(){let e=this.options.delay;return typeof e=="number"?Math.round(e):Array.isArray(e)?this.random(e):0}chance(e=0){return Math.random()<e}random([e,t]){return Math.round(Math.random()*(t-e)+e)}sleep(e){return new Promise(t=>setTimeout(t,e))}};var D=class n{constructor(e){this.config=e;this.enabled=this.enabled.bind(this),this.options=this.config.options.database(),this.options.enabled||u.rmSync(this.DATABASE_DIR,{force:true,recursive:true});}options;DATABASE_DIR=m.resolve(g,".fakelab/db");static register(e){return new n(e)}enabled(){return this.options.enabled??false}async initialize(){if(this.enabled())try{await u.ensureDir(this.DATABASE_DIR),await this.modifyGitignoreFile(".fakelab/*");}catch(e){o.error("Could not create database. error: %s",e);}}async modifyGitignoreFile(e){try{let t=m.resolve(g,".gitignore");if((await u.readFile(t,{encoding:"utf8"})).split(`
6
6
  `).some(r=>r.trim()===e.trim()))return;await u.appendFile(t,`
7
- ${e}`);}catch(t){i.warn("Cannot modify .gitignore file. error: %s",t);}}};var C=class n{constructor(e,t){this.serverCLIOptions=e;this.borrowedConfig=t;this.start=this.start.bind(this),this.xPoweredMiddleware=this.xPoweredMiddleware.bind(this),this.setupApplication=this.setupApplication.bind(this),this.setupTemplateEngine=this.setupTemplateEngine.bind(this),this.loadLocalEnv();}static init(e,t){return new n(e,t)}async start(){let e=R(),t=R.Router(),s=ee.createServer(e),r=T.initHandlers(this.borrowedConfig.config),o=D.register(this.borrowedConfig.config);this.setupApplication(e,r),this.setupTemplateEngine(e),await this.borrowedConfig.config.initializeRuntimeConfig(d,this.serverCLIOptions),await o.initialize(),await new E(t,this.borrowedConfig.config,r,o,this.serverCLIOptions).register(),e.use(t),this.run(s,o,this.serverCLIOptions);}setupApplication(e,t){e.disable("x-powered-by"),e.use(R.json()),e.use(Y({methods:"GET"})),e.use(R.static(d+"/public")),e.use(this.xPoweredMiddleware),e.use(t.middleware);}setupTemplateEngine(e){e.set("views",m.join(d,"views")),e.set("view engine","ejs"),e.use(te),e.set("layout","layouts/main");}listen(e,t){e.enabled()&&i.info("database: %s",e.DATABASE_DIR),i.info("Server listening to http://localhost:%d",t),console.log(se.textSync("FAKELAB"));}run(e,t,s){let{port:r}=this.borrowedConfig.config.options.server(s.pathPrefix,s.port);e.listen(r,"localhost",()=>this.listen(t,r));}xPoweredMiddleware(e,t,s){t.setHeader("x-powered-by","fakelab"),s();}loadLocalEnv(){}};async function P(){try{let e=await new ie().resolve({files:["fakelab.config.ts"]});if(!e)throw i.error("No fakelab config file is detected."),new Error("No fakelab config file is detected.");return (await bundleRequire({filepath:e})).mod.default}catch(n){n instanceof Error&&(i.error("Could not load the config file: %s",n.message),n.stack&&i.debug("Stack trace: %s",n.stack),process.exit(1)),i.error("Could not load the config file."),process.exit(1);}}var L=class{constructor(e,t){this.$emitter=e;this.hooks=t;this.captured=this.captured.bind(this),this.refreshed=this.refreshed.bind(this),this.deleted=this.deleted.bind(this),this.subscribe=this.subscribe.bind(this);}captured(e){let t=this.getTriggeredHook("snapshot:captured");t?(i.info("Dispatching %s for event %s.",i.blue(t.name),i.blue("snapshot:captured")),this.$emitter.emit("snapshot:captured",e)):i.warn("Webhook skipped: missing trigger event %s.",i.blue("snapshot:captured"));}refreshed(e){let t=this.getTriggeredHook("snapshot:refreshed");t?(i.info("Dispatching %s for event %s.",i.blue(t.name),i.blue("snapshot:refreshed")),this.$emitter.emit("snapshot:refreshed",e)):i.warn("Webhook skipped: missing trigger event %s.",i.blue("snapshot:refreshed"));}deleted(e){let t=this.getTriggeredHook("snapshot:deleted");t?(i.info("Dispatching %s for event %s.",i.blue(t.name),i.blue("snapshot:deleted")),this.$emitter.emit("snapshot:deleted",e)):i.warn("Webhook skipped: missing trigger event %s.",i.blue("snapshot:deleted"));}subscribe(e,t,s){return this.$emitter.on(t,s),()=>{i.info("Webhook %s unsubscribed.",i.blue(e)),this.$emitter.off(t,s);}}getTriggeredHook(e){return this.hooks.find(t=>t.trigger.event===e)}};var _=class{constructor(e){this.hooks=e;this._snapshotEventSubscriber=new L(this.$emitter,this.hooks),this.dispose();}$emitter=oe();_snapshotEventSubscriber;get snapshot(){return this._snapshotEventSubscriber}subscribe(e,t,s){return this.isSnapshotEvent(t)?this._snapshotEventSubscriber.subscribe(e,t,s):(i.warn("Webhook hook %s skipped (disabled or invalid).",i.blue(e)),()=>{})}clear(){this.$emitter.all.clear();}isSnapshotEvent(e){return e.startsWith("snapshot")}dispose(){["SIGINT","SIGTERM","SIGQUIT"].forEach(e=>process.on(e,()=>{this.clear();}));}};var N=class n{constructor(e,t,s){this.subscriber=e;this.config=t;this.history=s;this.options=this.config.options.webhook(),this.dispose();}options;unsubs=new Set;static processHandlersRegistered=false;activate(){if(this.flush("REACTIVATE"),!this.options.enabled){i.warn("Webhook is disabled. Skipping activation.");return}if(this.options.hooks.length===0){i.debug?.("Webhook enabled but no hooks configured. Skipping activation.");return}for(let e of this.options.hooks){let{error:t,message:s,args:r=[]}=this.validateHook(e);if(t){i.error(s,...r);continue}if(this.history.has(e.name))return;let o=new AbortController,a=this.subscriber.subscribe(e.name,e.trigger.event,p=>this.handle(e,p,o.signal));this.history.add(e.name),this.unsubs.add(p=>{try{a();}finally{this.history.delete(e.name),o.abort(p);}});}}flush(e){this.subscriber.clear();for(let t of this.unsubs)t(e);this.unsubs.clear();}async handle({name:e,method:t,url:s,headers:r,transform:o,trigger:a},p,c){let l=p;try{l=typeof o=="function"?o(p):p;}catch(h){i.error("Webhook %s payload transformation failed. error: %s",i.blue(e),h);}c.aborted?i.error("Webhook %s aborted",i.blue(e)):c.addEventListener("abort",()=>{i.error("Webhook %s aborted",i.blue(e));});try{i.info("Delivering %s to %s",i.blue(e),i.blue(s)),(await fetch(s,{method:t,body:JSON.stringify(l),signal:c,headers:this.requestHeaders(e,a.event,r)})).ok?i.success("Webhook %s delivered successfully.",i.blue(e)):i.error("Webhook %s request failed.",i.blue(e));}catch(h){i.error("Webhook %s network error: %s",i.blue(e),h);}}validateHook(e){if(e.method.toUpperCase()!=="POST")return {error:true,message:"Webhook hook method must be 'POST'. received %s",args:[e.method]};try{let t=new URL(e.url);if(t.protocol!=="http:"&&t.protocol!=="https:")return {error:!0,message:'Webhook hook URL must use http/https. Received "%s".',args:[t.protocol]}}catch(t){return {error:true,message:"Webhook hook URL is invalid. Received: %s. error: %s",args:[e.url,JSON.stringify(t)]}}return {error:false,message:null}}requestHeaders(e,t,s){let r=new Headers(s);return r.append("Content-Type","application/json"),r.append("X-Fakelab-Webhook",`name=${e},event=${t}`),r}dispose(){n.processHandlersRegistered||(n.processHandlersRegistered=true,["SIGINT","SIGTERM","SIGQUIT"].forEach(e=>process.on(e,()=>{this.history.clear(),this.flush(e);})));}};var w=class n{constructor(e,t){this.options=e;this.config=t;this.capture=this.capture.bind(this),this.__expose=this.__expose.bind(this),this.tryInitializeWebhook();}TARGET_LANGUAGE="typescript";DEFAULT_TYPE_NAME="Fakelab";SNAPSHOT_DIR=m.resolve(g,".fakelab/snapshots");history=new Set;static _instance;subscriber;webhook;__expose(){return {config:this.config,webhook:this.webhook}}static async init(e){let t=await P();return this._instance||(this._instance=new n(e,t)),this._instance.webhook&&this._instance.webhook.activate(),this._instance}static async prepare(e){let t=await P(),s=this._instance||new n({},t),{enabled:r,sources:o}=t.options.snapshot();return s.webhook&&s.webhook.activate(),r&&e.freshSnapshots&&await s.updateAll(o,true),s}async capture(e){let{enabled:t,sources:s}=this.config.options.snapshot();if(!t)return;await u.ensureDir(this.SNAPSHOT_DIR),await u.ensureFile(m.resolve(this.SNAPSHOT_DIR,"__schema.json"));let r=await this.readSnapshotSchema();if(await this.duplicateExists(r)&&(i.error("Snapshot source names must be unique."),process.exit(1)),!e)return this.options.refresh?await this.refresh(s,this.options.refresh,r):this.options.delete?await this.delete(s,this.options.delete,r):await this.updateAll(s);let o=this.suffix(r.sources);this.options?.refresh&&i.warn("--refresh flag has no effect when used with url. Refresh skipped."),this.options?.delete&&i.warn("--delete flag has no effect when used with url. Delete skipped.");let a=await this.fetch({url:e,name:this.options?.name||o});await this.save({url:e,name:this.options?.name||o},a,r),await this.modifyGitignoreFile(".fakelab/*");}async save(e,t,s){try{let r=s.sources.find(a=>a.url===e.url);if(r){i.warn("%s snapshot is already captured. Use \x1B[36mnpx fakelab snapshot --refresh %s\x1B[0m to update.",e.url,r.name);return}let o=this.snapshotName(e.url,!1);i.info("Capturing %s snapshot...",o),e.name||this.options?.name||i.warn("Snapshot source name not found. Auto-generating a name."),await this.write(e.url,t,e.name||this.options?.name),this.subscriber?.snapshot.captured({url:e.url,name:e.name??this.options?.name,content:t}),i.success("Snapshot %s captured successfully.",i.blue(o));}catch(r){console.log({error:r});}}async refresh(e,t,s){let r=e.find(p=>p.name===t.trim());if(r||(r=(s.sources||[]).find(p=>p.name===t)),!r){i.warn("Snapshot source not found. Refresh skipped.");return}i.info("Refreshing %s snapshot source...",i.blue(r.name));let o=m.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));if(await u.exists(o)){let p=await this.fetch(r);this.subscriber?.snapshot.refreshed({url:r.url,name:r.name??this.options?.name,content:p}),await u.writeFile(o,p);}else {let p=await this.fetch(r);this.subscriber?.snapshot.refreshed({url:r.url,name:r.name??this.options?.name,content:p}),await this.save(r,p,s);}i.success("Snapshot source %s refreshed successfully.",i.blue(r.name));}async delete(e,t,s){let r=e.find(a=>a.name===t.trim());if(r||(r=(s.sources||[]).find(a=>a.name===t)),!r){i.warn("Snapshot source not found. Delete skipped.");return}i.info("Deleting %s snapshot source...",i.blue(r.name));let o=m.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));await u.rm(o,{force:true}),await this.updateSnapshotSchema({url:r.url,delete:true}),this.subscriber?.snapshot.deleted({url:r.url,name:r.name??this.options?.name}),i.success("Snapshot source %s deleted successfully.",i.blue(r.name));}async updateAll(e,t=false){i.info(t?"Refreshing all snapshots...":"Updating all snapshots...");let s=await this.readSnapshotSchema();try{await Promise.all(e.map(async r=>{let o=m.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));if(await u.exists(o)){let p=await this.fetch(r);await u.writeFile(o,p);}else {let p=await this.fetch(r);await this.save(r,p,s);}})),i.success("All snapshots are updated.");}catch(r){i.error("Failed to update.",r),process.exit(1);}}async fetch({name:e,url:t,headers:s}){this.isValidUrl(t)||(i.error("Invalid snapshot URL. Please provide a valid http URL."),process.exit(1));let o=await(await fetch(t,{headers:s})).text();this.isValidJSON(o)||(i.error("Invalid snapshot response format. Expected JSON but received non-JSON data."),process.exit(1));let a=await import('quicktype-core'),p=a.jsonInputForTargetLanguage(this.TARGET_LANGUAGE);await p.addSource({name:e,samples:[o]});let c=new a.InputData;c.addInput(p);let{lines:l}=await a.quicktype({inputData:c,lang:this.TARGET_LANGUAGE,rendererOptions:{"just-types":true}});return l.join(`
8
- `)}async write(e,t,s,r){await u.writeFile(m.resolve(this.SNAPSHOT_DIR,this.snapshotName(e)),t),await this.updateSnapshotSchema({url:e,name:s,headers:r});}async readSnapshotSchema(){let e={sources:[]};try{e=await u.readJSON(m.resolve(this.SNAPSHOT_DIR,"__schema.json"));}catch(t){i.warn("Cannot read snapshot __schema file. error: %s",t);}return e.sources||(e.sources=[]),e}async updateSnapshotSchema(e){let t=await this.readSnapshotSchema(),s=t.sources||[],r=s.findIndex(o=>o.url===e.url);if(e.delete)s.splice(r,1);else if(r===-1)s.push({...e,name:e.name||this.suffix(s)});else {let o=s[r].name,a=s[r].headers;s.splice(r,1,{...s[r],name:e.name||o,headers:e.headers||a});}t.sources=s,await u.writeJSON(m.resolve(this.SNAPSHOT_DIR,"__schema.json"),t);}tryInitializeWebhook(){let{enabled:e}=this.config.options.snapshot();if(e){let t=this.config.options.webhook();t.enabled?(this.subscriber=new _(t.hooks),this.webhook=new N(this.subscriber,this.config,this.history)):i.warn("Webhook is disabled. Skipping initialization.");}}snapshotName(e,t=true){return e.replace(/^https?:\/\//,"").replace(/[/:?.&=#]/g,"_")+(t?".ts":"")}suffix(e){let t=e.filter(s=>s.name.startsWith(this.DEFAULT_TYPE_NAME));return `${this.DEFAULT_TYPE_NAME}${t.length}`}isValidUrl(e){try{let t=new URL(e);return !(t.protocol!=="http:"&&t.protocol!=="https:")}catch{return false}}isValidJSON(e){try{return JSON.parse(e),!0}catch{return false}}async duplicateExists(e){let t=e.sources.map(s=>s.name);return new Set(t).size!==t.length}async modifyGitignoreFile(e){try{let t=m.resolve(g,".gitignore");if((await u.readFile(t,{encoding:"utf8"})).split(`
7
+ ${e}`);}catch(t){o.warn("Cannot modify .gitignore file. error: %s",t);}}};var C=class n{constructor(e,t){this.serverCLIOptions=e;this.borrowedConfig=t;this.start=this.start.bind(this),this.xPoweredMiddleware=this.xPoweredMiddleware.bind(this),this.setupApplication=this.setupApplication.bind(this),this.setupTemplateEngine=this.setupTemplateEngine.bind(this),this.loadLocalEnv();}static init(e,t){return new n(e,t)}async start(){let e=R(),t=R.Router(),s=te.createServer(e),r=T.initHandlers(this.borrowedConfig.config),i=D.register(this.borrowedConfig.config);this.setupApplication(e,r),this.setupTemplateEngine(e),await this.borrowedConfig.config.initializeRuntimeConfig(d,this.serverCLIOptions),await i.initialize(),await new E(t,this.borrowedConfig.config,r,i,this.serverCLIOptions).register(),e.use(t),this.run(s,i,this.serverCLIOptions);}setupApplication(e,t){e.disable("x-powered-by"),e.use(R.json()),e.use(Z({methods:"GET"})),e.use(R.static(d+"/public")),e.use(this.xPoweredMiddleware),e.use(t.middleware);}setupTemplateEngine(e){e.set("views",m.join(d,"views")),e.set("view engine","ejs"),e.use(se),e.set("layout","layouts/main");}listen(e,t){e.enabled()&&o.info("database: %s",e.DATABASE_DIR),o.info("Server listening to http://localhost:%d",t),console.log(re.textSync("FAKELAB"));}run(e,t,s){let{port:r}=this.borrowedConfig.config.options.server(s.pathPrefix,s.port);e.listen(r,"localhost",()=>this.listen(t,r));}xPoweredMiddleware(e,t,s){t.setHeader("x-powered-by","fakelab"),s();}loadLocalEnv(){}};var M="fakelab.config.ts";async function P({cwd:n}={cwd:process.cwd()}){try{let e=new oe({cwd:n}),t=m.join(n,M);if(t||(t=await e.resolve({files:[M]})),!t)throw o.error("No fakelab config file is detected."),new Error("No fakelab config file is detected.");return (await bundleRequire({filepath:t})).mod.default}catch(e){let t=e instanceof Error?e.message:"Unknown error";throw o.error("Could not load the config file: %s",t),e instanceof Error&&e.stack&&o.debug("Stack trace: %s",e.stack),e instanceof Error?e:new Error(t)}}var L=class{constructor(e,t){this.$emitter=e;this.hooks=t;this.captured=this.captured.bind(this),this.refreshed=this.refreshed.bind(this),this.deleted=this.deleted.bind(this),this.subscribe=this.subscribe.bind(this);}captured(e){let t=this.getTriggeredHook("snapshot:captured");t?(o.info("Dispatching %s for event %s.",o.blue(t.name),o.blue("snapshot:captured")),this.$emitter.emit("snapshot:captured",e)):o.warn("Webhook skipped: missing trigger event %s.",o.blue("snapshot:captured"));}refreshed(e){let t=this.getTriggeredHook("snapshot:refreshed");t?(o.info("Dispatching %s for event %s.",o.blue(t.name),o.blue("snapshot:refreshed")),this.$emitter.emit("snapshot:refreshed",e)):o.warn("Webhook skipped: missing trigger event %s.",o.blue("snapshot:refreshed"));}deleted(e){let t=this.getTriggeredHook("snapshot:deleted");t?(o.info("Dispatching %s for event %s.",o.blue(t.name),o.blue("snapshot:deleted")),this.$emitter.emit("snapshot:deleted",e)):o.warn("Webhook skipped: missing trigger event %s.",o.blue("snapshot:deleted"));}subscribe(e,t,s){return this.$emitter.on(t,s),()=>{o.info("Webhook %s unsubscribed.",o.blue(e)),this.$emitter.off(t,s);}}getTriggeredHook(e){return this.hooks.find(t=>t.trigger.event===e)}};var _=class{constructor(e){this.hooks=e;this._snapshotEventSubscriber=new L(this.$emitter,this.hooks),this.dispose();}$emitter=ne();_snapshotEventSubscriber;get snapshot(){return this._snapshotEventSubscriber}subscribe(e,t,s){return this.isSnapshotEvent(t)?this._snapshotEventSubscriber.subscribe(e,t,s):(o.warn("Webhook hook %s skipped (disabled or invalid).",o.blue(e)),()=>{})}clear(){this.$emitter.all.clear();}isSnapshotEvent(e){return e.startsWith("snapshot")}dispose(){["SIGINT","SIGTERM","SIGQUIT"].forEach(e=>process.on(e,()=>{this.clear();}));}};var I=class n{constructor(e,t,s){this.subscriber=e;this.config=t;this.history=s;this.options=this.config.options.webhook(),this.dispose();}options;unsubs=new Set;static processHandlersRegistered=false;activate(){if(this.flush("REACTIVATE"),!this.options.enabled){o.warn("Webhook is disabled. Skipping activation.");return}if(this.options.hooks.length===0){o.debug?.("Webhook enabled but no hooks configured. Skipping activation.");return}for(let e of this.options.hooks){let{error:t,message:s,args:r=[]}=this.validateHook(e);if(t){o.error(s,...r);continue}if(this.history.has(e.name))return;let i=new AbortController,a=this.subscriber.subscribe(e.name,e.trigger.event,p=>this.handle(e,p,i.signal));this.history.add(e.name),this.unsubs.add(p=>{try{a();}finally{this.history.delete(e.name),i.abort(p);}});}}flush(e){this.subscriber.clear();for(let t of this.unsubs)t(e);this.unsubs.clear();}async handle({name:e,method:t,url:s,headers:r,transform:i,trigger:a},p,c){let l=p;try{l=typeof i=="function"?i(p):p;}catch(h){o.error("Webhook %s payload transformation failed. error: %s",o.blue(e),h);}c.aborted?o.error("Webhook %s aborted",o.blue(e)):c.addEventListener("abort",()=>{o.error("Webhook %s aborted",o.blue(e));});try{o.info("Delivering %s to %s",o.blue(e),o.blue(s)),(await fetch(s,{method:t,body:JSON.stringify(l),signal:c,headers:this.requestHeaders(e,a.event,r)})).ok?o.success("Webhook %s delivered successfully.",o.blue(e)):o.error("Webhook %s request failed.",o.blue(e));}catch(h){o.error("Webhook %s network error: %s",o.blue(e),h);}}validateHook(e){if(e.method.toUpperCase()!=="POST")return {error:true,message:"Webhook hook method must be 'POST'. received %s",args:[e.method]};try{let t=new URL(e.url);if(t.protocol!=="http:"&&t.protocol!=="https:")return {error:!0,message:'Webhook hook URL must use http/https. Received "%s".',args:[t.protocol]}}catch(t){return {error:true,message:"Webhook hook URL is invalid. Received: %s. error: %s",args:[e.url,JSON.stringify(t)]}}return {error:false,message:null}}requestHeaders(e,t,s){let r=new Headers(s);return r.append("Content-Type","application/json"),r.append("X-Fakelab-Webhook",`name=${e},event=${t}`),r}dispose(){n.processHandlersRegistered||(n.processHandlersRegistered=true,["SIGINT","SIGTERM","SIGQUIT"].forEach(e=>process.on(e,()=>{this.history.clear(),this.flush(e);})));}};var w=class n{constructor(e,t){this.options=e;this.config=t;this.capture=this.capture.bind(this),this.__expose=this.__expose.bind(this),this.tryInitializeWebhook();}TARGET_LANGUAGE="typescript";DEFAULT_TYPE_NAME="Fakelab";SNAPSHOT_DIR=m.resolve(g,".fakelab/snapshots");history=new Set;static _instance;subscriber;webhook;__expose(){return {config:this.config,webhook:this.webhook}}static async init(e){try{let t=await P();return this._instance||(this._instance=new n(e,t)),this._instance.webhook&&this._instance.webhook.activate(),this._instance}catch{process.exit(1);}}static async prepare(e){try{let t=await P(),s=this._instance||new n({},t),{enabled:r,sources:i}=t.options.snapshot();return s.webhook&&s.webhook.activate(),r&&e.freshSnapshots&&await s.updateAll(i,!0),s}catch{process.exit(1);}}async capture(e){let{enabled:t,sources:s}=this.config.options.snapshot();if(!t)return;await u.ensureDir(this.SNAPSHOT_DIR),await u.ensureFile(m.resolve(this.SNAPSHOT_DIR,"__schema.json"));let r=await this.readSnapshotSchema();if(await this.duplicateExists(r)&&(o.error("Snapshot source names must be unique."),process.exit(1)),!e)return this.options.refresh?await this.refresh(s,this.options.refresh,r):this.options.delete?await this.delete(s,this.options.delete,r):await this.updateAll(s);let i=this.suffix(r.sources);this.options?.refresh&&o.warn("--refresh flag has no effect when used with url. Refresh skipped."),this.options?.delete&&o.warn("--delete flag has no effect when used with url. Delete skipped.");let a=await this.fetch({url:e,name:this.options?.name||i});await this.save({url:e,name:this.options?.name||i},a,r),await this.modifyGitignoreFile(".fakelab/*");}async save(e,t,s){try{let r=s.sources.find(a=>a.url===e.url);if(r){o.warn("%s snapshot is already captured. Use \x1B[36mnpx fakelab snapshot --refresh %s\x1B[0m to update.",e.url,r.name);return}let i=this.snapshotName(e.url,!1);o.info("Capturing %s snapshot...",i),e.name||this.options?.name||o.warn("Snapshot source name not found. Auto-generating a name."),await this.write(e.url,t,e.name||this.options?.name),this.subscriber?.snapshot.captured({url:e.url,name:e.name??this.options?.name,content:t}),o.success("Snapshot %s captured successfully.",o.blue(i));}catch(r){console.log({error:r});}}async refresh(e,t,s){let r=e.find(p=>p.name===t.trim());if(r||(r=(s.sources||[]).find(p=>p.name===t)),!r){o.warn("Snapshot source not found. Refresh skipped.");return}o.info("Refreshing %s snapshot source...",o.blue(r.name));let i=m.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));if(await u.exists(i)){let p=await this.fetch(r);this.subscriber?.snapshot.refreshed({url:r.url,name:r.name??this.options?.name,content:p}),await u.writeFile(i,p);}else {let p=await this.fetch(r);this.subscriber?.snapshot.refreshed({url:r.url,name:r.name??this.options?.name,content:p}),await this.save(r,p,s);}o.success("Snapshot source %s refreshed successfully.",o.blue(r.name));}async delete(e,t,s){let r=e.find(a=>a.name===t.trim());if(r||(r=(s.sources||[]).find(a=>a.name===t)),!r){o.warn("Snapshot source not found. Delete skipped.");return}o.info("Deleting %s snapshot source...",o.blue(r.name));let i=m.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));await u.rm(i,{force:true}),await this.updateSnapshotSchema({url:r.url,delete:true}),this.subscriber?.snapshot.deleted({url:r.url,name:r.name??this.options?.name}),o.success("Snapshot source %s deleted successfully.",o.blue(r.name));}async updateAll(e,t=false){o.info(t?"Refreshing all snapshots...":"Updating all snapshots...");let s=await this.readSnapshotSchema();try{await Promise.all(e.map(async r=>{let i=m.resolve(this.SNAPSHOT_DIR,this.snapshotName(r.url));if(await u.exists(i)){let p=await this.fetch(r);await u.writeFile(i,p);}else {let p=await this.fetch(r);await this.save(r,p,s);}})),o.success("All snapshots are updated.");}catch(r){o.error("Failed to update.",r),process.exit(1);}}async fetch({name:e,url:t,headers:s}){this.isValidUrl(t)||(o.error("Invalid snapshot URL. Please provide a valid http URL."),process.exit(1));let i=await(await fetch(t,{headers:s})).text();this.isValidJSON(i)||(o.error("Invalid snapshot response format. Expected JSON but received non-JSON data."),process.exit(1));let a=await import('quicktype-core'),p=a.jsonInputForTargetLanguage(this.TARGET_LANGUAGE);await p.addSource({name:e,samples:[i]});let c=new a.InputData;c.addInput(p);let{lines:l}=await a.quicktype({inputData:c,lang:this.TARGET_LANGUAGE,rendererOptions:{"just-types":true}});return l.join(`
8
+ `)}async write(e,t,s,r){await u.writeFile(m.resolve(this.SNAPSHOT_DIR,this.snapshotName(e)),t),await this.updateSnapshotSchema({url:e,name:s,headers:r});}async readSnapshotSchema(){let e={sources:[]};try{e=await u.readJSON(m.resolve(this.SNAPSHOT_DIR,"__schema.json"));}catch(t){o.warn("Cannot read snapshot __schema file. error: %s",t);}return e.sources||(e.sources=[]),e}async updateSnapshotSchema(e){let t=await this.readSnapshotSchema(),s=t.sources||[],r=s.findIndex(i=>i.url===e.url);if(e.delete)s.splice(r,1);else if(r===-1)s.push({...e,name:e.name||this.suffix(s)});else {let i=s[r].name,a=s[r].headers;s.splice(r,1,{...s[r],name:e.name||i,headers:e.headers||a});}t.sources=s,await u.writeJSON(m.resolve(this.SNAPSHOT_DIR,"__schema.json"),t);}tryInitializeWebhook(){let{enabled:e}=this.config.options.snapshot();if(e){let t=this.config.options.webhook();t.enabled?(this.subscriber=new _(t.hooks),this.webhook=new I(this.subscriber,this.config,this.history)):o.warn("Webhook is disabled. Skipping initialization.");}}snapshotName(e,t=true){return e.replace(/^https?:\/\//,"").replace(/[/:?.&=#]/g,"_")+(t?".ts":"")}suffix(e){let t=e.filter(s=>s.name.startsWith(this.DEFAULT_TYPE_NAME));return `${this.DEFAULT_TYPE_NAME}${t.length}`}isValidUrl(e){try{let t=new URL(e);return !(t.protocol!=="http:"&&t.protocol!=="https:")}catch{return false}}isValidJSON(e){try{return JSON.parse(e),!0}catch{return false}}async duplicateExists(e){let t=e.sources.map(s=>s.name);return new Set(t).size!==t.length}async modifyGitignoreFile(e){try{let t=m.resolve(g,".gitignore");if((await u.readFile(t,{encoding:"utf8"})).split(`
9
9
  `).some(r=>r.trim()===e.trim()))return;await u.appendFile(t,`
10
- ${e}`);}catch(t){i.warn("Cannot modify .gitignore. error: %s",t);}}};var I=new Command,F=u.readJSONSync(m.join(d,"../package.json"));I.name(F.name).description(F.description).version(F.version);I.command("serve").description("start server").option("-s, --source <char>","config source path").option("-x, --pathPrefix <char>","server url path prefix").option("-p, --port <number>","server port number",parseInt).option("-l, --locale <char>","faker custom locale").option("-f, --fresh-snapshots","capture or refresh all snapshots").action(async n=>{let e=await w.prepare(n);C.init(n,e.__expose()).start();});I.command("snapshot").description("capture a url response to a fakelab entity").argument("[string]","url to capture").option("-s, --name <string>","specify snapshot source name").option("-r, --refresh <string>","refresh the specified snapshot").option("-d, --delete <string>","delete the specified snapshot").action(async(n,e)=>{(await w.init(e)).capture(n);});I.parse();
10
+ ${e}`);}catch(t){o.warn("Cannot modify .gitignore. error: %s",t);}}};var N=new Command,F=u.readJSONSync(m.join(d,"../package.json"));N.name(F.name).description(F.description).version(F.version);N.command("serve").description("start server").option("-s, --source <char>","config source path").option("-x, --pathPrefix <char>","server url path prefix").option("-p, --port <number>","server port number",parseInt).option("-l, --locale <char>","faker custom locale").option("-f, --fresh-snapshots","capture or refresh all snapshots").action(async n=>{let e=await w.prepare(n);C.init(n,e.__expose()).start();});N.command("snapshot").description("capture a url response to a fakelab entity").argument("[string]","url to capture").option("-s, --name <string>","specify snapshot source name").option("-r, --refresh <string>","refresh the specified snapshot").option("-d, --delete <string>","delete the specified snapshot").action(async(n,e)=>{(await w.init(e)).capture(n);});N.parse();
package/lib/main.js CHANGED
@@ -50,4 +50,4 @@ const fakelab = Object.freeze(fl);
50
50
  const database = Object.freeze(db);
51
51
 
52
52
  export { fakelab, database };
53
- `;var c=class i{constructor(t,e,r){this.port=t;this.prefix=e;this.dbOptions=r;this.enabled=this.dbOptions?.enabled??true?"true":"false";}enabled;transformOptions={minify:true,platform:"browser",target:"es2022"};static init(t,e,r){return new i(t,e,r)}replacer(t,e){let r=t;for(let o in e)r=r.replace(new RegExp(o,"g"),e[o].toString().trim());return r}async prepareSource(){let t=this.replacer(d,{PORT:this.port,PREFIX:this.prefix,ENABLED:this.enabled});try{let{code:e}=await transform(t,this.transformOptions);return e}catch(e){return e instanceof Error&&s.warn(e.message),t}}};a.dirname(fileURLToPath(import.meta.url));var b=process.cwd();var l=class{constructor(t){this.configOptions=t;this.files=this.files.bind(this),this._serverOptions=this._serverOptions.bind(this),this._databaseOptions=this._databaseOptions.bind(this),this._networkOptions=this._networkOptions.bind(this),this._snapshotOptions=this._snapshotOptions.bind(this),this._fakerOptions=this._fakerOptions.bind(this),this._webhookOptions=this._webhookOptions.bind(this),this.NETWORK_DEFAULT_OPTIONS=Object.freeze({delay:this.configOptions.network?.delay||0,errorRate:this.configOptions.network?.errorRate||0,timeoutRate:this.configOptions.network?.timeoutRate||0,offline:this.configOptions.network?.offline??false});}RUNTIME_SOURCE_FILENAME="runtime.js";FAKELAB_PERSIST_DIR=".fakelab";NETWORK_DEFAULT_OPTIONS;get options(){return {server:this._serverOptions,database:this._databaseOptions,network:this._networkOptions,snapshot:this._snapshotOptions,faker:this._fakerOptions,webhook:this._webhookOptions}}_serverOptions(t,e){return {pathPrefix:t||this.configOptions.server?.pathPrefix||"api",port:e||this.configOptions.server?.port||5200,includeSnapshots:this.configOptions.server?.includeSnapshots??true}}_databaseOptions(){return {enabled:this.configOptions.database?.enabled??false}}_networkOptions(){let t=this.configOptions.network?.preset,e=this.configOptions.network?.presets??{};return !t||!e[t]?this.NETWORK_DEFAULT_OPTIONS:{...e[t],...this.configOptions.network??{}}}_snapshotOptions(){return {enabled:this.configOptions.snapshot?.enabled??false,sources:this.configOptions.snapshot?.sources||[]}}_fakerOptions(t){let e=(t||this.configOptions.faker?.locale)?.toLowerCase();return e&&f.includes(e)?{locale:e}:{locale:u()}}_webhookOptions(){return {enabled:this.configOptions.snapshot?.enabled??false,hooks:this.configOptions.webhook?.hooks??[]}}async files(t){let e=this.resolveSourcePath(t||this.configOptions.sourcePath),r=Array.from(new Set((await Promise.all(e.map(o=>this.resolveTSFiles(o)))).flat()));if(this._serverOptions().includeSnapshots){let o=await this.getSnapshotSourceFiles();r.push(...o);}if(r.length===0){let o=e.map(p=>a.basename(p));o.length===0?s.error("No source path found."):s.error("No Typescript files found in: %s",s.list(o)),process.exit(1);}return r}async initializeRuntimeConfig(t,e){let{port:r,pathPrefix:o}=this._serverOptions(e.pathPrefix,e.port),p=a.resolve(t,this.RUNTIME_SOURCE_FILENAME),g=await c.init(r,o,this.configOptions.database).prepareSource();await F.writeFile(p,g);}async getSnapshotSourceFiles(){let t=await h(".fakelab/snapshots/**/*.ts",{absolute:true,ignore:["**/*.d.ts"],cwd:b});return t.length>0&&s.info("Snapshot(s): %s",s.list(t.map(e=>a.parse(e).name))),t}async tryStat(t){try{return await stat(t)}catch{return null}}async isReadable(t){try{return await access(t,constants.R_OK),!0}catch{return false}}resolveSourcePath(t){return (Array.isArray(t)?t:[t]).map(r=>O(r,{strict:true})?r:a.resolve(r))}async resolveTSFiles(t){if(O(t,{strict:true}))return s.info("Source: %s",t),h(t,{absolute:true,ignore:["**/*.d.ts"]});let e=a.resolve(t),r=e.endsWith(".ts")?e:e+".ts";return (await this.tryStat(r))?.isFile()?(await this.isReadable(r)||(s.error("Cannot read file: %s",r),process.exit(1)),s.info("Source: %s",r),[r]):(await this.tryStat(e))?.isDirectory()?(s.info("Source: %s",e),h("**/*.ts",{cwd:e,absolute:true,ignore:["**/*.d.ts"]})):(s.warn("invalid source: [REDACTED]/%s",a.basename(r)),[])}};function R(i){return new l(i)}export{R as defineConfig};
53
+ `;var c=class i{constructor(t,e,r){this.port=t;this.prefix=e;this.dbOptions=r;this.enabled=this.dbOptions?.enabled??true?"true":"false";}enabled;transformOptions={minify:true,platform:"browser",target:"es2022"};static init(t,e,r){return new i(t,e,r)}replacer(t,e){let r=t;for(let o in e)r=r.replace(new RegExp(o,"g"),e[o].toString().trim());return r}async prepareSource(){let t=this.replacer(d,{PORT:this.port,PREFIX:this.prefix,ENABLED:this.enabled});try{let{code:e}=await transform(t,this.transformOptions);return e}catch(e){return e instanceof Error&&s.warn(e.message),t}}};a.dirname(fileURLToPath(import.meta.url));var b=process.cwd();var l=class{constructor(t){this.configOptions=t;this.files=this.files.bind(this),this._serverOptions=this._serverOptions.bind(this),this._databaseOptions=this._databaseOptions.bind(this),this._networkOptions=this._networkOptions.bind(this),this._snapshotOptions=this._snapshotOptions.bind(this),this._fakerOptions=this._fakerOptions.bind(this),this._webhookOptions=this._webhookOptions.bind(this),this.NETWORK_DEFAULT_OPTIONS=Object.freeze({delay:this.configOptions.network?.delay||0,errorRate:this.configOptions.network?.errorRate||0,timeoutRate:this.configOptions.network?.timeoutRate||0,offline:this.configOptions.network?.offline??false});}RUNTIME_SOURCE_FILENAME="runtime.js";FAKELAB_PERSIST_DIR=".fakelab";NETWORK_DEFAULT_OPTIONS;get options(){return {server:this._serverOptions,database:this._databaseOptions,network:this._networkOptions,snapshot:this._snapshotOptions,faker:this._fakerOptions,webhook:this._webhookOptions}}_serverOptions(t,e){return {pathPrefix:t||this.configOptions.server?.pathPrefix||"api",port:e||this.configOptions.server?.port||5200,includeSnapshots:this.configOptions.server?.includeSnapshots??true}}_databaseOptions(){return {enabled:this.configOptions.database?.enabled??false}}_networkOptions(){let t=this.configOptions.network?.preset,e=this.configOptions.network?.presets??{};return !t||!e[t]?this.NETWORK_DEFAULT_OPTIONS:{...e[t],...this.configOptions.network??{}}}_snapshotOptions(){return {enabled:this.configOptions.snapshot?.enabled??false,sources:this.configOptions.snapshot?.sources||[]}}_fakerOptions(t){let e=(t||this.configOptions.faker?.locale)?.toLowerCase();return e&&f.includes(e)?{locale:e}:{locale:u()}}_webhookOptions(){return {enabled:this.configOptions.webhook?.enabled??false,hooks:this.configOptions.webhook?.hooks??[]}}async files(t){let e=this.resolveSourcePath(t||this.configOptions.sourcePath),r=Array.from(new Set((await Promise.all(e.map(o=>this.resolveTSFiles(o)))).flat()));if(this._serverOptions().includeSnapshots){let o=await this.getSnapshotSourceFiles();r.push(...o);}if(r.length===0){let o=e.map(p=>a.basename(p));o.length===0?s.error("No source path found."):s.error("No Typescript files found in: %s",s.list(o)),process.exit(1);}return r}async initializeRuntimeConfig(t,e){let{port:r,pathPrefix:o}=this._serverOptions(e.pathPrefix,e.port),p=a.resolve(t,this.RUNTIME_SOURCE_FILENAME),g=await c.init(r,o,this.configOptions.database).prepareSource();await F.writeFile(p,g);}async getSnapshotSourceFiles(){let t=await h(".fakelab/snapshots/**/*.ts",{absolute:true,ignore:["**/*.d.ts"],cwd:b});return t.length>0&&s.info("Snapshot(s): %s",s.list(t.map(e=>a.parse(e).name))),t}async tryStat(t){try{return await stat(t)}catch{return null}}async isReadable(t){try{return await access(t,constants.R_OK),!0}catch{return false}}resolveSourcePath(t){return (Array.isArray(t)?t:[t]).map(r=>O(r,{strict:true})?r:a.resolve(r))}async resolveTSFiles(t){if(O(t,{strict:true}))return s.info("Source: %s",t),h(t,{absolute:true,ignore:["**/*.d.ts"]});let e=a.resolve(t),r=e.endsWith(".ts")?e:e+".ts";return (await this.tryStat(r))?.isFile()?(await this.isReadable(r)||(s.error("Cannot read file: %s",r),process.exit(1)),s.info("Source: %s",r),[r]):(await this.tryStat(e))?.isDirectory()?(s.info("Source: %s",e),h("**/*.ts",{cwd:e,absolute:true,ignore:["**/*.d.ts"]})):(s.warn("invalid source: [REDACTED]/%s",a.basename(r)),[])}};function R(i){return new l(i)}export{R as defineConfig};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fakelab",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "A fast, easy-config mock server for frontend developers.",