fakelab 1.0.3 → 1.0.4
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/database.d.ts +1 -0
- package/database.js +1 -0
- package/lib/cli.js +68 -24
- package/lib/main.d.ts +69 -9
- package/lib/main.js +15 -9
- package/lib/public/css/style.css +232 -1
- package/lib/public/icons/graphql-logo.png +0 -0
- package/lib/public/js/gql.min.js +1 -0
- package/lib/public/js/preview.min.js +1 -0
- package/lib/public/js/reset.min.js +1 -12
- package/lib/runtime.d.ts +11 -6
- package/lib/views/components/database/sidebar.ejs +1 -1
- package/lib/views/components/database/toolbar.ejs +2 -2
- package/lib/views/components/graphql/sidebar.ejs +30 -0
- package/lib/views/components/sidebar.ejs +22 -14
- package/lib/views/components/toolbar.ejs +9 -4
- package/lib/views/graphql.ejs +53 -0
- package/lib/views/index.ejs +5 -1
- package/lib/views/layouts/main.ejs +3 -3
- package/lib/views/preview.ejs +5 -3
- package/lib/views/table.ejs +1 -1
- package/package.json +38 -10
- package/runtime.d.ts +1 -0
- package/runtime.js +1 -0
- package/type-utils.d.ts +1 -0
- package/lib/public/js/fakelab.min.js +0 -55
package/database.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./lib/database";
|
package/database.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./lib/database.js";
|
package/lib/cli.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
`).some(s=>s.trim()===e.trim()))return;await
|
|
3
|
-
${e}`);}catch(t){o.warn("Cannot modify .gitignore file. error: %s",t);}}};var
|
|
4
|
-
interface $$ {
|
|
5
|
-
${[...new Set(this.__targets.map(r=>{let s=r.getName(),i=r.getSourceFile().getFilePath();return `${s.toLowerCase()}: import("${i}").${s}`}))].join(`
|
|
6
|
-
`)}
|
|
7
|
-
}`;d.appendFile(b.resolve(h,"type-utils.d.ts"),t);}generateInFileEntitiyMap(){let t=`
|
|
8
|
-
interface Runtime$ {
|
|
9
|
-
${[...new Set(this.__targets.map(r=>{let s=r.getName(),i=r.getSourceFile().getFilePath();return `${s.toLowerCase()}: import("${i}").${s}`}))].join(`
|
|
10
|
-
`)}
|
|
11
|
-
}`;d.appendFile(b.resolve(h,"runtime.d.ts"),t);}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let r=t.getName().toLowerCase(),s=t.getType(),i=this.normalizePath(t.getSourceFile().getDirectoryPath()),n=t.getSourceFile().getBaseName(),p=this.address(i,n),l=b.resolve(y.DATABASE_DIR,`${r}.json`),c=this.address(this.normalizePath(y.DATABASE_DIR),b.basename(l)),u=i.includes("/.fakelab/snapshots"),f=await JSONFilePreset(l,[]);return [r,{type:s,filepath:p,snapshot:u,table:f,tablepath:c}]}));return new Map(e.sort((t,r)=>Number(t[1].snapshot)-Number(r[1].snapshot)))}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};async function S(a,e,t=[],r=0){if(a.isString())return e.string(t[r]);if(a.isNumber())return e.int(t[r]);if(a.isBoolean())return e.bool(t[r]);if(a.isBigInt())return e.bigInt(t[r]);if(a.isBooleanLiteral())return e.litbool(a.getText());if(a.isLiteral())return a.getLiteralValue();if(!a.isUndefined()){if(a.isUnion()){let s=a.getUnionTypes();return await e.union(s.map((i,n)=>S(i,e,t,n)))}if(a.isIntersection()){let s=a.getIntersectionTypes();return await e.intersection(s.map((i,n)=>S(i,e,t,n)))}if(a.isArray()){let s=a.getArrayElementTypeOrThrow();return [await S(s,e,t,r)]}if(a.isObject()){let s=a.getProperties();return await e.object(s,(i,n,p)=>S(i,n,p,r))}return null}}function X({each:a}){return {resolve:async t=>await Promise.all(Array.from({length:t},a))}}async function B(a,e){let t=await a.files(e.source),r=new k(t,e.tsConfig||a.tsConfig()),s=await r.entities(),i=await r.initFakerLibrary(a.options.faker(e.locale)),n=new T(i);async function p(l,c){let u=X({each:()=>S(l,n)}),f=await(c.count?u.resolve(parseInt(String(c.count))):S(l,n)),w=JSON.stringify(f,null,2);return {data:f,json:w}}return {entities:s,build:p}}var D=class{constructor(e,t,r){this.builder=e;this.database=t;this.pkg=r;}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,r)=>{let s=`${t.protocol}://${t.host}/`,i=t.params.name,n=await this.handleQueries(t),p=Y.stringify(n,{addQueryPrefix:true}),l=this.builder.entities.get(i.toLowerCase());if(l){let{json:c}=await this.builder.build(l.type,n),u=l.filepath;r.render("preview",{name:i,suffix:l.snapshot?"(snapshot)":"",filepath:u,address:s,search:p,json:c,prefix:e,entities:this.builder.entities,version:this.pkg.version,enabled:this.database.enabled()});}else r.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,r)=>{let s=`${t.protocol}://${t.host}/`,i=t.params.name,n=this.builder.entities.get(i.toLowerCase());if(!this.database.enabled())r.redirect("/");else if(n){await n.table.read();let l=n.table.data.length>0,c=JSON.stringify(n.table.data,null,2),u=n.filepath;r.render("table",{name:i,filepath:u,address:s,prefix:e,json:c,hasData:l,entities:this.builder.entities,version:this.pkg.version});}else r.redirect("/database");}}};var C=class{constructor(e,t,r){this.builder=e;this.network=t;this.database=r;}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:r}=this.network.state("offline");return e.status(t).json({message:r}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:r}=this.network.state("error");return e.status(t).json({message:r}),true}return false}entity(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,s);t.status(200).json(n);}else t.status(400).json({message:"The entity is not exists"});}catch(r){t.status(500).send(r);}}}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 r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.table.read(),t.status(200).json(s.table.data)):t.status(400).json({message:`${r} table is not exists`});}catch(r){t.status(500).send(r);}}}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 r=e.params.name,s=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,s);await i.table.update(p=>p.push(...Array.isArray(n)?n:[n])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:`${r} table is not exists`});}catch(r){t.status(500).send(r);}}}insert(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.body?.count||1,s=e.body?.strategy||"reset",i=e.params.name,n=this.builder.entities.get(i.toLowerCase());if(n){let{data:p}=await this.builder.build(n.type,{count:r});if(await n.table.read(),s==="once"&&n.table.data.length>0)return t.status(200).json({message:`${i} entity was seeded once before.`});await n.table.update(l=>{s!=="merge"&&(l.length=0);let c=Array.isArray(p)?p:[p];l.length+c.length<this.SEED_MERGE_THRESHOLD&&l.push(...c);}),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(r){t.status(500).send(r);}}}flush(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.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(r){t.status(500).send(r);}}}_update(){return async(e,t)=>{try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.params.name,s=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,s);await i.table.update(p=>p.push(n)),t.status(301).redirect(`/database/${r.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 r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.table.read(),s.table.data.length>0&&await s.table.update(i=>i.length=0),t.status(301).redirect(`/database/${r.toLowerCase()}`)):t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}};var te=d.readJSONSync(b.join(h,"../package.json")),L=class{constructor(e,t,r,s,i){this.router=e;this.config=t;this.network=r;this.database=s;this.options=i;let{pathPrefix:n}=this.config.options.server(this.options.pathPrefix,this.options.port);this.prefix=n;}prefix;async register(){let e=await B(this.config,this.options),t=new D(e,this.database,te),r=new C(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`,r.entity()),this.router.get(`/${this.prefix}/database/:name`,r.getTable()),this.router.post(`/${this.prefix}/database/:name`,r.updateTable()),this.router.post(`/${this.prefix}/database/insert/:name`,r.insert()),this.router.post(`/${this.prefix}/database/flush/:name`,r.flush()),this.router.post("/__update/:name",r._update()),this.router.post("/__delete/:name",r._clear());}};var N=class a{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 a(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||[],r=t.length>0?t[Math.floor(Math.random()*t.length)]:500,s=this.options.errors?.messages?.[r]??"Network error";return {status:r,message:s}}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,r){let s=this.options.errorRate||0,i=this.options.timeoutRate||0,n=this.options.offline??false,p=`delay=${this.resolveDelay()},error=${s},timeout=${i},offline=${n}`;t.setHeader("X-Fakelab-Network",p),r();}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 E=class a{constructor(e,t){this.subscriber=e;this.config=t;this.options=this.config.options.webhook(),this.dispose();}options;unsubs=new Set;history=new Set;activated=false;static processHandlersRegistered=false;isActivated(){return this.activated}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}this.activated=true;for(let e of this.options.hooks){let{error:t,message:r,args:s=[]}=this.validateHook(e);if(t){o.error(r,...s);continue}if(this.history.has(e.name))return;let i=new AbortController,n=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{n();}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:r,headers:s,transform:i,trigger:n},p,l){let c=p;try{c=typeof i=="function"?i(p):p;}catch(u){o.error("Webhook %s payload transformation failed. error: %s",o.blue(e),u);}l.aborted?o.error("Webhook %s aborted",o.blue(e)):l.addEventListener("abort",()=>{o.error("Webhook %s aborted",o.blue(e));});try{o.info("Delivering %s to %s",o.blue(e),o.blue(r)),(await fetch(r,{method:t,body:JSON.stringify(c),signal:l,headers:this.requestHeaders(e,n.event,s)})).ok?o.success("Webhook %s delivered successfully.",o.blue(e)):o.error("Webhook %s request failed.",o.blue(e));}catch(u){o.error("Webhook %s network error: %s",o.blue(e),u);}}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,r){let s=new Headers(r);return s.append("Content-Type","application/json"),s.append("X-Fakelab-Webhook",`name=${e},event=${t}`),s}dispose(){a.processHandlersRegistered||(a.processHandlersRegistered=true,["SIGINT","SIGTERM","SIGQUIT"].forEach(e=>process.on(e,()=>{this.activated=false,this.history.clear(),this.flush(e),process.exit(0);})));}};var x=class{constructor(e){this.$emitter=e;this.subscribe=this.subscribe.bind(this);}subscribe(e,t,r){return this.$emitter.on(t,r),()=>{o.info("Webhook %s unsubscribed.",o.blue(e)),this.$emitter.off(t,r);}}clear(){this.$emitter.all.clear();}};var _=class extends x{constructor(t){let r=re();super(r);this.hooks=t;this.captured=this.captured.bind(this),this.refreshed=this.refreshed.bind(this),this.deleted=this.deleted.bind(this),this.$emitter.all.clear();}captured(t){let r=this.getTriggeredHook("snapshot:captured");r?(o.info("Dispatching %s for event %s.",o.blue(r.name),o.blue("snapshot:captured")),this.$emitter.emit("snapshot:captured",t)):o.warn("Webhook skipped: missing trigger event %s.",o.blue("snapshot:captured"));}refreshed(t){let r=this.getTriggeredHook("snapshot:refreshed");r?(o.info("Dispatching %s for event %s.",o.blue(r.name),o.blue("snapshot:refreshed")),this.$emitter.emit("snapshot:refreshed",t)):o.warn("Webhook skipped: missing trigger event %s.",o.blue("snapshot:refreshed"));}deleted(t){let r=this.getTriggeredHook("snapshot:deleted");r?(o.info("Dispatching %s for event %s.",o.blue(r.name),o.blue("snapshot:deleted")),this.$emitter.emit("snapshot:deleted",t)):o.warn("Webhook skipped: missing trigger event %s.",o.blue("snapshot:deleted"));}getTriggeredHook(t){return this.hooks.find(r=>r.trigger.event===t)}};var F=class extends x{constructor(){let e=re();super(e),this.started=this.started.bind(this),this.shutdown=this.shutdown.bind(this);}started({pathPrefix:e,port:t}){this.$emitter.emit("server:started",{port:t,prefix:e});}shutdown({pathPrefix:e,port:t}){this.$emitter.emit("server:shutdown",{port:t,prefix:e});}};var G=`import { faker } from "@faker-js/faker/locale/LOCALE";
|
|
1
|
+
import v from'path';import g from'fs-extra';import {Command}from'commander';import W from'express';import He from'cors';import Me from'http';import Be from'express-ejs-layouts';import {Project,Node}from'ts-morph';import w from'picocolors';import {JSONFilePreset}from'lowdb/node';import {fileURLToPath}from'url';import {createHash}from'crypto';import {createHandler}from'graphql-http/lib/use/express';import {GraphQLString,GraphQLFloat,GraphQLBoolean,GraphQLList,GraphQLObjectType,GraphQLInputObjectType,GraphQLNonNull,GraphQLID,GraphQLSchema,GraphQLInt}from'graphql';import xe from'qs';import qe from'figlet';import De from'mitt';import {transform}from'esbuild';import Ie from'chokidar';import {bundleRequire}from'bundle-require';import $e from'joycon';var de=new Intl.ListFormat("en",{style:"long",type:"unit"}),o=class{static label(e){switch(e){case "info":return w.blueBright(e.toUpperCase());case "error":return w.redBright(e.toUpperCase());case "warn":return w.yellowBright(e.toUpperCase());case "success":return w.greenBright(e.toUpperCase())}}static log(e,t){return [w.dim(`[${new Date().toISOString()}]`),this.label(e),t].join(" ")}static blue(e){return w.blueBright(e)}static red(e){return w.redBright(e)}static yellow(e){return w.yellowBright(e)}static green(e){return w.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 de.format(e)}};var O=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 r={};return await Promise.all(e.map(async s=>{let i=s.getTypeAtLocation(s.getValueDeclarationOrThrow());r[s.getName()]=await t(i,this,this.readJSDocTags(s));})),r}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)]}async tuple(e){return await Promise.all(e)}evalArgs(e){if(!(!e||!e.trim()))return Function(`"use strict"; return (${e});`)()}readJSDocTags(e){let t=e.getJsDocTags().filter(r=>r.getName()===this.JSDOC_FAKER_FIELD);return t.length===0?[]:t.map(r=>{let[s]=r.getText();if(!s)return;let i=s.text.trim().match(this.FAKER_TAG_REGEX);if(!i)return;let[,a,n]=i,c=this.evalArgs(n);return {path:a,args:c}})}execute(e,t){if(!e)return t();let r=e.path.split("."),s=this.faker;for(let i of r)s=s[i],s||(o.error("Invalid faker module path: (%s)",e.path),process.exit(1));typeof s!="function"&&(o.error("Unresolvable faker function. (%s)",e.path),process.exit(1));try{return e.args?s(e.args):s()}catch(i){return o.error("Passed invalid arguments to faker function. error: %s",i),s()}}};var d=v.dirname(fileURLToPath(import.meta.url)),m=process.cwd();var k=class p{constructor(e){this.config=e;this.enabled=this.enabled.bind(this),this.options=this.config.options.database(),this.options.enabled||g.rmSync(p.DATABASE_DIR,{force:true,recursive:true});}options;static DATABASE_DIR=v.resolve(m,".fakelab/db");static register(e){return new p(e)}enabled(){return this.options.enabled??false}async initialize(){if(this.enabled()){if(!this.config.enabled()){o.warn("Fakelab is disabled. Skipping database initialization.");return}try{await g.ensureDir(p.DATABASE_DIR),await this.modifyGitignoreFile(".fakelab/*");}catch(e){o.error("Could not create database. error: %s",e);}}}async modifyGitignoreFile(e){try{let t=v.resolve(m,".gitignore");if((await g.readFile(t,{encoding:"utf8"})).split(`
|
|
2
|
+
`).some(s=>s.trim()===e.trim()))return;await g.appendFile(t,`
|
|
3
|
+
${e}`);}catch(t){o.warn("Cannot modify .gitignore file. error: %s",t);}}};var ie=`import { faker } from "@faker-js/faker/locale/LOCALE";
|
|
12
4
|
const _fakelab = {};
|
|
13
5
|
_fakelab.url = () => "http://localhost:PORT/PREFIX/";
|
|
14
6
|
|
|
@@ -23,7 +15,9 @@ const functions = {
|
|
|
23
15
|
};
|
|
24
16
|
|
|
25
17
|
|
|
26
|
-
_fakelab.
|
|
18
|
+
_fakelab.enabled = () => FAKELAB_ENABLED;
|
|
19
|
+
|
|
20
|
+
_fakelab.gen = function (name, options) {
|
|
27
21
|
const count = _count(options);
|
|
28
22
|
|
|
29
23
|
if(count === null) return functions[name]();
|
|
@@ -31,23 +25,73 @@ _fakelab.genSync = function (name, options) {
|
|
|
31
25
|
return Array.from({length:count},() => functions[name]())
|
|
32
26
|
}
|
|
33
27
|
|
|
34
|
-
_fakelab.gen = async function (name, options) {
|
|
35
|
-
return _fakelab.genSync(name, options);
|
|
36
|
-
}
|
|
37
28
|
|
|
29
|
+
_fakelab.fetch = async function (name, options) {
|
|
30
|
+
console.warn("[fakelab] fetch() is disabled in headless mode.")
|
|
31
|
+
|
|
32
|
+
return {}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
_fakelab.enabled = _fakelab.enabled.bind(_fakelab);
|
|
38
36
|
_fakelab.url = _fakelab.url.bind(_fakelab);
|
|
39
37
|
_fakelab.gen = _fakelab.gen.bind(_fakelab);
|
|
40
|
-
_fakelab.
|
|
38
|
+
_fakelab.fetch = _fakelab.fetch.bind(_fakelab);
|
|
41
39
|
|
|
42
40
|
const fakelab = Object.freeze(_fakelab);
|
|
43
41
|
|
|
44
42
|
|
|
45
43
|
export {fakelab};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
`,ae=`declare function gen<T extends keyof Runtime$>(name: T): Runtime$[T];
|
|
45
|
+
declare function gen<T extends keyof Runtime$>(name: T, options: Options): Runtime$[T][];
|
|
46
|
+
|
|
47
|
+
declare function fetch<T extends keyof Runtime$>(name: T, options?: Options): never;
|
|
48
|
+
|
|
49
|
+
declare function enabled(): boolean;
|
|
50
|
+
declare function url(): string;
|
|
51
|
+
|
|
52
|
+
declare const fakelab: {
|
|
53
|
+
gen: typeof gen;
|
|
54
|
+
fetch: typeof fetch;
|
|
55
|
+
url: typeof url;
|
|
56
|
+
enabled: typeof enabled;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
type Options = { count: number };
|
|
60
|
+
|
|
61
|
+
interface Runtime$ {}
|
|
62
|
+
|
|
63
|
+
export { fakelab };
|
|
64
|
+
`;var A=class p{constructor(e,t,r=false){this.files=e;this.tsConfigFilePath=t;this.headless=r;let i=new Project({tsConfigFilePath:this.tsConfigFilePath}).addSourceFilesAtPaths(this.files);this.__targets=i.flatMap(a=>{let n=a.getInterfaces(),c=a.getTypeAliases(),u=a.getExportDeclarations().flatMap(l=>l.getNamedExports().flatMap(h=>h.getLocalTargetDeclarations()));return [...n,...c,...u]}),this.generateTypeUtils();}__targets;static async init(e,t,r=false){let s=new p(e,t,r);return await s.generateInFileEntitiyMap(),s}normalizePath(e){return e.split(v.sep).join(v.posix.sep)}address(e,t){let r=this.normalizePath(m);return `${e.replace(r,"")}/${t}`}getDeclarationTextFromType(e){let t=e.getSymbol()??e.getAliasSymbol();if(!t)return null;let r=t.getDeclarations(),s=r.find(Node.isInterfaceDeclaration),i=r.find(Node.isTypeAliasDeclaration),a=r.find(Node.isExportDeclaration),n=s??i??a;return n?n.getText().replace(/export|interface|type|;|\s+/g,""):null}getDeclarationsMap(){return [...new Set(this.__targets.map(e=>{let t=e.getName(),r=e.getSourceFile().getFilePath();return `${t.toLowerCase()}: import("${r}").${t}`}))]}generateTypeUtils(){let e=this.getDeclarationsMap();if(e.length===0)return;let t=`
|
|
65
|
+
interface $$ {
|
|
66
|
+
${e.join(`
|
|
67
|
+
`)}
|
|
68
|
+
}`;g.appendFile(v.resolve(d,"type-utils.d.ts"),t);}argsToString(e){let t=Object.entries(e??{}).filter(([,r])=>r!=null);return t.length===0?null:t.map(r=>r.join("_")).join("_")}async typeToString(e,t){let r=this.argsToString(t),s=this.getDeclarationTextFromType(e);return r&&(s+=`(${r})`),s}async generateInFileEntitiyMap(){this.headless&&await g.writeFile(v.resolve(d,"runtime.d.ts"),ae);let e=this.getDeclarationsMap();if(e.length===0)return;let t=`
|
|
69
|
+
interface Runtime$ {
|
|
70
|
+
${e.join(`
|
|
71
|
+
`)}
|
|
72
|
+
}`;await g.appendFile(v.resolve(d,"runtime.d.ts"),t);}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let r=t.getName().toLowerCase(),s=t.getType(),i=this.normalizePath(t.getSourceFile().getDirectoryPath()),a=t.getSourceFile().getBaseName(),n=this.address(i,a),c=v.resolve(k.DATABASE_DIR,`${r}.json`),u=this.address(this.normalizePath(k.DATABASE_DIR),v.basename(c)),l=i.includes("/.fakelab/snapshots"),h=await JSONFilePreset(c,[]);return [r,{type:s,filepath:n,snapshot:l,table:h,tablepath:u}]}));return new Map(e.sort((t,r)=>Number(t[1].snapshot)-Number(r[1].snapshot)))}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};var X=class p{constructor(e){this.config=e;this.config.options.cache().enabled||this.clear();}static CACHE_DIR=v.resolve(m,".fakelab/cache");__key__(e){return createHash("sha1").update(e).digest("hex").slice(0,24)}async get(e,t){let{enabled:r,ttl:s}=this.config.options.cache();if(!r||t===null)return null;let i=this.__key__(t),a=v.resolve(p.CACHE_DIR,`${e}_${i}.json`);if(!await g.exists(a))return null;try{let n=await this.ensureStats(a);if(n&&s>0&&Date.now()-n.mtimeMs>s)return null;let c=await g.readFile(a,"utf8");return {json:c,data:JSON.parse(c)}}catch(n){return n instanceof Error&&o.error(n.message),null}}async set(e,t,r){let{enabled:s}=this.config.options.cache();if(!s||t===null)return;let i=this.__key__(t),a=v.resolve(p.CACHE_DIR),n=v.resolve(a,`${e}_${i}.json`),c=n+".tmp";await g.ensureDir(a),await g.writeFile(c,r),await g.rename(c,n);}async delete(e,t){let{enabled:r}=this.config.options.cache();if(!r||t===null)return;let s=this.__key__(t),i=v.resolve(p.CACHE_DIR,`${e}_${s}.json`);await g.rm(i,{force:true});}async clear(){await g.rm(p.CACHE_DIR,{recursive:true,force:true});}async ensureStats(e){try{return await g.stat(e)}catch{return null}}};function ne(p){return new X(p)}async function x(p,e,t=[],r=0){if(p.isString())return e.string(t[r]);if(p.isNumber())return e.int(t[r]);if(p.isBoolean())return e.bool(t[r]);if(p.isBigInt())return e.bigInt(t[r]);if(p.isBooleanLiteral())return e.litbool(p.getText());if(p.isLiteral())return p.getLiteralValue();if(!p.isUndefined()){if(p.isUnion()){let s=p.getUnionTypes();return await e.union(s.map((i,a)=>x(i,e,t,a)))}if(p.isTuple()){let s=p.getTupleElements();return await e.tuple(s.map((i,a)=>x(i,e,t,a)))}if(p.isIntersection()){let s=p.getIntersectionTypes();return await e.intersection(s.map(i=>x(i,e,t,r)))}if(p.isArray()){let s=p.getArrayElementTypeOrThrow();return [await x(s,e,t,r)]}if(p.isObject()){let s=p.getProperties();return await e.object(s,(i,a,n)=>x(i,a,n,r))}return null}}function ve({each:p}){return {resolve:async t=>await Promise.all(Array.from({length:t},p))}}async function oe(p,e,t){let r=await p.files(e.source,t),s=await A.init(r,p.getTSConfigFilePath(e.tsConfigPath)),i=await s.entities(),a=await s.initFakerLibrary(p.options.faker(e.locale)),n=new O(a),c=ne(p);async function u(l,h,f){let b=await s.typeToString(h,{...f,...p.options.faker()}),y=await c.get(l,b);if(y)return {...y,fromCache:true};let fe=ve({each:()=>x(h,n)}),te=await(f.count?fe.resolve(parseInt(String(f.count))):x(h,n)),re=JSON.stringify(te,null,2);return c.set(l,b,re),{data:te,json:re,fromCache:false}}return {entities:i,build:u}}var P=class{constructor(e){this.builder=e;}typeMap=new Map;tsTypeToGraphQLType(e,t=false){if(e.isString())return GraphQLString;if(e.isNumber())return GraphQLFloat;if(e.isBoolean())return GraphQLBoolean;if(e.isBigInt())return GraphQLString;if(e.isArray()){let r=e.getArrayElementTypeOrThrow();return new GraphQLList(this.tsTypeToGraphQLType(r,t))}if(e.isUnion())return GraphQLString;if(e.isObject()){let r=this.getTypeName(e);if(!r)return GraphQLString;let s=this.typeMap.get(r);if(s)return t&&s instanceof GraphQLObjectType?void 0:s;let i=this.createObjectType(e,r,t);if(i)return this.typeMap.set(r,i),i}return GraphQLString}getTypeName(e){let t=e.getSymbol();return t?t.getName():null}createObjectType(e,t,r){let s=e.getProperties(),i={};for(let a of s){let n=a.getTypeAtLocation(a.getValueDeclarationOrThrow()),c=this.tsTypeToGraphQLType(n,r);c&&(i[a.getName()]={type:c});}return Object.keys(i).length===0?null:r?new GraphQLInputObjectType({name:`${t}Input`,fields:i}):new GraphQLObjectType({name:t,fields:i})}generateSchema(){let e={},t={};for(let[i,a]of this.builder.entities){let n=this.tsTypeToGraphQLType(a.type);if(n instanceof GraphQLObjectType||n instanceof GraphQLList){let c=n instanceof GraphQLList?n:new GraphQLList(n);e[i]={type:c,args:{count:{type:GraphQLInt}},resolve:async(l,h)=>{let{data:f}=await this.builder.build(i.toLowerCase(),a.type,{count:h.count||1});return Array.isArray(f)?f:[f]}},e[`${i}ById`]={type:n instanceof GraphQLList?n.ofType:n,args:{id:{type:new GraphQLNonNull(GraphQLID)}},resolve:async(l,h,f)=>{if(!f.database?.enabled())throw new Error("Database is not enabled");return await a.table.read(),a.table.data.find(y=>String(y.id)===h.id)||null}},e[`all${i.charAt(0).toUpperCase()+i.slice(1)}`]={type:new GraphQLList(n instanceof GraphQLList?n.ofType:n),resolve:async(l,h,f)=>{if(!f.database?.enabled())throw new Error("Database is not enabled");return await a.table.read(),a.table.data}};let u=this.tsTypeToGraphQLType(a.type,true);u instanceof GraphQLInputObjectType&&(t[`create${i.charAt(0).toUpperCase()+i.slice(1)}`]={type:n instanceof GraphQLList?n.ofType:n,args:{input:{type:new GraphQLNonNull(u)}},resolve:async(l,h,f)=>{if(!f.database?.enabled())throw new Error("Database is not enabled");await a.table.read();let b={...h.input,id:h.input.id||`${Date.now()}-${Math.random()}`};return await a.table.update(y=>y.push(b)),b}}),t[`delete${i.charAt(0).toUpperCase()+i.slice(1)}`]={type:GraphQLBoolean,args:{id:{type:new GraphQLNonNull(GraphQLID)}},resolve:async(l,h,f)=>{if(!f.database?.enabled())throw new Error("Database is not enabled");await a.table.read();let b=a.table.data.findIndex(y=>String(y.id)===h.id);return b!==-1?(await a.table.update(y=>y.splice(b,1)),true):false}},t[`flush${i.charAt(0).toUpperCase()+i.slice(1)}`]={type:GraphQLBoolean,resolve:async(l,h,f)=>{if(!f.database?.enabled())throw new Error("Database is not enabled");return await a.table.update(b=>b.length=0),true}};}}let r=new GraphQLObjectType({name:"Query",fields:e}),s=new GraphQLObjectType({name:"Mutation",fields:t});return new GraphQLSchema({query:r,mutation:Object.keys(t).length>0?s:void 0})}};var I=class{constructor(e,t,r,s){this.builder=e;this.network=t;this.database=r;this.config=s;}schema=null;async applyNetworkHandlers(e){if(this.network.offline()){let{status:t,message:r}=this.network.state("offline");return e.status(t).json({errors:[{message:r}]}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:r}=this.network.state("error");return e.status(t).json({errors:[{message:r}]}),true}return false}getSchema(){if(!this.schema){let e=new P(this.builder);this.schema=e.generateSchema();}return this.schema}createMiddleware(){let e=this.getSchema();return async(t,r,s)=>!this.config.enabled()||await this.applyNetworkHandlers(r)?void 0:createHandler({schema:e,context:()=>({database:this.database,network:this.network}),formatError:a=>a})(t,r,s)}buildQuery(e,t){let r=this.extractFields(t,new Set,1);return `query {
|
|
73
|
+
${e}(count: 1) {
|
|
74
|
+
${r.join(`
|
|
75
|
+
`)}
|
|
76
|
+
}
|
|
77
|
+
}`}extractFields(e,t=new Set,r=1){let s=[],i=e.getSymbol()?.getName();if(i&&t.has(i))return s;i&&t.add(i);let a=e.getProperties(),n=" ".repeat(r);for(let c of a){let u=c.getTypeAtLocation(c.getValueDeclarationOrThrow()),l=c.getName();if(u.isArray()){let h=u.getArrayElementTypeOrThrow();if(h.isObject()&&!t.has(h.getSymbol()?.getName()||"")){let f=this.extractFields(h,t,r+1);f.length>0?s.push(`${n}${n}${l} {
|
|
78
|
+
${f.join(`
|
|
79
|
+
`)}
|
|
80
|
+
${n}}`):s.push(`${n}${n}${l}`);}else s.push(`${n}${n}${l}`);}else if(u.isObject()&&!t.has(u.getSymbol()?.getName()||"")){let h=this.extractFields(u,t,r+1);h.length>0?s.push(`${n}${n}${l} {
|
|
81
|
+
${h.join(`
|
|
82
|
+
`)}
|
|
83
|
+
${n}${n}}`):s.push(`${n}${n}${l}`);}else s.push(`${n}${n}${l}`);}return i&&t.delete(i),s}};var j=class{constructor(e,t,r,s,i){this.builder=e;this.database=t;this.config=r;this.graphqlBuilder=s;this.pkg=i;this.isFakelabEnabled=this.config.enabled(),this.isDatabaseEnabled=this.database.enabled();}isFakelabEnabled;isDatabaseEnabled;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,isFakelabEnabled:this.isFakelabEnabled,isDatabaseEnabled:this.isDatabaseEnabled});}}preview(e){return async(t,r)=>{if(!this.isFakelabEnabled)return r.redirect("/");let s=`${t.protocol}://${t.host}/`,i=t.params.name,a=await this.handleQueries(t),n=xe.stringify(a,{addQueryPrefix:true}),c=this.builder.entities.get(i.toLowerCase());if(c){let{json:u,fromCache:l}=await this.builder.build(i.toLowerCase(),c.type,a),h=c.filepath,f=this.config.options.graphQL();r.render("preview",{name:i,suffix:c.snapshot?"(snapshot)":"",filepath:h,address:s,search:n,json:u,prefix:e,fromCache:l,entities:this.builder.entities,version:this.pkg.version,isDatabaseEnabled:this.isDatabaseEnabled,isFakelabEnabled:this.isFakelabEnabled,isGraphqlEnabled:f.enabled});}else r.redirect("/");}}db(){return (e,t)=>{if(!this.isFakelabEnabled)return t.redirect("/");this.isDatabaseEnabled?t.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):t.redirect("/");}}table(e){return async(t,r)=>{if(!this.isFakelabEnabled)return r.redirect("/");let s=`${t.protocol}://${t.host}/`,i=t.params.name,a=this.builder.entities.get(i.toLowerCase());if(!this.isDatabaseEnabled)r.redirect("/");else if(a){await a.table.read();let c=a.table.data.length>0,u=JSON.stringify(a.table.data,null,2),l=a.filepath;r.render("table",{name:i,filepath:l,address:s,prefix:e,json:u,hasData:c,entities:this.builder.entities,version:this.pkg.version,isFakelabEnabled:this.isFakelabEnabled});}else r.redirect("/database");}}graphql(e){return (t,r)=>{if(!this.isFakelabEnabled)return r.redirect("/");let{enabled:s}=this.config.options.graphQL();s?r.render("graphql",{name:null,address:"",prefix:e,query:"",entities:this.builder.entities,version:this.pkg.version,isDatabaseEnabled:this.isDatabaseEnabled,isFakelabEnabled:this.isFakelabEnabled}):r.redirect("/");}}graphqlEntity(e){return (t,r)=>{if(!this.isFakelabEnabled)return r.redirect("/");let s=`${t.protocol}://${t.host}/`,i=t.params.name,a=this.builder.entities.get(i.toLowerCase()),{enabled:n}=this.config.options.graphQL();if(!n)r.redirect("/");else if(a){let c=this.graphqlBuilder.buildQuery(i.toLowerCase(),a.type);r.render("graphql",{name:i,address:s,prefix:e,query:c,entities:this.builder.entities,version:this.pkg.version,isDatabaseEnabled:this.isDatabaseEnabled,isFakelabEnabled:this.isFakelabEnabled});}else r.redirect("/graphql");}}};var H=class{constructor(e,t,r,s){this.builder=e;this.network=t;this.database=r;this.config=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:r}=this.network.state("offline");return e.status(t).json({message:r}),true}if(await this.network.wait(),this.network.timeout())return true;if(this.network.error()){let{status:t,message:r}=this.network.state("error");return e.status(t).json({message:r}),true}return false}entity(){return async(e,t)=>{if(!this.config.enabled())return t.status(500).json({error:"Fakelab is disabled."});try{if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:a}=await this.builder.build(r.toLowerCase(),i.type,s);t.status(200).json(a);}else t.status(400).json({message:"The entity is not exists"});}catch(r){t.status(500).send(r);}}}getTable(){return async(e,t)=>{if(!this.config.enabled())return t.status(500).json({error:"Fakelab is disabled."});try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.table.read(),t.status(200).json(s.table.data)):t.status(400).json({message:`${r} table is not exists`});}catch(r){t.status(500).send(r);}}}updateTable(){return async(e,t)=>{if(!this.config.enabled())return t.status(500).json({error:"Fakelab is disabled."});try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});if(await this.applyNetworkHandlers(t))return;let r=e.params.name,s=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:a}=await this.builder.build(r.toLowerCase(),i.type,s);await i.table.update(n=>n.push(...Array.isArray(a)?a:[a])),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:`${r} table is not exists`});}catch(r){t.status(500).send(r);}}}insert(){return async(e,t)=>{if(!this.config.enabled())return t.status(500).json({error:"Fakelab is disabled."});try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.body?.count||1,s=e.body?.strategy||"reset",i=e.params.name,a=this.builder.entities.get(i.toLowerCase());if(a){let{data:n}=await this.builder.build(i.toLowerCase(),a.type,{count:r});if(await a.table.read(),s==="once"&&a.table.data.length>0)return t.status(200).json({message:`${i} entity was seeded once before.`});await a.table.update(c=>{s!=="merge"&&(c.length=0);let u=Array.isArray(n)?n:[n];c.length+u.length<this.SEED_MERGE_THRESHOLD&&c.push(...u);}),t.status(200).json({success:!0});}else t.status(400).json({success:!1,message:"The table is not exists"});}catch(r){t.status(500).send(r);}}}flush(){return async(e,t)=>{if(!this.config.enabled())return t.status(500).json({error:"Fakelab is disabled."});try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.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(r){t.status(500).send(r);}}}_update(){return async(e,t)=>{if(!this.config.enabled())return t.status(500).json({error:"Fakelab is disabled."});try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.params.name,s=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:a}=await this.builder.build(r.toLowerCase(),i.type,s);await i.table.update(n=>n.push(a)),t.status(301).redirect(`/database/${r.toLowerCase()}`);}else t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}_clear(){return async(e,t)=>{if(!this.config.enabled())return t.status(500).json({error:"Fakelab is disabled."});try{if(!this.database.enabled())return t.status(403).json({message:"database is not enabled or initialized."});let r=e.params.name,s=this.builder.entities.get(r.toLowerCase());s?(await s.table.read(),s.table.data.length>0&&await s.table.update(i=>i.length=0),t.status(301).redirect(`/database/${r.toLowerCase()}`)):t.status(400).redirect("/database");}catch{t.status(500).redirect("/database");}}}};var Ce=g.readJSONSync(v.join(d,"../package.json")),G=class{constructor(e,t,r,s,i){this.router=e;this.config=t;this.network=r;this.database=s;this.options=i;}instantiateRegistryHandlers(e,t){let r=new I(t,this.network,this.database,e),s=new H(t,this.network,this.database,e),i=new j(t,this.database,e,r,Ce);return {gql:r,handler:s,renderer:i}}async register({fresh:e}){let t=await oe(this.config,this.options,e),{gql:r,handler:s,renderer:i}=this.instantiateRegistryHandlers(this.config,t),{pathPrefix:a}=this.config.options.server(this.options.pathPrefix,this.options.port);this.router.get("/",i.index()),this.router.get("/graphql",i.graphql(a)),this.router.get("/graphql/:name",i.graphqlEntity(a)),this.router.get("/entities/:name",i.preview(a)),this.router.get("/database",i.db()),this.router.get("/database/:name",i.table(a)),this.router.all(`/${a}/graphql`,r.createMiddleware()),this.router.get(`/${a}/:name`,s.entity()),this.router.get(`/${a}/database/:name`,s.getTable()),this.router.post(`/${a}/database/:name`,s.updateTable()),this.router.post(`/${a}/database/insert/:name`,s.insert()),this.router.post(`/${a}/database/flush/:name`,s.flush()),this.router.post("/__update/:name",s._update()),this.router.post("/__delete/:name",s._clear());}};var M=class p{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.wait=this.wait.bind(this),this.offline=this.offline.bind(this);}options;static initHandlers(e){return new p(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||[],r=t.length>0?t[Math.floor(Math.random()*t.length)]:500,s=this.options.errors?.messages?.[r]??"Network error";return {status:r,message:s}}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}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 R=class p{constructor(e,t){this.subscriber=e;this.config=t;this.options=this.config.options.webhook(),this.dispose();}options;unsubs=new Set;history=new Set;activated=false;static processHandlersRegistered=false;isActivated(){return this.activated}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}this.activated=true;for(let e of this.options.hooks){let{error:t,message:r,args:s=[]}=this.validateHook(e);if(t){o.error(r,...s);continue}if(this.history.has(e.name))continue;let i=new AbortController,a=this.subscriber.subscribe(e.name,e.trigger.event,n=>this.handle(e,n,i.signal));this.history.add(e.name),this.unsubs.add(n=>{try{a();}finally{this.history.delete(e.name),i.abort(n);}});}}flush(e){this.subscriber.clear();for(let t of this.unsubs)t(e);this.unsubs.clear();}async handle({name:e,method:t,url:r,headers:s,transform:i,trigger:a},n,c){let u=n;try{u=typeof i=="function"?i(n):n;}catch(l){o.error("Webhook %s payload transformation failed. error: %s",o.blue(e),l);}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(r)),(await fetch(r,{method:t,body:JSON.stringify(u),signal:c,headers:this.requestHeaders(e,a.event,s)})).ok?o.success("Webhook %s delivered successfully.",o.blue(e)):o.error("Webhook %s request failed.",o.blue(e));}catch(l){o.error("Webhook %s network error: %s",o.blue(e),l);}}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,r){let s=new Headers(r);return s.append("Content-Type","application/json"),s.append("X-Fakelab-Webhook",`name=${e},event=${t}`),s}dispose(){p.processHandlersRegistered||(p.processHandlersRegistered=true,["SIGINT","SIGTERM","SIGQUIT"].forEach(e=>process.on(e,()=>{this.activated=false,this.history.clear(),this.flush(e),process.exit(0);})));}};var C=class{constructor(e){this.$emitter=e;this.subscribe=this.subscribe.bind(this);}subscribe(e,t,r){return this.$emitter.on(t,r),()=>{o.info("Webhook %s unsubscribed.",o.blue(e)),this.$emitter.off(t,r);}}clear(){this.$emitter.all.clear();}};var B=class extends C{constructor(t){let r=De();super(r);this.hooks=t;this.captured=this.captured.bind(this),this.refreshed=this.refreshed.bind(this),this.deleted=this.deleted.bind(this),this.$emitter.all.clear();}captured(t){let r=this.getTriggeredHook("snapshot:captured");r?(o.info("Dispatching %s for event %s.",o.blue(r.name),o.blue("snapshot:captured")),this.$emitter.emit("snapshot:captured",t)):o.warn("Webhook skipped: missing trigger event %s.",o.blue("snapshot:captured"));}refreshed(t){let r=this.getTriggeredHook("snapshot:refreshed");r?(o.info("Dispatching %s for event %s.",o.blue(r.name),o.blue("snapshot:refreshed")),this.$emitter.emit("snapshot:refreshed",t)):o.warn("Webhook skipped: missing trigger event %s.",o.blue("snapshot:refreshed"));}deleted(t){let r=this.getTriggeredHook("snapshot:deleted");r?(o.info("Dispatching %s for event %s.",o.blue(r.name),o.blue("snapshot:deleted")),this.$emitter.emit("snapshot:deleted",t)):o.warn("Webhook skipped: missing trigger event %s.",o.blue("snapshot:deleted"));}getTriggeredHook(t){return this.hooks.find(r=>r.trigger.event===t)}};var q=class extends C{constructor(){let e=De();super(e),this.started=this.started.bind(this),this.shutdown=this.shutdown.bind(this);}started({pathPrefix:e,port:t}){this.$emitter.emit("server:started",{port:t,prefix:e});}shutdown({pathPrefix:e,port:t}){this.$emitter.emit("server:shutdown",{port:t,prefix:e});}};var U=class{constructor(e){this.config=e;}HEADLESS_FILENAME="runtime.js";JSDOC_FAKER_FIELD="faker";FAKER_TAG_REGEX=/^([a-zA-Z0-9._]+)(?:\((.*)\))?$/;async generate(e){try{let t=await this.config.files(e.source,!1),s=await(await A.init(t,this.config.getTSConfigFilePath(e.tsConfigPath),!0)).entities(),i=[],a=new Map;for(let[l]of s.entries())a.set(l.toLowerCase(),l);for(let[l,h]of s.entries()){let f=this.generateFunction(h.type,l,a);i.push(f);}let{locale:n}=this.config.options.faker(),{code:c}=await transform(this.generateSource(i,n),{minify:!0,platform:"browser",target:"es2022"}),u=v.resolve(d,this.HEADLESS_FILENAME);return await g.ensureDir(v.dirname(u)),await Promise.all([g.writeFile(u,c)]),!0}catch(t){return o.error(t instanceof Error?t.message:t),false}}replacer(e,t){let r=e;for(let s in t)r=r.replace(new RegExp(s,"g"),t[s].toString().trim());return r}generateSource(e,t){let{pathPrefix:r,port:s}=this.config.options.server();return this.replacer(ie,{FAKELAB_ENABLED:this.config.enabled(),PORT:s,PREFIX:r,LOCALE:t,FUNCTIONS:e.join(`,
|
|
84
|
+
`)})}generateFunction(e,t,r){let i=this.extractProperties(e).map(a=>this.generateProperty(a,r,1));return `${t}(){return {${i.join(",")}};}`}extractProperties(e){return e.isObject()?e.getProperties().map(r=>{let s=r.getTypeAtLocation(r.getValueDeclarationOrThrow()),i=this.extractFakerTag(r);return {name:r.getName(),type:s,fakerTag:i,array:s.isArray()}}):[]}extractFakerTag(e){let t=e.getJsDocTags().filter(s=>s.getName()===this.JSDOC_FAKER_FIELD);if(t.length===0)return;let[r]=t[0].getText();if(r)return r.text.trim()}generateProperty(e,t,r=0){let{name:s,type:i,fakerTag:a,array:n}=e,c=" ".repeat(r);if(a){let u=this.parseFakerTag(a);return n?`${s}: [${u}]`:`${s}: ${u}`}if(i.isArray()){let l=i.getArrayElementTypeOrThrow().getSymbol();if(l){let h=l.getName().toLowerCase();if(t.has(h))return `${s}: [this.${h}()]`}}else {let u=i.getSymbol();if(u){let l=u.getName().toLowerCase();if(t.has(l))return `${s}: this.${l}()`}}if(i.isString())return n?`${s}: [faker.word.noun()]`:`${s}: faker.word.noun()`;if(i.isNumber())return n?`${s}: [faker.number.int()]`:`${s}: faker.number.int()`;if(i.isBoolean())return n?`${s}: [faker.datatype.boolean()]`:`${s}: faker.datatype.boolean()`;if(i.isArray()){let u=i.getArrayElementTypeOrThrow();return [this.generateProperty({name:s,type:u,array:true},t,r+1)]}if(i.isObject()){let u=this.extractProperties(i);if(u.length===0)return `${s}: {}`;let l=u.map(f=>this.generateProperty(f,t,r+1)),h=" ".repeat(r+1);return `${s}: {
|
|
85
|
+
${h}${l.map(f=>f).join(`,
|
|
86
|
+
${h}`)}
|
|
87
|
+
${c}}`}return `${s}: null`}parseFakerTag(e){let t=e.match(this.FAKER_TAG_REGEX);if(!t)return "faker.word.noun()";let[,r,s]=t;return s&&s.trim()?`faker.${r}(${s})`:`faker.${r}()`}};var Pe="fakelab.config.ts";async function F(){try{let e=await new $e().resolve({files:[Pe]});if(!e)throw new Error("No fakelab config file is detected.");let t=v.resolve(e);if(!await g.pathExists(t))throw new Error(`Config file not found: ${t}`);return (await bundleRequire({filepath:t,cwd:m})).mod.default}catch(p){throw p instanceof Error&&p.stack&&o.debug("Stack trace: %s",p.stack),p}}var Q=class p{constructor(e,t,r,s){this.app=e;this.initial=t;this.config=r;this.options=s;this.router=this.initial,this.middleware=this.middleware.bind(this),this.app.use(this.middleware),this.app.get(this.RELOAD_PATH,(i,a)=>this.subscribe(a)),this.ping();}router;clients=new Set;_ref={current:null};_pingTimer;RELOAD_PATH="/__reload";headers={"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"};database;static register(e,t,r,s){return new p(e,t,r,s)}set(e){this.router=e;}subscribe(e){e.writeHead(200,this.headers),e.write(`
|
|
88
|
+
`),this.clients.add(e),e.on("close",()=>{this.clients.delete(e);});}broadcast(){for(let e of this.clients)e.write(`event: reload
|
|
89
|
+
data: ${Date.now()}
|
|
90
|
+
|
|
91
|
+
`);}watch(){let e=this.createTrigger(),t=Ie.watch(this.watcherPaths(),{ignoreInitial:true,persistent:true,ignorePermissionErrors:true,ignored:["**/*.js","**/*.jsx","**/*.json","**/*.map","**/*.d.ts","**/node_modules/**"]});return t.on("add",e),t.on("change",e),t.on("unlink",e),t.on("unlinkDir",e),t.on("error",r=>{o.error("Hot reload: watcher error \u274C. error: %s",r instanceof Error?r.message:String(r));}),()=>{t.close(),this._ref.current=null;for(let r of this.clients)try{r.end();}catch{}this.clients.clear(),this._pingTimer&&clearInterval(this._pingTimer),this._pingTimer=void 0;}}async onReady(e){let t=W.Router();this._ref.current=e,this.database=await e(t,false),this.set(t);}ping(){this._pingTimer||(this._pingTimer=setInterval(()=>{for(let e of this.clients)try{e.write(`event: ping
|
|
92
|
+
data: ${Date.now()}
|
|
93
|
+
|
|
94
|
+
`);}catch{this.clients.delete(e);}},25e3));}async reload(){if(!this._ref.current)return;let e=W.Router(),t=await this._ref.current(e,true);this.database=t,this.set(e);}async prepareConfig(e,t){return t?await F():e}watcherPaths(){let e=v.resolve(m,"fakelab.config.ts");return [...this.config.getSourceFiles(this.options.source),e]}middleware(e,t,r){return this.router(e,t,r)}createTrigger(){let e,t=false,r=false,s=()=>{e&&clearTimeout(e),e=setTimeout(async()=>{if(t){r=true;return}t=true;let i=Date.now();o.info("Hot reload: change detected.");try{o.info("Hot reload: rebuilding routes..."),await this.reload(),this.broadcast();let a=Date.now()-i;o.success("Hot reload: completed in %dms \u2705",a);}catch(a){o.error("Hot reload: rebuild failed \u274C. error: %s",a instanceof Error?a.message:String(a));}finally{t=false,r&&(r=false,s());}},250);};return s}};var J=class p{constructor(e,t){this.options=e;this.config=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(),this.initWebhook(),process.on("SIGINT",()=>{let r=this.config.options.server(this.options.pathPrefix,this.options.port);this.subscriber?.shutdown(r),process.exit(0);});}webhook;subscriber;initWebhook(){if(this.config.options.webhook().enabled){if(!this.config.enabled()){o.warn("Fakelab is disabled. Skipped webhook initialization.");return}this.subscriber=new q,o.warn("Initializating webhook..."),this.webhook=new R(this.subscriber,this.config);}}static init(e,t){let r=new p(e,t);return r.webhook&&!r.webhook.isActivated()&&r.webhook.activate(),r}async shouldRunHeadlessMode(){if(this.options.headless||this.config.isHeadless()){let t=await new U(this.config).generate(this.options);return t||o.error("Headless mode failed. Falling back to standard server mode."),t}return false}async start(){if(await this.shouldRunHeadlessMode()){o.info("Headless mode enabled. Server startup skipped.");return}let e=W(),t=W.Router(),r=Me.createServer(e);this.setupApplication(e),this.setupTemplateEngine(e),await this.config.initializeRuntimeConfig(d,this.options);let s=Q.register(e,t,this.config,this.options);await s.onReady(async(a,n)=>{await this.config.initializeRuntimeConfig(d,this.options);let c=await s.prepareConfig(this.config,n),u=M.initHandlers(c),l=k.register(c);return await l.initialize(),await new G(a,c,u,l,this.options).register({fresh:n}),l});let i=s.watch();process.on("SIGINT",i),s.database&&this.run(r,s.database,this.options);}setupApplication(e){e.disable("x-powered-by"),e.use(W.json()),e.use(He({methods:["GET","POST","OPTIONS"]})),e.use(W.static(d+"/public")),e.use(this.xPoweredMiddleware);}setupTemplateEngine(e){e.set("views",v.join(d,"views")),e.set("view engine","ejs"),e.use(Be),e.set("layout","layouts/main");}listen(e,t){this.subscriber?.started(t),e.enabled()&&this.config.enabled()&&o.info("database: %s",k.DATABASE_DIR),o.info("Server%s listening at http://localhost:%d",this.config.enabled()?"":"(disabled)",t.port),this.config.enabled()&&console.log(qe.textSync("FAKELAB"));}run(e,t,r){let s=this.config.options.server(r.pathPrefix,r.port);e.listen(s.port,"localhost",()=>this.listen(t,s));}xPoweredMiddleware(e,t,r){t.setHeader("x-powered-by","fakelab"),r();}loadLocalEnv(){try{process.env.NODE_ENV==="development"&&process.loadEnvFile("./.env.local");}catch(e){process.env.NODE_ENV==="development"&&o.warn("Cannot load .env.local file for debugging. error: %s",e);}}};var $=class p{constructor(e,t){this.options=e;this.config=t;this.capture=this.capture.bind(this),this.initWebhook();}TARGET_LANGUAGE="typescript";DEFAULT_TYPE_NAME="Fakelab";static SNAPSHOT_DIR=v.resolve(m,".fakelab/snapshots");static _instance;subscriber;webhook;static async init(e){try{let t=await F();return this._instance||(this._instance=new p(e,t)),this._instance.webhook&&!this._instance.webhook.isActivated()&&this._instance.webhook.activate(),this._instance}catch{process.exit(1);}}static async prepare(e){try{let t=await F(),r=this._instance||new p({},t),{enabled:s,sources:i}=t.options.snapshot();return r.webhook&&!r.webhook.isActivated()&&r.webhook.activate(),s&&r.config.enabled()&&e.freshSnapshots&&await r.updateAll(i,!0),r}catch{process.exit(1);}}async capture(e){if(!this.config.enabled()){o.error("Fakelab is disabled. Capture Skipped.");return}let{enabled:t,sources:r}=this.config.options.snapshot();if(!t){o.warn("Snapshot is disabled. Capture Skipped.");return}await g.ensureDir(p.SNAPSHOT_DIR),await g.ensureFile(v.resolve(p.SNAPSHOT_DIR,"__schema.json"));let s=await this.readSnapshotSchema();if(await this.duplicateExists(s)&&(o.error("Snapshot source names must be unique."),process.exit(1)),!e)return this.options.refresh?await this.refresh(r,this.options.refresh,s):this.options.delete?await this.delete(r,this.options.delete,s):await this.updateAll(r);let i=this.suffix(s.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,s),await this.modifyGitignoreFile(".fakelab/*");}async save(e,t,r){try{let s=r.sources.find(a=>a.url===e.url);if(s){o.warn("%s snapshot is already captured. Use \x1B[36mnpx fakelab snapshot --refresh %s\x1B[0m to update.",e.url,s.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?.captured({url:e.url,name:e.name??this.options?.name,content:t}),o.success("Snapshot %s captured successfully.",o.blue(i));}catch(s){o.error("Cannot save the captured snapshot."),s instanceof Error&&o.debug(s.message),process.exit(1);}}async refresh(e,t,r){let s=e.find(n=>n.name===t.trim());if(s||(s=(r.sources||[]).find(n=>n.name===t)),!s){o.warn("Snapshot source not found. Refresh skipped.");return}o.info("Refreshing %s snapshot source...",o.blue(s.name));let i=v.resolve(p.SNAPSHOT_DIR,this.snapshotName(s.url));if(await g.exists(i)){let n=await this.fetch(s);this.subscriber?.refreshed({url:s.url,name:s.name??this.options?.name,content:n}),await g.writeFile(i,n);}else {let n=await this.fetch(s);this.subscriber?.refreshed({url:s.url,name:s.name??this.options?.name,content:n}),await this.save(s,n,r);}o.success("Snapshot source %s refreshed successfully.",o.blue(s.name));}async delete(e,t,r){let s=e.find(a=>a.name===t.trim());if(s||(s=(r.sources||[]).find(a=>a.name===t)),!s){o.warn("Snapshot source not found. Delete skipped.");return}o.info("Deleting %s snapshot source...",o.blue(s.name));let i=v.resolve(p.SNAPSHOT_DIR,this.snapshotName(s.url));await g.rm(i,{force:true}),await this.updateSnapshotSchema({url:s.url,delete:true}),this.subscriber?.deleted({url:s.url,name:s.name??this.options?.name}),o.success("Snapshot source %s deleted successfully.",o.blue(s.name));}async updateAll(e,t=false){o.info(t?"Refreshing all snapshots...":"Updating all snapshots...");let r=await this.readSnapshotSchema();try{await Promise.all(e.map(async s=>{let i=v.resolve(p.SNAPSHOT_DIR,this.snapshotName(s.url));if(await g.exists(i)){let n=await this.fetch(s);await g.writeFile(i,n);}else {let n=await this.fetch(s);await this.save(s,n,r);}})),o.success("All snapshots are updated.");}catch(s){o.error("Failed to update.",s),process.exit(1);}}async fetch({name:e,url:t,headers:r}){this.isValidUrl(t)||(o.error("Invalid snapshot URL. Please provide a valid http URL."),process.exit(1));let i=await(await fetch(t,{headers:r})).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'),n=a.jsonInputForTargetLanguage(this.TARGET_LANGUAGE);await n.addSource({name:e,samples:[i]});let c=new a.InputData;c.addInput(n);let{lines:u}=await a.quicktype({inputData:c,lang:this.TARGET_LANGUAGE,rendererOptions:{"just-types":true}});return u.join(`
|
|
95
|
+
`)}async write(e,t,r,s){await g.writeFile(v.resolve(p.SNAPSHOT_DIR,this.snapshotName(e)),t),await this.updateSnapshotSchema({url:e,name:r,headers:s});}async readSnapshotSchema(){let e={sources:[]};try{e=await g.readJSON(v.resolve(p.SNAPSHOT_DIR,"__schema.json"));}catch{}return e.sources||(e.sources=[]),e}async updateSnapshotSchema(e){let t=await this.readSnapshotSchema(),r=t.sources||[],s=r.findIndex(i=>i.url===e.url);if(e.delete)r.splice(s,1);else if(s===-1)r.push({...e,name:e.name||this.suffix(r)});else {let i=r[s].name,a=r[s].headers;r.splice(s,1,{...r[s],name:e.name||i,headers:e.headers||a});}t.sources=r,await g.writeJSON(v.resolve(p.SNAPSHOT_DIR,"__schema.json"),t);}initWebhook(){if(!this.config.enabled())return;let{enabled:e}=this.config.options.snapshot();if(e){let t=this.config.options.webhook();t.enabled&&(this.subscriber=new B(t.hooks),o.warn("Initializating webhook..."),this.webhook=new R(this.subscriber,this.config));}}snapshotName(e,t=true){return e.replace(/^https?:\/\//,"").replace(/[/:?.&=#]/g,"_")+(t?".ts":"")}suffix(e){let t=e.filter(r=>r.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(r=>r.name);return new Set(t).size!==t.length}async modifyGitignoreFile(e){try{let t=v.resolve(m,".gitignore");if((await g.readFile(t,{encoding:"utf8"})).split(`
|
|
96
|
+
`).some(s=>s.trim()===e.trim()))return;await g.appendFile(t,`
|
|
97
|
+
${e}`);}catch(t){o.warn("Cannot modify .gitignore. error: %s",t);}}};var z=new Command,ee=g.readJSONSync(v.join(d,"../package.json"));z.name(ee.name).description(ee.description).version(ee.version);z.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("-h, --headless","headless mode").option("-t, --ts-config-path <char>","typescript config file path","tsconfig.json").option("-f, --fresh-snapshots","capture or refresh all snapshots").action(async p=>{let e=await $.prepare(p);J.init(p,e.config).start();});z.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(p,e)=>{(await $.init(e)).capture(p);});z.parse();
|
package/lib/main.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { allLocales } from '@faker-js/faker';
|
|
2
|
+
|
|
3
|
+
type FakerLocale = keyof typeof allLocales;
|
|
3
4
|
|
|
4
5
|
type ServerEvent = "server:started" | "server:shutdown";
|
|
5
6
|
type SnapshotEvent = "snapshot:captured" | "snapshot:refreshed" | "snapshot:deleted";
|
|
@@ -18,7 +19,7 @@ type ServerOptions = {
|
|
|
18
19
|
*/
|
|
19
20
|
pathPrefix?: string;
|
|
20
21
|
/**
|
|
21
|
-
* Includes
|
|
22
|
+
* Includes snapshot sources if exists.
|
|
22
23
|
* @default true
|
|
23
24
|
*/
|
|
24
25
|
includeSnapshots?: boolean;
|
|
@@ -27,7 +28,7 @@ type FakerEngineOptions = {
|
|
|
27
28
|
/**
|
|
28
29
|
* Locale used by the faker engine when generating mock data.
|
|
29
30
|
* Controls language-specific values such as names, addresses, etc.
|
|
30
|
-
* @example "
|
|
31
|
+
* @example "en"
|
|
31
32
|
*/
|
|
32
33
|
locale?: FakerLocale;
|
|
33
34
|
};
|
|
@@ -88,7 +89,7 @@ type NetworkOptions = NetworkBehaviourOptions & {
|
|
|
88
89
|
};
|
|
89
90
|
type SnapshotDataSource = {
|
|
90
91
|
/**
|
|
91
|
-
* http
|
|
92
|
+
* Target http or https URL
|
|
92
93
|
* @example "https://api.example.com/users"
|
|
93
94
|
*/
|
|
94
95
|
url: string;
|
|
@@ -163,11 +164,46 @@ type WebhookOptions = {
|
|
|
163
164
|
*/
|
|
164
165
|
hooks: Hook[];
|
|
165
166
|
};
|
|
167
|
+
type GraphQLOptions = {
|
|
168
|
+
/**
|
|
169
|
+
* Enables graphQL.
|
|
170
|
+
*/
|
|
171
|
+
enabled: boolean;
|
|
172
|
+
};
|
|
173
|
+
type CacheOptions = {
|
|
174
|
+
/**
|
|
175
|
+
* Enables the file-based cache.
|
|
176
|
+
*
|
|
177
|
+
* @default true
|
|
178
|
+
*/
|
|
179
|
+
enabled: boolean;
|
|
180
|
+
/**
|
|
181
|
+
* Time-to-live (TTL) for cache entries in milliseconds.
|
|
182
|
+
*
|
|
183
|
+
* When set, cached files older than this value are considered expired
|
|
184
|
+
* and will be ignored or regenerated on the next request.
|
|
185
|
+
*
|
|
186
|
+
* @default 15 * 60 * 1000 // 15 minutes
|
|
187
|
+
*/
|
|
188
|
+
ttl?: number;
|
|
189
|
+
};
|
|
166
190
|
type ConfigOptions = {
|
|
167
191
|
/**
|
|
168
192
|
* Path or paths to the source files that define the typescript types.
|
|
169
193
|
*/
|
|
170
194
|
sourcePath: string | string[];
|
|
195
|
+
/**
|
|
196
|
+
* Enables or disables Fakelab.
|
|
197
|
+
*
|
|
198
|
+
* When set to `false`, Fakelab will not initialize or start any services,
|
|
199
|
+
* regardless of other configuration options.
|
|
200
|
+
*
|
|
201
|
+
* This is useful for conditionally enabling Fakelab based on the current
|
|
202
|
+
* runtime environment (for example, `process.env.NODE_ENV === "development"`).
|
|
203
|
+
*
|
|
204
|
+
* @default true
|
|
205
|
+
*/
|
|
206
|
+
enabled?: boolean;
|
|
171
207
|
/**
|
|
172
208
|
* Enables headless mode.
|
|
173
209
|
*
|
|
@@ -180,7 +216,19 @@ type ConfigOptions = {
|
|
|
180
216
|
* @see {@link https://alirezahematidev.github.io/fakelab/docs/guides/headless|Headless Documentation}
|
|
181
217
|
*/
|
|
182
218
|
headless?: boolean;
|
|
183
|
-
|
|
219
|
+
/**
|
|
220
|
+
* Path to a custom typescript config file.
|
|
221
|
+
*
|
|
222
|
+
* If not specified, Fakelab attempts to resolve the nearest `tsconfig.json`
|
|
223
|
+
* automatically.
|
|
224
|
+
*/
|
|
225
|
+
tsConfigFilePath?: string;
|
|
226
|
+
/**
|
|
227
|
+
* Cache configuration.
|
|
228
|
+
*
|
|
229
|
+
* @see {@link https://alirezahematidev.github.io/fakelab/docs/guides/cache|Cache Documentation}
|
|
230
|
+
*/
|
|
231
|
+
cache?: CacheOptions;
|
|
184
232
|
/**
|
|
185
233
|
* Server-related configuration.
|
|
186
234
|
* @see {@link https://alirezahematidev.github.io/fakelab/docs/guides/server-command|Server Documentation}
|
|
@@ -203,6 +251,7 @@ type ConfigOptions = {
|
|
|
203
251
|
network?: NetworkOptions;
|
|
204
252
|
/**
|
|
205
253
|
* Snapshot configuration.
|
|
254
|
+
*
|
|
206
255
|
* @see {@link https://alirezahematidev.github.io/fakelab/docs/guides/snapshot|Snapshot Documentation}
|
|
207
256
|
*/
|
|
208
257
|
snapshot?: SnapshotOptions;
|
|
@@ -211,6 +260,11 @@ type ConfigOptions = {
|
|
|
211
260
|
* @see {@link https://alirezahematidev.github.io/fakelab/docs/guides/webhook|Webhook Documentation}
|
|
212
261
|
*/
|
|
213
262
|
webhook?: WebhookOptions;
|
|
263
|
+
/**
|
|
264
|
+
* GraphQL configuration.
|
|
265
|
+
* @see {@link https://alirezahematidev.github.io/fakelab/docs/guides/graphQL|GraphQL Documentation}
|
|
266
|
+
*/
|
|
267
|
+
graphQL?: GraphQLOptions;
|
|
214
268
|
};
|
|
215
269
|
type ServerCLIOptions = {
|
|
216
270
|
source?: string;
|
|
@@ -218,7 +272,7 @@ type ServerCLIOptions = {
|
|
|
218
272
|
port?: number;
|
|
219
273
|
locale?: string;
|
|
220
274
|
freshSnapshots?: boolean;
|
|
221
|
-
|
|
275
|
+
tsConfigPath?: string;
|
|
222
276
|
headless?: boolean;
|
|
223
277
|
};
|
|
224
278
|
type ErrorStatusCode = 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511;
|
|
@@ -235,16 +289,22 @@ declare class Config {
|
|
|
235
289
|
snapshot: () => Required<SnapshotOptions>;
|
|
236
290
|
faker: (locale?: FakerLocale) => Required<FakerEngineOptions>;
|
|
237
291
|
webhook: () => Required<WebhookOptions>;
|
|
292
|
+
graphQL: () => Required<GraphQLOptions>;
|
|
293
|
+
cache: () => Required<CacheOptions>;
|
|
238
294
|
};
|
|
239
|
-
|
|
295
|
+
getTSConfigFilePath(tsConfigFilePath?: string): string;
|
|
240
296
|
isHeadless(): boolean;
|
|
297
|
+
private _cacheOptions;
|
|
241
298
|
private _serverOptions;
|
|
242
299
|
private _databaseOptions;
|
|
243
300
|
private _networkOptions;
|
|
244
301
|
private _snapshotOptions;
|
|
302
|
+
private _graphQLOptions;
|
|
245
303
|
private _fakerOptions;
|
|
246
304
|
private _webhookOptions;
|
|
247
|
-
|
|
305
|
+
enabled(): boolean;
|
|
306
|
+
getSourceFiles(_sourcePath?: string): string[];
|
|
307
|
+
files(_sourcePath: string | undefined, fresh: boolean): Promise<string[]>;
|
|
248
308
|
initializeRuntimeConfig(dirname: string, options: ServerCLIOptions): Promise<void>;
|
|
249
309
|
private getSnapshotSourceFiles;
|
|
250
310
|
private tryStat;
|
package/lib/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import g from'fast-glob';import l from'path';import P from'fs-extra';import {stat,access,constants}from'fs/promises';import y from'is-glob';import p from'picocolors';import {allLocales}from'@faker-js/faker';import {fileURLToPath}from'url';import {transform}from'esbuild';var L=new Intl.ListFormat("en",{style:"long",type:"unit"}),n=class{static label(t){switch(t){case "info":return p.blueBright(t.toUpperCase());case "error":return p.redBright(t.toUpperCase());case "warn":return p.yellowBright(t.toUpperCase());case "success":return p.greenBright(t.toUpperCase())}}static log(t,e){return [p.dim(`[${new Date().toISOString()}]`),this.label(t),e].join(" ")}static blue(t){return p.blueBright(t)}static red(t){return p.redBright(t)}static yellow(t){return p.yellowBright(t)}static green(t){return p.greenBright(t)}static info(t,...e){console.log(this.log("info",t),...e);}static warn(t,...e){console.log(this.log("warn",t),...e);}static error(t,...e){console.error(this.log("error",t),...e);}static success(t,...e){console.log(this.log("success",t),...e);}static debug(t,...e){typeof process>"u"||!process.env.DEBUG||console.log(this.log("info",t),...e);}static list(t){return L.format(t)}};var O="api",m=5200;var b=Object.keys(allLocales);function _(){let a=Intl.DateTimeFormat().resolvedOptions().locale;if(!a)return "en";let[t]=a.split("-");return b.includes(t)?t:"en"}l.dirname(fileURLToPath(import.meta.url));var k=process.cwd();var c=class{constructor(t,e={}){this.dirname=t;this._args=e;}transformOptions={minify:true,platform:"browser",target:"es2022"};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 prepare(t,e){let r=this.replacer(t,this._args),o=l.resolve(this.dirname,e);try{let{code:s}=await transform(r,this.transformOptions);return {code:s,filepath:o}}catch(s){return s instanceof Error&&n.warn(s.message),{code:r,filepath:o}}}};var w=`const _fakelab = {};
|
|
2
2
|
_fakelab.url = () => "http://localhost:PORT/PREFIX/";
|
|
3
3
|
|
|
4
4
|
function _count(opts = {}) {
|
|
@@ -7,7 +7,9 @@ function _count(opts = {}) {
|
|
|
7
7
|
return null;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
_fakelab.
|
|
10
|
+
_fakelab.enabled = () => FAKELAB_ENABLED;
|
|
11
|
+
|
|
12
|
+
_fakelab.fetch = async function (name, options) {
|
|
11
13
|
const count = _count(options);
|
|
12
14
|
const search = count !== null ? "?count=" + count : "";
|
|
13
15
|
|
|
@@ -18,20 +20,24 @@ _fakelab.gen = async function (name, options) {
|
|
|
18
20
|
return res.json();
|
|
19
21
|
};
|
|
20
22
|
|
|
21
|
-
_fakelab.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
_fakelab.gen = function (name, options) {
|
|
24
|
+
const count = _count(options);
|
|
25
|
+
|
|
26
|
+
if(count === null) return functions[name]();
|
|
27
|
+
|
|
28
|
+
return Array.from({length:count},() => functions[name]())
|
|
29
|
+
}
|
|
25
30
|
|
|
31
|
+
_fakelab.enabled = _fakelab.enabled.bind(_fakelab);
|
|
26
32
|
_fakelab.url = _fakelab.url.bind(_fakelab);
|
|
33
|
+
_fakelab.fetch = _fakelab.fetch.bind(_fakelab);
|
|
27
34
|
_fakelab.gen = _fakelab.gen.bind(_fakelab);
|
|
28
|
-
_fakelab.genSync = _fakelab.genSync.bind(_fakelab);
|
|
29
35
|
|
|
30
36
|
const fakelab = Object.freeze(_fakelab);
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
export {fakelab};
|
|
34
|
-
`;var
|
|
40
|
+
`;var f=class a extends c{constructor(e,r,o,s){super(e,{PORT:r,PREFIX:o,FAKELAB_ENABLED:s});this.dirname=e;this.port=r;this.prefix=o;this.enabled=s;}SOURCE_FILENAME="runtime.js";static init(e,r,o,s){return new a(e,r,o,s)}prepare(){return super.prepare(w,this.SOURCE_FILENAME)}};var E=`const _database = {};
|
|
35
41
|
const url = "http://localhost:PORT/PREFIX/";
|
|
36
42
|
|
|
37
43
|
_database.enabled = () => ENABLED;
|
|
@@ -74,4 +80,4 @@ database.seed.bind(database);
|
|
|
74
80
|
database.flush.bind(database);
|
|
75
81
|
|
|
76
82
|
export { database };
|
|
77
|
-
`;var
|
|
83
|
+
`;var u=class a extends c{constructor(e,r,o,s){super(e,{PORT:r,PREFIX:o,ENABLED:s});this.dirname=e;this.port=r;this.prefix=o;this.enabled=s;}SOURCE_FILENAME="database.js";static init(e,r,o,s){return new a(e,r,o,s)}prepare(){return super.prepare(E,this.SOURCE_FILENAME)}};var h=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._graphQLOptions=this._graphQLOptions.bind(this),this._cacheOptions=this._cacheOptions.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});}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,graphQL:this._graphQLOptions,cache:this._cacheOptions}}getTSConfigFilePath(t){return t||this.configOptions.tsConfigFilePath||"tsconfig.json"}isHeadless(){return this.configOptions.headless??false}_cacheOptions(){return {enabled:this.configOptions.cache?.enabled??true,ttl:this.configOptions.cache?.ttl||900*1e3}}_serverOptions(t,e){return {pathPrefix:t||this.configOptions.server?.pathPrefix||O,port:e||this.configOptions.server?.port||m,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||[]}}_graphQLOptions(){return {enabled:this.configOptions.graphQL?.enabled??false}}_fakerOptions(t){let e=t||this.configOptions.faker?.locale;return e&&b.includes(e)?{locale:e}:{locale:_()}}_webhookOptions(){return {enabled:this.configOptions.webhook?.enabled??false,hooks:this.configOptions.webhook?.hooks??[]}}enabled(){return this.configOptions.enabled??true}getSourceFiles(t){let e=t||this.configOptions.sourcePath;return this.resolveSourcePath(e)}async files(t,e){let r=t||this.configOptions.sourcePath;if(!this.enabled())return [];let o=this.resolveSourcePath(r),s=o.length>0?Array.from(new Set((await Promise.all(o.map(i=>this.resolveTSFiles(i,e)))).flat())):[];if(this._serverOptions().includeSnapshots){let i=await this.getSnapshotSourceFiles();s.push(...i);}if(s.length===0){let i=o.map(d=>l.basename(d));i.length===0?n.error("No source path found."):n.error("No Typescript files found in: %s",n.list(i)),process.exit(1);}return s}async initializeRuntimeConfig(t,e){if(!this.enabled()){n.warn("Fakelab is disabled. Skipping runtime initialization.");return}let{port:r,pathPrefix:o}=this._serverOptions(e.pathPrefix,e.port),s=f.init(t,r,o,this.enabled()),i=u.init(t,r,o,this.options.database().enabled),d=await Promise.all([s.prepare(),i.prepare()]);await Promise.all(d.map(({filepath:S,code:F})=>P.writeFile(S,F)));}async getSnapshotSourceFiles(){let t=await g(".fakelab/snapshots/**/*.ts",{absolute:true,ignore:["**/*.d.ts"],cwd:k});return t.length>0&&n.info("Snapshot(s): %s",n.list(t.map(e=>l.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=>y(r,{strict:true})?r:l.resolve(r))}async resolveTSFiles(t,e){if(y(t,{strict:true}))return e||n.info("Source: %s",t),g(t,{absolute:true,ignore:["**/*.d.ts"]});let r=l.resolve(t),o=r.endsWith(".ts")?r:r+".ts";return (await this.tryStat(o))?.isFile()?(await this.isReadable(o)||(n.error("Cannot read file: %s",o),process.exit(1)),e||n.info("Source: %s",o),[o]):(await this.tryStat(r))?.isDirectory()?(e||n.info("Source: %s",r),g("**/*.ts",{cwd:r,absolute:true,ignore:["**/*.d.ts"]})):(e||n.warn("invalid source: [REDACTED]/%s",l.basename(o)),[])}};function N(a){return new h(a)}export{N as defineConfig};
|