fakelab 0.0.17 → 0.0.18
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/README.md +76 -3
- package/lib/cli.js +4 -4
- package/lib/main.d.ts +16 -1
- package/lib/main.js +5 -5
- package/lib/views/components/database/sidebar.ejs +1 -1
- package/lib/views/database.ejs +1 -1
- package/lib/views/index.ejs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,9 +30,12 @@ export default defineConfig({
|
|
|
30
30
|
sourcePath: ["./types", "./fixtures/**/*.ts"], // supports glob pattern
|
|
31
31
|
faker: { locale: "en" }, // optional
|
|
32
32
|
server: { pathPrefix: "api/v1", port: 8080 }, // optional
|
|
33
|
+
browser: { expose: { mode: "module" } }, // optional
|
|
33
34
|
});
|
|
34
35
|
```
|
|
35
36
|
|
|
37
|
+
## Faker Annotations
|
|
38
|
+
|
|
36
39
|
Fakelab allows you to control generated mock data using JSDoc tags.
|
|
37
40
|
You simply annotate your TypeScript interfaces with the @faker tag, and Fakelab uses the corresponding [faker](https://fakerjs.dev/)
|
|
38
41
|
method when generating mock values.
|
|
@@ -85,14 +88,14 @@ export interface User {
|
|
|
85
88
|
|
|
86
89
|
## Fakelab Runtime
|
|
87
90
|
|
|
88
|
-
`fakelab/runtime` enables
|
|
91
|
+
`fakelab/runtime` enables `fakelab` module at runtime, allowing your frontend or Node environment to communicate with the running Fakelab mock server.
|
|
89
92
|
|
|
90
|
-
## `fakelab.
|
|
93
|
+
## `fakelab.url()`
|
|
91
94
|
|
|
92
95
|
The base URL of the running Fakelab server.
|
|
93
96
|
|
|
94
97
|
```ts
|
|
95
|
-
fakelab.
|
|
98
|
+
fakelab.url();
|
|
96
99
|
// e.g. "http://localhost:50000/api"
|
|
97
100
|
```
|
|
98
101
|
|
|
@@ -116,6 +119,15 @@ fakelab.fetch(name: string, count?: number): Promise<T>
|
|
|
116
119
|
### Basic example
|
|
117
120
|
|
|
118
121
|
```ts
|
|
122
|
+
import { fakelab } from "fakelab/runtime";
|
|
123
|
+
|
|
124
|
+
const users = await fakelab.fetch("User", 10);
|
|
125
|
+
|
|
126
|
+
console.log(users);
|
|
127
|
+
|
|
128
|
+
// or
|
|
129
|
+
|
|
130
|
+
// can be enabled as a global object
|
|
119
131
|
import "fakelab/runtime";
|
|
120
132
|
|
|
121
133
|
const users = await fakelab.fetch("User", 10);
|
|
@@ -125,6 +137,67 @@ console.log(users);
|
|
|
125
137
|
|
|
126
138
|
**NOTE:** Set count to a negative number to get an empty array.
|
|
127
139
|
|
|
140
|
+
## Database Mode
|
|
141
|
+
|
|
142
|
+
Fakelab can persist generated mock data to a local database.
|
|
143
|
+
|
|
144
|
+
Under the hood, Fakelab uses the lightweight [lowdb](https://github.com/typicode/lowdb)
|
|
145
|
+
library for persistence, ensuring fast reads, simple JSON storage, and zero external dependencies.
|
|
146
|
+
|
|
147
|
+
### Database Options
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
export type DatabaseOptions = {
|
|
151
|
+
enabled: boolean;
|
|
152
|
+
dest?: string;
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Basic example
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
export default defineConfig({
|
|
160
|
+
database: { enabled: true, dest: "db" },
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Network Simulation
|
|
165
|
+
|
|
166
|
+
Fakelab can simulate real-world network conditions such as latency, random failures, timeouts, and offline mode.
|
|
167
|
+
This is useful for testing loading states, retry logic, and poor network UX without changing frontend code.
|
|
168
|
+
|
|
169
|
+
### Network Options
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
type NetworkBehaviourOptions = {
|
|
173
|
+
delay?: number | [number, number];
|
|
174
|
+
errorRate?: number;
|
|
175
|
+
timeoutRate?: number;
|
|
176
|
+
offline?: boolean;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export type NetworkOptions = NetworkBehaviourOptions & {
|
|
180
|
+
preset?: string;
|
|
181
|
+
presets?: Record<string, NetworkBehaviourOptions>;
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Basic example
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
export default defineConfig({
|
|
189
|
+
network: {
|
|
190
|
+
delay: [300, 1200],
|
|
191
|
+
errorRate: 0.1,
|
|
192
|
+
timeoutRate: 0.05,
|
|
193
|
+
presets: { wifi: { errorRate: 1 } },
|
|
194
|
+
preset: "wifi",
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**NOTE:** When both inline network options and a `preset` are defined, inline options always take precedence and override the preset values.
|
|
200
|
+
|
|
128
201
|
## Server Command
|
|
129
202
|
|
|
130
203
|
Run:
|
package/lib/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import g from'path';import O from'fs-extra';import {Command}from'commander';import R from'express';import H from'cors';import K from'express-ejs-layouts';import X from'http';import'ejs';import {Project}from'ts-morph';import d from'winston';import b from'picocolors';import {JSONFilePreset}from'lowdb/node';import {fileURLToPath}from'url';import U from'qs';import V from'figlet';import {bundleRequire}from'bundle-require';import se from'joycon';function S(s){switch(s){case "info":return b.blueBright(s.toUpperCase());case "error":return b.redBright(s.toUpperCase());case "warn":return b.yellowBright(s.toUpperCase());default:return s.toUpperCase()}}var j=d.format.printf(({level:s,message:e,timestamp:t})=>`${b.dim(`[${t}]`)} ${S(s)} ${e}`),m=d.createLogger({format:d.format.combine(d.format.timestamp(),d.format.splat(),j),transports:[new d.transports.Console]}),I=new Intl.ListFormat("en",{style:"long",type:"unit"}),c=class{static info(e,...t){return m.info(e,...t)}static warn(e,...t){return m.warn(e,...t)}static error(e,...t){return m.error(e,...t)}static debug(e,...t){if(!(typeof process>"u"||!process.env.DEBUG))return m.info(e,...t)}static list(e){return I.format(e)}static close(){m.removeAllListeners(),m.close();}};var w=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 o=>{let i=o.getTypeAtLocation(o.getValueDeclarationOrThrow());r[o.getName()]=await t(i,this,this.readJSDocTags(o));})),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)]}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[o]=r.getText();if(!o)return;let i=o.text.trim().match(this.FAKER_TAG_REGEX);if(!i)return;let[,n,a]=i,p=this.evalArgs(a);return {path:n,args:p}})}execute(e,t){if(!e)return t();let r=e.path.split("."),o=this.faker;for(let i of r)o=o[i],o||(c.error("Invalid faker module path:",e.path),process.exit(1));typeof o!="function"&&(c.error("Unresolvable faker function.",e.path),process.exit(1));try{return e.args?o(e.args):o()}catch{return c.error("Passed invalid arguments to faker function."),o()}}};var u=g.dirname(fileURLToPath(import.meta.url)),F=process.cwd();var k=class{constructor(e,t){this.files=e;this.config=t;let o=new Project({tsConfigFilePath:"tsconfig.json"}).addSourceFilesAtPaths(e);this.__targets=o.flatMap(i=>{let n=i.getInterfaces(),a=i.getTypeAliases(),p=i.getExportDeclarations().flatMap(l=>l.getNamedExports().flatMap(f=>f.getLocalTargetDeclarations()));return [...n,...a,...p]}),this.generateInFileEntitiyMap(this.__targets);}__targets;async run(e){return await e()}normalizePath(e){return e.split(g.sep).join(g.posix.sep)}generateInFileEntitiyMap(e){let t=[...new Set(e.map(i=>{let n=i.getName(),a=i.getSourceFile().getFilePath();return `${n}: import("${a}").${n}`}))],{expose:r}=this.config.browserOpts(),o=`
|
|
2
2
|
interface Runtime$ {
|
|
3
3
|
${t.join(`
|
|
4
4
|
`)}
|
|
5
|
-
}`;r.mode==="global"&&(
|
|
6
|
-
declare global {${
|
|
7
|
-
}`),O.appendFile(
|
|
5
|
+
}`;r.mode==="global"&&(o=`
|
|
6
|
+
declare global {${o}
|
|
7
|
+
}`),O.appendFile(g.resolve(u,this.config.RUNTIME_DECL_FILENAME),o);}address(e,t){let r=this.normalizePath(F);return `${e.replace(r,"")}/${t}`}async entities(){let e=await Promise.all(this.__targets.map(async t=>{let r=t.getName().toLowerCase(),o=t.getType(),i=this.normalizePath(t.getSourceFile().getDirectoryPath()),n=t.getSourceFile().getBaseName(),a=this.address(i,n),p=this.config.getDatabaseDirectoryPath(),l=g.resolve(p,`${r}.json`),f=this.address(this.normalizePath(p),g.basename(l)),y=await JSONFilePreset(l,[]);return [r,{type:o,filepath:a,table:y,tablepath:f}]}));return new Map(e)}async initFakerLibrary(e){let{faker:t}=await import(`@faker-js/faker/locale/${e.locale}`);return t}};async function h(s,e,t=[],r=0){if(s.isString())return e.string(t[r]);if(s.isNumber())return e.int(t[r]);if(s.isBoolean())return e.bool(t[r]);if(s.isBigInt())return e.bigInt(t[r]);if(s.isBooleanLiteral())return e.litbool(s.getText());if(s.isLiteral())return s.getLiteralValue();if(!s.isUndefined()){if(s.isUnion()){let o=s.getUnionTypes();return await e.union(o.map((i,n)=>h(i,e,t,n)))}if(s.isIntersection()){let o=s.getIntersectionTypes();return await e.intersection(o.map((i,n)=>h(i,e,t,n)))}if(s.isArray()){let o=s.getArrayElementTypeOrThrow();return [await h(o,e,t,r)]}if(s.isObject()){let o=s.getProperties();return await e.object(o,(i,n,a)=>h(i,n,a,r))}return null}}function J({each:s}){return {resolve:async t=>await Promise.all(Array.from({length:t},s))}}async function A(s,e){let t=await s.files(e.source),r=new k(t,s),o=await r.entities(),i=await r.initFakerLibrary(s.fakerOpts(e.locale)),n=new w(i);async function a(p,l){let f=J({each:()=>h(p,n)}),y=await(l.count?f.resolve(parseInt(l.count)):h(p,n)),D=JSON.stringify(y,null,2);return {data:y,json:D}}return {entities:o,build:a}}var v=class{constructor(e,t,r){this.builder=e;this.config=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.config.databaseEnabled()});}}preview(e){return async(t,r)=>{let o=`${t.protocol}://${t.host}/`,i=t.params.name,n=await this.handleQueries(t),a=U.stringify(n,{addQueryPrefix:true}),p=this.builder.entities.get(i.toLowerCase());if(p){let{json:l}=await this.builder.build(p.type,n),f=p.filepath;r.render("preview",{name:i,filepath:f,address:o,search:a,json:l,prefix:e,entities:this.builder.entities,version:this.pkg.version,enabled:this.config.databaseEnabled()});}else r.redirect("/");}}database(){return (e,t)=>{this.config.databaseEnabled()?t.render("database",{name:null,entities:this.builder.entities,version:this.pkg.version}):t.redirect("/");}}table(e){return async(t,r)=>{let o=`${t.protocol}://${t.host}/`,i=t.params.name,n=this.builder.entities.get(i.toLowerCase());if(!this.config.databaseEnabled())r.redirect("/");else if(n){await n.table.read();let p=n.table.data.length>0,l=JSON.stringify(n.table.data,null,2),f=n.filepath;r.render("table",{name:i,filepath:f,address:o,prefix:e,json:l,hasData:p,entities:this.builder.entities,version:this.pkg.version});}else r.redirect("/database");}}};var x=class{constructor(e,t){this.builder=e;this.network=t;}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,o=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,o);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(await this.applyNetworkHandlers(t))return;let r=e.params.name,o=this.builder.entities.get(r.toLowerCase());o?(await o.table.read(),t.status(200).json(o.table.data)):t.status(400).json({message:"The table is not exists"});}catch(r){t.status(500).send(r);}}}updateTable(){return async(e,t)=>{try{if(await this.applyNetworkHandlers(t))return;let r=e.params.name,o=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,o);await i.table.update(a=>a.push(n)),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);}}}_update(){return async(e,t)=>{try{let r=e.params.name,o=await this.handleQueries(e),i=this.builder.entities.get(r.toLowerCase());if(i){let{data:n}=await this.builder.build(i.type,o);await i.table.update(a=>a.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{let r=e.params.name,o=this.builder.entities.get(r.toLowerCase());o?(await o.table.read(),o.table.data.length>0&&await o.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 G=O.readJSONSync(g.join(u,"../package.json")),C=class{constructor(e,t,r,o){this.router=e;this.config=t;this.opts=r;this.network=o;let{pathPrefix:i}=this.config.serverOpts(this.opts.pathPrefix,this.opts.port);this.prefix=i;}prefix;async register(){let e=await A(this.config,this.opts),t=new v(e,this.config,G),r=new x(e,this.network);this.router.get("/",t.index()),this.router.get("/entities/:name",t.preview(this.prefix)),this.router.get("/database",t.database()),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("/__update/:name",r._update()),this.router.post("/__delete/:name",r._clear());}};var T=class s{constructor(e){this.config=e;this.options=this.config.networkOpts(),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 s(e)}timeout(){let e=this.chance(this.options?.timeoutRate);return e&&c.debug("Network timeout..."),e}error(){let e=this.chance(this.options?.errorRate);return e&&c.debug("Network error..."),e}state(e){switch(e){case "error":return {status:500,message:"Network error"};case "offline":return {status:503,message:"Network offline"};default:return {status:500,message:"Unknown error"}}}async wait(){let e=this.resolveDelay();e>0&&(c.debug("Network waiting (%d ms)...",e),await this.sleep(e));}offline(){return this.options?.offline??false}middleware(e,t,r){let o=this.options?.errorRate||0,i=this.options?.timeoutRate||0,n=this.options?.offline??false,a=`delay=${this.resolveDelay()},error=${o},timeout=${i},offline=${n}`;t.setHeader("X-Fakelab-Network",a),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))}};function W(s,e){try{process.loadEnvFile("./.env.local");}catch{}s.databaseEnabled()&&c.info(`database: ${s.getDatabaseDirectoryPath()}`),c.info(`server: http://localhost:${e}`),console.log(V.textSync("FAKELAB"));}function Z(s,e,t){let{port:r}=e.serverOpts(t.pathPrefix,t.port);s.listen(r,"localhost",()=>W(e,r)),s.on("close",()=>{c.close();});}function Y(s,e,t){e.setHeader("x-powered-by","fakelab"),t();}function ee(s,e){s.disable("x-powered-by"),s.use(R.json()),s.use(H({methods:"GET"})),s.use(R.static(u+"/public")),s.use(Y),s.use(e.middleware);}function te(s){s.set("views",g.join(u,"views")),s.set("view engine","ejs"),s.use(K),s.set("layout","layouts/main");}async function L(s,e){let t=R(),r=R.Router(),o=X.createServer(t),i=T.initHandlers(s);ee(t,i),te(t),await s.generateInFileRuntimeConfig(u,e),await new C(r,s,e,i).register(),t.use(r),Z(o,s,e);}async function N(){try{let e=await new se().resolve({files:["fakelab.config.ts"]});return e||(c.error("No fakelab config file is detected."),process.exit(1)),(await bundleRequire({filepath:e})).mod.default}catch{c.error("Could not load the config file."),process.exit(1);}}var P=new Command,E=O.readJSONSync(g.join(u,"../package.json"));P.name(E.name).description(E.description).version(E.version);P.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").action(async s=>{let e=await N();L(e,s);});P.parse();
|
package/lib/main.d.ts
CHANGED
|
@@ -25,12 +25,24 @@ type BrowserExposeOptions = {
|
|
|
25
25
|
type BrowserOptions = {
|
|
26
26
|
expose?: Partial<BrowserExposeOptions>;
|
|
27
27
|
};
|
|
28
|
+
type NetworkBehaviourOptions = {
|
|
29
|
+
delay?: number | [number, number];
|
|
30
|
+
errorRate?: number;
|
|
31
|
+
timeoutRate?: number;
|
|
32
|
+
offline?: boolean;
|
|
33
|
+
};
|
|
34
|
+
type NetworkOptions = NetworkBehaviourOptions & {
|
|
35
|
+
preset?: string;
|
|
36
|
+
presets?: Record<string, NetworkBehaviourOptions>;
|
|
37
|
+
};
|
|
28
38
|
type ConfigOptions = {
|
|
29
39
|
sourcePath: string | string[];
|
|
30
40
|
server?: ServerOptions;
|
|
31
41
|
faker?: FakerEngineOptions;
|
|
32
42
|
database?: DatabaseOptions;
|
|
33
43
|
browser?: BrowserOptions;
|
|
44
|
+
network?: NetworkOptions;
|
|
45
|
+
transform?: Record<string, (data: any) => any | Promise<any>>;
|
|
34
46
|
};
|
|
35
47
|
type ServerCLIOptions = {
|
|
36
48
|
source?: string;
|
|
@@ -43,15 +55,18 @@ declare class Config {
|
|
|
43
55
|
private readonly opts;
|
|
44
56
|
readonly RUNTIME_SOURCE_FILENAME = "runtime.js";
|
|
45
57
|
readonly RUNTIME_DECL_FILENAME = "runtime.d.ts";
|
|
58
|
+
readonly FAKELAB_PERSIST_DIR = ".fakelab";
|
|
59
|
+
NETWORK_DEFAULT_OPTIONS: Readonly<NetworkOptions>;
|
|
46
60
|
constructor(opts: ConfigOptions);
|
|
47
61
|
files(_sourcePath?: string): Promise<string[]>;
|
|
48
62
|
serverOpts(prefix?: string, port?: number): Required<ServerOptions>;
|
|
49
63
|
browserOpts(name?: string, mode?: "module" | "global"): Required<BrowserOptions>;
|
|
64
|
+
networkOpts(): NetworkOptions;
|
|
50
65
|
fakerOpts(locale?: FakerLocale): Required<FakerEngineOptions>;
|
|
51
66
|
generateInFileRuntimeConfig(dirname: string, options: ServerCLIOptions): Promise<void>;
|
|
52
67
|
getDatabaseDirectoryPath(): string;
|
|
53
68
|
databaseEnabled(): boolean;
|
|
54
|
-
private
|
|
69
|
+
private initializeDatabase;
|
|
55
70
|
private modifyGitignoreFile;
|
|
56
71
|
private tryStat;
|
|
57
72
|
private isReadable;
|
package/lib/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import N from'fast-glob';import i from'path';import d from'fs-extra';import {stat,access,constants}from'fs/promises';import F from'is-glob';import p from'winston';import u from'picocolors';import {transform}from'esbuild';function M(a){switch(a){case "info":return u.blueBright(a.toUpperCase());case "error":return u.redBright(a.toUpperCase());case "warn":return u.yellowBright(a.toUpperCase());default:return a.toUpperCase()}}var D=p.format.printf(({level:a,message:e,timestamp:t})=>`${u.dim(`[${t}]`)} ${M(a)} ${e}`),l=p.createLogger({format:p.format.combine(p.format.timestamp(),p.format.splat(),D),transports:[new p.transports.Console]}),_=new Intl.ListFormat("en",{style:"long",type:"unit"}),s=class{static info(e,...t){return l.info(e,...t)}static warn(e,...t){return l.warn(e,...t)}static error(e,...t){return l.error(e,...t)}static debug(e,...t){if(!(typeof process>"u"||!process.env.DEBUG))return l.info(e,...t)}static list(e){return _.format(e)}static close(){l.removeAllListeners(),l.close();}};var g="fakelab",y="module",h=["af","ar","az","bn","cs","cy","da","de","dv","el","en","eo","es","fa","fi","fr","he","hr","hu","hy","id","it","ja","ka","ko","ku","lv","mk","nb","ne","nl","pl","pt","ro"];function O(){let a=Intl.DateTimeFormat().resolvedOptions().locale;if(!a)return "en";let[e]=a.split("-"),t=e.toLowerCase();return h.includes(t)?t:"en"}var T=`let fl = {};
|
|
2
2
|
let db = {};
|
|
3
3
|
fl.url = () => "http://localhost:PORT/PREFIX/";
|
|
4
4
|
fl.fetch = async function (name, count) {
|
|
@@ -90,7 +90,7 @@ global.NAME.database.post = async function (name) {
|
|
|
90
90
|
const NAME = Object.freeze(fl);
|
|
91
91
|
const database = Object.freeze(db);
|
|
92
92
|
|
|
93
|
-
export { NAME, database };`,
|
|
93
|
+
export { NAME, database };`,R=`export {};
|
|
94
94
|
|
|
95
95
|
declare global {
|
|
96
96
|
const database: {
|
|
@@ -114,6 +114,6 @@ declare global {
|
|
|
114
114
|
NAME: typeof NAME;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
}`;var
|
|
118
|
-
`).some(
|
|
119
|
-
${
|
|
117
|
+
}`;var w=class{constructor(e,t,r,o){this.port=e;this.prefix=t;this.browserOptions=r;this.dbOptions=o;this.name=this.browserOptions?.expose?.name||"fakelab",this.databaseEnabled=this.dbOptions?.enabled??true?"true":"false";}name;databaseEnabled;transformOptions={minify:true,platform:"browser",target:"es2022"};replacer(e,t){let r=e;for(let o in t)r=r.replace(new RegExp(o,"g"),t[o].toString().trim());return r}async prepareGlobalSource(){let e=this.replacer(L,{NAME:this.name,PORT:this.port,PREFIX:this.prefix,ENABLED_COND:this.databaseEnabled});try{let{code:t}=await transform(e,this.transformOptions);return t}catch(t){return t instanceof Error&&s.warn(t.message),e}}globalDeclaration(){return this.replacer(R,{NAME:this.name})}},b=class a extends w{constructor(t,r,o,c){super(t,r,o,c);this.port=t;this.prefix=r;this.browserOptions=o;this.dbOptions=c;}static init(t,r,o,c){let E=new a(t,r,o,c);return new Proxy(E,{get(n,f){return f==="prepareSource"?o?.expose?.mode==="global"?n.prepareGlobalSource:n.prepareSource:f==="declaration"?o?.expose?.mode==="global"?n.globalDeclaration:n.declaration:n[f]}})}async prepareSource(){let t=this.replacer(T,{NAME:this.name,PORT:this.port,PREFIX:this.prefix,ENABLED_COND:this.databaseEnabled});try{let{code:r}=await transform(t,this.transformOptions);return r}catch(r){return r instanceof Error&&s.warn(r.message),t}}declaration(){return this.replacer(A,{NAME:this.name})}};var m=class{constructor(e){this.opts=e;this.files=this.files.bind(this),this.serverOpts=this.serverOpts.bind(this),this.fakerOpts=this.fakerOpts.bind(this),this.browserOpts=this.browserOpts.bind(this),this.NETWORK_DEFAULT_OPTIONS=Object.freeze({delay:this.opts.network?.delay||0,errorRate:this.opts.network?.errorRate||0,timeoutRate:this.opts.network?.timeoutRate||0,offline:this.opts.network?.offline??false});}RUNTIME_SOURCE_FILENAME="runtime.js";RUNTIME_DECL_FILENAME="runtime.d.ts";FAKELAB_PERSIST_DIR=".fakelab";NETWORK_DEFAULT_OPTIONS;async files(e){let t=this.resolveSourcePath(e||this.opts.sourcePath),r=Array.from(new Set((await Promise.all(t.map(o=>this.resolveTSFiles(o)))).flat()));return r.length===0&&(s.error("No Typescript files found in: %s",s.list(t.map(o=>i.basename(o)))),process.exit(1)),r}serverOpts(e,t){return {pathPrefix:e||this.opts.server?.pathPrefix||"api",port:t||this.opts.server?.port||5200}}browserOpts(e,t){return {expose:{mode:t||this.opts.browser?.expose?.mode||y,name:e||this.opts.browser?.expose?.name||g}}}networkOpts(){let e=this.opts.network?.preset,t=this.opts.network?.presets??{};return !e||!t[e]?this.NETWORK_DEFAULT_OPTIONS:{...t[e],...this.opts.network??{}}}fakerOpts(e){let t=(e||this.opts.faker?.locale)?.toLowerCase();return t&&h.includes(t)?{locale:t}:{locale:O()}}async generateInFileRuntimeConfig(e,t){let{port:r,pathPrefix:o}=this.serverOpts(t.pathPrefix,t.port);await this.initializeDatabase();let c=i.resolve(e,this.RUNTIME_SOURCE_FILENAME),E=i.resolve(e,this.RUNTIME_DECL_FILENAME),n=b.init(r,o,this.opts.browser,this.opts.database),f=await n.prepareSource();await Promise.all([d.writeFile(c,f),d.writeFile(E,n.declaration())]);}getDatabaseDirectoryPath(){let e=this.opts.database?.dest||"db";return i.resolve(process.cwd(),e)}databaseEnabled(){return this.opts.database?.enabled??false}async initializeDatabase(){if(this.databaseEnabled())try{let e=this.opts.database?.dest||"db";await d.ensureDir(this.getDatabaseDirectoryPath()),await this.modifyGitignoreFile(e);}catch{s.error("Could not create database.");}else (!this.opts.database||!this.databaseEnabled())&&await d.rm(this.getDatabaseDirectoryPath(),{force:true,recursive:true});}async modifyGitignoreFile(e){try{let t=i.resolve(process.cwd(),".gitignore");if((await d.readFile(t,{encoding:"utf8"})).split(`
|
|
118
|
+
`).some(o=>o.trim()===e.trim()))return;await d.appendFile(t,`
|
|
119
|
+
${e}`);}catch{s.error("Could not modify .gitignore file.");}}async tryStat(e){try{return await stat(e)}catch{return null}}async isReadable(e){try{return await access(e,constants.R_OK),!0}catch{return false}}resolveSourcePath(e){return (Array.isArray(e)?e:[e]).map(r=>F(r,{strict:true})?r:i.resolve(r))}async resolveTSFiles(e){if(F(e,{strict:true}))return s.info("source: %s",e),N(e,{absolute:true,ignore:["**/*.d.ts"]});let t=i.resolve(e),r=t.endsWith(".ts")?t:t+".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(t))?.isDirectory()?(s.info("source: %s",t),N("**/*.ts",{cwd:t,absolute:true,ignore:["**/*.d.ts"]})):(s.warn("invalid source: [REDACTED]/%s",i.basename(r)),[])}};function v(a){return new m(a)}export{v as defineConfig};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
<div class="sidebar_content_leading">
|
|
11
11
|
<div class="sidebar_content_indicator_icon"></div>
|
|
12
|
-
<span class="sidebar_content_title">Registered
|
|
12
|
+
<span class="sidebar_content_title">Registered tables</span>
|
|
13
13
|
</div>
|
|
14
14
|
<div class="sidebar_content_list">
|
|
15
15
|
<% entities.forEach((entity,entityName) => { %>
|
package/lib/views/database.ejs
CHANGED
package/lib/views/index.ejs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fakelab",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "A easy-config mock server for frontend developers.",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"url": "https://github.com/alirezahematidev/fakelab/issues"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
|
-
"build": "tsup",
|
|
36
|
+
"build": "tsc && tsup",
|
|
37
37
|
"release": "release-it --ci",
|
|
38
38
|
"dev": "node --import=tsx src/cli.ts serve",
|
|
39
39
|
"serve": "yarn build && node lib/cli.js serve"
|